1use cm_rust::{
8 ConfigNestedValueType, ConfigSingleValue, ConfigValue, ConfigValueType, ConfigVectorValue,
9};
10use serde_json::Value as JsonValue;
11use std::fmt::{Display, Formatter, Result as FmtResult};
12use std::num::TryFromIntError;
13
14pub fn config_value_from_json_value(
17 val: &JsonValue,
18 value_type: &ConfigValueType,
19) -> Result<ConfigValue, FieldError> {
20 Ok(match value_type {
21 ConfigValueType::Bool => ConfigValue::Single(ConfigSingleValue::Bool(val.parse_bool()?)),
22 ConfigValueType::Uint8 => ConfigValue::Single(ConfigSingleValue::Uint8(val.parse_u8()?)),
23 ConfigValueType::Uint16 => ConfigValue::Single(ConfigSingleValue::Uint16(val.parse_u16()?)),
24 ConfigValueType::Uint32 => ConfigValue::Single(ConfigSingleValue::Uint32(val.parse_u32()?)),
25 ConfigValueType::Uint64 => ConfigValue::Single(ConfigSingleValue::Uint64(val.parse_u64()?)),
26 ConfigValueType::Int8 => ConfigValue::Single(ConfigSingleValue::Int8(val.parse_i8()?)),
27 ConfigValueType::Int16 => ConfigValue::Single(ConfigSingleValue::Int16(val.parse_i16()?)),
28 ConfigValueType::Int32 => ConfigValue::Single(ConfigSingleValue::Int32(val.parse_i32()?)),
29 ConfigValueType::Int64 => ConfigValue::Single(ConfigSingleValue::Int64(val.parse_i64()?)),
30 ConfigValueType::String { max_size } => {
31 ConfigValue::Single(ConfigSingleValue::String(val.parse_string(*max_size)?.into()))
32 }
33 ConfigValueType::Vector { max_count, nested_type } => {
34 ConfigValue::Vector(vector_value_from_json(val, max_count, nested_type)?)
35 }
36 })
37}
38
39fn vector_value_from_json(
41 val: &JsonValue,
42 max_count: &u32,
43 nested_type: &ConfigNestedValueType,
44) -> Result<ConfigVectorValue, FieldError> {
45 let array = val.as_array().ok_or_else(|| FieldError::JsonTypeMismatch {
47 expected: JsonTy::Array,
48 received: val.ty(),
49 })?;
50 let max = *max_count as usize;
51 if array.len() > max {
52 return Err(FieldError::VectorTooLong { max, actual: array.len() });
53 }
54
55 macro_rules! list_from_array {
59 ($list_variant:ident, $val:ident => $convert:expr) => {{
60 let mut list = vec![];
61 for $val in array {
62 list.push($convert);
63 }
64 ConfigVectorValue::$list_variant(list.into())
65 }};
66 }
67
68 Ok(match nested_type {
69 ConfigNestedValueType::Bool => {
70 list_from_array!(BoolVector, v => v.parse_bool()?)
71 }
72 ConfigNestedValueType::Uint8 => list_from_array!(Uint8Vector, v => v.parse_u8()?),
73 ConfigNestedValueType::Uint16 => {
74 list_from_array!(Uint16Vector, v => v.parse_u16()?)
75 }
76 ConfigNestedValueType::Uint32 => {
77 list_from_array!(Uint32Vector, v => v.parse_u32()?)
78 }
79 ConfigNestedValueType::Uint64 => {
80 list_from_array!(Uint64Vector, v => v.parse_u64()?)
81 }
82 ConfigNestedValueType::Int8 => list_from_array!(Int8Vector, v => v.parse_i8()?),
83 ConfigNestedValueType::Int16 => list_from_array!(Int16Vector, v => v.parse_i16()?),
84 ConfigNestedValueType::Int32 => list_from_array!(Int32Vector, v => v.parse_i32()?),
85 ConfigNestedValueType::Int64 => list_from_array!(Int64Vector, v => v.parse_i64()?),
86 ConfigNestedValueType::String { max_size } => {
87 list_from_array!(StringVector, v => v.parse_string(*max_size)?.into())
88 }
89 })
90}
91
92trait JsonValueExt {
93 fn parse_bool(&self) -> Result<bool, FieldError>;
94 fn parse_u8(&self) -> Result<u8, FieldError>;
95 fn parse_u16(&self) -> Result<u16, FieldError>;
96 fn parse_u32(&self) -> Result<u32, FieldError>;
97 fn parse_u64(&self) -> Result<u64, FieldError>;
98 fn parse_i8(&self) -> Result<i8, FieldError>;
99 fn parse_i16(&self) -> Result<i16, FieldError>;
100 fn parse_i32(&self) -> Result<i32, FieldError>;
101 fn parse_i64(&self) -> Result<i64, FieldError>;
102 fn parse_string(&self, max: u32) -> Result<String, FieldError>;
103 fn ty(&self) -> JsonTy;
104 fn expected(&self, ty: JsonTy) -> FieldError;
105}
106
107fn check_integer(v: &JsonValue) -> Result<(), FieldError> {
108 if !v.is_number() {
109 Err(FieldError::JsonTypeMismatch { expected: JsonTy::Number, received: v.ty() })
110 } else if !(v.is_i64()) {
111 Err(FieldError::NumberNotInteger)
112 } else {
113 Ok(())
114 }
115}
116
117fn check_unsigned(v: &JsonValue) -> Result<(), FieldError> {
118 if v.is_u64() { Ok(()) } else { Err(FieldError::NumberNotUnsigned) }
119}
120
121impl JsonValueExt for JsonValue {
122 fn parse_bool(&self) -> Result<bool, FieldError> {
123 self.as_bool().ok_or_else(|| self.expected(JsonTy::Bool))
124 }
125 fn parse_u8(&self) -> Result<u8, FieldError> {
126 check_integer(self)?;
127 check_unsigned(self)?;
128 Ok(<u8>::try_from(self.as_u64().ok_or_else(|| self.expected(JsonTy::Number))?)?)
129 }
130 fn parse_u16(&self) -> Result<u16, FieldError> {
131 check_integer(self)?;
132 check_unsigned(self)?;
133 Ok(<u16>::try_from(self.as_u64().ok_or_else(|| self.expected(JsonTy::Number))?)?)
134 }
135 fn parse_u32(&self) -> Result<u32, FieldError> {
136 check_integer(self)?;
137 check_unsigned(self)?;
138 Ok(<u32>::try_from(self.as_u64().ok_or_else(|| self.expected(JsonTy::Number))?)?)
139 }
140 fn parse_u64(&self) -> Result<u64, FieldError> {
141 check_integer(self)?;
142 check_unsigned(self)?;
143 self.as_u64().ok_or_else(|| self.expected(JsonTy::Number))
144 }
145 fn parse_i8(&self) -> Result<i8, FieldError> {
146 check_integer(self)?;
147 Ok(<i8>::try_from(self.as_i64().ok_or_else(|| self.expected(JsonTy::Number))?)?)
148 }
149 fn parse_i16(&self) -> Result<i16, FieldError> {
150 check_integer(self)?;
151 Ok(<i16>::try_from(self.as_i64().ok_or_else(|| self.expected(JsonTy::Number))?)?)
152 }
153 fn parse_i32(&self) -> Result<i32, FieldError> {
154 check_integer(self)?;
155 Ok(<i32>::try_from(self.as_i64().ok_or_else(|| self.expected(JsonTy::Number))?)?)
156 }
157 fn parse_i64(&self) -> Result<i64, FieldError> {
158 check_integer(self)?;
159 self.as_i64().ok_or_else(|| self.expected(JsonTy::Number))
160 }
161 fn parse_string(&self, max: u32) -> Result<String, FieldError> {
162 let max = max as usize;
163 let s = self.as_str().ok_or_else(|| FieldError::JsonTypeMismatch {
164 expected: JsonTy::String,
165 received: self.ty(),
166 })?;
167 if s.len() > max {
168 Err(FieldError::StringTooLong { max, actual: s.len() })
169 } else {
170 Ok(s.to_owned())
171 }
172 }
173
174 fn ty(&self) -> JsonTy {
175 match self {
176 JsonValue::Null => JsonTy::Null,
177 JsonValue::Bool(_) => JsonTy::Bool,
178 JsonValue::Number(_) => JsonTy::Number,
179 JsonValue::String(_) => JsonTy::String,
180 JsonValue::Array(_) => JsonTy::Array,
181 JsonValue::Object(_) => JsonTy::Object,
182 }
183 }
184
185 fn expected(&self, expected: JsonTy) -> FieldError {
186 FieldError::JsonTypeMismatch { expected, received: self.ty() }
187 }
188}
189
190#[derive(Debug, thiserror::Error, PartialEq)]
192#[allow(missing_docs)]
193pub enum FieldError {
194 #[error("Expected value of type {expected}, received {received}.")]
195 JsonTypeMismatch { expected: JsonTy, received: JsonTy },
196
197 #[error("Expected number to be unsigned.")]
198 NumberNotUnsigned,
199
200 #[error("Expected number to be an integer.")]
201 NumberNotInteger,
202
203 #[error("String of size {actual} provided for a field with maximum of {max}.")]
204 StringTooLong { max: usize, actual: usize },
205
206 #[error("Vector of count {actual} provided for a field with maximum of {max}.")]
207 VectorTooLong { max: usize, actual: usize },
208
209 #[error("Couldn't parse provided integer as expected type.")]
210 InvalidNumber(
211 #[from]
212 #[source]
213 TryFromIntError,
214 ),
215}
216
217#[allow(missing_docs)]
219#[derive(Clone, Copy, Debug, PartialEq)]
220pub enum JsonTy {
221 Null,
222 Bool,
223 Number,
224 String,
225 Array,
226 Object,
227}
228
229impl Display for JsonTy {
230 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
231 match self {
232 JsonTy::Null => write!(f, "null"),
233 JsonTy::Bool => write!(f, "bool"),
234 JsonTy::Number => write!(f, "number"),
235 JsonTy::String => write!(f, "string"),
236 JsonTy::Array => write!(f, "array"),
237 JsonTy::Object => write!(f, "object"),
238 }
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use fidl_fuchsia_component_config_ext::config_ty;
246 use serde_json::json;
247
248 use FieldError::*;
249
250 fn try_from_int_underflow_error() -> TryFromIntError {
251 u64::try_from(-1i32).unwrap_err()
252 }
253
254 fn try_from_int_overflow_error() -> TryFromIntError {
255 u64::try_from(u128::MAX).unwrap_err()
256 }
257
258 macro_rules! field_parse_tests {
259 (
260 mod: $mod_name:ident,
261 type: { $($type_toks:tt)+ },
262 tests: [$(
263 $eq_test_name:ident: $eq_input:expr => $eq_output:expr,
264 )+]
265 ) => {
266 mod $mod_name {
267 use super::*;
268
269 fn __config_value_type() -> ConfigValueType {
272 config_ty!( $($type_toks)+ )
273 }
274 $(
275 #[test]
276 fn $eq_test_name() {
277 let value = $eq_input;
278 let config_ty = __config_value_type();
279 assert_eq!(
280 config_value_from_json_value(&value, &config_ty),
281 $eq_output,
282 );
283 }
284 )+
285 }
286 };
287 }
288
289 field_parse_tests! {
290 mod: parse_bool,
291 type: { bool },
292 tests: [
293 cant_be_null: json!(null) =>
294 Err(JsonTypeMismatch { expected: JsonTy::Bool, received: JsonTy::Null }),
295 cant_be_number: json!(1) =>
296 Err(JsonTypeMismatch { expected: JsonTy::Bool, received: JsonTy::Number }),
297 cant_be_string: json!("hello, world!") =>
298 Err(JsonTypeMismatch { expected: JsonTy::Bool, received: JsonTy::String }),
299 cant_be_array: json!([1, 2, 3]) =>
300 Err(JsonTypeMismatch { expected: JsonTy::Bool, received: JsonTy::Array }),
301 cant_be_object: json!({"foo": 1, "bar": 2}) =>
302 Err(JsonTypeMismatch { expected: JsonTy::Bool, received: JsonTy::Object }),
303 ]
304 }
305
306 field_parse_tests! {
307 mod: parse_uint8,
308 type: { uint8 },
309 tests: [
310 cant_overflow: json!(256) => Err(InvalidNumber(try_from_int_overflow_error())),
311 cant_be_negative: json!(-1) =>
312 Err(NumberNotUnsigned),
313 cant_be_float: json!(1.0) =>
314 Err(NumberNotInteger),
315 cant_be_null: json!(null) =>
316 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Null }),
317 cant_be_bool: json!(true) =>
318 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Bool }),
319 cant_be_string: json!("hello, world!") =>
320 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::String }),
321 cant_be_array: json!([1, 2, 3]) =>
322 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Array }),
323 cant_be_object: json!({"foo": 1, "bar": 2}) =>
324 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Object }),
325 ]
326 }
327
328 field_parse_tests! {
329 mod: parse_uint16,
330 type: { uint16 },
331 tests: [
332 cant_overflow: json!(65_536) => Err(InvalidNumber(try_from_int_overflow_error())),
333 cant_be_negative: json!(-1) =>
334 Err(NumberNotUnsigned),
335 cant_be_float: json!(1.0) =>
336 Err(NumberNotInteger),
337 cant_be_null: json!(null) =>
338 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Null }),
339 cant_be_bool: json!(true) =>
340 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Bool }),
341 cant_be_string: json!("hello, world!") =>
342 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::String }),
343 cant_be_array: json!([1, 2, 3]) =>
344 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Array }),
345 cant_be_object: json!({"foo": 1, "bar": 2}) =>
346 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Object }),
347 ]
348 }
349
350 field_parse_tests! {
351 mod: parse_uint32,
352 type: { uint32 },
353 tests: [
354 cant_overflow: json!(4_294_967_296u64) => Err(InvalidNumber(try_from_int_overflow_error())),
355 cant_be_negative: json!(-1) =>
356 Err(NumberNotUnsigned),
357 cant_be_float: json!(1.0) =>
358 Err(NumberNotInteger),
359 cant_be_null: json!(null) =>
360 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Null }),
361 cant_be_bool: json!(true) =>
362 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Bool }),
363 cant_be_string: json!("hello, world!") =>
364 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::String }),
365 cant_be_array: json!([1, 2, 3]) =>
366 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Array }),
367 cant_be_object: json!({"foo": 1, "bar": 2}) =>
368 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Object }),
369 ]
370 }
371
372 field_parse_tests! {
373 mod: parse_uint64,
374 type: { uint64 },
375 tests: [
376 cant_be_negative: json!(-1) =>
377 Err(NumberNotUnsigned),
378 cant_be_float: json!(1.0) =>
379 Err(NumberNotInteger),
380 cant_be_null: json!(null) =>
381 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Null }),
382 cant_be_bool: json!(true) =>
383 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Bool }),
384 cant_be_string: json!("hello, world!") =>
385 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::String }),
386 cant_be_array: json!([1, 2, 3]) =>
387 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Array }),
388 cant_be_object: json!({"foo": 1, "bar": 2}) =>
389 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Object }),
390 ]
391 }
392
393 field_parse_tests! {
394 mod: parse_int8,
395 type: { int8 },
396 tests: [
397 cant_underflow: json!(-129) => Err(InvalidNumber(try_from_int_underflow_error())),
398 cant_overflow: json!(128) => Err(InvalidNumber(try_from_int_overflow_error())),
399 cant_be_float: json!(1.0) =>
400 Err(NumberNotInteger),
401 cant_be_null: json!(null) =>
402 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Null }),
403 cant_be_bool: json!(true) =>
404 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Bool }),
405 cant_be_string: json!("hello, world!") =>
406 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::String }),
407 cant_be_array: json!([1, 2, 3]) =>
408 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Array }),
409 cant_be_object: json!({"foo": 1, "bar": 2}) =>
410 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Object }),
411 ]
412 }
413
414 field_parse_tests! {
415 mod: parse_int16,
416 type: { int16 },
417 tests: [
418 cant_underflow: json!(-32_769i32) => Err(InvalidNumber(try_from_int_underflow_error())),
419 cant_overflow: json!(32_768) => Err(InvalidNumber(try_from_int_overflow_error())),
420 cant_be_float: json!(1.0) =>
421 Err(NumberNotInteger),
422 cant_be_null: json!(null) =>
423 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Null }),
424 cant_be_bool: json!(true) =>
425 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Bool }),
426 cant_be_string: json!("hello, world!") =>
427 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::String }),
428 cant_be_array: json!([1, 2, 3]) =>
429 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Array }),
430 cant_be_object: json!({"foo": 1, "bar": 2}) =>
431 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Object }),
432 ]
433 }
434
435 field_parse_tests! {
436 mod: parse_int32,
437 type: { int32 },
438 tests: [
439 cant_underflow: json!(-2_147_483_649i64) => Err(InvalidNumber(try_from_int_underflow_error())),
440 cant_overflow: json!(2_147_483_648i64) => Err(InvalidNumber(try_from_int_overflow_error())),
441 cant_be_float: json!(1.0) =>
442 Err(NumberNotInteger),
443 cant_be_null: json!(null) =>
444 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Null }),
445 cant_be_bool: json!(true) =>
446 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Bool }),
447 cant_be_string: json!("hello, world!") =>
448 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::String }),
449 cant_be_array: json!([1, 2, 3]) =>
450 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Array }),
451 cant_be_object: json!({"foo": 1, "bar": 2}) =>
452 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Object }),
453 ]
454 }
455
456 field_parse_tests! {
457 mod: parse_int64,
458 type: { int64 },
459 tests: [
460 cant_overflow: json!(9_223_372_036_854_775_808u64) =>
461 Err(NumberNotInteger),
462 cant_be_float: json!(1.0) =>
463 Err(NumberNotInteger),
464 cant_be_null: json!(null) =>
465 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Null }),
466 cant_be_bool: json!(true) =>
467 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Bool }),
468 cant_be_string: json!("hello, world!") =>
469 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::String }),
470 cant_be_array: json!([1, 2, 3]) =>
471 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Array }),
472 cant_be_object: json!({"foo": 1, "bar": 2}) =>
473 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::Object }),
474 ]
475 }
476
477 field_parse_tests! {
478 mod: parse_string,
479 type: { string, max_size: 13 },
480 tests: [
481 can_be_empty: json!("") => Ok(ConfigValue::Single(ConfigSingleValue::String("".into()))),
482 max_length_fits: json!("hello, world!") =>
483 Ok(ConfigValue::Single(ConfigSingleValue::String("hello, world!".into()))),
484 cant_be_too_long: json!("1234567890 uhoh") =>
485 Err(StringTooLong { max: 13, actual: 15 }),
486 cant_be_null: json!(null) =>
487 Err(JsonTypeMismatch { expected: JsonTy::String, received: JsonTy::Null }),
488 cant_be_bool: json!(true) =>
489 Err(JsonTypeMismatch { expected: JsonTy::String, received: JsonTy::Bool }),
490 cant_be_number: json!(1) =>
491 Err(JsonTypeMismatch { expected: JsonTy::String, received: JsonTy::Number }),
492 cant_be_array: json!([1, 2, 3]) =>
493 Err(JsonTypeMismatch { expected: JsonTy::String, received: JsonTy::Array }),
494 cant_be_object: json!({"foo": 1, "bar": 2}) =>
495 Err(JsonTypeMismatch { expected: JsonTy::String, received: JsonTy::Object }),
496 ]
497 }
498
499 field_parse_tests! {
500 mod: parse_vector,
501 type: { vector, element: int32, max_count: 5 },
502 tests: [
503 max_length_fits: json!([1, 2, 3, 4, 5]) =>
504 Ok(ConfigValue::Vector(ConfigVectorValue::Int32Vector(Box::from([1, 2, 3, 4, 5])))),
505 can_be_empty: json!([]) => Ok(ConfigValue::Vector(ConfigVectorValue::Int32Vector(Box::from([])))),
506 cant_be_too_long: json!([1, 2, 3, 4, 5, 6]) => Err(VectorTooLong { max: 5, actual: 6}),
507 element_type_must_match: json!(["foo"]) =>
508 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::String }),
509 needs_uniform_elements: json!([1, 2, "hello, world!"]) =>
510 Err(JsonTypeMismatch { expected: JsonTy::Number, received: JsonTy::String }),
511 cant_be_null: json!(null) =>
512 Err(JsonTypeMismatch { expected: JsonTy::Array, received: JsonTy::Null }),
513 cant_be_bool: json!(true) =>
514 Err(JsonTypeMismatch { expected: JsonTy::Array, received: JsonTy::Bool }),
515 cant_be_number: json!(1) =>
516 Err(JsonTypeMismatch { expected: JsonTy::Array, received: JsonTy::Number }),
517 cant_be_object: json!({"foo": 1, "bar": 2}) =>
518 Err(JsonTypeMismatch { expected: JsonTy::Array, received: JsonTy::Object }),
519 ]
520 }
521}