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)?))
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! vector_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)
65 }};
66 }
67
68 Ok(match nested_type {
69 ConfigNestedValueType::Bool => {
70 vector_from_array!(BoolVector, v => v.parse_bool()?)
71 }
72 ConfigNestedValueType::Uint8 => vector_from_array!(Uint8Vector, v => v.parse_u8()?),
73 ConfigNestedValueType::Uint16 => {
74 vector_from_array!(Uint16Vector, v => v.parse_u16()?)
75 }
76 ConfigNestedValueType::Uint32 => {
77 vector_from_array!(Uint32Vector, v => v.parse_u32()?)
78 }
79 ConfigNestedValueType::Uint64 => {
80 vector_from_array!(Uint64Vector, v => v.parse_u64()?)
81 }
82 ConfigNestedValueType::Int8 => vector_from_array!(Int8Vector, v => v.parse_i8()?),
83 ConfigNestedValueType::Int16 => vector_from_array!(Int16Vector, v => v.parse_i16()?),
84 ConfigNestedValueType::Int32 => vector_from_array!(Int32Vector, v => v.parse_i32()?),
85 ConfigNestedValueType::Int64 => vector_from_array!(Int64Vector, v => v.parse_i64()?),
86 ConfigNestedValueType::String { max_size } => {
87 vector_from_array!(StringVector, v => v.parse_string(*max_size)?)
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() {
119 Ok(())
120 } else {
121 Err(FieldError::NumberNotUnsigned)
122 }
123}
124
125impl JsonValueExt for JsonValue {
126 fn parse_bool(&self) -> Result<bool, FieldError> {
127 self.as_bool().ok_or_else(|| self.expected(JsonTy::Bool))
128 }
129 fn parse_u8(&self) -> Result<u8, FieldError> {
130 check_integer(self)?;
131 check_unsigned(self)?;
132 Ok(<u8>::try_from(self.as_u64().ok_or_else(|| self.expected(JsonTy::Number))?)?)
133 }
134 fn parse_u16(&self) -> Result<u16, FieldError> {
135 check_integer(self)?;
136 check_unsigned(self)?;
137 Ok(<u16>::try_from(self.as_u64().ok_or_else(|| self.expected(JsonTy::Number))?)?)
138 }
139 fn parse_u32(&self) -> Result<u32, FieldError> {
140 check_integer(self)?;
141 check_unsigned(self)?;
142 Ok(<u32>::try_from(self.as_u64().ok_or_else(|| self.expected(JsonTy::Number))?)?)
143 }
144 fn parse_u64(&self) -> Result<u64, FieldError> {
145 check_integer(self)?;
146 check_unsigned(self)?;
147 self.as_u64().ok_or_else(|| self.expected(JsonTy::Number))
148 }
149 fn parse_i8(&self) -> Result<i8, FieldError> {
150 check_integer(self)?;
151 Ok(<i8>::try_from(self.as_i64().ok_or_else(|| self.expected(JsonTy::Number))?)?)
152 }
153 fn parse_i16(&self) -> Result<i16, FieldError> {
154 check_integer(self)?;
155 Ok(<i16>::try_from(self.as_i64().ok_or_else(|| self.expected(JsonTy::Number))?)?)
156 }
157 fn parse_i32(&self) -> Result<i32, FieldError> {
158 check_integer(self)?;
159 Ok(<i32>::try_from(self.as_i64().ok_or_else(|| self.expected(JsonTy::Number))?)?)
160 }
161 fn parse_i64(&self) -> Result<i64, FieldError> {
162 check_integer(self)?;
163 self.as_i64().ok_or_else(|| self.expected(JsonTy::Number))
164 }
165 fn parse_string(&self, max: u32) -> Result<String, FieldError> {
166 let max = max as usize;
167 let s = self.as_str().ok_or_else(|| FieldError::JsonTypeMismatch {
168 expected: JsonTy::String,
169 received: self.ty(),
170 })?;
171 if s.len() > max {
172 Err(FieldError::StringTooLong { max, actual: s.len() })
173 } else {
174 Ok(s.to_owned())
175 }
176 }
177
178 fn ty(&self) -> JsonTy {
179 match self {
180 JsonValue::Null => JsonTy::Null,
181 JsonValue::Bool(_) => JsonTy::Bool,
182 JsonValue::Number(_) => JsonTy::Number,
183 JsonValue::String(_) => JsonTy::String,
184 JsonValue::Array(_) => JsonTy::Array,
185 JsonValue::Object(_) => JsonTy::Object,
186 }
187 }
188
189 fn expected(&self, expected: JsonTy) -> FieldError {
190 FieldError::JsonTypeMismatch { expected, received: self.ty() }
191 }
192}
193
194#[derive(Debug, thiserror::Error, PartialEq)]
196#[allow(missing_docs)]
197pub enum FieldError {
198 #[error("Expected value of type {expected}, received {received}.")]
199 JsonTypeMismatch { expected: JsonTy, received: JsonTy },
200
201 #[error("Expected number to be unsigned.")]
202 NumberNotUnsigned,
203
204 #[error("Expected number to be an integer.")]
205 NumberNotInteger,
206
207 #[error("String of size {actual} provided for a field with maximum of {max}.")]
208 StringTooLong { max: usize, actual: usize },
209
210 #[error("Vector of count {actual} provided for a field with maximum of {max}.")]
211 VectorTooLong { max: usize, actual: usize },
212
213 #[error("Couldn't parse provided integer as expected type.")]
214 InvalidNumber(
215 #[from]
216 #[source]
217 TryFromIntError,
218 ),
219}
220
221#[allow(missing_docs)]
223#[derive(Clone, Copy, Debug, PartialEq)]
224pub enum JsonTy {
225 Null,
226 Bool,
227 Number,
228 String,
229 Array,
230 Object,
231}
232
233impl Display for JsonTy {
234 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
235 match self {
236 JsonTy::Null => write!(f, "null"),
237 JsonTy::Bool => write!(f, "bool"),
238 JsonTy::Number => write!(f, "number"),
239 JsonTy::String => write!(f, "string"),
240 JsonTy::Array => write!(f, "array"),
241 JsonTy::Object => write!(f, "object"),
242 }
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use fidl_fuchsia_component_config_ext::config_ty;
250 use serde_json::json;
251
252 use FieldError::*;
253
254 fn try_from_int_error() -> TryFromIntError {
255 u64::try_from(-1i32).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_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_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_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_error())),
398 cant_overflow: json!(128) => Err(InvalidNumber(try_from_int_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_error())),
419 cant_overflow: json!(32_768) => Err(InvalidNumber(try_from_int_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_error())),
440 cant_overflow: json!(2_147_483_648i64) => Err(InvalidNumber(try_from_int_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(vec![1, 2, 3, 4, 5]))),
505 can_be_empty: json!([]) => Ok(ConfigValue::Vector(ConfigVectorValue::Int32Vector(vec![]))),
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}