nimbus_fml/backends/kotlin/gen_structs/
object.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 askama::Template;
6use std::fmt::Display;
7
8use crate::backends::{
9    CodeDeclaration, CodeOracle, CodeType, LiteralRenderer, TypeIdentifier, VariablesType,
10};
11use crate::intermediate_representation::{FeatureManifest, Literal, ObjectDef};
12
13use super::filters;
14
15use super::common::{self, code_type};
16
17pub struct ObjectRuntime;
18
19impl CodeDeclaration for ObjectRuntime {}
20
21pub struct ObjectCodeType {
22    id: String,
23}
24
25impl ObjectCodeType {
26    pub fn new(id: String) -> Self {
27        Self { id }
28    }
29}
30
31impl CodeType for ObjectCodeType {
32    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
33        common::class_name(&self.id)
34    }
35
36    fn property_getter(
37        &self,
38        oracle: &dyn CodeOracle,
39        vars: &dyn Display,
40        prop: &dyn Display,
41        default: &dyn Display,
42    ) -> String {
43        code_type::property_getter(self, oracle, vars, prop, default)
44    }
45
46    fn value_getter(
47        &self,
48        oracle: &dyn CodeOracle,
49        vars: &dyn Display,
50        prop: &dyn Display,
51    ) -> String {
52        code_type::value_getter(self, oracle, vars, prop)
53    }
54
55    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
56        code_type::value_mapper(self, oracle)
57    }
58
59    /// The language specific expression that gets a value of the `prop` from the `vars` object.
60    ///
61    /// The name of the type as it's represented in the `Variables` object.
62    /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
63    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
64        VariablesType::Variables
65    }
66
67    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
68        Some(format!("{}::create", self.type_label(oracle)))
69    }
70
71    fn merge_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
72        Some(format!("{}::mergeWith", self.type_label(oracle)))
73    }
74
75    fn value_merger(&self, _oracle: &dyn CodeOracle, default: &dyn Display) -> Option<String> {
76        Some(format!("_mergeWith({})", default))
77    }
78
79    fn as_json_transform(&self, _oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
80        Some(format!("{}.toJSONObject()", prop))
81    }
82
83    fn literal(
84        &self,
85        oracle: &dyn CodeOracle,
86        ctx: &dyn Display,
87        renderer: &dyn LiteralRenderer,
88        literal: &Literal,
89    ) -> String {
90        renderer.literal(
91            oracle,
92            &TypeIdentifier::Object(self.id.clone()),
93            literal,
94            ctx,
95        )
96    }
97}
98
99#[derive(Template)]
100#[template(syntax = "kt", escape = "none", path = "ObjectTemplate.kt")]
101pub(crate) struct ObjectCodeDeclaration {
102    inner: ObjectDef,
103    fm: FeatureManifest,
104}
105
106impl ObjectCodeDeclaration {
107    pub fn new(fm: &FeatureManifest, inner: &ObjectDef) -> Self {
108        Self {
109            fm: fm.clone(),
110            inner: inner.clone(),
111        }
112    }
113    pub fn inner(&self) -> ObjectDef {
114        self.inner.clone()
115    }
116}
117
118impl CodeDeclaration for ObjectCodeDeclaration {
119    fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
120        Some(self.render().unwrap())
121    }
122
123    fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
124        Some(vec![
125            "org.mozilla.experiments.nimbus.internal.FMLObjectInterface".to_string(),
126        ])
127    }
128}
129
130impl LiteralRenderer for ObjectCodeDeclaration {
131    fn literal(
132        &self,
133        oracle: &dyn CodeOracle,
134        typ: &TypeIdentifier,
135        value: &Literal,
136        ctx: &dyn Display,
137    ) -> String {
138        object_literal(&self.fm, ctx, &self, oracle, typ, value)
139    }
140}
141
142pub(crate) fn object_literal(
143    fm: &FeatureManifest,
144    ctx: &dyn Display,
145    renderer: &dyn LiteralRenderer,
146    oracle: &dyn CodeOracle,
147    typ: &TypeIdentifier,
148    value: &Literal,
149) -> String {
150    let id = if let TypeIdentifier::Object(id) = typ {
151        id
152    } else {
153        return oracle.find(typ).literal(oracle, ctx, renderer, value);
154    };
155    let literal_map = if let Literal::Object(map) = value {
156        map
157    } else {
158        unreachable!(
159            "An JSON object is expected for {} object literal",
160            oracle.find(typ).type_label(oracle)
161        )
162    };
163
164    let def = fm.find_object(id).unwrap();
165
166    let args: Vec<String> = literal_map
167        .iter()
168        .map(|(k, v)| {
169            let prop = def.find_prop(k);
170
171            format!(
172                "{var_name} = {var_value}",
173                var_name = common::var_name(k),
174                var_value = oracle.find(&prop.typ).literal(oracle, ctx, renderer, v)
175            )
176        })
177        .collect();
178
179    format!(
180        "{typelabel}({args})",
181        typelabel = oracle.find(typ).type_label(oracle),
182        args = args.join(", ")
183    )
184}
185
186#[cfg(test)]
187mod unit_tests {
188    use serde_json::json;
189
190    use crate::{backends::TypeIdentifier, intermediate_representation::Literal};
191
192    use super::*;
193
194    struct TestCodeOracle;
195    impl CodeOracle for TestCodeOracle {
196        fn find(&self, _type_: &TypeIdentifier) -> Box<dyn CodeType> {
197            unreachable!()
198        }
199    }
200
201    struct TestRenderer;
202    impl LiteralRenderer for TestRenderer {
203        fn literal(
204            &self,
205            _oracle: &dyn CodeOracle,
206            typ: &TypeIdentifier,
207            _value: &Literal,
208            _ctx: &dyn Display,
209        ) -> String {
210            if let TypeIdentifier::Object(nm) = typ {
211                format!("{}()", nm)
212            } else {
213                unreachable!()
214            }
215        }
216    }
217
218    fn oracle() -> Box<dyn CodeOracle> {
219        Box::new(TestCodeOracle) as Box<dyn CodeOracle>
220    }
221
222    fn code_type(name: &str) -> Box<dyn CodeType> {
223        Box::new(ObjectCodeType::new(name.to_string())) as Box<dyn CodeType>
224    }
225
226    fn getter_with_fallback(
227        ct: &dyn CodeType,
228        vars: &dyn Display,
229        prop: &dyn Display,
230        def: &dyn Display,
231    ) -> String {
232        let oracle = &*oracle();
233        ct.property_getter(oracle, vars, prop, def)
234    }
235
236    #[test]
237    fn test_type_label() {
238        let ct = code_type("AnObject");
239        let oracle = &*oracle();
240        assert_eq!("AnObject".to_string(), ct.type_label(oracle))
241    }
242
243    #[test]
244    fn test_literal() {
245        let ct = code_type("AnObject");
246        let oracle = &*oracle();
247        let finder = &TestRenderer;
248        let ctx = "ctx".to_string();
249        assert_eq!(
250            "AnObject()".to_string(),
251            ct.literal(oracle, &ctx, finder, &json!({}))
252        );
253    }
254
255    #[test]
256    fn test_get_value() {
257        let ct = code_type("AnObject");
258        let oracle = &*oracle();
259
260        assert_eq!(
261            r#"v.getVariables("the-property")"#.to_string(),
262            ct.value_getter(oracle, &"v", &"the-property")
263        );
264    }
265
266    #[test]
267    fn test_getter_with_fallback() {
268        let ct = code_type("AnObject");
269        assert_eq!(
270            r#"vars.getVariables("the-property")?.let(AnObject::create)?._mergeWith(default) ?: default"#
271            .to_string(),
272            getter_with_fallback(&*ct, &"vars", &"the-property", &"default"));
273    }
274}