config_value_file/
field.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//! Validating and encoding individual configuration fields.
6
7use 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
14/// Returns a FIDL value to encode in the file for the provided JSON value, if and only if the value
15/// matches the type from the declaration.
16pub 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
39/// Parse `val` as a vector of configuration values.
40fn vector_value_from_json(
41    val: &JsonValue,
42    max_count: &u32,
43    nested_type: &ConfigNestedValueType,
44) -> Result<ConfigVectorValue, FieldError> {
45    // define our array up here so its identifier is available for our helper macro
46    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    /// Build a ConfigVectorValue out of all the array elements.
56    ///
57    /// A macro because enum variants don't exist at the type level in Rust at time of writing.
58    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/// Error from working with a field from a configuration value file.
195#[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/// The types a [`serde_json::Value`] can have. Used for error reporting.
222#[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                // macro repetitions need to have the same nesting in expansion as they do in
270                // parsing, so we need a top-level function here rather than inside the tests repeat
271                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}