1use 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 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 fn property_getter(
38 &self,
39 oracle: &dyn CodeOracle,
40 vars: &dyn Display,
41 prop: &dyn Display,
42 default: &dyn Display,
43 ) -> String {
44 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 fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
60 oracle.find(&self.inner).create_transform(oracle)
61 }
62
63 fn variables_type(&self, oracle: &dyn CodeOracle) -> VariablesType {
66 oracle.find(&self.inner).variables_type(oracle)
67 }
68
69 fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
72 oracle.find(&self.inner).value_mapper(oracle)
73 }
74
75 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 let inner = oracle.find(&self.inner).defaults_type(oracle);
86 format!("{}?", inner)
87 }
88
89 fn defaults_mapper(
90 &self,
91 oracle: &dyn CodeOracle,
92 value: &dyn Display,
93 vars: &dyn Display,
94 ) -> Option<String> {
95 let id = "it";
96 let mapper = oracle
97 .find(&self.inner)
98 .defaults_mapper(oracle, &id, vars)?;
99 Some(format!(
100 "{value}?.let {{ {mapper} }}",
101 value = value,
102 mapper = mapper
103 ))
104 }
105
106 fn as_json_transform(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
108 let prop = format!("{}?", prop);
111 oracle.find(&self.inner).as_json_transform(oracle, &prop)
112 }
113
114 fn literal(
117 &self,
118 oracle: &dyn CodeOracle,
119 ctx: &dyn Display,
120 renderer: &dyn LiteralRenderer,
121 literal: &Literal,
122 ) -> String {
123 match literal {
124 serde_json::Value::Null => "null".to_string(),
125 _ => oracle
126 .find(&self.inner)
127 .literal(oracle, ctx, renderer, literal),
128 }
129 }
130}
131
132pub(crate) struct MapCodeType {
135 k_type: TypeIdentifier,
136 v_type: TypeIdentifier,
137}
138
139impl MapCodeType {
140 pub(crate) fn new(k: &TypeIdentifier, v: &TypeIdentifier) -> Self {
141 Self {
142 k_type: k.clone(),
143 v_type: v.clone(),
144 }
145 }
146}
147
148impl CodeType for MapCodeType {
149 fn property_getter(
150 &self,
151 oracle: &dyn CodeOracle,
152 vars: &dyn Display,
153 prop: &dyn Display,
154 default: &dyn Display,
155 ) -> String {
156 code_type::property_getter(self, oracle, vars, prop, default)
157 }
158
159 fn type_label(&self, oracle: &dyn CodeOracle) -> String {
162 format!(
163 "Map<{k}, {v}>",
164 k = oracle.find(&self.k_type).type_label(oracle),
165 v = oracle.find(&self.v_type).type_label(oracle),
166 )
167 }
168
169 fn value_getter(
170 &self,
171 oracle: &dyn CodeOracle,
172 vars: &dyn Display,
173 prop: &dyn Display,
174 ) -> String {
175 let v_type = oracle.find(&self.v_type);
176 format!(
177 "{vars}.get{vt}Map({prop})",
178 vars = vars,
179 vt = v_type.variables_type(oracle),
180 prop = common::quoted(prop),
181 )
182 }
183
184 fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
185 let k_type = oracle.find(&self.k_type);
186 let v_type = oracle.find(&self.v_type);
187 Some(
188 match (
189 k_type.create_transform(oracle),
190 v_type.create_transform(oracle),
191 ) {
192 (Some(k), Some(v)) => {
193 if v.starts_with('{') {
194 format!("mapEntriesNotNull({k}) {v}", k = k, v = v)
195 } else {
196 format!("mapEntriesNotNull({k}, {v})", k = k, v = v)
197 }
198 }
199 (None, Some(v)) => {
200 if v.starts_with('{') {
201 format!("mapValuesNotNull {v}", v = v)
202 } else {
203 format!("mapValuesNotNull({v})", v = v)
204 }
205 }
206 (Some(k), None) => format!("mapKeysNotNull({k})", k = k),
208 _ => return None,
209 },
210 )
211 }
212
213 fn value_merger(&self, oracle: &dyn CodeOracle, default: &dyn Display) -> Option<String> {
214 let v_type = oracle.find(&self.v_type);
215 Some(match v_type.merge_transform(oracle) {
216 Some(transform) if transform.starts_with('{') => format!(
217 "mergeWith({default}) {transform}",
218 default = default,
219 transform = transform
220 ),
221 Some(transform) => format!(
222 "mergeWith({default}, {transform})",
223 default = default,
224 transform = transform
225 ),
226 None => format!("mergeWith({})", default),
227 })
228 }
229
230 fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
231 let vtype = oracle.find(&self.v_type).variables_type(oracle);
232
233 self.value_mapper(oracle)
234 .map(|mapper| {
235 format!(
236 r#"{{ _vars -> _vars.as{vtype}Map()?.{mapper} }}"#,
237 vtype = vtype,
238 mapper = mapper
239 )
240 })
241 .or_else(|| {
242 Some(format!(
243 r#"{{ _vars -> _vars.as{vtype}Map()? }}"#,
244 vtype = vtype
245 ))
246 })
247 }
248
249 fn merge_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
250 let overrides = "_overrides";
251 let defaults = "_defaults";
252
253 self.value_merger(oracle, &defaults).map(|merger| {
254 format!(
255 r#"{{ {overrides}, {defaults} -> {overrides}.{merger} }}"#,
256 overrides = overrides,
257 defaults = defaults,
258 merger = merger
259 )
260 })
261 }
262
263 fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
266 VariablesType::Variables
267 }
268
269 fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
270 let k_type = oracle.find(&self.k_type).defaults_type(oracle);
271 let v_type = oracle.find(&self.v_type).defaults_type(oracle);
272 format!("Map<{}, {}>", k_type, v_type)
273 }
274
275 fn defaults_mapper(
276 &self,
277 oracle: &dyn CodeOracle,
278 value: &dyn Display,
279 vars: &dyn Display,
280 ) -> Option<String> {
281 let id = "it.value";
282 let mapper = oracle
283 .find(&self.v_type)
284 .defaults_mapper(oracle, &id, vars)?;
285 Some(format!(
286 "{value}.mapValues {{ {mapper} }}",
287 value = value,
288 mapper = mapper
289 ))
290 }
291
292 fn as_json_transform(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
293 let k_type = oracle.find(&self.k_type);
294 let v_type = oracle.find(&self.v_type);
295 Some(
296 match (
297 k_type.as_json_transform(oracle, &"it".to_string()),
298 v_type.as_json_transform(oracle, &"it".to_string()),
299 ) {
300 (Some(k), Some(v)) => {
301 format!(
302 "{prop}.mapEntriesNotNull({{ {k} }}, {{ {v} }})",
303 prop = prop,
304 k = k,
305 v = v
306 )
307 }
308 (None, Some(v)) => {
309 format!("{prop}.mapValuesNotNull {{ {v} }}", prop = prop, v = v)
310 }
311 (Some(k), None) => {
313 format!("{prop}.mapKeysNotNull {{ {k} }}", prop = prop, k = k)
314 }
315 _ => return None,
316 },
317 )
318 }
319
320 fn literal(
323 &self,
324 oracle: &dyn CodeOracle,
325 ctx: &dyn Display,
326 renderer: &dyn LiteralRenderer,
327 literal: &Literal,
328 ) -> String {
329 let variant = match literal {
330 serde_json::Value::Object(v) => v,
331 _ => unreachable!(),
332 };
333 let k_type = oracle.find(&self.k_type);
334 let v_type = oracle.find(&self.v_type);
335 let src: Vec<String> = variant
336 .iter()
337 .map(|(k, v)| {
338 format!(
339 "{k} to {v}",
340 k = k_type.literal(oracle, ctx, renderer, &Literal::String(k.clone())),
341 v = v_type.literal(oracle, ctx, renderer, v)
342 )
343 })
344 .collect();
345
346 format!("mapOf({})", src.join(", "))
347 }
348
349 fn imports(&self, oracle: &dyn CodeOracle) -> Option<Vec<String>> {
350 let k_type = oracle.find(&self.k_type);
351 let v_type = oracle.find(&self.v_type);
352 let mapper = map_functions(
353 k_type.create_transform(oracle),
354 v_type.create_transform(oracle),
355 );
356
357 let json_mapper = map_functions(
358 k_type.as_json_transform(oracle, &"k".to_string()),
359 v_type.as_json_transform(oracle, &"v".to_string()),
360 );
361
362 let merger = Some("org.mozilla.experiments.nimbus.internal.mergeWith".to_string());
363 Some(
364 [mapper, json_mapper, merger]
365 .iter()
366 .filter_map(|i| i.to_owned())
367 .collect(),
368 )
369 }
370}
371
372fn map_functions(k: Option<String>, v: Option<String>) -> Option<String> {
373 match (k, v) {
374 (Some(_), Some(_)) => {
375 Some("org.mozilla.experiments.nimbus.internal.mapEntriesNotNull".to_string())
376 }
377 (None, Some(_)) => {
378 Some("org.mozilla.experiments.nimbus.internal.mapValuesNotNull".to_string())
379 }
380 (Some(_), None) => {
381 Some("org.mozilla.experiments.nimbus.internal.mapKeysNotNull".to_string())
382 }
383 _ => None,
384 }
385}
386
387pub(crate) struct ListCodeType {
390 inner: TypeIdentifier,
391}
392
393impl ListCodeType {
394 pub(crate) fn new(inner: &TypeIdentifier) -> Self {
395 Self {
396 inner: inner.clone(),
397 }
398 }
399}
400
401impl CodeType for ListCodeType {
402 fn type_label(&self, oracle: &dyn CodeOracle) -> String {
405 format!(
406 "List<{item}>",
407 item = oracle.find(&self.inner).type_label(oracle),
408 )
409 }
410
411 fn property_getter(
412 &self,
413 oracle: &dyn CodeOracle,
414 vars: &dyn Display,
415 prop: &dyn Display,
416 default: &dyn Display,
417 ) -> String {
418 code_type::property_getter(self, oracle, vars, prop, default)
419 }
420
421 fn value_getter(
422 &self,
423 oracle: &dyn CodeOracle,
424 vars: &dyn Display,
425 prop: &dyn Display,
426 ) -> String {
427 let vtype = oracle.find(&self.inner).variables_type(oracle);
428 format!(
429 "{vars}.get{vt}List(\"{prop}\")",
430 vars = vars,
431 vt = vtype,
432 prop = prop
433 )
434 }
435
436 fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
437 let transform = oracle.find(&self.inner).create_transform(oracle)?;
438 Some(if transform.starts_with('{') {
439 format!("mapNotNull {}", transform)
440 } else {
441 format!("mapNotNull({})", transform)
442 })
443 }
444
445 fn value_merger(&self, _oracle: &dyn CodeOracle, _default: &dyn Display) -> Option<String> {
446 None
448 }
449
450 fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
453 unimplemented!("Lists and maps of lists aren't supported. The workaround is to use a list of map of list holder objects")
456 }
457
458 fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
459 let inner = oracle.find(&self.inner).defaults_type(oracle);
460 format!("List<{}>", inner)
461 }
462
463 fn defaults_mapper(
464 &self,
465 oracle: &dyn CodeOracle,
466 value: &dyn Display,
467 vars: &dyn Display,
468 ) -> Option<String> {
469 let id = "it";
470 let mapper = oracle
471 .find(&self.inner)
472 .defaults_mapper(oracle, &id, vars)?;
473 Some(format!(
474 "{value}.map {{ {mapper} }}",
475 value = value,
476 mapper = mapper
477 ))
478 }
479
480 fn as_json_transform(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
481 let mapper = oracle
482 .find(&self.inner)
483 .as_json_transform(oracle, &"it".to_string())?;
484 Some(format!(
485 "{prop}.map {{ {mapper} }}",
486 prop = prop,
487 mapper = mapper
488 ))
489 }
490
491 fn literal(
494 &self,
495 oracle: &dyn CodeOracle,
496 ctx: &dyn Display,
497 renderer: &dyn LiteralRenderer,
498 literal: &Literal,
499 ) -> String {
500 let variant = match literal {
501 serde_json::Value::Array(v) => v,
502 _ => unreachable!(),
503 };
504
505 let v_type = oracle.find(&self.inner);
506 let src: Vec<String> = variant
507 .iter()
508 .map(|v| v_type.literal(oracle, ctx, renderer, v))
509 .collect();
510
511 format!("listOf({})", src.join(", "))
512 }
513}
514
515#[cfg(test)]
516mod unit_tests {
517
518 use serde_json::json;
519
520 use crate::backends::kotlin::gen_structs::{
521 enum_::EnumCodeType, object::ObjectCodeType, primitives::StringCodeType,
522 };
523 use crate::backends::TypeIdentifier;
524
525 use super::*;
526
527 struct TestCodeOracle;
528 impl CodeOracle for TestCodeOracle {
529 fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
530 match type_ {
531 TypeIdentifier::String => Box::new(StringCodeType) as Box<dyn CodeType>,
532 TypeIdentifier::Enum(s) => {
533 Box::new(EnumCodeType::new(s.clone())) as Box<dyn CodeType>
534 }
535 TypeIdentifier::Object(s) => {
536 Box::new(ObjectCodeType::new(s.clone())) as Box<dyn CodeType>
537 }
538 TypeIdentifier::List(i) => Box::new(ListCodeType::new(i)),
539 TypeIdentifier::EnumMap(k, v) => Box::new(MapCodeType::new(k, v)),
540 _ => unreachable!(),
541 }
542 }
543 }
544
545 struct TestRenderer;
546 impl LiteralRenderer for TestRenderer {
547 fn literal(
548 &self,
549 _oracle: &dyn CodeOracle,
550 _typ: &TypeIdentifier,
551 _value: &Literal,
552 _ctx: &dyn Display,
553 ) -> String {
554 unreachable!()
555 }
556 }
557
558 fn oracle() -> Box<dyn CodeOracle> {
559 Box::new(TestCodeOracle) as Box<dyn CodeOracle>
560 }
561
562 fn type_(nm: &str) -> TypeIdentifier {
563 match nm {
564 "String" => TypeIdentifier::String,
565 "AnObject" => TypeIdentifier::Object("AnObject".to_string()),
566 nm => TypeIdentifier::Enum(nm.to_string()),
567 }
568 }
569
570 fn list_type(item: &str) -> Box<dyn CodeType> {
571 Box::new(ListCodeType::new(&type_(item)))
572 }
573
574 fn map_type(k: &str, v: &str) -> Box<dyn CodeType> {
575 Box::new(MapCodeType::new(&type_(k), &type_(v)))
576 }
577
578 fn getter_with_fallback(
579 ct: &dyn CodeType,
580 vars: &dyn Display,
581 prop: &dyn Display,
582 def: &dyn Display,
583 ) -> String {
584 let oracle = &*oracle();
585 ct.property_getter(oracle, vars, prop, def)
586 }
587
588 #[test]
589 fn test_list_type_label() {
590 let oracle = &*oracle();
591 let ct = list_type("String");
592 assert_eq!("List<String>".to_string(), ct.type_label(oracle));
593
594 let ct = list_type("AnEnum");
595 assert_eq!("List<AnEnum>".to_string(), ct.type_label(oracle));
596 }
597
598 #[test]
599 fn test_list_literal() {
600 let oracle = &*oracle();
601 let finder = &TestRenderer;
602
603 let ct = list_type("String");
604 let ctx = "_context".to_string();
605 assert_eq!(
606 r#"listOf("x", "y", "z")"#.to_string(),
607 ct.literal(oracle, &ctx, finder, &json!(["x", "y", "z"]))
608 );
609
610 let ct = list_type("AnEnum");
611 assert_eq!(
612 r#"listOf(AnEnum.X, AnEnum.Y, AnEnum.Z)"#.to_string(),
613 ct.literal(oracle, &ctx, finder, &json!(["x", "y", "z"]))
614 );
615 }
616
617 #[test]
618 fn test_list_get_value() {
619 let oracle = &*oracle();
620
621 let ct = list_type("AnEnum");
622 assert_eq!(
623 r#"v.getStringList("the-property")"#.to_string(),
624 ct.value_getter(oracle, &"v", &"the-property")
625 );
626
627 let ct = list_type("AnObject");
628 assert_eq!(
629 r#"v.getVariablesList("the-property")"#.to_string(),
630 ct.value_getter(oracle, &"v", &"the-property")
631 );
632
633 let ct = list_type("String");
634 assert_eq!(
635 r#"v.getStringList("the-property")"#.to_string(),
636 ct.value_getter(oracle, &"v", &"the-property")
637 );
638 }
639
640 #[test]
641 fn test_list_getter_with_fallback() {
642 let ct = list_type("String");
643 assert_eq!(
644 r#"vars.getStringList("the-property") ?: default"#.to_string(),
645 getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
646 );
647
648 let ct = list_type("AnEnum");
649 assert_eq!(
650 r#"vars.getStringList("the-property")?.mapNotNull(AnEnum::enumValue) ?: default"#
651 .to_string(),
652 getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
653 );
654
655 let ct = list_type("AnObject");
656 assert_eq!(
657 r#"vars.getVariablesList("the-property")?.mapNotNull(AnObject::create) ?: default"#
658 .to_string(),
659 getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
660 );
661 }
662
663 #[test]
664 fn test_map_type_label() {
665 let oracle = &*oracle();
666 let ct = map_type("String", "String");
667 assert_eq!("Map<String, String>".to_string(), ct.type_label(oracle));
668
669 let ct = map_type("String", "AnEnum");
670 assert_eq!("Map<String, AnEnum>".to_string(), ct.type_label(oracle));
671 }
672
673 #[test]
674 fn test_map_literal() {
675 let oracle = &*oracle();
676 let finder = &TestRenderer;
677 let ctx = "context".to_string();
678 let ct = map_type("String", "AnEnum");
679 assert_eq!(
680 r#"mapOf("a" to AnEnum.A, "b" to AnEnum.B)"#.to_string(),
681 ct.literal(oracle, &ctx, finder, &json!({"a": "a", "b": "b"}))
682 );
683
684 let ct = map_type("AnEnum", "String");
685 assert_eq!(
686 r#"mapOf(AnEnum.A to "a", AnEnum.B to "b")"#.to_string(),
687 ct.literal(oracle, &ctx, finder, &json!({"a": "a", "b": "b"}))
688 );
689 }
690
691 #[test]
692 fn test_map_get_value() {
693 let oracle = &*oracle();
694
695 let ct = map_type("String", "AnEnum");
696 assert_eq!(
697 r#"v.getStringMap("the-property")"#.to_string(),
698 ct.value_getter(oracle, &"v", &"the-property")
699 );
700
701 let ct = map_type("AnEnum", "String");
702 assert_eq!(
703 r#"v.getStringMap("the-property")"#.to_string(),
704 ct.value_getter(oracle, &"v", &"the-property")
705 );
706
707 let ct = map_type("AnEnum", "Another");
708 assert_eq!(
709 r#"v.getStringMap("the-property")"#.to_string(),
710 ct.value_getter(oracle, &"v", &"the-property")
711 );
712 }
713
714 #[test]
715 fn test_map_getter_with_fallback() {
716 let oracle = &*oracle();
717
718 let ct = map_type("String", "AnEnum");
719 assert_eq!(
720 r#"v.getStringMap("the-property")?.mapValuesNotNull(AnEnum::enumValue)?.mergeWith(def) ?: def"#.to_string(),
721 ct.property_getter(oracle, &"v", &"the-property", &"def")
722 );
723
724 let ct = map_type("AnEnum", "String");
725 assert_eq!(
726 r#"v.getStringMap("the-property")?.mapKeysNotNull(AnEnum::enumValue)?.mergeWith(def) ?: def"#
727 .to_string(),
728 ct.property_getter(oracle, &"v", &"the-property", &"def")
729 );
730
731 let ct = map_type("AnEnum", "Another");
732 assert_eq!(
733 r#"v.getStringMap("the-property")?.mapEntriesNotNull(AnEnum::enumValue, Another::enumValue)?.mergeWith(def) ?: def"#
734 .to_string(),
735 ct.property_getter(oracle, &"v", &"the-property", &"def")
736 );
737
738 let ct = map_type("AnEnum", "AnObject");
739 assert_eq!(
740 r#"v.getVariablesMap("the-property")?.mapEntriesNotNull(AnEnum::enumValue, AnObject::create)?.mergeWith(def, AnObject::mergeWith) ?: def"#.to_string(),
741 ct.property_getter(oracle, &"v", &"the-property", &"def"));
742 }
743}