platz_chart_ext/
ui_schema.rs

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, // Parsed from actual fields: type, item_type and collection, see SerializedUiSchemaInputType
242    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
473/// Extends insert_into_map by supporting syntax for path items which are array cell's references.
474///
475/// A path element of [NUMBER] (with the bracket) will regard current location as an array and will create null
476/// items as neccessary up to required.
477///
478/// A path element of [=] will reference the last existing element of the array (if empty, will create an element)
479///
480/// A path element of [+] will add an element in the end of the array and refer to it.
481/// UiSchemaOutputs's values are resolved in order of appearance, so this creates a predictable array
482///
483/// Arrays of arrays, maps of arrays, arrays of maps, maps of maps, are all supported.
484/// Array sizes are limited to protect from bad input.
485///
486/// A fair share of examples is present in the test_insert_into_map_ex function below.
487///
488/// Do note that if you supply the path in a yaml array (as in values-ui.yaml outputs), you'll have to quote around the brackets
489/// Examples for values-ui.yaml:
490///
491/// ```yaml
492/// outputs:
493///   values:
494///   - path:
495///       - config
496///       - selected
497///       - "[+]"
498///       - id
499///     value:
500///       FieldProperty:
501///         input: conditional_select1
502///         property: id
503///   - path:
504///       - config
505///       - selected
506///       - "[=]"
507///       - name
508///     value:
509///       FieldProperty:
510///         input: conditional_select1
511///         property: name
512///   - path:
513///       - config
514///       - selected
515///       - "[+]"
516///       - id
517///     value:
518///       FieldProperty:
519///         input: conditional_select2
520///         property: id
521///   - path:
522///       - config
523///       - selected
524///       - "[=]"
525///       - name
526///     value:
527///       FieldProperty:
528///         input: conditional_select2
529///         property: name
530/// ```
531///
532/// In the example, we output an array "config.selected", whose items are values with keys "id", and "name"
533fn 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(); // Safe to unwrap since we alwasy peek before we next
548    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(); // Safe to unwrap since we alwasy peek before we next
590
591    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        // Gotta protect ourselves from bad input
600        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}