nimbus_fml/backends/kotlin/gen_structs/
object.rs
1use 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 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}