blob: e2e28c15334bb24f0021cfb03222ef486315f50d [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![warn(missing_docs)]
//! Library for resolving and encoding the runtime configuration values of a component.
use cm_rust::{
ConfigChecksum, ConfigDecl, ConfigField as ConfigFieldDecl, ConfigMutability,
ConfigNestedValueType, ConfigOverride, ConfigSingleValue, ConfigValue, ConfigValueType,
ConfigValuesData, ConfigVectorValue, NativeIntoFidl,
};
use dynfidl::{BasicField, Field, Structure, VectorField};
use fidl_fuchsia_component_decl as fdecl;
use thiserror::Error;
/// The resolved configuration for a component.
#[derive(Clone, Debug, PartialEq)]
pub struct ConfigFields {
/// A list of all resolved fields, in the order of the compiled manifest.
pub fields: Vec<ConfigField>,
/// A checksum from the compiled manifest.
pub checksum: ConfigChecksum,
}
impl ConfigFields {
/// Resolve a component's configuration values according to its declared schema. Should not fail if
/// `decl` and `specs` are well-formed.
pub fn resolve(
decl: &ConfigDecl,
base_values: ConfigValuesData,
parent_overrides: Option<&Vec<ConfigOverride>>,
) -> Result<Self, ResolutionError> {
// base values must have been packaged for the exact same layout as the decl contains
if decl.checksum != base_values.checksum {
return Err(ResolutionError::ChecksumFailure {
expected: decl.checksum.clone(),
received: base_values.checksum.clone(),
});
}
// ensure we have the correct number of base values for resolving by offset
if decl.fields.len() != base_values.values.len() {
return Err(ResolutionError::WrongNumberOfValues {
expected: decl.fields.len(),
received: base_values.values.len(),
});
}
// reject overrides which don't match any fields in the schema
if let Some(overrides) = parent_overrides {
for key in overrides.iter().map(|o| &o.key) {
if !decl.fields.iter().any(|f| &f.key == key) {
return Err(ResolutionError::FieldDoesNotExist { key: key.to_owned() });
}
}
}
let mut resolved_fields = vec![];
for (decl_field, spec_field) in decl.fields.iter().zip(base_values.values.into_iter()) {
// see if the parent component provided a value
let mut from_parent = None;
if let Some(parent_overrides) = parent_overrides {
for o in parent_overrides {
// parent overrides are resolved according to string key, not index
if o.key == decl_field.key {
if !decl_field.mutability.contains(ConfigMutability::PARENT) {
return Err(ResolutionError::FieldIsNotMutable {
key: decl_field.key.clone(),
});
}
from_parent = Some(o.value.clone());
}
}
}
// prefer parent-provided values over packaged values
let value = if let Some(v) = from_parent { v } else { spec_field.value };
let resolved_field = ConfigField::resolve(value, &decl_field).map_err(|source| {
ResolutionError::InvalidValue { key: decl_field.key.clone(), source }
})?;
resolved_fields.push(resolved_field);
}
Ok(Self { fields: resolved_fields, checksum: decl.checksum.clone() })
}
/// Encode the resolved fields as a FIDL struct with every field non-nullable.
///
/// The first two bytes of the encoded buffer are a little-endian unsigned integer which denotes
/// the `checksum_length`. Bytes `2..2+checksum_length` are used to store the declaration
/// checksum. All remaining bytes are used to store the FIDL header and struct.
pub fn encode_as_fidl_struct(self) -> Vec<u8> {
let Self { fields, checksum: ConfigChecksum::Sha256(checksum) } = self;
let mut structure = Structure::default();
for ConfigField { value, .. } in fields {
structure = match value {
ConfigValue::Single(ConfigSingleValue::Bool(b)) => {
structure.field(Field::Basic(BasicField::Bool(b)))
}
ConfigValue::Single(ConfigSingleValue::Uint8(n)) => {
structure.field(Field::Basic(BasicField::UInt8(n)))
}
ConfigValue::Single(ConfigSingleValue::Uint16(n)) => {
structure.field(Field::Basic(BasicField::UInt16(n)))
}
ConfigValue::Single(ConfigSingleValue::Uint32(n)) => {
structure.field(Field::Basic(BasicField::UInt32(n)))
}
ConfigValue::Single(ConfigSingleValue::Uint64(n)) => {
structure.field(Field::Basic(BasicField::UInt64(n)))
}
ConfigValue::Single(ConfigSingleValue::Int8(n)) => {
structure.field(Field::Basic(BasicField::Int8(n)))
}
ConfigValue::Single(ConfigSingleValue::Int16(n)) => {
structure.field(Field::Basic(BasicField::Int16(n)))
}
ConfigValue::Single(ConfigSingleValue::Int32(n)) => {
structure.field(Field::Basic(BasicField::Int32(n)))
}
ConfigValue::Single(ConfigSingleValue::Int64(n)) => {
structure.field(Field::Basic(BasicField::Int64(n)))
}
ConfigValue::Single(ConfigSingleValue::String(s)) => {
// TODO(https://fxbug.dev/42169377) improve string representation too
structure.field(Field::Vector(VectorField::UInt8Vector(s.into_bytes())))
}
ConfigValue::Vector(ConfigVectorValue::BoolVector(b)) => {
structure.field(Field::Vector(VectorField::BoolVector(b)))
}
ConfigValue::Vector(ConfigVectorValue::Uint8Vector(n)) => {
structure.field(Field::Vector(VectorField::UInt8Vector(n)))
}
ConfigValue::Vector(ConfigVectorValue::Uint16Vector(n)) => {
structure.field(Field::Vector(VectorField::UInt16Vector(n)))
}
ConfigValue::Vector(ConfigVectorValue::Uint32Vector(n)) => {
structure.field(Field::Vector(VectorField::UInt32Vector(n)))
}
ConfigValue::Vector(ConfigVectorValue::Uint64Vector(n)) => {
structure.field(Field::Vector(VectorField::UInt64Vector(n)))
}
ConfigValue::Vector(ConfigVectorValue::Int8Vector(n)) => {
structure.field(Field::Vector(VectorField::Int8Vector(n)))
}
ConfigValue::Vector(ConfigVectorValue::Int16Vector(n)) => {
structure.field(Field::Vector(VectorField::Int16Vector(n)))
}
ConfigValue::Vector(ConfigVectorValue::Int32Vector(n)) => {
structure.field(Field::Vector(VectorField::Int32Vector(n)))
}
ConfigValue::Vector(ConfigVectorValue::Int64Vector(n)) => {
structure.field(Field::Vector(VectorField::Int64Vector(n)))
}
ConfigValue::Vector(ConfigVectorValue::StringVector(s)) => {
structure.field(Field::Vector(
// TODO(https://fxbug.dev/42169377) improve string representation too
VectorField::UInt8VectorVector(
s.into_iter().map(|s| s.into_bytes()).collect(),
),
))
}
};
}
let mut buf = Vec::new();
buf.extend((checksum.len() as u16).to_le_bytes());
buf.extend(checksum);
buf.extend(structure.encode_persistent());
buf
}
}
impl Into<fdecl::ResolvedConfig> for ConfigFields {
fn into(self) -> fdecl::ResolvedConfig {
let checksum = self.checksum.native_into_fidl();
let fields = self.fields.into_iter().map(|f| f.into()).collect();
fdecl::ResolvedConfig { checksum, fields }
}
}
/// A single resolved configuration field.
#[derive(Clone, Debug, PartialEq)]
pub struct ConfigField {
/// The configuration field's key.
pub key: String,
/// The configuration field's value.
pub value: ConfigValue,
/// Ways this component's packaged values could have been overridden.
pub mutability: ConfigMutability,
}
impl ConfigField {
/// Reconciles a config field schema from the manifest with a value from the value file.
/// If the types and constraints don't match, an error is returned.
pub fn resolve(value: ConfigValue, decl_field: &ConfigFieldDecl) -> Result<Self, ValueError> {
let key = decl_field.key.clone();
match (&value, &decl_field.type_) {
(ConfigValue::Single(ConfigSingleValue::Bool(_)), ConfigValueType::Bool)
| (ConfigValue::Single(ConfigSingleValue::Uint8(_)), ConfigValueType::Uint8)
| (ConfigValue::Single(ConfigSingleValue::Uint16(_)), ConfigValueType::Uint16)
| (ConfigValue::Single(ConfigSingleValue::Uint32(_)), ConfigValueType::Uint32)
| (ConfigValue::Single(ConfigSingleValue::Uint64(_)), ConfigValueType::Uint64)
| (ConfigValue::Single(ConfigSingleValue::Int8(_)), ConfigValueType::Int8)
| (ConfigValue::Single(ConfigSingleValue::Int16(_)), ConfigValueType::Int16)
| (ConfigValue::Single(ConfigSingleValue::Int32(_)), ConfigValueType::Int32)
| (ConfigValue::Single(ConfigSingleValue::Int64(_)), ConfigValueType::Int64) => (),
(
ConfigValue::Single(ConfigSingleValue::String(text)),
ConfigValueType::String { max_size },
) => {
let max_size = *max_size as usize;
if text.len() > max_size {
return Err(ValueError::StringTooLong { max: max_size, actual: text.len() });
}
}
(ConfigValue::Vector(list), ConfigValueType::Vector { nested_type, max_count }) => {
let max_count = *max_count as usize;
let actual_count = match (list, nested_type) {
(ConfigVectorValue::BoolVector(l), ConfigNestedValueType::Bool) => l.len(),
(ConfigVectorValue::Uint8Vector(l), ConfigNestedValueType::Uint8) => l.len(),
(ConfigVectorValue::Uint16Vector(l), ConfigNestedValueType::Uint16) => l.len(),
(ConfigVectorValue::Uint32Vector(l), ConfigNestedValueType::Uint32) => l.len(),
(ConfigVectorValue::Uint64Vector(l), ConfigNestedValueType::Uint64) => l.len(),
(ConfigVectorValue::Int8Vector(l), ConfigNestedValueType::Int8) => l.len(),
(ConfigVectorValue::Int16Vector(l), ConfigNestedValueType::Int16) => l.len(),
(ConfigVectorValue::Int32Vector(l), ConfigNestedValueType::Int32) => l.len(),
(ConfigVectorValue::Int64Vector(l), ConfigNestedValueType::Int64) => l.len(),
(
ConfigVectorValue::StringVector(l),
ConfigNestedValueType::String { max_size },
) => {
let max_size = *max_size as usize;
for (i, s) in l.iter().enumerate() {
if s.len() > max_size {
return Err(ValueError::VectorElementInvalid {
offset: i,
source: Box::new(ValueError::StringTooLong {
max: max_size,
actual: s.len(),
}),
});
}
}
l.len()
}
(other_list, other_ty) => {
return Err(ValueError::TypeMismatch {
expected: format!("{:?}", other_ty),
received: format!("{:?}", other_list),
})
}
};
if actual_count > max_count {
return Err(ValueError::VectorTooLong { max: max_count, actual: actual_count });
}
}
(other_val, other_ty) => {
return Err(ValueError::TypeMismatch {
expected: format!("{:?}", other_ty),
received: format!("{:?}", other_val),
});
}
}
Ok(ConfigField { key, value, mutability: decl_field.mutability })
}
}
impl Into<fdecl::ResolvedConfigField> for ConfigField {
fn into(self) -> fdecl::ResolvedConfigField {
fdecl::ResolvedConfigField { key: self.key, value: self.value.native_into_fidl() }
}
}
#[derive(Clone, Debug, Error, PartialEq)]
#[allow(missing_docs)]
pub enum ResolutionError {
#[error("Checksums in declaration and value file do not match. Expected {expected:04x?}, received {received:04x?}")]
ChecksumFailure { expected: ConfigChecksum, received: ConfigChecksum },
#[error("Value file has a different number of values ({received}) than declaration has fields ({expected}).")]
WrongNumberOfValues { expected: usize, received: usize },
#[error("Provided a value for `{key}` which is not in the component's configuration schema.")]
FieldDoesNotExist { key: String },
#[error("Provided an override which is not mutable in the component's configuration schema.")]
FieldIsNotMutable { key: String },
#[error("Received invalid value for `{key}`.")]
InvalidValue {
key: String,
#[source]
source: ValueError,
},
}
#[derive(Clone, Debug, Error, PartialEq)]
#[allow(missing_docs)]
pub enum ValueError {
#[error("Value of type `{received}` does not match declaration of type {expected}.")]
TypeMismatch { expected: String, received: String },
#[error("Received string of length {actual} for a field with a max of {max}.")]
StringTooLong { max: usize, actual: usize },
#[error("Received vector of length {actual} for a field with a max of {max}.")]
VectorTooLong { max: usize, actual: usize },
#[error("Vector element at {offset} index is invalid.")]
VectorElementInvalid {
offset: usize,
#[source]
source: Box<Self>,
},
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_component_config_ext::{config_decl, values_data};
use fidl_test_config_encoder::BasicSuccessSchema;
use ConfigSingleValue::*;
use ConfigValue::*;
use ConfigVectorValue::*;
#[test]
fn basic_success() {
let decl = config_decl! {
ck@ ConfigChecksum::Sha256([0; 32]),
my_flag: { bool },
my_uint8: { uint8 },
my_uint16: { uint16 },
my_uint32: { uint32 },
my_uint64: { uint64 },
my_int8: { int8 },
my_int16: { int16 },
my_int32: { int32 },
my_int64: { int64 },
my_string: { string, max_size: 100 },
my_vector_of_flag: { vector, element: bool, max_count: 100 },
my_vector_of_uint8: { vector, element: uint8, max_count: 100 },
my_vector_of_uint16: { vector, element: uint16, max_count: 100 },
my_vector_of_uint32: { vector, element: uint32, max_count: 100 },
my_vector_of_uint64: { vector, element: uint64, max_count: 100 },
my_vector_of_int8: { vector, element: int8, max_count: 100 },
my_vector_of_int16: { vector, element: int16, max_count: 100 },
my_vector_of_int32: { vector, element: int32, max_count: 100 },
my_vector_of_int64: { vector, element: int64, max_count: 100 },
my_vector_of_string: {
vector,
element: { string, max_size: 100 },
max_count: 100
},
};
let specs = values_data![
ck@ decl.checksum.clone(),
Single(Bool(false)),
Single(Uint8(255u8)),
Single(Uint16(65535u16)),
Single(Uint32(4000000000u32)),
Single(Uint64(8000000000u64)),
Single(Int8(-127i8)),
Single(Int16(-32766i16)),
Single(Int32(-2000000000i32)),
Single(Int64(-4000000000i64)),
Single(String("hello, world!".into())),
Vector(BoolVector(vec![true, false])),
Vector(Uint8Vector(vec![1, 2, 3])),
Vector(Uint16Vector(vec![2, 3, 4])),
Vector(Uint32Vector(vec![3, 4, 5])),
Vector(Uint64Vector(vec![4, 5, 6])),
Vector(Int8Vector(vec![-1, -2, 3])),
Vector(Int16Vector(vec![-2, -3, 4])),
Vector(Int32Vector(vec![-3, -4, 5])),
Vector(Int64Vector(vec![-4, -5, 6])),
Vector(StringVector(vec!["valid".into(), "valid".into()])),
];
let resolved = ConfigFields::resolve(&decl, specs, None).unwrap();
assert_eq!(
resolved,
ConfigFields {
fields: vec![
ConfigField {
key: "my_flag".to_string(),
value: Single(Bool(false)),
mutability: Default::default(),
},
ConfigField {
key: "my_uint8".to_string(),
value: Single(Uint8(255)),
mutability: Default::default(),
},
ConfigField {
key: "my_uint16".to_string(),
value: Single(Uint16(65535)),
mutability: Default::default(),
},
ConfigField {
key: "my_uint32".to_string(),
value: Single(Uint32(4000000000)),
mutability: Default::default(),
},
ConfigField {
key: "my_uint64".to_string(),
value: Single(Uint64(8000000000)),
mutability: Default::default(),
},
ConfigField {
key: "my_int8".to_string(),
value: Single(Int8(-127)),
mutability: Default::default(),
},
ConfigField {
key: "my_int16".to_string(),
value: Single(Int16(-32766)),
mutability: Default::default(),
},
ConfigField {
key: "my_int32".to_string(),
value: Single(Int32(-2000000000)),
mutability: Default::default(),
},
ConfigField {
key: "my_int64".to_string(),
value: Single(Int64(-4000000000)),
mutability: Default::default(),
},
ConfigField {
key: "my_string".to_string(),
value: Single(String("hello, world!".into())),
mutability: Default::default(),
},
ConfigField {
key: "my_vector_of_flag".to_string(),
value: Vector(BoolVector(vec![true, false])),
mutability: Default::default(),
},
ConfigField {
key: "my_vector_of_uint8".to_string(),
value: Vector(Uint8Vector(vec![1, 2, 3])),
mutability: Default::default(),
},
ConfigField {
key: "my_vector_of_uint16".to_string(),
value: Vector(Uint16Vector(vec![2, 3, 4])),
mutability: Default::default(),
},
ConfigField {
key: "my_vector_of_uint32".to_string(),
value: Vector(Uint32Vector(vec![3, 4, 5])),
mutability: Default::default(),
},
ConfigField {
key: "my_vector_of_uint64".to_string(),
value: Vector(Uint64Vector(vec![4, 5, 6])),
mutability: Default::default(),
},
ConfigField {
key: "my_vector_of_int8".to_string(),
value: Vector(Int8Vector(vec![-1, -2, 3])),
mutability: Default::default(),
},
ConfigField {
key: "my_vector_of_int16".to_string(),
value: Vector(Int16Vector(vec![-2, -3, 4])),
mutability: Default::default(),
},
ConfigField {
key: "my_vector_of_int32".to_string(),
value: Vector(Int32Vector(vec![-3, -4, 5])),
mutability: Default::default(),
},
ConfigField {
key: "my_vector_of_int64".to_string(),
value: Vector(Int64Vector(vec![-4, -5, 6])),
mutability: Default::default(),
},
ConfigField {
key: "my_vector_of_string".to_string(),
value: Vector(StringVector(vec!["valid".into(), "valid".into()])),
mutability: Default::default(),
},
],
checksum: decl.checksum.clone(),
}
);
let encoded = resolved.encode_as_fidl_struct();
let checksum_len = u16::from_le_bytes(encoded[..2].try_into().unwrap());
let struct_start = 2 + checksum_len as usize;
assert_eq!(&encoded[2..struct_start], [0; 32]);
let decoded: BasicSuccessSchema = fidl::unpersist(&encoded[struct_start..]).unwrap();
assert_eq!(
decoded,
BasicSuccessSchema {
my_flag: false,
my_uint8: 255,
my_uint16: 65535,
my_uint32: 4000000000,
my_uint64: 8000000000,
my_int8: -127,
my_int16: -32766,
my_int32: -2000000000,
my_int64: -4000000000,
my_string: "hello, world!".into(),
my_vector_of_flag: vec![true, false],
my_vector_of_uint8: vec![1, 2, 3],
my_vector_of_uint16: vec![2, 3, 4],
my_vector_of_uint32: vec![3, 4, 5],
my_vector_of_uint64: vec![4, 5, 6],
my_vector_of_int8: vec![-1, -2, 3],
my_vector_of_int16: vec![-2, -3, 4],
my_vector_of_int32: vec![-3, -4, 5],
my_vector_of_int64: vec![-4, -5, 6],
my_vector_of_string: vec!["valid".into(), "valid".into()],
}
);
}
#[test]
fn checksums_must_match() {
let expected = ConfigChecksum::Sha256([0; 32]);
let received = ConfigChecksum::Sha256([0xFF; 32]);
let decl = config_decl! {
ck@ expected.clone(),
foo: { bool },
};
let specs = values_data! [
ck@ received.clone(),
ConfigValue::Single(ConfigSingleValue::Bool(true)),
];
assert_eq!(
ConfigFields::resolve(&decl, specs, None).unwrap_err(),
ResolutionError::ChecksumFailure { expected, received }
);
}
#[test]
fn too_many_values_fails() {
let decl = config_decl! {
ck@ ConfigChecksum::Sha256([0; 32]),
foo: { bool },
};
let specs = values_data! [
ck@ decl.checksum.clone(),
ConfigValue::Single(ConfigSingleValue::Bool(true)),
ConfigValue::Single(ConfigSingleValue::Bool(false)),
];
assert_eq!(
ConfigFields::resolve(&decl, specs, None).unwrap_err(),
ResolutionError::WrongNumberOfValues { expected: 1, received: 2 }
);
}
#[test]
fn not_enough_values_fails() {
let decl = config_decl! {
ck@ ConfigChecksum::Sha256([0; 32]),
foo: { bool },
};
let specs = values_data! {
ck@ decl.checksum.clone(),
};
assert_eq!(
ConfigFields::resolve(&decl, specs, None).unwrap_err(),
ResolutionError::WrongNumberOfValues { expected: 1, received: 0 }
);
}
#[test]
fn string_length_is_validated() {
let decl = config_decl! {
ck@ ConfigChecksum::Sha256([0; 32]),
foo: { string, max_size: 10 },
};
let specs = values_data! [
ck@ decl.checksum.clone(),
ConfigValue::Single(ConfigSingleValue::String("hello, world!".into())),
];
assert_eq!(
ConfigFields::resolve(&decl, specs, None).unwrap_err(),
ResolutionError::InvalidValue {
key: "foo".to_string(),
source: ValueError::StringTooLong { max: 10, actual: 13 },
}
);
}
#[test]
fn vector_length_is_validated() {
let decl = config_decl! {
ck@ ConfigChecksum::Sha256([0; 32]),
foo: { vector, element: uint8, max_count: 2 },
};
let specs = values_data! [
ck@ decl.checksum.clone(),
ConfigValue::Vector(ConfigVectorValue::Uint8Vector(vec![1, 2, 3])),
];
assert_eq!(
ConfigFields::resolve(&decl, specs, None).unwrap_err(),
ResolutionError::InvalidValue {
key: "foo".to_string(),
source: ValueError::VectorTooLong { max: 2, actual: 3 },
}
);
}
#[test]
fn vector_elements_validated() {
let decl = config_decl! {
ck@ ConfigChecksum::Sha256([0; 32]),
foo: { vector, element: { string, max_size: 5 }, max_count: 2 },
};
let specs = values_data! [
ck@ decl.checksum.clone(),
ConfigValue::Vector(ConfigVectorValue::StringVector(vec![
"valid".into(),
"invalid".into(),
])),
];
assert_eq!(
ConfigFields::resolve(&decl, specs, None).unwrap_err(),
ResolutionError::InvalidValue {
key: "foo".to_string(),
source: ValueError::VectorElementInvalid {
offset: 1,
source: Box::new(ValueError::StringTooLong { max: 5, actual: 7 })
},
}
);
}
#[test]
fn parent_overrides_take_precedence() {
let overridden_key = "my_flag".to_string();
let defaulted_key = "my_other_flag".to_string();
let decl = cm_rust::ConfigDecl {
fields: vec![
cm_rust::ConfigField {
key: overridden_key.clone(),
type_: cm_rust::ConfigValueType::Bool,
mutability: ConfigMutability::PARENT,
},
cm_rust::ConfigField {
key: defaulted_key.clone(),
type_: cm_rust::ConfigValueType::Bool,
mutability: ConfigMutability::empty(),
},
],
checksum: ConfigChecksum::Sha256([0; 32]),
value_source: cm_rust::ConfigValueSource::PackagePath("fake.cvf".to_string()),
};
let packaged = values_data![
ck@ decl.checksum.clone(),
Single(Bool(false)),
Single(Bool(false)),
];
let expected_value = Single(Bool(true));
let overrides =
vec![ConfigOverride { key: overridden_key.clone(), value: expected_value.clone() }];
assert_eq!(
ConfigFields::resolve(&decl, packaged, Some(&overrides)).unwrap(),
ConfigFields {
fields: vec![
ConfigField {
key: overridden_key.clone(),
value: expected_value,
mutability: ConfigMutability::PARENT,
},
ConfigField {
key: defaulted_key.clone(),
value: Single(Bool(false)),
mutability: ConfigMutability::empty(),
},
],
checksum: decl.checksum.clone(),
}
);
}
#[test]
fn overrides_must_match_declared_fields() {
let decl = cm_rust::ConfigDecl {
fields: vec![cm_rust::ConfigField {
key: "my_flag".to_string(),
type_: cm_rust::ConfigValueType::Bool,
mutability: ConfigMutability::PARENT,
}],
checksum: ConfigChecksum::Sha256([0; 32]),
value_source: cm_rust::ConfigValueSource::PackagePath("fake.cvf".to_string()),
};
let packaged = values_data![
ck@ decl.checksum.clone(),
Single(Bool(false)),
];
let overridden_key = "not_my_flag".to_string();
let expected_value = Single(Bool(true));
let overrides =
vec![ConfigOverride { key: overridden_key.clone(), value: expected_value.clone() }];
assert_eq!(
ConfigFields::resolve(&decl, packaged, Some(&overrides)).unwrap_err(),
ResolutionError::FieldDoesNotExist { key: overridden_key.clone() },
);
}
#[test]
fn overrides_must_be_for_mutable_by_parent_fields() {
let overridden_key = "my_flag".to_string();
let defaulted_key = "my_other_flag".to_string();
let decl = cm_rust::ConfigDecl {
fields: vec![
cm_rust::ConfigField {
key: overridden_key.clone(),
type_: cm_rust::ConfigValueType::Bool,
mutability: ConfigMutability::empty(),
},
// this field having parent mutability should not allow the other field to mutate
cm_rust::ConfigField {
key: defaulted_key.clone(),
type_: cm_rust::ConfigValueType::Bool,
mutability: ConfigMutability::PARENT,
},
],
checksum: ConfigChecksum::Sha256([0; 32]),
value_source: cm_rust::ConfigValueSource::PackagePath("fake.cvf".to_string()),
};
let packaged = values_data![
ck@ decl.checksum.clone(),
Single(Bool(false)),
Single(Bool(false)),
];
let overrides =
vec![ConfigOverride { key: overridden_key.clone(), value: Single(Bool(true)) }];
assert_eq!(
ConfigFields::resolve(&decl, packaged, Some(&overrides)).unwrap_err(),
ResolutionError::FieldIsNotMutable { key: overridden_key.clone() },
);
}
#[test]
fn overrides_must_match_declared_type_exactly() {
let overridden_key = "my_flag".to_string();
let decl = cm_rust::ConfigDecl {
fields: vec![cm_rust::ConfigField {
key: overridden_key.clone(),
type_: cm_rust::ConfigValueType::Bool,
mutability: ConfigMutability::PARENT,
}],
checksum: ConfigChecksum::Sha256([0; 32]),
value_source: cm_rust::ConfigValueSource::PackagePath("fake.cvf".to_string()),
};
let packaged = values_data![
ck@ decl.checksum.clone(),
Single(Bool(false)),
];
let overrides =
vec![ConfigOverride { key: overridden_key.clone(), value: Single(Uint8(1)) }];
assert_eq!(
ConfigFields::resolve(&decl, packaged, Some(&overrides)).unwrap_err(),
ResolutionError::InvalidValue {
key: overridden_key.clone(),
source: ValueError::TypeMismatch {
expected: "Bool".to_string(),
received: "Single(Uint8(1))".to_string()
},
},
);
}
macro_rules! type_mismatch_test {
($test_name:ident: { $($ty_toks:tt)* }, $valid_spec:pat) => {
#[test]
fn $test_name() {
let decl = ConfigFieldDecl {
key: "test_key".to_string(),
type_: fidl_fuchsia_component_config_ext::config_ty!($($ty_toks)*),
mutability: Default::default(),
};
for value in [
// one value of each type
Single(Bool(true)),
Single(Uint8(1)),
Single(Uint16(1)),
Single(Uint32(1)),
Single(Uint64(1)),
Single(Int8(1)),
Single(Int16(1)),
Single(Int32(1)),
Single(Int64(1)),
Single(String("".to_string())),
Vector(BoolVector(vec![])),
Vector(Uint8Vector(vec![])),
Vector(Uint16Vector(vec![])),
Vector(Uint32Vector(vec![])),
Vector(Uint64Vector(vec![])),
Vector(Int8Vector(vec![])),
Vector(Int16Vector(vec![])),
Vector(Int32Vector(vec![])),
Vector(Int64Vector(vec![])),
Vector(StringVector(vec![])),
] {
let should_succeed = matches!(value, $valid_spec);
match ConfigField::resolve(value, &decl) {
Ok(..) if should_succeed => (),
Err(ValueError::TypeMismatch { .. }) if !should_succeed => (),
other => panic!(
"test case {:?} received unexpected resolved value {:#?}",
decl, other
),
}
}
}
};
}
type_mismatch_test!(bool_type_mismatches: { bool }, Single(Bool(..)));
type_mismatch_test!(uint8_type_mismatches: { uint8 }, Single(Uint8(..)));
type_mismatch_test!(uint16_type_mismatches: { uint16 }, Single(Uint16(..)));
type_mismatch_test!(uint32_type_mismatches: { uint32 }, Single(Uint32(..)));
type_mismatch_test!(uint64_type_mismatches: { uint64 }, Single(Uint64(..)));
type_mismatch_test!(int8_type_mismatches: { int8 }, Single(Int8(..)));
type_mismatch_test!(int16_type_mismatches: { int16 }, Single(Int16(..)));
type_mismatch_test!(int32_type_mismatches: { int32 }, Single(Int32(..)));
type_mismatch_test!(int64_type_mismatches: { int64 }, Single(Int64(..)));
type_mismatch_test!(string_type_mismatches: { string, max_size: 10 }, Single(String(..)));
type_mismatch_test!(
bool_vector_type_mismatches: { vector, element: bool, max_count: 1 }, Vector(BoolVector(..))
);
type_mismatch_test!(
uint8_vector_type_mismatches:
{ vector, element: uint8, max_count: 1 },
Vector(Uint8Vector(..))
);
type_mismatch_test!(
uint16_vector_type_mismatches:
{ vector, element: uint16, max_count: 1 },
Vector(Uint16Vector(..))
);
type_mismatch_test!(
uint32_vector_type_mismatches:
{ vector, element: uint32, max_count: 1 },
Vector(Uint32Vector(..))
);
type_mismatch_test!(
uint64_vector_type_mismatches:
{ vector, element: uint64, max_count: 1 },
Vector(Uint64Vector(..))
);
type_mismatch_test!(
int8_vector_type_mismatches:
{ vector, element: int8, max_count: 1 },
Vector(Int8Vector(..))
);
type_mismatch_test!(
int16_vector_type_mismatches:
{ vector, element: int16, max_count: 1 },
Vector(Int16Vector(..))
);
type_mismatch_test!(
int32_vector_type_mismatches:
{ vector, element: int32, max_count: 1 },
Vector(Int32Vector(..))
);
type_mismatch_test!(
int64_vector_type_mismatches:
{ vector, element: int64, max_count: 1 },
Vector(Int64Vector(..))
);
type_mismatch_test!(
string_vector_type_mismatches:
{ vector, element: { string, max_size: 10 }, max_count: 1 },
Vector(StringVector(..))
);
}