1use crate::versions::ChartExtKindValuesUi;
2use crate::versions::ChartExtVersionV1Beta1;
3use crate::UiSchemaCollections;
4use crate::UiSchemaInputError;
5use rust_decimal::Decimal;
6use serde::Deserialize;
7use serde::Serialize;
8use std::collections::BTreeMap;
9use std::collections::HashMap;
10use strum::{EnumDiscriminants, EnumString};
11use uuid::Uuid;
12
13#[derive(Clone, Debug, Deserialize, Serialize)]
14#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
15#[serde(untagged)]
16pub enum UiSchema {
17 V1Beta1(UiSchemaV1Beta1),
18 V0(UiSchemaV0),
19}
20
21impl UiSchema {
22 pub fn get_inputs(&self) -> &[UiSchemaInput] {
23 match self {
24 Self::V1Beta1(v1) => &v1.inner.inputs,
25 Self::V0(v0) => &v0.inputs,
26 }
27 }
28
29 pub fn get_outputs(&self) -> &UiSchemaOutputs {
30 match self {
31 Self::V1Beta1(v1) => &v1.inner.outputs,
32 Self::V0(v0) => &v0.outputs,
33 }
34 }
35
36 pub fn is_collection_in_inputs<C>(
37 &self,
38 inputs: &serde_json::Value,
39 collection: &C,
40 id: &str,
41 ) -> bool
42 where
43 C: UiSchemaCollections,
44 {
45 let collection_value = serde_json::to_value(collection).unwrap();
46 self.get_inputs().iter().any(|input| {
47 let used_collection = match &input.input_type.single_type {
48 UiSchemaInputSingleType::CollectionSelect { collection } => Some(collection),
49 _ => None,
50 };
51 used_collection == Some(&collection_value) && inputs[&input.id] == id
52 })
53 }
54
55 pub async fn get_values<C>(
56 &self,
57 env_id: Uuid,
58 inputs: &serde_json::Value,
59 ) -> Result<Map, UiSchemaInputError<C::Error>>
60 where
61 C: UiSchemaCollections,
62 {
63 let schema_inputs = self.get_inputs();
64 let mut values = Map::new();
65 for output in self.get_outputs().values.iter() {
66 output
67 .resolve_into::<C>(env_id, schema_inputs, inputs, &mut values)
68 .await?;
69 }
70 Ok(values)
71 }
72
73 pub async fn get_secrets<C>(
74 &self,
75 env_id: Uuid,
76 inputs: &serde_json::Value,
77 ) -> Result<Vec<RenderedSecret>, UiSchemaInputError<C::Error>>
78 where
79 C: UiSchemaCollections,
80 {
81 let mut result: Vec<RenderedSecret> = Vec::new();
82 let schema_inputs = self.get_inputs();
83 for (secret_name, attrs_schema) in self.get_outputs().secrets.0.iter() {
84 let mut attrs: BTreeMap<String, String> = Default::default();
85 for (key, attr_schema) in attrs_schema.iter() {
86 let value = match attr_schema
87 .resolve::<C>(env_id, schema_inputs, inputs)
88 .await
89 {
90 Ok(x) => x,
91 Err(UiSchemaInputError::OptionalInputMissing(_)) => continue,
92 Err(other_err) => return Err(other_err),
93 };
94 attrs.insert(
95 key.clone(),
96 value
97 .as_str()
98 .map_or_else(|| value.to_string(), |v| v.to_owned()),
99 );
100 }
101
102 if !attrs.is_empty() {
103 result.push(RenderedSecret {
104 name: secret_name.to_owned(),
105 attrs,
106 })
107 }
108 }
109 Ok(result)
110 }
111}
112
113#[derive(Clone, Debug, Deserialize, Serialize)]
114#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
115#[serde(deny_unknown_fields)]
116pub struct UiSchemaV0 {
117 pub inputs: Vec<UiSchemaInput>,
118 #[serde(default)]
119 pub outputs: UiSchemaOutputs,
120}
121
122#[derive(Clone, Debug, Deserialize, Serialize)]
123#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
124#[serde(rename_all = "camelCase")]
125pub struct UiSchemaV1Beta1 {
126 pub api_version: ChartExtVersionV1Beta1,
127 pub kind: ChartExtKindValuesUi,
128 #[serde(flatten)]
129 pub inner: UiSchemaV0,
130}
131
132#[derive(Clone, Debug, Deserialize, Serialize, EnumString, EnumDiscriminants)]
133#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
134#[strum_discriminants(derive(EnumString, strum::Display))]
135#[strum_discriminants(strum(ascii_case_insensitive))]
136pub enum UiSchemaInputSingleType {
137 #[serde(rename = "text")]
138 Text,
139 #[serde(rename = "number")]
140 Number,
141 CollectionSelect {
142 collection: serde_json::Value,
143 },
144 RadioSelect,
145 DaysAndHour,
146 Checkbox,
147}
148
149#[derive(Clone, Debug, Deserialize, Serialize)]
150#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
151#[serde(try_from = "SerializedUiSchemaInputType")]
152#[serde(into = "SerializedUiSchemaInputType")]
153pub struct UiSchemaInputType {
154 pub single_type: UiSchemaInputSingleType,
155 pub is_array: bool,
156}
157
158#[derive(Clone, Debug, Deserialize, Serialize)]
159#[serde(rename_all = "camelCase")]
160#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
161pub struct SerializedUiSchemaInputType {
162 r#type: String,
163 item_type: Option<String>,
164 collection: Option<serde_json::Value>,
165}
166
167impl TryFrom<SerializedUiSchemaInputType> for UiSchemaInputType {
168 type Error = strum::ParseError;
169
170 fn try_from(s: SerializedUiSchemaInputType) -> Result<Self, Self::Error> {
171 let is_array = s.r#type == "array";
172 let single_type_disc = if is_array {
173 s.item_type.ok_or(strum::ParseError::VariantNotFound)?
174 } else {
175 s.r#type
176 };
177
178 let disc: UiSchemaInputSingleTypeDiscriminants = single_type_disc.parse()?;
179 let single_type = match disc {
180 UiSchemaInputSingleTypeDiscriminants::CollectionSelect => {
181 UiSchemaInputSingleType::CollectionSelect {
182 collection: s.collection.ok_or(strum::ParseError::VariantNotFound)?,
183 }
184 }
185 UiSchemaInputSingleTypeDiscriminants::Text => UiSchemaInputSingleType::Text,
186 UiSchemaInputSingleTypeDiscriminants::Number => UiSchemaInputSingleType::Number,
187 UiSchemaInputSingleTypeDiscriminants::RadioSelect => {
188 UiSchemaInputSingleType::RadioSelect
189 }
190 UiSchemaInputSingleTypeDiscriminants::Checkbox => UiSchemaInputSingleType::Checkbox,
191 UiSchemaInputSingleTypeDiscriminants::DaysAndHour => {
192 UiSchemaInputSingleType::DaysAndHour
193 }
194 };
195 Ok(Self {
196 single_type,
197 is_array,
198 })
199 }
200}
201
202impl From<UiSchemaInputType> for SerializedUiSchemaInputType {
203 fn from(input_type: UiSchemaInputType) -> Self {
204 let (r#type, collection) = match input_type.single_type {
205 UiSchemaInputSingleType::Text => ("text".to_owned(), None),
206 UiSchemaInputSingleType::Number => ("number".to_owned(), None),
207 UiSchemaInputSingleType::CollectionSelect { collection } => {
208 ("CollectionSelect".to_owned(), Some(collection))
209 }
210 UiSchemaInputSingleType::RadioSelect => ("RadioSelect".to_owned(), None),
211 UiSchemaInputSingleType::DaysAndHour => ("DaysAndHour".to_owned(), None),
212 UiSchemaInputSingleType::Checkbox => ("Checkbox".to_owned(), None),
213 };
214 let (r#type, item_type) = if input_type.is_array {
215 ("array".to_owned(), Some(r#type))
216 } else {
217 (r#type, None)
218 };
219 Self {
220 r#type,
221 item_type,
222 collection,
223 }
224 }
225}
226
227#[derive(Clone, Debug, Deserialize, Serialize)]
228#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
229pub struct UiSchemaFieldValuePair {
230 field: String,
231 value: serde_json::Value,
232}
233
234#[derive(Clone, Debug, Deserialize, Serialize)]
235#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
236#[serde(rename_all = "camelCase")]
237pub struct UiSchemaInput {
238 pub id: String,
239 #[serde(flatten)]
240 #[cfg_attr(feature = "utoipa", schema(value_type = SerializedUiSchemaInputType))]
241 pub input_type: UiSchemaInputType, label: String,
243 #[serde(default)]
244 initial_value: Option<serde_json::Value>,
245 #[serde(default)]
246 help_text: Option<String>,
247 #[serde(default)]
248 pub required: bool,
249 #[serde(default)]
250 pub sensitive: bool,
251 #[serde(default)]
252 pub options: Option<Vec<UiSchemaInputFieldOption>>,
253 #[serde(default)]
254 show_if_all: Option<Vec<UiSchemaFieldValuePair>>,
255 #[serde(default)]
256 show_if: Option<serde_json::Value>,
257 #[serde(default)]
258 filters: Option<Vec<UiSchemaInputFieldValue>>,
259 #[serde(default)]
260 minimum: Option<Decimal>,
261 #[serde(default)]
262 maximum: Option<Decimal>,
263 #[serde(default)]
264 step: Option<Decimal>,
265}
266
267#[derive(Clone, Debug, Deserialize, Serialize)]
268#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
269#[serde(rename_all = "camelCase")]
270pub struct UiSchemaInputFieldOption {
271 pub value: serde_json::Value,
272 #[serde(default)]
273 pub label: Option<String>,
274 #[serde(default)]
275 pub help_text: Option<String>,
276}
277
278#[derive(Clone, Debug, Deserialize, Serialize)]
279#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
280pub struct UiSchemaInputFieldValue {
281 pub field: String,
282 pub value: serde_json::Value,
283}
284
285#[derive(Clone, Debug, Default, Deserialize, Serialize)]
286#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
287pub struct UiSchemaOutputSecrets(pub HashMap<String, HashMap<String, UiSchemaInputRef>>);
288
289#[derive(Clone, Debug, Default, Deserialize, Serialize)]
290#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
291pub struct UiSchemaOutputs {
292 pub values: Vec<UiSchemaOutputValue>,
293 #[serde(default)]
294 pub secrets: UiSchemaOutputSecrets,
295}
296
297#[derive(Clone, Debug, Deserialize, Serialize)]
298#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
299pub enum UiSchemaInputRef {
300 FieldValue(UiSchemaInputRefField),
301 FieldProperty(UiSchemaInputRefProperty),
302}
303
304#[derive(Clone, Debug, Deserialize, Serialize)]
305#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
306pub struct UiSchemaInputRefField {
307 pub input: String,
308}
309
310#[derive(Clone, Debug, Deserialize, Serialize)]
311#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
312pub struct UiSchemaInputRefProperty {
313 pub input: String,
314 pub property: String,
315}
316
317impl UiSchemaInputRef {
318 fn get_input_schema<'a, C>(
319 input_schema: &'a [UiSchemaInput],
320 id: &str,
321 ) -> Result<&'a UiSchemaInput, UiSchemaInputError<C::Error>>
322 where
323 C: UiSchemaCollections,
324 {
325 input_schema
326 .iter()
327 .find(|i| i.id == id)
328 .ok_or_else(|| UiSchemaInputError::MissingInputSchema(id.to_owned()))
329 }
330
331 fn get_input<C>(
332 schema: &UiSchemaInput,
333 inputs: &serde_json::Value,
334 id: &str,
335 ) -> Result<serde_json::Value, UiSchemaInputError<C::Error>>
336 where
337 C: UiSchemaCollections,
338 {
339 if let Some(show_if) = schema.show_if.as_ref() {
340 let res = jsonlogic_rs::apply(show_if, inputs);
341 if !matches!(res, Ok(serde_json::Value::Bool(true))) {
342 return Err(UiSchemaInputError::OptionalInputMissing(id.to_owned()));
343 }
344 } else if let Some(show_if_all) = schema.show_if_all.as_ref() {
345 if show_if_all
346 .iter()
347 .any(|fv| inputs.get(&fv.field) != Some(&fv.value))
348 {
349 return Err(UiSchemaInputError::OptionalInputMissing(id.to_owned()));
350 }
351 }
352 Ok(inputs
353 .get(id)
354 .ok_or_else(|| {
355 if schema.required {
356 UiSchemaInputError::MissingInputValue(id.to_owned())
357 } else {
358 UiSchemaInputError::OptionalInputMissing(id.to_owned())
359 }
360 })?
361 .clone())
362 }
363
364 pub async fn resolve<C>(
365 &self,
366 env_id: Uuid,
367 input_schema: &[UiSchemaInput],
368 inputs: &serde_json::Value,
369 ) -> Result<serde_json::Value, UiSchemaInputError<C::Error>>
370 where
371 C: UiSchemaCollections,
372 {
373 match self {
374 Self::FieldValue(fv) => Self::get_input::<C>(
375 Self::get_input_schema::<C>(input_schema, &fv.input)?,
376 inputs,
377 &fv.input,
378 ),
379 Self::FieldProperty(fp) => {
380 let schema = Self::get_input_schema::<C>(input_schema, &fp.input)?;
381 match &schema.input_type.single_type {
382 UiSchemaInputSingleType::CollectionSelect { collection } => {
383 let collections: C = serde_json::from_value(collection.to_owned())
384 .map_err(|err| {
385 UiSchemaInputError::InvalidCollectionName(
386 collection.to_owned(),
387 err,
388 )
389 })?;
390 let id_value = Self::get_input::<C>(schema, inputs, &fp.input)?;
391 if schema.input_type.is_array {
392 let id_value_arr = id_value.as_array().ok_or_else(|| {
393 UiSchemaInputError::InputNotStringArray(fp.input.clone())
394 })?;
395
396 let mut resolved_arr = Vec::new();
397
398 for id_value in id_value_arr {
399 let id = id_value.as_str().ok_or_else(|| {
400 UiSchemaInputError::InputNotStringArray(fp.input.clone())
401 })?;
402 let resolved_value =
403 collections.resolve(env_id, id, &fp.property).await?;
404 resolved_arr.push(resolved_value);
405 }
406 Ok(serde_json::to_value(resolved_arr).unwrap())
407 } else {
408 let id = id_value.as_str().ok_or_else(|| {
409 UiSchemaInputError::InputNotString(fp.input.clone())
410 })?;
411 collections.resolve(env_id, id, &fp.property).await
412 }
413 }
414 _ => Err(UiSchemaInputError::InputNotACollection(fp.input.clone())),
415 }
416 }
417 }
418 }
419}
420
421type Map = serde_json::Map<String, serde_json::Value>;
422
423#[derive(Clone, Debug, Deserialize, Serialize)]
424#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
425pub struct UiSchemaOutputValue {
426 pub path: Vec<String>,
427 pub value: UiSchemaInputRef,
428}
429
430pub fn insert_into_map(map: &mut Map, path: &[String], value: serde_json::Value) {
431 let mut cur_node = map;
432 let mut iter = path.iter().peekable();
433
434 while let Some(part) = iter.next() {
435 if iter.peek().is_none() {
436 cur_node.insert(part.to_owned(), value);
437 return;
438 }
439 if !cur_node.contains_key(part) {
440 cur_node.insert(part.to_owned(), serde_json::Value::Object(Map::new()));
441 }
442 cur_node = cur_node.get_mut(part).unwrap().as_object_mut().unwrap();
443 }
444}
445
446impl UiSchemaOutputValue {
447 pub async fn resolve_into<C>(
448 &self,
449 env_id: Uuid,
450 input_schema: &[UiSchemaInput],
451 inputs: &serde_json::Value,
452 outputs: &mut Map,
453 ) -> Result<(), UiSchemaInputError<C::Error>>
454 where
455 C: UiSchemaCollections,
456 {
457 match self.value.resolve::<C>(env_id, input_schema, inputs).await {
458 Ok(value) => {
459 insert_into_map_ex(outputs, &self.path, value);
460 Ok(())
461 }
462 Err(UiSchemaInputError::OptionalInputMissing(_)) => Ok(()),
463 Err(e) => Err(e),
464 }
465 }
466}
467
468pub struct RenderedSecret {
469 pub name: String,
470 pub attrs: BTreeMap<String, String>,
471}
472
473fn insert_into_map_ex(map: &mut Map, path: &[String], value: serde_json::Value) {
534 let mut iter: std::iter::Peekable<std::slice::Iter<'_, String>> = path.iter().peekable();
535 if iter.peek().is_some() {
536 recursively_insert_into_map(map, &mut iter, value);
537 }
538}
539
540const MAX_ARRAY_SIZE: usize = 1024;
541
542fn recursively_insert_into_map(
543 map: &mut Map,
544 iter: &mut std::iter::Peekable<std::slice::Iter<'_, String>>,
545 value: serde_json::Value,
546) {
547 let part = iter.next().unwrap(); let next_part = iter.peek();
549
550 match next_part {
551 None => {
552 map.insert(part.to_owned(), value);
553 }
554 Some(next_part) => {
555 if map.contains_key(part) {
556 match map.get_mut(part).unwrap() {
557 serde_json::Value::Array(inner_vec) => {
558 recursively_insert_into_vec(inner_vec, iter, value);
559 }
560 serde_json::Value::Object(inner_map) => {
561 recursively_insert_into_map(inner_map, iter, value);
562 }
563 _ => (),
564 }
565 } else if next_part.starts_with('[') && next_part.ends_with(']') {
566 let inner_vec = map
567 .entry(part.to_owned())
568 .or_insert(serde_json::Value::Array(Vec::new()))
569 .as_array_mut()
570 .unwrap();
571 recursively_insert_into_vec(inner_vec, iter, value);
572 } else {
573 let inner_map = map
574 .entry(part.to_owned())
575 .or_insert(serde_json::Value::Object(Map::new()))
576 .as_object_mut()
577 .unwrap();
578 recursively_insert_into_map(inner_map, iter, value);
579 }
580 }
581 };
582}
583
584fn recursively_insert_into_vec(
585 vec: &mut Vec<serde_json::Value>,
586 iter: &mut std::iter::Peekable<std::slice::Iter<'_, String>>,
587 value: serde_json::Value,
588) {
589 let part = iter.next().unwrap(); let inner_part =
592 if let Some(inner_part) = part.strip_prefix('[').and_then(|x| x.strip_suffix(']')) {
593 inner_part
594 } else {
595 return;
596 };
597
598 let cell = if let Ok(number) = inner_part.parse::<usize>() {
599 if number >= MAX_ARRAY_SIZE {
601 return;
602 }
603 if vec.len() < number + 1 {
604 vec.resize(number + 1, Default::default());
605 }
606 &mut vec[number]
607 } else if inner_part == "+" {
608 if vec.len() >= MAX_ARRAY_SIZE {
609 return;
610 }
611
612 vec.push(Default::default());
613 vec.last_mut().unwrap()
614 } else if inner_part == "=" {
615 if vec.is_empty() {
616 vec.push(Default::default());
617 }
618 vec.last_mut().unwrap()
619 } else {
620 return;
621 };
622
623 let next_part = iter.peek();
624
625 match next_part {
626 None => {
627 *cell = value;
628 }
629 Some(next_part) => match cell {
630 serde_json::Value::Object(inner_map) => {
631 recursively_insert_into_map(inner_map, iter, value);
632 }
633 serde_json::Value::Array(inner_vec) => {
634 recursively_insert_into_vec(inner_vec, iter, value);
635 }
636 serde_json::Value::Null => {
637 if next_part.starts_with('[') && next_part.ends_with(']') {
638 *cell = serde_json::Value::Array(Vec::new());
639 recursively_insert_into_vec(cell.as_array_mut().unwrap(), iter, value);
640 } else {
641 *cell = serde_json::Value::Object(Map::new());
642 recursively_insert_into_map(cell.as_object_mut().unwrap(), iter, value);
643 }
644 }
645 _ => (),
646 },
647 }
648}
649
650#[cfg(test)]
651mod test {
652 use super::insert_into_map_ex;
653
654 fn str_arr(strs: &[&str]) -> Vec<String> {
655 strs.iter().map(|s| s.to_string()).collect()
656 }
657
658 #[test]
659 fn test_insert_into_map_ex() {
660 let mut value = serde_json::json!({});
661
662 insert_into_map_ex(
663 value.as_object_mut().unwrap(),
664 &str_arr(&["v"]),
665 "v_val".into(),
666 );
667
668 insert_into_map_ex(
669 value.as_object_mut().unwrap(),
670 &str_arr(&["m", "k1"]),
671 "m_k1_val".into(),
672 );
673
674 insert_into_map_ex(
675 value.as_object_mut().unwrap(),
676 &str_arr(&["m", "k2"]),
677 "m_k2_val".into(),
678 );
679
680 insert_into_map_ex(
681 value.as_object_mut().unwrap(),
682 &str_arr(&["m", "m", "k1"]),
683 "m_m_k1_val".into(),
684 );
685
686 insert_into_map_ex(
687 value.as_object_mut().unwrap(),
688 &str_arr(&["a1", "[=]"]),
689 "a1_0_val".into(),
690 );
691
692 insert_into_map_ex(
693 value.as_object_mut().unwrap(),
694 &str_arr(&["a1", "[+]"]),
695 "a1_1_val".into(),
696 );
697
698 insert_into_map_ex(
699 value.as_object_mut().unwrap(),
700 &str_arr(&["a2", "[=]", "k1"]),
701 "a2_0_k1_val".into(),
702 );
703
704 insert_into_map_ex(
705 value.as_object_mut().unwrap(),
706 &str_arr(&["a2", "[=]", "k2"]),
707 "a2_0_k2_val".into(),
708 );
709
710 insert_into_map_ex(
711 value.as_object_mut().unwrap(),
712 &str_arr(&["a2", "[+]", "k1"]),
713 "a2_1_k1_val".into(),
714 );
715
716 insert_into_map_ex(
717 value.as_object_mut().unwrap(),
718 &str_arr(&["a2", "[+]", "k1"]),
719 "a2_2_k1_val".into(),
720 );
721
722 insert_into_map_ex(
723 value.as_object_mut().unwrap(),
724 &str_arr(&["a2", "[1]", "k2"]),
725 "a2_1_k2_val".into(),
726 );
727
728 insert_into_map_ex(
729 value.as_object_mut().unwrap(),
730 &str_arr(&["a3", "[=]", "[=]"]),
731 "a3_0_0_val".into(),
732 );
733
734 insert_into_map_ex(
735 value.as_object_mut().unwrap(),
736 &str_arr(&["a3", "[=]", "[+]"]),
737 "a3_0_1_val".into(),
738 );
739
740 insert_into_map_ex(
741 value.as_object_mut().unwrap(),
742 &str_arr(&["a3", "[=]", "[+]", "k1"]),
743 "a3_0_2_k1_val".into(),
744 );
745
746 insert_into_map_ex(
747 value.as_object_mut().unwrap(),
748 &str_arr(&["a3", "[=]", "[=]", "k2"]),
749 "a3_0_2_k2_val".into(),
750 );
751
752 insert_into_map_ex(
753 value.as_object_mut().unwrap(),
754 &str_arr(&["a3", "[+]", "[=]", "k1"]),
755 "a3_1_0_k1_val".into(),
756 );
757
758 insert_into_map_ex(
759 value.as_object_mut().unwrap(),
760 &str_arr(&["a3", "[=]", "[2]"]),
761 "a3_1_2_val".into(),
762 );
763
764 insert_into_map_ex(
765 value.as_object_mut().unwrap(),
766 &str_arr(&["a3", "[0]", "[=]", "k3"]),
767 "a3_0_2_k3_val".into(),
768 );
769
770 let expected = serde_json::json!({
771 "v": "v_val",
772 "m": {
773 "k1": "m_k1_val",
774 "k2": "m_k2_val",
775 "m": {
776 "k1" : "m_m_k1_val"
777 }
778 },
779 "a1": [
780 "a1_0_val",
781 "a1_1_val"
782 ],
783 "a2": [
784 {
785 "k1": "a2_0_k1_val",
786 "k2": "a2_0_k2_val"
787 },
788 {
789 "k1": "a2_1_k1_val",
790 "k2": "a2_1_k2_val"
791 },
792 {
793 "k1": "a2_2_k1_val",
794 }
795 ],
796 "a3": [
797 [
798 "a3_0_0_val",
799 "a3_0_1_val",
800 {
801 "k1": "a3_0_2_k1_val",
802 "k2": "a3_0_2_k2_val",
803 "k3": "a3_0_2_k3_val",
804 }
805 ],
806 [
807 {
808 "k1": "a3_1_0_k1_val"
809 },
810 null,
811 "a3_1_2_val"
812 ]
813 ]
814 });
815
816 assert_eq!(value, expected)
817 }
818}