nimbus_fml/backends/swift/gen_structs/
structural.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::fmt::Display;
6
7use super::common::{self, code_type};
8use crate::backends::{LiteralRenderer, VariablesType};
9use crate::{
10    backends::{CodeOracle, CodeType, TypeIdentifier},
11    intermediate_representation::Literal,
12};
13
14pub(crate) struct OptionalCodeType {
15    inner: TypeIdentifier,
16}
17
18impl OptionalCodeType {
19    pub(crate) fn new(inner: &TypeIdentifier) -> Self {
20        Self {
21            inner: inner.clone(),
22        }
23    }
24}
25
26impl CodeType for OptionalCodeType {
27    /// The language specific label used to reference this type. This will be used in
28    /// method signatures and property declarations.
29    fn type_label(&self, oracle: &dyn CodeOracle) -> String {
30        format!(
31            "{item}?",
32            item = oracle.find(&self.inner).type_label(oracle),
33        )
34    }
35
36    /// The language specific expression that gets a value of the `prop` from the `vars` object.
37    fn property_getter(
38        &self,
39        oracle: &dyn CodeOracle,
40        vars: &dyn Display,
41        prop: &dyn Display,
42        default: &dyn Display,
43    ) -> String {
44        // all getters are optional.
45        code_type::property_getter(self, oracle, vars, prop, default)
46    }
47
48    fn value_getter(
49        &self,
50        oracle: &dyn CodeOracle,
51        vars: &dyn Display,
52        prop: &dyn Display,
53    ) -> String {
54        code_type::value_getter(self, oracle, vars, prop)
55    }
56
57    /// The name of the type as it's represented in the `Variables` object.
58    /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
59    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
60        oracle.find(&self.inner).create_transform(oracle)
61    }
62
63    /// The name of the type as it's represented in the `Variables` object.
64    /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
65    fn variables_type(&self, oracle: &dyn CodeOracle) -> VariablesType {
66        oracle.find(&self.inner).variables_type(oracle)
67    }
68
69    /// The method call here will use the `create_transform` to transform the value coming out of
70    /// the `Variables` object into the desired type.
71    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
72        oracle.find(&self.inner).value_mapper(oracle)
73    }
74
75    /// The method call to merge the value with the defaults.
76    ///
77    /// This may use the `merge_transform`.
78    ///
79    /// If this returns `None`, no merging happens, and implicit `null` replacement happens.
80    fn value_merger(&self, oracle: &dyn CodeOracle, default: &dyn Display) -> Option<String> {
81        oracle.find(&self.inner).value_merger(oracle, default)
82    }
83
84    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
85        format!("{}?", oracle.find(&self.inner).defaults_type(oracle))
86    }
87
88    fn defaults_mapper(
89        &self,
90        oracle: &dyn CodeOracle,
91        value: &dyn Display,
92        vars: &dyn Display,
93    ) -> Option<String> {
94        let id = "$0";
95        let mapper = oracle
96            .find(&self.inner)
97            .defaults_mapper(oracle, &id, vars)?;
98        Some(format!(
99            "{value}.map {{ {mapper} }}",
100            value = value,
101            mapper = mapper
102        ))
103    }
104
105    /// Implement these in different code types, and call recursively from different code types.
106    fn as_json_transform(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
107        // We want to return None if the inner's json transform is none,
108        // but if it's not, then use `prop?` as the new prop
109        let prop = format!("{}?", prop);
110        oracle.find(&self.inner).as_json_transform(oracle, &prop)
111    }
112
113    /// A representation of the given literal for this type.
114    /// N.B. `Literal` is aliased from `serde_json::Value`.
115    fn literal(
116        &self,
117        oracle: &dyn CodeOracle,
118        ctx: &dyn Display,
119        renderer: &dyn LiteralRenderer,
120        literal: &Literal,
121    ) -> String {
122        match literal {
123            serde_json::Value::Null => "nil".to_string(),
124            _ => oracle
125                .find(&self.inner)
126                .literal(oracle, ctx, renderer, literal),
127        }
128    }
129}
130
131// Map type
132
133pub(crate) struct MapCodeType {
134    k_type: TypeIdentifier,
135    v_type: TypeIdentifier,
136}
137
138impl MapCodeType {
139    pub(crate) fn new(k: &TypeIdentifier, v: &TypeIdentifier) -> Self {
140        Self {
141            k_type: k.clone(),
142            v_type: v.clone(),
143        }
144    }
145}
146
147impl CodeType for MapCodeType {
148    fn property_getter(
149        &self,
150        oracle: &dyn CodeOracle,
151        vars: &dyn Display,
152        prop: &dyn Display,
153        default: &dyn Display,
154    ) -> String {
155        code_type::property_getter(self, oracle, vars, prop, default)
156    }
157
158    /// The language specific label used to reference this type. This will be used in
159    /// method signatures and property declarations.
160    fn type_label(&self, oracle: &dyn CodeOracle) -> String {
161        format!(
162            "[{k}: {v}]",
163            k = oracle.find(&self.k_type).type_label(oracle),
164            v = oracle.find(&self.v_type).type_label(oracle),
165        )
166    }
167
168    fn value_getter(
169        &self,
170        oracle: &dyn CodeOracle,
171        vars: &dyn Display,
172        prop: &dyn Display,
173    ) -> String {
174        let v_type = oracle.find(&self.v_type);
175        format!(
176            "{vars}.get{vt}Map({prop})",
177            vars = vars,
178            vt = v_type.variables_type(oracle),
179            prop = common::quoted(prop),
180        )
181    }
182
183    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
184        let k_type = oracle.find(&self.k_type);
185        let v_type = oracle.find(&self.v_type);
186        Some(
187            match (
188                k_type.create_transform(oracle),
189                v_type.create_transform(oracle),
190            ) {
191                (Some(k), Some(v)) => format!("mapEntriesNotNull({k}, {v})", k = k, v = v),
192                (None, Some(v)) => format!("mapValuesNotNull({v})", v = v),
193                // We could do something with keys, but it's only every strings and enums.
194                (Some(k), None) => format!("mapKeysNotNull({k})", k = k),
195                _ => return None,
196            },
197        )
198    }
199
200    fn value_merger(&self, oracle: &dyn CodeOracle, default: &dyn Display) -> Option<String> {
201        let v_type = oracle.find(&self.v_type);
202        Some(match v_type.merge_transform(oracle) {
203            Some(transform) => format!(
204                "mergeWith({default}, {transform})",
205                default = default,
206                transform = transform
207            ),
208            None => format!("mergeWith({})", default),
209        })
210    }
211
212    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
213        let vtype = oracle.find(&self.v_type).variables_type(oracle);
214
215        self.value_mapper(oracle)
216            .map(|mapper| {
217                format!(
218                    r#"{{ (_vars) in return _vars.as{vtype}Map()?.{mapper} }}"#,
219                    vtype = vtype,
220                    mapper = mapper
221                )
222            })
223            .or_else(|| {
224                Some(format!(
225                    r#"{{ (_vars) in return _vars.as{vtype}Map()? }}"#,
226                    vtype = vtype
227                ))
228            })
229    }
230
231    fn merge_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
232        let overrides = "_overrides";
233        let defaults = "_defaults";
234
235        self.value_merger(oracle, &defaults).map(|merger| {
236            format!(
237                r#"{{ ({overrides}, {defaults}) in return {overrides}.{merger} }}"#,
238                overrides = overrides,
239                defaults = defaults,
240                merger = merger
241            )
242        })
243    }
244
245    /// The name of the type as it's represented in the `Variables` object.
246    /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
247    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
248        VariablesType::Variables
249    }
250
251    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
252        let k = oracle.find(&self.k_type).defaults_type(oracle);
253        let v = oracle.find(&self.v_type).defaults_type(oracle);
254        format!("[{k}: {v}]", k = k, v = v)
255    }
256
257    fn defaults_mapper(
258        &self,
259        oracle: &dyn CodeOracle,
260        value: &dyn Display,
261        vars: &dyn Display,
262    ) -> Option<String> {
263        let id = "$0";
264        let mapper = oracle
265            .find(&self.v_type)
266            .defaults_mapper(oracle, &id, vars)?;
267        Some(format!(
268            "{value}.mapValues {{ {mapper} }}",
269            value = value,
270            mapper = mapper
271        ))
272    }
273
274    fn as_json_transform(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
275        let k_type = oracle.find(&self.k_type);
276        let v_type = oracle.find(&self.v_type);
277        Some(
278            match (
279                k_type.as_json_transform(oracle, &"$0"),
280                v_type.as_json_transform(oracle, &"$0"),
281            ) {
282                (Some(k), Some(v)) => {
283                    format!(
284                        "{prop}.mapEntriesNotNull({{ {k} }}, {{ {v} }})",
285                        prop = prop,
286                        k = k,
287                        v = v
288                    )
289                }
290                (None, Some(v)) => {
291                    format!("{prop}.mapValuesNotNull {{ {v} }}", prop = prop, v = v)
292                }
293                // We could do something with keys, but it's only every strings and enums.
294                (Some(k), None) => {
295                    format!("{prop}.mapKeysNotNull {{ {k} }}", prop = prop, k = k)
296                }
297                _ => return None,
298            },
299        )
300    }
301
302    /// A representation of the given literal for this type.
303    /// N.B. `Literal` is aliased from `serde_json::Value`.
304    fn literal(
305        &self,
306        oracle: &dyn CodeOracle,
307        ctx: &dyn Display,
308        renderer: &dyn LiteralRenderer,
309        literal: &Literal,
310    ) -> String {
311        let variant = match literal {
312            serde_json::Value::Object(v) => v,
313            _ => unreachable!(),
314        };
315        let k_type = oracle.find(&self.k_type);
316        let v_type = oracle.find(&self.v_type);
317        let src: Vec<String> = variant
318            .iter()
319            .map(|(k, v)| {
320                format!(
321                    "{k}: {v}",
322                    k = k_type.literal(oracle, ctx, renderer, &Literal::String(k.clone())),
323                    v = v_type.literal(oracle, ctx, renderer, v)
324                )
325            })
326            .collect();
327
328        if src.is_empty() {
329            "[:]".to_string()
330        } else {
331            format!("[{}]", src.join(", "))
332        }
333    }
334}
335
336// List type
337
338pub(crate) struct ListCodeType {
339    inner: TypeIdentifier,
340}
341
342impl ListCodeType {
343    pub(crate) fn new(inner: &TypeIdentifier) -> Self {
344        Self {
345            inner: inner.clone(),
346        }
347    }
348}
349
350impl CodeType for ListCodeType {
351    /// The language specific label used to reference this type. This will be used in
352    /// method signatures and property declarations.
353    fn type_label(&self, oracle: &dyn CodeOracle) -> String {
354        format!(
355            "[{item}]",
356            item = oracle.find(&self.inner).type_label(oracle),
357        )
358    }
359
360    fn property_getter(
361        &self,
362        oracle: &dyn CodeOracle,
363        vars: &dyn Display,
364        prop: &dyn Display,
365        default: &dyn Display,
366    ) -> String {
367        code_type::property_getter(self, oracle, vars, prop, default)
368    }
369
370    fn value_getter(
371        &self,
372        oracle: &dyn CodeOracle,
373        vars: &dyn Display,
374        prop: &dyn Display,
375    ) -> String {
376        let vtype = oracle.find(&self.inner).variables_type(oracle);
377        format!(
378            "{vars}.get{vt}List(\"{prop}\")",
379            vars = vars,
380            vt = vtype,
381            prop = prop
382        )
383    }
384
385    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
386        let transform = oracle.find(&self.inner).create_transform(oracle)?;
387        Some(if transform.starts_with('{') {
388            format!("mapNotNull {}", transform)
389        } else {
390            format!("mapNotNull({})", transform)
391        })
392    }
393
394    fn value_merger(&self, _oracle: &dyn CodeOracle, _default: &dyn Display) -> Option<String> {
395        // We never merge lists.
396        None
397    }
398
399    /// The name of the type as it's represented in the `Variables` object.
400    /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
401    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
402        // Our current implementation of Variables doesn't have a getListList() or getListMap().
403        // We do allow getVariablesList and getVariablesMap, but not an vars.asList().
404        unimplemented!("Lists and maps of lists aren't supported. The workaround is to use a list of map of list holder objects")
405    }
406
407    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
408        format!("[{}]", oracle.find(&self.inner).defaults_type(oracle))
409    }
410
411    fn defaults_mapper(
412        &self,
413        oracle: &dyn CodeOracle,
414        value: &dyn Display,
415        vars: &dyn Display,
416    ) -> Option<String> {
417        let id = "$0";
418        let mapper = oracle
419            .find(&self.inner)
420            .defaults_mapper(oracle, &id, vars)?;
421        Some(format!(
422            "{value}.map {{ {mapper} }}",
423            value = value,
424            mapper = mapper
425        ))
426    }
427
428    fn as_json_transform(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
429        let mapper = oracle.find(&self.inner).as_json_transform(oracle, &"$0")?;
430        Some(format!(
431            "{prop}.map {{ {mapper} }}",
432            prop = prop,
433            mapper = mapper
434        ))
435    }
436
437    /// A representation of the given literal for this type.
438    /// N.B. `Literal` is aliased from `serde_json::Value`.
439    fn literal(
440        &self,
441        oracle: &dyn CodeOracle,
442        ctx: &dyn Display,
443        renderer: &dyn LiteralRenderer,
444        literal: &Literal,
445    ) -> String {
446        let variant = match literal {
447            serde_json::Value::Array(v) => v,
448            _ => unreachable!(),
449        };
450
451        let v_type = oracle.find(&self.inner);
452        let src: Vec<String> = variant
453            .iter()
454            .map(|v| v_type.literal(oracle, ctx, renderer, v))
455            .collect();
456
457        format!("[{}]", src.join(", "))
458    }
459}
460
461#[cfg(test)]
462mod unit_tests {
463
464    use serde_json::json;
465
466    use crate::backends::swift::gen_structs::{
467        enum_::EnumCodeType, object::ObjectCodeType, primitives::StringCodeType,
468    };
469    use crate::backends::TypeIdentifier;
470
471    use super::*;
472
473    struct TestCodeOracle;
474    impl CodeOracle for TestCodeOracle {
475        fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
476            match type_ {
477                TypeIdentifier::String => Box::new(StringCodeType) as Box<dyn CodeType>,
478                TypeIdentifier::Enum(s) => {
479                    Box::new(EnumCodeType::new(s.clone())) as Box<dyn CodeType>
480                }
481                TypeIdentifier::Object(s) => {
482                    Box::new(ObjectCodeType::new(s.clone())) as Box<dyn CodeType>
483                }
484                TypeIdentifier::List(i) => Box::new(ListCodeType::new(i)),
485                TypeIdentifier::EnumMap(k, v) => Box::new(MapCodeType::new(k, v)),
486                _ => unreachable!(),
487            }
488        }
489    }
490
491    struct TestRenderer;
492    impl LiteralRenderer for TestRenderer {
493        fn literal(
494            &self,
495            _oracle: &dyn CodeOracle,
496            _typ: &TypeIdentifier,
497            _value: &Literal,
498            _ctx: &dyn Display,
499        ) -> String {
500            unreachable!()
501        }
502    }
503
504    fn oracle() -> Box<dyn CodeOracle> {
505        Box::new(TestCodeOracle) as Box<dyn CodeOracle>
506    }
507
508    fn type_(nm: &str) -> TypeIdentifier {
509        match nm {
510            "String" => TypeIdentifier::String,
511            "AnObject" => TypeIdentifier::Object("AnObject".to_string()),
512            nm => TypeIdentifier::Enum(nm.to_string()),
513        }
514    }
515
516    fn list_type(item: &str) -> Box<dyn CodeType> {
517        Box::new(ListCodeType::new(&type_(item)))
518    }
519
520    fn map_type(k: &str, v: &str) -> Box<dyn CodeType> {
521        Box::new(MapCodeType::new(&type_(k), &type_(v)))
522    }
523
524    fn getter_with_fallback(
525        ct: &dyn CodeType,
526        vars: &dyn Display,
527        prop: &dyn Display,
528        def: &dyn Display,
529    ) -> String {
530        let oracle = &*oracle();
531        ct.property_getter(oracle, vars, prop, def)
532    }
533
534    #[test]
535    fn test_list_type_label() {
536        let oracle = &*oracle();
537        let ct = list_type("String");
538        assert_eq!("[String]".to_string(), ct.type_label(oracle));
539
540        let ct = list_type("AnEnum");
541        assert_eq!("[AnEnum]".to_string(), ct.type_label(oracle));
542    }
543
544    #[test]
545    fn test_list_literal() {
546        let oracle = &*oracle();
547        let finder = &TestRenderer;
548        let ctx = String::from("ctx");
549        let ct = list_type("String");
550        assert_eq!(
551            r#"["x", "y", "z"]"#.to_string(),
552            ct.literal(oracle, &ctx, finder, &json!(["x", "y", "z"]))
553        );
554
555        let ct = list_type("AnEnum");
556        assert_eq!(
557            r#"[.x, .y, .z]"#.to_string(),
558            ct.literal(oracle, &ctx, finder, &json!(["x", "y", "z"]))
559        );
560    }
561
562    #[test]
563    fn test_list_get_value() {
564        let oracle = &*oracle();
565
566        let ct = list_type("AnEnum");
567        assert_eq!(
568            r#"v.getStringList("the-property")"#.to_string(),
569            ct.value_getter(oracle, &"v", &"the-property")
570        );
571
572        let ct = list_type("AnObject");
573        assert_eq!(
574            r#"v.getVariablesList("the-property")"#.to_string(),
575            ct.value_getter(oracle, &"v", &"the-property")
576        );
577
578        let ct = list_type("String");
579        assert_eq!(
580            r#"v.getStringList("the-property")"#.to_string(),
581            ct.value_getter(oracle, &"v", &"the-property")
582        );
583    }
584
585    #[test]
586    fn test_list_getter_with_fallback() {
587        let ct = list_type("String");
588        assert_eq!(
589            r#"vars.getStringList("the-property") ?? default"#.to_string(),
590            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
591        );
592
593        let ct = list_type("AnEnum");
594        assert_eq!(
595            r#"vars.getStringList("the-property")?.mapNotNull(AnEnum.enumValue) ?? default"#
596                .to_string(),
597            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
598        );
599
600        let ct = list_type("AnObject");
601        assert_eq!(
602            r#"vars.getVariablesList("the-property")?.mapNotNull(AnObject.create) ?? default"#
603                .to_string(),
604            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
605        );
606    }
607
608    #[test]
609    fn test_map_type_label() {
610        let oracle = &*oracle();
611        let ct = map_type("String", "String");
612        assert_eq!("[String: String]".to_string(), ct.type_label(oracle));
613
614        let ct = map_type("String", "AnEnum");
615        assert_eq!("[String: AnEnum]".to_string(), ct.type_label(oracle));
616    }
617
618    #[test]
619    fn test_map_literal() {
620        let oracle = &*oracle();
621        let finder = &TestRenderer;
622        let ctx = String::from("ctx");
623
624        let ct = map_type("String", "AnEnum");
625        assert_eq!(
626            r#"["a": .a, "b": .b]"#.to_string(),
627            ct.literal(oracle, &ctx, finder, &json!({"a": "a", "b": "b"}))
628        );
629
630        let ct = map_type("AnEnum", "String");
631        assert_eq!(
632            r#"[.a: "a", .b: "b"]"#.to_string(),
633            ct.literal(oracle, &ctx, finder, &json!({"a": "a", "b": "b"}))
634        );
635    }
636
637    #[test]
638    fn test_map_get_value() {
639        let oracle = &*oracle();
640
641        let ct = map_type("String", "AnEnum");
642        assert_eq!(
643            r#"v.getStringMap("the-property")"#.to_string(),
644            ct.value_getter(oracle, &"v", &"the-property")
645        );
646
647        let ct = map_type("AnEnum", "String");
648        assert_eq!(
649            r#"v.getStringMap("the-property")"#.to_string(),
650            ct.value_getter(oracle, &"v", &"the-property")
651        );
652
653        let ct = map_type("AnEnum", "Another");
654        assert_eq!(
655            r#"v.getStringMap("the-property")"#.to_string(),
656            ct.value_getter(oracle, &"v", &"the-property")
657        );
658    }
659
660    #[test]
661    fn test_map_getter_with_fallback() {
662        let oracle = &*oracle();
663
664        let ct = map_type("String", "AnEnum");
665        assert_eq!(
666             r#"v.getStringMap("the-property")?.mapValuesNotNull(AnEnum.enumValue).mergeWith(def) ?? def"#.to_string(),
667             ct.property_getter(oracle, &"v", &"the-property", &"def")
668         );
669
670        let ct = map_type("AnEnum", "String");
671        assert_eq!(
672            r#"v.getStringMap("the-property")?.mapKeysNotNull(AnEnum.enumValue).mergeWith(def) ?? def"#
673                .to_string(),
674            ct.property_getter(oracle, &"v", &"the-property", &"def")
675        );
676
677        let ct = map_type("AnEnum", "Another");
678        assert_eq!(
679             r#"v.getStringMap("the-property")?.mapEntriesNotNull(AnEnum.enumValue, Another.enumValue).mergeWith(def) ?? def"#
680                 .to_string(),
681             ct.property_getter(oracle, &"v", &"the-property", &"def")
682         );
683
684        let ct = map_type("AnEnum", "AnObject");
685        assert_eq!(
686             r#"v.getVariablesMap("the-property")?.mapEntriesNotNull(AnEnum.enumValue, AnObject.create).mergeWith(def, AnObject.mergeWith) ?? def"#.to_string(),
687             ct.property_getter(oracle, &"v", &"the-property", &"def"));
688    }
689}