1use alloc::format;
17use alloc::string::{String, ToString};
18use alloc::vec::Vec;
19
20use zerodds_idl::ast::{
21 AttrDecl, ComponentDef, ComponentExport, EventDef, Export, HomeDef, Identifier, InterfaceDef,
22 InterfaceKind, OpDecl, ParamAttribute, ParamDecl, PrimitiveType, ScopedName, StringType,
23 TypeSpec, ValueKind,
24};
25use zerodds_idl::errors::Span;
26
27#[derive(Debug, Clone, PartialEq)]
31pub struct ComponentEquivalent {
32 pub equivalent_interface: InterfaceDef,
34 pub event_consumer_interfaces: Vec<InterfaceDef>,
38}
39
40#[derive(Debug, Clone, PartialEq)]
43pub struct HomeEquivalent {
44 pub explicit: InterfaceDef,
46 pub implicit: InterfaceDef,
49 pub equivalent: InterfaceDef,
51}
52
53#[derive(Debug, Clone, PartialEq)]
62pub struct EventTypeEquivalent {
63 pub valuetype_name: Identifier,
66 pub valuetype_bases: Vec<ScopedName>,
70 pub consumer_interface: InterfaceDef,
72}
73
74#[must_use]
85pub fn transform_component(comp: &ComponentDef) -> ComponentEquivalent {
86 let span = Span::SYNTHETIC;
87
88 let bases = component_bases(comp, span);
90
91 let mut exports: Vec<Export> = Vec::new();
92 let mut event_consumer_ifaces: Vec<InterfaceDef> = Vec::new();
93 let mut emitted_consumers: Vec<String> = Vec::new();
94
95 for export in &comp.body {
96 match export {
97 ComponentExport::Provides {
98 type_spec, name, ..
99 } => {
100 exports.push(Export::Op(provide_facet_op(name, type_spec, span)));
101 }
102 ComponentExport::Uses {
103 type_spec,
104 name,
105 multiple,
106 ..
107 } => {
108 exports.extend(uses_ops(name, type_spec, *multiple, span));
109 }
110 ComponentExport::Attribute(attr) => {
111 exports.push(Export::Attr(attr_to_attr_decl(attr, span)));
112 }
113 ComponentExport::Emits {
114 type_spec, name, ..
115 } => {
116 ensure_consumer_interface(
117 type_spec,
118 span,
119 &mut event_consumer_ifaces,
120 &mut emitted_consumers,
121 );
122 exports.extend(emits_ops(name, type_spec, span));
123 }
124 ComponentExport::Publishes {
125 type_spec, name, ..
126 } => {
127 ensure_consumer_interface(
128 type_spec,
129 span,
130 &mut event_consumer_ifaces,
131 &mut emitted_consumers,
132 );
133 exports.extend(publishes_ops(name, type_spec, span));
134 }
135 ComponentExport::Consumes {
136 type_spec, name, ..
137 } => {
138 ensure_consumer_interface(
139 type_spec,
140 span,
141 &mut event_consumer_ifaces,
142 &mut emitted_consumers,
143 );
144 exports.push(Export::Op(consumes_op(name, type_spec, span)));
145 }
146 ComponentExport::Port { .. } => {
147 }
154 }
155 }
156
157 let equivalent_interface = InterfaceDef {
158 kind: InterfaceKind::Plain,
159 name: comp.name.clone(),
160 bases,
161 exports,
162 annotations: Vec::new(),
163 span,
164 };
165
166 ComponentEquivalent {
167 equivalent_interface,
168 event_consumer_interfaces: event_consumer_ifaces,
169 }
170}
171
172#[must_use]
175pub fn transform_home(home: &HomeDef) -> HomeEquivalent {
176 let span = Span::SYNTHETIC;
177 let h = &home.name.text;
178
179 let mut explicit_bases = alloc::vec![scoped(&["Components", "CCMHome"], span)];
181 explicit_bases.extend(home.supports.iter().cloned());
182 if let Some(base) = &home.base {
183 explicit_bases = alloc::vec![base_explicit_name(base, span)];
186 explicit_bases.extend(home.supports.iter().cloned());
187 }
188
189 let explicit = InterfaceDef {
190 kind: InterfaceKind::Plain,
191 name: Identifier::new(format!("{h}Explicit"), span),
192 bases: explicit_bases,
193 exports: Vec::new(),
200 annotations: Vec::new(),
201 span,
202 };
203
204 let implicit = if let Some(pk) = &home.primary_key {
206 build_keyed_implicit(h, &home.manages, pk, span)
207 } else {
208 build_keyless_implicit(h, &home.manages, span)
209 };
210
211 let equivalent = InterfaceDef {
213 kind: InterfaceKind::Plain,
214 name: home.name.clone(),
215 bases: alloc::vec![
216 ScopedName::single(Identifier::new(format!("{h}Explicit"), span)),
217 ScopedName::single(Identifier::new(format!("{h}Implicit"), span)),
218 ],
219 exports: Vec::new(),
220 annotations: Vec::new(),
221 span,
222 };
223
224 HomeEquivalent {
225 explicit,
226 implicit,
227 equivalent,
228 }
229}
230
231#[must_use]
233pub fn transform_event_type(et: &EventDef) -> EventTypeEquivalent {
234 let span = Span::SYNTHETIC;
235
236 let mut bases: Vec<ScopedName> = Vec::new();
241 let mut has_inheritance = false;
242 if let Some(inherit) = &et.inheritance {
243 if !inherit.bases.is_empty() {
244 has_inheritance = true;
245 bases.extend(inherit.bases.iter().cloned());
246 }
247 }
248 if !has_inheritance {
249 bases.push(scoped(&["Components", "EventBase"], span));
251 }
252
253 let consumer_iface_name = format!("{}Consumer", et.name.text);
255 let push_op = OpDecl {
256 name: Identifier::new(format!("push_{}", et.name.text), span),
257 oneway: false,
258 return_type: None,
259 params: alloc::vec![ParamDecl {
260 attribute: ParamAttribute::In,
261 type_spec: TypeSpec::Scoped(ScopedName::single(et.name.clone())),
262 name: Identifier::new(format!("the_{}", lowercase_first(&et.name.text)), span),
263 annotations: Vec::new(),
264 span,
265 }],
266 raises: Vec::new(),
267 annotations: Vec::new(),
268 span,
269 };
270 let consumer_bases = if has_inheritance {
271 et.inheritance.as_ref().map_or_else(
274 || alloc::vec![scoped(&["Components", "EventConsumerBase"], span)],
275 |inh| {
276 inh.bases
277 .iter()
278 .map(|b| {
279 let last = b
280 .parts
281 .last()
282 .map_or("Base", |i| i.text.as_str())
283 .to_string();
284 ScopedName::single(Identifier::new(format!("{last}Consumer"), span))
285 })
286 .collect()
287 },
288 )
289 } else {
290 alloc::vec![scoped(&["Components", "EventConsumerBase"], span)]
291 };
292
293 let consumer_iface = InterfaceDef {
294 kind: InterfaceKind::Plain,
295 name: Identifier::new(consumer_iface_name, span),
296 bases: consumer_bases,
297 exports: alloc::vec![Export::Op(push_op)],
298 annotations: Vec::new(),
299 span,
300 };
301
302 let _ = ValueKind::Concrete; EventTypeEquivalent {
305 valuetype_name: et.name.clone(),
306 valuetype_bases: bases,
307 consumer_interface: consumer_iface,
308 }
309}
310
311fn component_bases(comp: &ComponentDef, span: Span) -> Vec<ScopedName> {
316 let mut bases = Vec::new();
318 if let Some(b) = &comp.base {
319 bases.push(b.clone());
320 } else {
321 bases.push(scoped(&["Components", "CCMObject"], span));
322 }
323 bases.extend(comp.supports.iter().cloned());
324 bases
325}
326
327fn provide_facet_op(name: &Identifier, iface_type: &ScopedName, span: Span) -> OpDecl {
329 OpDecl {
330 name: Identifier::new(format!("provide_{}", name.text), span),
331 oneway: false,
332 return_type: Some(TypeSpec::Scoped(iface_type.clone())),
333 params: Vec::new(),
334 raises: Vec::new(),
335 annotations: Vec::new(),
336 span,
337 }
338}
339
340fn uses_ops(name: &Identifier, iface_type: &ScopedName, multiple: bool, span: Span) -> Vec<Export> {
343 let n = &name.text;
344 let mut out = Vec::new();
345 if multiple {
346 out.push(Export::Op(OpDecl {
349 name: Identifier::new(format!("connect_{n}"), span),
350 oneway: false,
351 return_type: Some(TypeSpec::Scoped(scoped(&["Components", "Cookie"], span))),
352 params: alloc::vec![ParamDecl {
353 attribute: ParamAttribute::In,
354 type_spec: TypeSpec::Scoped(iface_type.clone()),
355 name: Identifier::new("connection", span),
356 annotations: Vec::new(),
357 span,
358 }],
359 raises: alloc::vec![
360 scoped(&["Components", "ExceededConnectionLimit"], span),
361 scoped(&["Components", "InvalidConnection"], span),
362 ],
363 annotations: Vec::new(),
364 span,
365 }));
366 out.push(Export::Op(OpDecl {
368 name: Identifier::new(format!("disconnect_{n}"), span),
369 oneway: false,
370 return_type: Some(TypeSpec::Scoped(iface_type.clone())),
371 params: alloc::vec![ParamDecl {
372 attribute: ParamAttribute::In,
373 type_spec: TypeSpec::Scoped(scoped(&["Components", "Cookie"], span)),
374 name: Identifier::new("ck", span),
375 annotations: Vec::new(),
376 span,
377 }],
378 raises: alloc::vec![scoped(&["Components", "InvalidConnection"], span)],
379 annotations: Vec::new(),
380 span,
381 }));
382 out.push(Export::Op(OpDecl {
384 name: Identifier::new(format!("get_connections_{n}"), span),
385 oneway: false,
386 return_type: Some(TypeSpec::Scoped(ScopedName::single(Identifier::new(
387 format!("{n}Connections"),
388 span,
389 )))),
390 params: Vec::new(),
391 raises: Vec::new(),
392 annotations: Vec::new(),
393 span,
394 }));
395 } else {
396 out.push(Export::Op(OpDecl {
398 name: Identifier::new(format!("connect_{n}"), span),
399 oneway: false,
400 return_type: None,
401 params: alloc::vec![ParamDecl {
402 attribute: ParamAttribute::In,
403 type_spec: TypeSpec::Scoped(iface_type.clone()),
404 name: Identifier::new("conxn", span),
405 annotations: Vec::new(),
406 span,
407 }],
408 raises: alloc::vec![
409 scoped(&["Components", "AlreadyConnected"], span),
410 scoped(&["Components", "InvalidConnection"], span),
411 ],
412 annotations: Vec::new(),
413 span,
414 }));
415 out.push(Export::Op(OpDecl {
416 name: Identifier::new(format!("disconnect_{n}"), span),
417 oneway: false,
418 return_type: Some(TypeSpec::Scoped(iface_type.clone())),
419 params: Vec::new(),
420 raises: alloc::vec![scoped(&["Components", "NoConnection"], span)],
421 annotations: Vec::new(),
422 span,
423 }));
424 out.push(Export::Op(OpDecl {
425 name: Identifier::new(format!("get_connection_{n}"), span),
426 oneway: false,
427 return_type: Some(TypeSpec::Scoped(iface_type.clone())),
428 params: Vec::new(),
429 raises: Vec::new(),
430 annotations: Vec::new(),
431 span,
432 }));
433 }
434 out
435}
436
437fn emits_ops(name: &Identifier, event_type: &ScopedName, span: Span) -> Vec<Export> {
440 let n = &name.text;
441 let consumer_type = consumer_type_of(event_type, span);
442 alloc::vec![
443 Export::Op(OpDecl {
444 name: Identifier::new(format!("connect_{n}"), span),
445 oneway: false,
446 return_type: None,
447 params: alloc::vec![ParamDecl {
448 attribute: ParamAttribute::In,
449 type_spec: TypeSpec::Scoped(consumer_type.clone()),
450 name: Identifier::new("consumer", span),
451 annotations: Vec::new(),
452 span,
453 }],
454 raises: alloc::vec![scoped(&["Components", "AlreadyConnected"], span)],
455 annotations: Vec::new(),
456 span,
457 }),
458 Export::Op(OpDecl {
459 name: Identifier::new(format!("disconnect_{n}"), span),
460 oneway: false,
461 return_type: Some(TypeSpec::Scoped(consumer_type)),
462 params: Vec::new(),
463 raises: alloc::vec![scoped(&["Components", "NoConnection"], span)],
464 annotations: Vec::new(),
465 span,
466 }),
467 ]
468}
469
470fn publishes_ops(name: &Identifier, event_type: &ScopedName, span: Span) -> Vec<Export> {
474 let n = &name.text;
475 let consumer_type = consumer_type_of(event_type, span);
476 alloc::vec![
477 Export::Op(OpDecl {
478 name: Identifier::new(format!("subscribe_{n}"), span),
479 oneway: false,
480 return_type: Some(TypeSpec::Scoped(scoped(&["Components", "Cookie"], span))),
481 params: alloc::vec![ParamDecl {
482 attribute: ParamAttribute::In,
483 type_spec: TypeSpec::Scoped(consumer_type.clone()),
484 name: Identifier::new("consumer", span),
485 annotations: Vec::new(),
486 span,
487 }],
488 raises: alloc::vec![scoped(&["Components", "ExceededConnectionLimit"], span)],
489 annotations: Vec::new(),
490 span,
491 }),
492 Export::Op(OpDecl {
493 name: Identifier::new(format!("unsubscribe_{n}"), span),
494 oneway: false,
495 return_type: Some(TypeSpec::Scoped(consumer_type)),
496 params: alloc::vec![ParamDecl {
497 attribute: ParamAttribute::In,
498 type_spec: TypeSpec::Scoped(scoped(&["Components", "Cookie"], span)),
499 name: Identifier::new("ck", span),
500 annotations: Vec::new(),
501 span,
502 }],
503 raises: alloc::vec![scoped(&["Components", "InvalidConnection"], span)],
504 annotations: Vec::new(),
505 span,
506 }),
507 ]
508}
509
510fn consumes_op(name: &Identifier, event_type: &ScopedName, span: Span) -> OpDecl {
513 let n = &name.text;
514 let consumer_type = consumer_type_of(event_type, span);
515 OpDecl {
516 name: Identifier::new(format!("get_consumer_{n}"), span),
517 oneway: false,
518 return_type: Some(TypeSpec::Scoped(consumer_type)),
519 params: Vec::new(),
520 raises: Vec::new(),
521 annotations: Vec::new(),
522 span,
523 }
524}
525
526fn ensure_consumer_interface(
527 event_type: &ScopedName,
528 span: Span,
529 out: &mut Vec<InterfaceDef>,
530 emitted: &mut Vec<String>,
531) {
532 let consumer_name = consumer_simple_name(event_type);
533 if emitted.iter().any(|n| n == &consumer_name) {
534 return;
535 }
536 emitted.push(consumer_name.clone());
537 let push_op = OpDecl {
538 name: Identifier::new(
539 format!(
540 "push_{}",
541 event_type.parts.last().map_or("E", |i| i.text.as_str())
542 ),
543 span,
544 ),
545 oneway: false,
546 return_type: None,
547 params: alloc::vec![ParamDecl {
548 attribute: ParamAttribute::In,
549 type_spec: TypeSpec::Scoped(event_type.clone()),
550 name: Identifier::new(
551 format!(
552 "the_{}",
553 lowercase_first(event_type.parts.last().map_or("e", |i| i.text.as_str()))
554 ),
555 span,
556 ),
557 annotations: Vec::new(),
558 span,
559 }],
560 raises: Vec::new(),
561 annotations: Vec::new(),
562 span,
563 };
564 out.push(InterfaceDef {
565 kind: InterfaceKind::Plain,
566 name: Identifier::new(consumer_name, span),
567 bases: alloc::vec![scoped(&["Components", "EventConsumerBase"], span)],
568 exports: alloc::vec![Export::Op(push_op)],
569 annotations: Vec::new(),
570 span,
571 });
572}
573
574fn consumer_type_of(event_type: &ScopedName, span: Span) -> ScopedName {
575 let mut parts = event_type.parts.clone();
576 if let Some(last) = parts.last_mut() {
577 last.text.push_str("Consumer");
578 } else {
579 parts.push(Identifier::new("EConsumer", span));
580 }
581 ScopedName {
582 absolute: event_type.absolute,
583 parts,
584 span,
585 }
586}
587
588fn consumer_simple_name(event_type: &ScopedName) -> String {
589 event_type.parts.last().map_or_else(
590 || String::from("EConsumer"),
591 |i| format!("{}Consumer", i.text),
592 )
593}
594
595fn attr_to_attr_decl(attr: &zerodds_idl::ast::AttrDcl, span: Span) -> AttrDecl {
598 AttrDecl {
599 name: attr.name.clone(),
600 type_spec: attr.type_spec.clone(),
601 readonly: attr.readonly,
602 get_raises: Vec::new(),
603 set_raises: Vec::new(),
604 annotations: Vec::new(),
605 span,
606 }
607}
608
609fn build_keyless_implicit(
610 home_name: &str,
611 component_type: &ScopedName,
612 span: Span,
613) -> InterfaceDef {
614 InterfaceDef {
618 kind: InterfaceKind::Plain,
619 name: Identifier::new(format!("{home_name}Implicit"), span),
620 bases: alloc::vec![scoped(&["Components", "KeylessCCMHome"], span)],
621 exports: alloc::vec![Export::Op(OpDecl {
622 name: Identifier::new("create", span),
623 oneway: false,
624 return_type: Some(TypeSpec::Scoped(component_type.clone())),
625 params: Vec::new(),
626 raises: alloc::vec![scoped(&["Components", "CreateFailure"], span)],
627 annotations: Vec::new(),
628 span,
629 })],
630 annotations: Vec::new(),
631 span,
632 }
633}
634
635fn build_keyed_implicit(
636 home_name: &str,
637 component_type: &ScopedName,
638 key_type: &ScopedName,
639 span: Span,
640) -> InterfaceDef {
641 let exports = alloc::vec![
645 Export::Op(OpDecl {
647 name: Identifier::new("create", span),
648 oneway: false,
649 return_type: Some(TypeSpec::Scoped(component_type.clone())),
650 params: alloc::vec![ParamDecl {
651 attribute: ParamAttribute::In,
652 type_spec: TypeSpec::Scoped(key_type.clone()),
653 name: Identifier::new("key", span),
654 annotations: Vec::new(),
655 span,
656 }],
657 raises: alloc::vec![
658 scoped(&["Components", "CreateFailure"], span),
659 scoped(&["Components", "DuplicateKeyValue"], span),
660 scoped(&["Components", "InvalidKey"], span),
661 ],
662 annotations: Vec::new(),
663 span,
664 }),
665 Export::Op(OpDecl {
667 name: Identifier::new("find_by_primary_key", span),
668 oneway: false,
669 return_type: Some(TypeSpec::Scoped(component_type.clone())),
670 params: alloc::vec![ParamDecl {
671 attribute: ParamAttribute::In,
672 type_spec: TypeSpec::Scoped(key_type.clone()),
673 name: Identifier::new("key", span),
674 annotations: Vec::new(),
675 span,
676 }],
677 raises: alloc::vec![
678 scoped(&["Components", "FinderFailure"], span),
679 scoped(&["Components", "UnknownKeyValue"], span),
680 scoped(&["Components", "InvalidKey"], span),
681 ],
682 annotations: Vec::new(),
683 span,
684 }),
685 Export::Op(OpDecl {
687 name: Identifier::new("remove", span),
688 oneway: false,
689 return_type: None,
690 params: alloc::vec![ParamDecl {
691 attribute: ParamAttribute::In,
692 type_spec: TypeSpec::Scoped(key_type.clone()),
693 name: Identifier::new("key", span),
694 annotations: Vec::new(),
695 span,
696 }],
697 raises: alloc::vec![
698 scoped(&["Components", "RemoveFailure"], span),
699 scoped(&["Components", "UnknownKeyValue"], span),
700 scoped(&["Components", "InvalidKey"], span),
701 ],
702 annotations: Vec::new(),
703 span,
704 }),
705 Export::Op(OpDecl {
707 name: Identifier::new("get_primary_key", span),
708 oneway: false,
709 return_type: Some(TypeSpec::Scoped(key_type.clone())),
710 params: alloc::vec![ParamDecl {
711 attribute: ParamAttribute::In,
712 type_spec: TypeSpec::Scoped(component_type.clone()),
713 name: Identifier::new("comp", span),
714 annotations: Vec::new(),
715 span,
716 }],
717 raises: Vec::new(),
718 annotations: Vec::new(),
719 span,
720 }),
721 ];
722
723 InterfaceDef {
724 kind: InterfaceKind::Plain,
725 name: Identifier::new(format!("{home_name}Implicit"), span),
726 bases: Vec::new(),
729 exports,
730 annotations: Vec::new(),
731 span,
732 }
733}
734
735fn base_explicit_name(base: &ScopedName, span: Span) -> ScopedName {
736 let last_text = base
737 .parts
738 .last()
739 .map_or_else(|| String::from("BaseHome"), |i| i.text.clone());
740 ScopedName {
741 absolute: base.absolute,
742 parts: alloc::vec![Identifier::new(format!("{last_text}Explicit"), span)],
743 span,
744 }
745}
746
747fn scoped(parts: &[&str], span: Span) -> ScopedName {
748 ScopedName {
749 absolute: false,
750 parts: parts
751 .iter()
752 .map(|p| Identifier::new((*p).to_string(), span))
753 .collect(),
754 span,
755 }
756}
757
758#[must_use]
760pub fn scoped_name(parts: &[&str], span: Span) -> ScopedName {
761 scoped(parts, span)
762}
763
764fn lowercase_first(s: &str) -> String {
765 let mut chars = s.chars();
766 chars.next().map_or_else(String::new, |c| {
767 let mut out: String = c.to_lowercase().collect();
768 out.push_str(chars.as_str());
769 out
770 })
771}
772
773const _: fn() = || {
775 let _ = core::marker::PhantomData::<StringType>;
776 let _ = core::marker::PhantomData::<PrimitiveType>;
777};
778
779#[cfg(test)]
780#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
781mod tests {
782 use super::*;
783 use zerodds_idl::ast::{ComponentDef, ComponentExport, IntegerType};
784
785 fn span() -> Span {
786 Span::SYNTHETIC
787 }
788
789 fn ident(s: &str) -> Identifier {
790 Identifier::new(s, span())
791 }
792
793 fn sn(parts: &[&str]) -> ScopedName {
794 scoped(parts, span())
795 }
796
797 fn comp(name: &str, body: Vec<ComponentExport>) -> ComponentDef {
798 ComponentDef {
799 name: ident(name),
800 base: None,
801 supports: Vec::new(),
802 body,
803 annotations: Vec::new(),
804 span: span(),
805 }
806 }
807
808 fn comp_with_supports(
809 name: &str,
810 supports: Vec<ScopedName>,
811 body: Vec<ComponentExport>,
812 ) -> ComponentDef {
813 ComponentDef {
814 name: ident(name),
815 base: None,
816 supports,
817 body,
818 annotations: Vec::new(),
819 span: span(),
820 }
821 }
822
823 fn comp_with_base(name: &str, base: ScopedName, body: Vec<ComponentExport>) -> ComponentDef {
824 ComponentDef {
825 name: ident(name),
826 base: Some(base),
827 supports: Vec::new(),
828 body,
829 annotations: Vec::new(),
830 span: span(),
831 }
832 }
833
834 fn op_names(iface: &InterfaceDef) -> Vec<String> {
835 iface
836 .exports
837 .iter()
838 .filter_map(|e| match e {
839 Export::Op(o) => Some(o.name.text.clone()),
840 _ => None,
841 })
842 .collect()
843 }
844
845 #[test]
846 fn simple_basic_component_inherits_ccmobject() {
847 let c = comp("C", alloc::vec![]);
850 let out = transform_component(&c);
851 assert_eq!(out.equivalent_interface.name.text, "C");
852 assert_eq!(out.equivalent_interface.bases.len(), 1);
853 let parts: Vec<&str> = out.equivalent_interface.bases[0]
854 .parts
855 .iter()
856 .map(|p| p.text.as_str())
857 .collect();
858 assert_eq!(parts, alloc::vec!["Components", "CCMObject"]);
859 }
860
861 #[test]
862 fn component_with_supports_inherits_ccmobject_plus_supported() {
863 let c = comp_with_supports("C", alloc::vec![sn(&["I1"]), sn(&["I2"])], alloc::vec![]);
866 let out = transform_component(&c);
867 let names: Vec<String> = out
868 .equivalent_interface
869 .bases
870 .iter()
871 .map(|b| b.parts.last().map_or(String::new(), |i| i.text.clone()))
872 .collect();
873 assert_eq!(names, alloc::vec!["CCMObject", "I1", "I2"]);
874 }
875
876 #[test]
877 fn component_with_base_inherits_base_not_ccmobject() {
878 let c = comp_with_base("C", sn(&["B"]), alloc::vec![]);
881 let out = transform_component(&c);
882 let parts: Vec<String> = out.equivalent_interface.bases[0]
883 .parts
884 .iter()
885 .map(|p| p.text.clone())
886 .collect();
887 assert_eq!(parts, alloc::vec!["B"]);
888 }
889
890 #[test]
891 fn provides_decl_yields_provide_underscore_name_op() {
892 let c = comp(
894 "C",
895 alloc::vec![ComponentExport::Provides {
896 type_spec: sn(&["M", "I"]),
897 name: ident("foo"),
898 span: span(),
899 }],
900 );
901 let out = transform_component(&c);
902 let names = op_names(&out.equivalent_interface);
903 assert!(names.contains(&String::from("provide_foo")));
904 }
905
906 #[test]
907 fn uses_simplex_yields_three_ops_with_correct_signatures() {
908 let c = comp(
910 "C",
911 alloc::vec![ComponentExport::Uses {
912 type_spec: sn(&["I"]),
913 name: ident("manager"),
914 multiple: false,
915 span: span(),
916 }],
917 );
918 let out = transform_component(&c);
919 let names = op_names(&out.equivalent_interface);
920 assert!(names.contains(&String::from("connect_manager")));
921 assert!(names.contains(&String::from("disconnect_manager")));
922 assert!(names.contains(&String::from("get_connection_manager")));
923 }
924
925 #[test]
926 fn uses_multiple_yields_get_connections_plural_op() {
927 let c = comp(
929 "C",
930 alloc::vec![ComponentExport::Uses {
931 type_spec: sn(&["I"]),
932 name: ident("managers"),
933 multiple: true,
934 span: span(),
935 }],
936 );
937 let out = transform_component(&c);
938 let names = op_names(&out.equivalent_interface);
939 assert!(names.contains(&String::from("connect_managers")));
940 assert!(names.contains(&String::from("disconnect_managers")));
941 assert!(names.contains(&String::from("get_connections_managers")));
942 let connect = out
944 .equivalent_interface
945 .exports
946 .iter()
947 .find_map(|e| match e {
948 Export::Op(o) if o.name.text == "connect_managers" => Some(o),
949 _ => None,
950 })
951 .expect("connect");
952 let TypeSpec::Scoped(ret) = connect.return_type.as_ref().expect("return") else {
953 panic!()
954 };
955 assert_eq!(
956 ret.parts
957 .iter()
958 .map(|i| i.text.as_str())
959 .collect::<Vec<_>>(),
960 alloc::vec!["Components", "Cookie"]
961 );
962 }
963
964 #[test]
965 fn emits_decl_yields_connect_disconnect_with_consumer() {
966 let c = comp(
968 "C",
969 alloc::vec![ComponentExport::Emits {
970 type_spec: sn(&["Tick"]),
971 name: ident("ticker"),
972 span: span(),
973 }],
974 );
975 let out = transform_component(&c);
976 let names = op_names(&out.equivalent_interface);
977 assert!(names.contains(&String::from("connect_ticker")));
978 assert!(names.contains(&String::from("disconnect_ticker")));
979 assert_eq!(out.event_consumer_interfaces.len(), 1);
981 assert_eq!(out.event_consumer_interfaces[0].name.text, "TickConsumer");
982 }
983
984 #[test]
985 fn publishes_decl_yields_subscribe_unsubscribe_with_cookie() {
986 let c = comp(
988 "C",
989 alloc::vec![ComponentExport::Publishes {
990 type_spec: sn(&["Tick"]),
991 name: ident("ticker"),
992 span: span(),
993 }],
994 );
995 let out = transform_component(&c);
996 let names = op_names(&out.equivalent_interface);
997 assert!(names.contains(&String::from("subscribe_ticker")));
998 assert!(names.contains(&String::from("unsubscribe_ticker")));
999 let sub = out
1001 .equivalent_interface
1002 .exports
1003 .iter()
1004 .find_map(|e| match e {
1005 Export::Op(o) if o.name.text == "subscribe_ticker" => Some(o),
1006 _ => None,
1007 })
1008 .expect("subscribe");
1009 let TypeSpec::Scoped(ret) = sub.return_type.as_ref().expect("return") else {
1010 panic!()
1011 };
1012 assert_eq!(
1013 ret.parts
1014 .iter()
1015 .map(|i| i.text.as_str())
1016 .collect::<Vec<_>>(),
1017 alloc::vec!["Components", "Cookie"]
1018 );
1019 }
1020
1021 #[test]
1022 fn consumes_decl_yields_get_consumer_op() {
1023 let c = comp(
1026 "C",
1027 alloc::vec![ComponentExport::Consumes {
1028 type_spec: sn(&["Tick"]),
1029 name: ident("sink"),
1030 span: span(),
1031 }],
1032 );
1033 let out = transform_component(&c);
1034 let names = op_names(&out.equivalent_interface);
1035 assert!(names.contains(&String::from("get_consumer_sink")));
1036 let op = out
1038 .equivalent_interface
1039 .exports
1040 .iter()
1041 .find_map(|e| match e {
1042 Export::Op(o) if o.name.text == "get_consumer_sink" => Some(o),
1043 _ => None,
1044 })
1045 .expect("op");
1046 let TypeSpec::Scoped(ret) = op.return_type.as_ref().expect("return") else {
1047 panic!()
1048 };
1049 assert_eq!(
1050 ret.parts.last().expect("last").text.as_str(),
1051 "TickConsumer"
1052 );
1053 }
1054
1055 #[test]
1056 fn duplicate_event_type_yields_only_one_consumer_interface() {
1057 let c = comp(
1059 "C",
1060 alloc::vec![
1061 ComponentExport::Emits {
1062 type_spec: sn(&["Tick"]),
1063 name: ident("a"),
1064 span: span(),
1065 },
1066 ComponentExport::Publishes {
1067 type_spec: sn(&["Tick"]),
1068 name: ident("b"),
1069 span: span(),
1070 },
1071 ComponentExport::Consumes {
1072 type_spec: sn(&["Tick"]),
1073 name: ident("c"),
1074 span: span(),
1075 },
1076 ],
1077 );
1078 let out = transform_component(&c);
1079 assert_eq!(out.event_consumer_interfaces.len(), 1);
1080 }
1081
1082 #[test]
1083 fn attribute_is_propagated_to_equivalent_interface() {
1084 let c = comp(
1085 "C",
1086 alloc::vec![ComponentExport::Attribute(zerodds_idl::ast::AttrDcl {
1087 readonly: false,
1088 type_spec: TypeSpec::Primitive(PrimitiveType::Integer(IntegerType::Long)),
1089 name: ident("rate"),
1090 span: span(),
1091 })],
1092 );
1093 let out = transform_component(&c);
1094 let attr = out
1095 .equivalent_interface
1096 .exports
1097 .iter()
1098 .find_map(|e| match e {
1099 Export::Attr(a) => Some(a),
1100 _ => None,
1101 })
1102 .expect("attr");
1103 assert_eq!(attr.name.text, "rate");
1104 assert!(!attr.readonly);
1105 }
1106
1107 #[test]
1108 fn home_without_primary_key_yields_keyless_implicit() {
1109 let h = HomeDef {
1111 name: ident("CManager"),
1112 base: None,
1113 supports: Vec::new(),
1114 manages: sn(&["C"]),
1115 primary_key: None,
1116 annotations: Vec::new(),
1117 span: span(),
1118 };
1119 let out = transform_home(&h);
1120 assert_eq!(out.explicit.name.text, "CManagerExplicit");
1121 assert_eq!(out.implicit.name.text, "CManagerImplicit");
1122 assert_eq!(out.equivalent.name.text, "CManager");
1123 let parts = out.explicit.bases[0]
1125 .parts
1126 .iter()
1127 .map(|i| i.text.as_str())
1128 .collect::<Vec<_>>();
1129 assert_eq!(parts, alloc::vec!["Components", "CCMHome"]);
1130 let parts = out.implicit.bases[0]
1132 .parts
1133 .iter()
1134 .map(|i| i.text.as_str())
1135 .collect::<Vec<_>>();
1136 assert_eq!(parts, alloc::vec!["Components", "KeylessCCMHome"]);
1137 let names = op_names(&out.implicit);
1139 assert_eq!(names, alloc::vec!["create"]);
1140 }
1141
1142 #[test]
1143 fn home_with_primary_key_yields_keyed_implicit_with_four_ops() {
1144 let h = HomeDef {
1146 name: ident("CManager"),
1147 base: None,
1148 supports: Vec::new(),
1149 manages: sn(&["C"]),
1150 primary_key: Some(sn(&["CKey"])),
1151 annotations: Vec::new(),
1152 span: span(),
1153 };
1154 let out = transform_home(&h);
1155 let names = op_names(&out.implicit);
1156 for expected in ["create", "find_by_primary_key", "remove", "get_primary_key"] {
1157 assert!(
1158 names.contains(&String::from(expected)),
1159 "missing {expected} in {names:?}"
1160 );
1161 }
1162 assert!(out.implicit.bases.is_empty());
1164 }
1165
1166 #[test]
1167 fn derived_home_inherits_base_explicit() {
1168 let h = HomeDef {
1172 name: ident("CManagerExt"),
1173 base: Some(sn(&["CManagerBase"])),
1174 supports: Vec::new(),
1175 manages: sn(&["C"]),
1176 primary_key: None,
1177 annotations: Vec::new(),
1178 span: span(),
1179 };
1180 let out = transform_home(&h);
1181 assert_eq!(out.explicit.name.text, "CManagerExtExplicit");
1185 assert_eq!(
1186 out.explicit.bases.len(),
1187 1,
1188 "derived home explicit must inherit exactly the base's Explicit"
1189 );
1190 let parts = out.explicit.bases[0]
1191 .parts
1192 .iter()
1193 .map(|i| i.text.as_str())
1194 .collect::<Vec<_>>();
1195 assert_eq!(
1196 parts,
1197 alloc::vec!["CManagerBaseExplicit"],
1198 "expected CManagerBaseExplicit, got {parts:?}"
1199 );
1200 }
1201
1202 #[test]
1203 fn derived_home_with_supports_extends_base_explicit() {
1204 let h = HomeDef {
1207 name: ident("CManagerExt"),
1208 base: Some(sn(&["CManagerBase"])),
1209 supports: alloc::vec![sn(&["IExtraIface"])],
1210 manages: sn(&["C"]),
1211 primary_key: None,
1212 annotations: Vec::new(),
1213 span: span(),
1214 };
1215 let out = transform_home(&h);
1216 let names: Vec<String> = out
1217 .explicit
1218 .bases
1219 .iter()
1220 .map(|b| b.parts.last().map_or(String::new(), |i| i.text.clone()))
1221 .collect();
1222 assert_eq!(
1223 names,
1224 alloc::vec![
1225 String::from("CManagerBaseExplicit"),
1226 String::from("IExtraIface")
1227 ]
1228 );
1229 }
1230
1231 #[test]
1232 fn equivalent_home_inherits_explicit_and_implicit() {
1233 let h = HomeDef {
1234 name: ident("CManager"),
1235 base: None,
1236 supports: Vec::new(),
1237 manages: sn(&["C"]),
1238 primary_key: None,
1239 annotations: Vec::new(),
1240 span: span(),
1241 };
1242 let out = transform_home(&h);
1243 let names: Vec<String> = out
1244 .equivalent
1245 .bases
1246 .iter()
1247 .map(|b| b.parts.last().map_or(String::new(), |i| i.text.clone()))
1248 .collect();
1249 assert_eq!(names, alloc::vec!["CManagerExplicit", "CManagerImplicit"]);
1250 }
1251
1252 #[test]
1253 fn event_type_first_in_chain_inherits_event_base() {
1254 let et = EventDef {
1256 name: ident("Tick"),
1257 kind: ValueKind::Concrete,
1258 inheritance: None,
1259 elements: Vec::new(),
1260 annotations: Vec::new(),
1261 span: span(),
1262 };
1263 let out = transform_event_type(&et);
1264 let parts = out.valuetype_bases[0]
1266 .parts
1267 .iter()
1268 .map(|i| i.text.as_str())
1269 .collect::<Vec<_>>();
1270 assert_eq!(parts, alloc::vec!["Components", "EventBase"]);
1271 assert_eq!(out.consumer_interface.name.text, "TickConsumer");
1273 let cb = out.consumer_interface.bases[0]
1274 .parts
1275 .iter()
1276 .map(|i| i.text.as_str())
1277 .collect::<Vec<_>>();
1278 assert_eq!(cb, alloc::vec!["Components", "EventConsumerBase"]);
1279 let push = out.consumer_interface.exports.iter().find_map(|e| match e {
1281 Export::Op(o) => Some(o),
1282 _ => None,
1283 });
1284 let push = push.expect("push op");
1285 assert_eq!(push.name.text, "push_Tick");
1286 assert_eq!(push.params[0].name.text, "the_tick");
1287 }
1288
1289 #[test]
1290 fn full_stockmanager_component_yields_all_expected_ops() {
1291 let c = comp(
1293 "Trader",
1294 alloc::vec![
1295 ComponentExport::Provides {
1296 type_spec: sn(&["StockManager"]),
1297 name: ident("manager"),
1298 span: span(),
1299 },
1300 ComponentExport::Uses {
1301 type_spec: sn(&["Bank"]),
1302 name: ident("bank"),
1303 multiple: false,
1304 span: span(),
1305 },
1306 ComponentExport::Uses {
1307 type_spec: sn(&["Feed"]),
1308 name: ident("feeds"),
1309 multiple: true,
1310 span: span(),
1311 },
1312 ComponentExport::Emits {
1313 type_spec: sn(&["Tick"]),
1314 name: ident("ticker"),
1315 span: span(),
1316 },
1317 ComponentExport::Publishes {
1318 type_spec: sn(&["Tick"]),
1319 name: ident("public_ticker"),
1320 span: span(),
1321 },
1322 ComponentExport::Consumes {
1323 type_spec: sn(&["Order"]),
1324 name: ident("order_sink"),
1325 span: span(),
1326 },
1327 ],
1328 );
1329 let out = transform_component(&c);
1330 let names = op_names(&out.equivalent_interface);
1331 for expected in [
1332 "provide_manager",
1333 "connect_bank",
1334 "disconnect_bank",
1335 "get_connection_bank",
1336 "connect_feeds",
1337 "disconnect_feeds",
1338 "get_connections_feeds",
1339 "connect_ticker",
1340 "disconnect_ticker",
1341 "subscribe_public_ticker",
1342 "unsubscribe_public_ticker",
1343 "get_consumer_order_sink",
1344 ] {
1345 assert!(
1346 names.contains(&String::from(expected)),
1347 "missing {expected} in {names:?}"
1348 );
1349 }
1350 let consumer_names: Vec<String> = out
1352 .event_consumer_interfaces
1353 .iter()
1354 .map(|i| i.name.text.clone())
1355 .collect();
1356 assert!(consumer_names.contains(&String::from("TickConsumer")));
1357 assert!(consumer_names.contains(&String::from("OrderConsumer")));
1358 }
1359}