config_value_file/
lib.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![warn(missing_docs)]
6
7//! A library for creating configuration value files as described in [Fuchsia RFC-0127].
8//!
9//! [Fuchsia RFC-0127]: https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/0127_structured_configuration
10
11pub mod field;
12
13use crate::field::{config_value_from_json_value, FieldError};
14use cm_rust::{ConfigDecl, ConfigValueSpec, ConfigValuesData};
15use serde_json::Value as JsonValue;
16use std::collections::BTreeMap;
17
18/// Create a configuration value file from the compiled manifest's config declaration and a map of
19/// configuration keys to JSON values.
20// TODO(https://fxbug.dev/42167846) decide on a better interface than json values?
21pub fn populate_value_file(
22    config_decl: &ConfigDecl,
23    mut json_values: BTreeMap<String, JsonValue>,
24) -> Result<ConfigValuesData, FileError> {
25    let values = config_decl
26        .fields
27        .iter()
28        .map(|field| {
29            let json_value = json_values
30                .remove(&field.key)
31                .ok_or_else(|| FileError::MissingValue { key: field.key.clone() })?;
32            let value = config_value_from_json_value(&json_value, &field.type_)
33                .map_err(|reason| FileError::InvalidField { key: field.key.clone(), reason })?;
34            Ok(ConfigValueSpec { value })
35        })
36        .collect::<Result<Vec<ConfigValueSpec>, _>>()?;
37
38    // we remove the definitions from the values map above, so any remaining keys are undefined
39    // in the manifest
40    if !json_values.is_empty() {
41        return Err(FileError::ExtraValues { keys: json_values.into_keys().collect() });
42    }
43
44    Ok(ConfigValuesData { values, checksum: config_decl.checksum.clone() })
45}
46
47/// Error from working with a configuration value file.
48#[derive(Debug, thiserror::Error, PartialEq)]
49#[allow(missing_docs)]
50pub enum FileError {
51    #[error("Invalid config field `{key}`")]
52    InvalidField {
53        key: String,
54        #[source]
55        reason: FieldError,
56    },
57
58    #[error("`{key}` field in manifest does not have a value defined.")]
59    MissingValue { key: String },
60
61    #[error("Fields `{keys:?}` in value definition do not exist in manifest.")]
62    ExtraValues { keys: Vec<String> },
63}
64
65#[cfg(test)]
66mod tests {
67    use super::field::JsonTy;
68    use super::*;
69    use cm_rust::{ConfigChecksum, ConfigSingleValue, ConfigValue, ConfigVectorValue};
70    use fidl_fuchsia_component_config_ext::{config_decl, values_data};
71    use serde_json::json;
72
73    fn test_checksum() -> ConfigChecksum {
74        // sha256("Back to the Fuchsia")
75        ConfigChecksum::Sha256([
76            0xb5, 0xf9, 0x33, 0xe8, 0x94, 0x56, 0x3a, 0xf9, 0x61, 0x39, 0xe5, 0x05, 0x79, 0x4b,
77            0x88, 0xa5, 0x3e, 0xd4, 0xd1, 0x5c, 0x32, 0xe2, 0xb4, 0x49, 0x9e, 0x42, 0xeb, 0xa3,
78            0x32, 0xb1, 0xf5, 0xbb,
79        ])
80    }
81
82    #[test]
83    fn basic_success() {
84        let decl = config_decl! {
85            ck@ test_checksum(),
86            my_flag: { bool },
87            my_uint8: { uint8 },
88            my_uint16: { uint16 },
89            my_uint32: { uint32 },
90            my_uint64: { uint64 },
91            my_int8: { int8 },
92            my_int16: { int16 },
93            my_int32: { int32 },
94            my_int64: { int64 },
95            my_string: { string, max_size: 100 },
96            my_vector_of_flag: { vector, element: bool, max_count: 100 },
97            my_vector_of_uint8: { vector, element: uint8, max_count: 100 },
98            my_vector_of_uint16: { vector, element: uint16, max_count: 100 },
99            my_vector_of_uint32: { vector, element: uint32, max_count: 100 },
100            my_vector_of_uint64: { vector, element: uint64, max_count: 100 },
101            my_vector_of_int8: { vector, element: int8, max_count: 100 },
102            my_vector_of_int16: { vector, element: int16, max_count: 100 },
103            my_vector_of_int32: { vector, element: int32, max_count: 100 },
104            my_vector_of_int64: { vector, element: int64, max_count: 100 },
105            my_vector_of_string: {
106                vector,
107                element: { string, max_size: 100 },
108                max_count: 100
109            },
110        };
111
112        let values: BTreeMap<String, serde_json::Value> = serde_json::from_value(json!({
113            "my_flag": false,
114            "my_uint8": 255u8,
115            "my_uint16": 65535u16,
116            "my_uint32": 4000000000u32,
117            "my_uint64": 8000000000u64,
118            "my_int8": -127i8,
119            "my_int16": -32766i16,
120            "my_int32": -2000000000i32,
121            "my_int64": -4000000000i64,
122            "my_string": "hello, world!",
123            "my_vector_of_flag": [ true, false ],
124            "my_vector_of_uint8": [ 1, 2, 3 ],
125            "my_vector_of_uint16": [ 2, 3, 4 ],
126            "my_vector_of_uint32": [ 3, 4, 5 ],
127            "my_vector_of_uint64": [ 4, 5, 6 ],
128            "my_vector_of_int8": [ -1, -2, 3 ],
129            "my_vector_of_int16": [ -2, -3, 4 ],
130            "my_vector_of_int32": [ -3, -4, 5 ],
131            "my_vector_of_int64": [ -4, -5, 6 ],
132            "my_vector_of_string": [ "hello, world!", "hello, again!" ],
133        }))
134        .unwrap();
135
136        let expected = values_data![
137            ck@ test_checksum(),
138            ConfigValue::Single(ConfigSingleValue::Bool(false)),
139            ConfigValue::Single(ConfigSingleValue::Uint8(255u8)),
140            ConfigValue::Single(ConfigSingleValue::Uint16(65535u16)),
141            ConfigValue::Single(ConfigSingleValue::Uint32(4000000000u32)),
142            ConfigValue::Single(ConfigSingleValue::Uint64(8000000000u64)),
143            ConfigValue::Single(ConfigSingleValue::Int8(-127i8)),
144            ConfigValue::Single(ConfigSingleValue::Int16(-32766i16)),
145            ConfigValue::Single(ConfigSingleValue::Int32(-2000000000i32)),
146            ConfigValue::Single(ConfigSingleValue::Int64(-4000000000i64)),
147            ConfigValue::Single(ConfigSingleValue::String("hello, world!".into())),
148            ConfigValue::Vector(ConfigVectorValue::BoolVector(vec![true, false])),
149            ConfigValue::Vector(ConfigVectorValue::Uint8Vector(vec![1, 2, 3])),
150            ConfigValue::Vector(ConfigVectorValue::Uint16Vector(vec![2, 3, 4])),
151            ConfigValue::Vector(ConfigVectorValue::Uint32Vector(vec![3, 4, 5])),
152            ConfigValue::Vector(ConfigVectorValue::Uint64Vector(vec![4, 5, 6])),
153            ConfigValue::Vector(ConfigVectorValue::Int8Vector(vec![-1, -2, 3])),
154            ConfigValue::Vector(ConfigVectorValue::Int16Vector(vec![-2, -3, 4])),
155            ConfigValue::Vector(ConfigVectorValue::Int32Vector(vec![-3, -4, 5])),
156            ConfigValue::Vector(ConfigVectorValue::Int64Vector(vec![-4, -5, 6])),
157            ConfigValue::Vector(ConfigVectorValue::StringVector(
158                vec!["hello, world!".into(), "hello, again!".into()])
159            ),
160        ];
161
162        let observed = populate_value_file(&decl, values).unwrap();
163        assert_eq!(observed, expected);
164    }
165
166    #[test]
167    fn invalid_field_is_correctly_identified() {
168        let decl = config_decl! {
169            ck@ test_checksum(),
170            my_flag: { bool },
171            my_uint8: { uint8 },
172        };
173
174        let values: BTreeMap<String, serde_json::Value> = serde_json::from_value(json!({
175            "my_flag": false,
176            "my_uint8": true,
177        }))
178        .unwrap();
179
180        assert_eq!(
181            populate_value_file(&decl, values).unwrap_err(),
182            FileError::InvalidField {
183                key: "my_uint8".to_string(),
184                reason: FieldError::JsonTypeMismatch {
185                    expected: JsonTy::Number,
186                    received: JsonTy::Bool
187                }
188            }
189        );
190    }
191
192    #[test]
193    fn all_keys_must_be_defined() {
194        let decl = config_decl! {
195            ck@ test_checksum(),
196            my_flag: { bool },
197            my_uint8: { uint8 },
198        };
199
200        let values: BTreeMap<String, serde_json::Value> = serde_json::from_value(json!({
201            "my_flag": false,
202        }))
203        .unwrap();
204
205        assert_eq!(
206            populate_value_file(&decl, values).unwrap_err(),
207            FileError::MissingValue { key: "my_uint8".to_string() }
208        );
209    }
210
211    #[test]
212    fn no_extra_keys_can_be_defined() {
213        let decl = config_decl! {
214            ck@ test_checksum(),
215            my_flag: { bool },
216        };
217
218        let values: BTreeMap<String, serde_json::Value> = serde_json::from_value(json!({
219            "my_flag": false,
220            "my_uint8": 1,
221            "my_uint16": 2,
222        }))
223        .unwrap();
224
225        assert_eq!(
226            populate_value_file(&decl, values).unwrap_err(),
227            FileError::ExtraValues { keys: vec!["my_uint16".into(), "my_uint8".into()] }
228        );
229    }
230}