1use std::{collections::HashMap, mem, rc::Rc, str::FromStr};
8
9use log::{debug, info, trace};
10
11use orrery_core::{color::Color, draw, identifier::Id, semantic};
12
13use crate::{
14 builtin_types, elaborate_utils,
15 error::{Diagnostic, ErrorCode, Result},
16 parser_types,
17 span::{Span, Spanned},
18};
19
20#[derive(Debug, Clone, Default)]
25pub struct ElaborateConfig {
26 pub component_layout: semantic::LayoutEngine,
28 pub sequence_layout: semantic::LayoutEngine,
30}
31
32impl ElaborateConfig {
33 pub fn new(
35 component_layout: semantic::LayoutEngine,
36 sequence_layout: semantic::LayoutEngine,
37 ) -> Self {
38 Self {
39 component_layout,
40 sequence_layout,
41 }
42 }
43}
44
45pub struct Builder {
59 cfg: ElaborateConfig,
60 type_definitions: HashMap<Id, elaborate_utils::TypeDefinition>,
61}
62
63impl Builder {
64 pub fn new(cfg: ElaborateConfig) -> Self {
74 Self {
75 cfg,
76 type_definitions: HashMap::new(),
77 }
78 }
79
80 pub fn build(mut self, ast: &parser_types::FileAst) -> Result<semantic::Diagram> {
104 debug!("Building elaborated diagram");
105 self.build_diagram_from_file_ast(ast)
106 }
107
108 fn build_diagram_from_file_ast(
123 &mut self,
124 file_ast: &parser_types::FileAst,
125 ) -> Result<semantic::Diagram> {
126 let (kind_spanned, attributes) = match &file_ast.header {
127 parser_types::FileHeader::Diagram { kind, attributes } => (kind, attributes),
128 parser_types::FileHeader::Library { span } => {
129 return Err(Diagnostic::error("expected diagram, found library")
130 .with_code(ErrorCode::E306)
131 .with_label(*span, "expected diagram"));
132 }
133 };
134
135 info!("Processing diagram of kind: {kind_spanned}");
136 trace!("Type definitions: {:?}", file_ast.type_definitions);
137 trace!("Elements count: {}", file_ast.elements.len());
138
139 let saved_type_defs = mem::replace(
140 &mut self.type_definitions,
141 Self::builtin_type_definitions_map(),
142 );
143
144 debug!("Updating type definitions");
145 self.update_type_direct_definitions(&file_ast.type_definitions)?;
146
147 let kind = **kind_spanned;
148
149 debug!("Building block from elements");
150 let block = self.build_block_from_elements(&file_ast.elements, kind)?;
151
152 let scope = match block {
153 semantic::Block::None => {
154 debug!("Empty block, using default scope");
155 semantic::Scope::default()
156 }
157 semantic::Block::Scope(scope) => {
158 debug!(
159 elements_len = scope.elements().len();
160 "Using scope from block",
161 );
162 scope
163 }
164 semantic::Block::Diagram(_) => {
165 return Err(Diagnostic::error("nested diagram not allowed")
166 .with_code(ErrorCode::E305)
167 .with_label(kind_spanned.span(), "nested diagram")
168 .with_help("diagrams cannot be nested inside other diagrams"));
169 }
170 };
171
172 let (layout_engine, background_color, lifeline_definition) =
173 self.extract_diagram_attributes(kind, attributes)?;
174
175 info!(kind:?; "Diagram elaboration completed successfully");
176
177 self.type_definitions = saved_type_defs;
179
180 Ok(semantic::Diagram::new(
181 kind,
182 scope,
183 layout_engine,
184 background_color,
185 lifeline_definition,
186 ))
187 }
188
189 fn build_diagram_from_diagram_source(
204 &mut self,
205 source: &parser_types::DiagramSource,
206 ) -> Result<semantic::Diagram> {
207 match source {
208 parser_types::DiagramSource::Inline(rc) => {
209 let file_ast = rc.borrow();
210 self.build_diagram_from_file_ast(&file_ast)
211 }
212 parser_types::DiagramSource::Ref(id) => Err(Diagnostic::error(format!(
215 "unresolved embed reference `{id}`",
216 ))
217 .with_code(ErrorCode::E309)
218 .with_label(id.span(), "expected inlined embedded diagram")),
219 }
220 }
221
222 fn extract_type_spec<'b>(
234 attr: &'b parser_types::Attribute<'b>,
235 key: &str,
236 ) -> Result<&'b parser_types::TypeSpec<'b>> {
237 attr.value.as_type_spec().map_err(|err| {
238 Diagnostic::error(err.to_string())
239 .with_code(ErrorCode::E302)
240 .with_label(attr.span(), format!("invalid {key} attribute value"))
241 .with_help(format!(
242 "{key} attribute must be a type reference or inline attributes"
243 ))
244 })
245 }
246
247 fn extract_string<'b>(attr: &'b parser_types::Attribute<'b>, key: &str) -> Result<&'b str> {
253 attr.value.as_str().map_err(|err| {
254 Diagnostic::error(err.to_string())
255 .with_code(ErrorCode::E302)
256 .with_label(attr.span(), format!("invalid {key} value"))
257 .with_help(format!("{key} values must be strings"))
258 })
259 }
260
261 fn extract_color(attr: &parser_types::Attribute<'_>, key: &str) -> Result<Color> {
268 let color_str = attr.value.as_str().map_err(|err| {
269 Diagnostic::error(err.to_string())
270 .with_code(ErrorCode::E302)
271 .with_label(attr.span(), "invalid color value")
272 .with_help("color values must be strings")
273 })?;
274
275 Color::new(color_str).map_err(|err| {
276 Diagnostic::error(format!("invalid {key} `{color_str}`: {err}"))
277 .with_code(ErrorCode::E302)
278 .with_label(attr.span(), "invalid color")
279 .with_help("use a valid CSS color")
280 })
281 }
282
283 fn extract_positive_float(attr: &parser_types::Attribute<'_>, key: &str) -> Result<f32> {
289 attr.value.as_float().map_err(|err| {
290 Diagnostic::error(err.to_string())
291 .with_code(ErrorCode::E302)
292 .with_label(attr.span(), format!("invalid {key} value"))
293 .with_help(format!("{key} must be a positive number"))
294 })
295 }
296
297 fn extract_usize(attr: &parser_types::Attribute<'_>, key: &str, hint: &str) -> Result<usize> {
304 attr.value.as_usize().map_err(|err| {
305 Diagnostic::error(err.to_string())
306 .with_code(ErrorCode::E302)
307 .with_label(attr.span(), format!("invalid {key} value"))
308 .with_help(format!("{key} {hint}"))
309 })
310 }
311
312 fn builtin_type_definitions_map() -> HashMap<Id, elaborate_utils::TypeDefinition> {
318 builtin_types::defaults()
319 .into_iter()
320 .map(|def| (def.id(), def))
321 .collect()
322 }
323
324 fn insert_type_definition(
326 &mut self,
327 type_def: elaborate_utils::TypeDefinition,
328 span: Span,
329 ) -> Result<elaborate_utils::TypeDefinition> {
330 let id = type_def.id();
331
332 if self.type_definitions.insert(id, type_def.clone()).is_none() {
334 Ok(type_def)
335 } else {
336 Err(Diagnostic::error(format!("cannot override type `{id}`"))
337 .with_code(ErrorCode::E301)
338 .with_label(span, "type override not supported")
339 .with_help("built-in types cannot be redefined"))
340 }
341 }
342
343 fn update_type_direct_definitions(
344 &mut self,
345 type_definitions: &Vec<parser_types::TypeDefinition>,
346 ) -> Result<()> {
347 for type_def in type_definitions {
348 let base_type_name = type_def
349 .type_spec
350 .type_name
351 .as_ref()
352 .expect("TypeDefinition should always have a type_name in TypeSpec");
353
354 let base = self
355 .type_definitions
356 .get(base_type_name.inner())
357 .ok_or_else(|| {
358 self.create_undefined_type_error(
360 base_type_name,
361 &format!("base type `{}` not found", base_type_name.inner()),
362 )
363 })?;
364
365 let new_type_def = self.build_type_from_base(
367 *type_def.name.inner(),
368 base,
369 &type_def.type_spec.attributes,
370 )?;
371 self.insert_type_definition(new_type_def, type_def.span())?;
372 }
373 Ok(())
374 }
375
376 fn build_block_from_elements(
388 &mut self,
389 parser_elements: &[parser_types::Element],
390 diagram_kind: semantic::DiagramKind,
391 ) -> Result<semantic::Block> {
392 if parser_elements.is_empty() {
393 Ok(semantic::Block::None)
394 } else {
395 Ok(semantic::Block::Scope(self.build_scope_from_elements(
396 parser_elements,
397 diagram_kind,
398 )?))
399 }
400 }
401
402 fn build_scope_from_elements(
413 &mut self,
414 parser_elements: &[parser_types::Element],
415 diagram_kind: semantic::DiagramKind,
416 ) -> Result<semantic::Scope> {
417 let mut elements = Vec::new();
418
419 for parser_elm in parser_elements {
420 let element = match parser_elm {
421 parser_types::Element::Component {
422 name,
423 display_name,
424 type_spec,
425 content,
426 } => self.build_component_element(
427 name,
428 display_name,
429 type_spec,
430 content,
431 parser_elm,
432 diagram_kind,
433 )?,
434 parser_types::Element::Relation {
435 source,
436 target,
437 relation_type,
438 type_spec,
439 label,
440 } => {
441 self.build_relation_element(source, target, relation_type, type_spec, label)?
442 }
443 parser_types::Element::ActivateBlock { .. } => {
444 unreachable!(
445 "ActivateBlock should have been desugared into explicit activate/deactivate statements before elaboration"
446 );
447 }
448 parser_types::Element::Activate {
449 component,
450 type_spec,
451 } => self.build_activate_element(component, type_spec, diagram_kind)?,
452 parser_types::Element::Deactivate { component } => {
453 self.build_deactivate_element(component, diagram_kind)?
454 }
455 parser_types::Element::Fragment(fragment) => {
456 self.build_fragment_element(fragment, diagram_kind)?
457 }
458 parser_types::Element::AltElseBlock { .. }
459 | parser_types::Element::OptBlock { .. }
460 | parser_types::Element::LoopBlock { .. }
461 | parser_types::Element::ParBlock { .. }
462 | parser_types::Element::BreakBlock { .. }
463 | parser_types::Element::CriticalBlock { .. } => {
464 unreachable!(
465 "Fragment sugar syntax should have been desugared into Fragment elements before elaboration"
466 );
467 }
468 parser_types::Element::Note(note) => self.build_note_element(note, diagram_kind)?,
469 };
470 elements.push(element);
471 }
472 Ok(semantic::Scope::new(elements))
473 }
474
475 fn build_component_element(
493 &mut self,
494 name: &Spanned<Id>,
495 display_name: &Option<Spanned<String>>,
496 type_spec: &parser_types::TypeSpec,
497 content: &parser_types::ComponentContent,
498 parser_elm: &parser_types::Element,
499 diagram_kind: semantic::DiagramKind,
500 ) -> Result<semantic::Element> {
501 let type_def = self.build_type_definition(type_spec)?;
502
503 let shape_def = type_def.shape_definition().map_err(|err| {
504 Diagnostic::error(err)
505 .with_code(ErrorCode::E307)
506 .with_label(type_spec.span(), "invalid shape type")
507 })?;
508
509 if !matches!(content, parser_types::ComponentContent::None) && !shape_def.supports_content()
510 {
511 let type_name = type_spec
512 .type_name
513 .as_ref()
514 .map_or(type_def.id(), |name| *name.inner());
515 return Err(Diagnostic::error(format!(
516 "shape type `{type_name}` does not support nested content"
517 ))
518 .with_code(ErrorCode::E308)
519 .with_label(parser_elm.span(), "content not supported")
520 .with_help(format!(
521 "shape `{type_name}` is content-free and cannot contain nested elements or embedded diagrams"
522 )));
523 }
524
525 let block = match content {
526 parser_types::ComponentContent::None => semantic::Block::None,
527 parser_types::ComponentContent::Scope(elements) => {
528 self.build_block_from_elements(elements, diagram_kind)?
529 }
530 parser_types::ComponentContent::Diagram(source) => {
531 semantic::Block::Diagram(self.build_diagram_from_diagram_source(source)?)
532 }
533 };
534
535 let node = semantic::Node::new(
536 *name.inner(),
537 display_name.as_ref().map(|n| n.to_string()),
538 block,
539 Rc::clone(shape_def),
540 );
541
542 Ok(semantic::Element::Node(node))
543 }
544
545 fn build_relation_element(
556 &mut self,
557 source: &Spanned<Id>,
558 target: &Spanned<Id>,
559 relation_type: &Spanned<&str>,
560 type_spec: &parser_types::TypeSpec,
561 label: &Option<Spanned<String>>,
562 ) -> Result<semantic::Element> {
563 let relation_type_def = self.build_type_definition(type_spec)?;
565
566 let arrow_def = relation_type_def.arrow_definition().map_err(|err| {
567 Diagnostic::error(err)
568 .with_code(ErrorCode::E307)
569 .with_label(type_spec.span(), "invalid arrow type")
570 })?;
571
572 let arrow_direction = draw::ArrowDirection::from_str(relation_type).map_err(|_| {
573 Diagnostic::error(format!("invalid arrow direction `{relation_type}`"))
574 .with_code(ErrorCode::E302)
575 .with_label(relation_type.span(), "invalid direction")
576 .with_help("arrow direction must be `->`, `<-`, `<->`, or `-`")
577 })?;
578
579 Ok(semantic::Element::Relation(semantic::Relation::new(
580 *source.inner(),
581 *target.inner(),
582 arrow_direction,
583 label.as_ref().map(|l| l.to_string()),
584 Rc::clone(arrow_def),
585 )))
586 }
587
588 fn build_activate_element(
599 &mut self,
600 component: &Spanned<Id>,
601 type_spec: &parser_types::TypeSpec,
602 diagram_kind: semantic::DiagramKind,
603 ) -> Result<semantic::Element> {
604 if diagram_kind != semantic::DiagramKind::Sequence {
606 return Err(Diagnostic::error(
607 "activate statements are only supported in sequence diagrams",
608 )
609 .with_code(ErrorCode::E304)
610 .with_label(component.span(), "activate not allowed here")
611 .with_help("activate statements are used for temporal grouping in sequence diagrams"));
612 }
613
614 let activate_type_def = self.build_type_definition(type_spec)?;
615
616 let activation_box_def = activate_type_def
617 .activation_box_definition()
618 .map_err(|err| {
619 Diagnostic::error(err)
620 .with_code(ErrorCode::E307)
621 .with_label(type_spec.span(), "invalid activation box type")
622 })?;
623
624 Ok(semantic::Element::Activate(semantic::Activate::new(
625 *component.inner(),
626 Rc::clone(activation_box_def),
627 )))
628 }
629
630 fn build_deactivate_element(
639 &mut self,
640 component: &Spanned<Id>,
641 diagram_kind: semantic::DiagramKind,
642 ) -> Result<semantic::Element> {
643 if diagram_kind != semantic::DiagramKind::Sequence {
645 return Err(Diagnostic::error(
646 "deactivate statements are only supported in sequence diagrams",
647 )
648 .with_code(ErrorCode::E304)
649 .with_label(component.span(), "deactivate not allowed here")
650 .with_help(
651 "deactivate statements are used for temporal grouping in sequence diagrams",
652 ));
653 }
654
655 Ok(semantic::Element::Deactivate(*component.inner()))
656 }
657
658 fn build_fragment_element(
669 &mut self,
670 fragment: &parser_types::Fragment,
671 diagram_kind: semantic::DiagramKind,
672 ) -> Result<semantic::Element> {
673 if diagram_kind != semantic::DiagramKind::Sequence {
675 return Err(Diagnostic::error(
676 "fragment blocks are only supported in sequence diagrams",
677 )
678 .with_code(ErrorCode::E304)
679 .with_label(fragment.span(), "fragment not allowed here")
680 .with_help("fragment blocks are used for grouping in sequence diagrams"));
681 }
682
683 let type_def = self
685 .build_type_definition(&fragment.type_spec)
686 .map_err(|_| {
687 Diagnostic::error(format!(
688 "invalid fragment type for operation `{}`",
689 fragment.operation.inner()
690 ))
691 .with_code(ErrorCode::E300)
692 .with_label(fragment.operation.span(), "invalid fragment type")
693 .with_help("fragment types must be defined in the type system")
694 })?;
695
696 let fragment_def = type_def.fragment_definition().map_err(|err| {
697 Diagnostic::error(err)
698 .with_code(ErrorCode::E307)
699 .with_label(fragment.type_spec.span(), "invalid fragment type")
700 })?;
701
702 let mut sections = Vec::new();
703 for parser_section in &fragment.sections {
704 let scope = self.build_scope_from_elements(&parser_section.elements, diagram_kind)?;
705 let elements_vec = scope.elements().to_vec();
706
707 sections.push(semantic::FragmentSection::new(
708 parser_section.title.as_ref().map(|t| t.inner().to_string()),
709 elements_vec,
710 ));
711 }
712
713 Ok(semantic::Element::Fragment(semantic::Fragment::new(
714 fragment.operation.inner().to_string(),
715 sections,
716 Rc::clone(fragment_def),
717 )))
718 }
719
720 fn build_type_definition(
721 &mut self,
722 type_spec: &parser_types::TypeSpec,
723 ) -> Result<elaborate_utils::TypeDefinition> {
724 let type_name = type_spec.type_name.as_ref().ok_or_else(|| {
725 Diagnostic::error("base type `type_spec` must have a type name")
726 .with_code(ErrorCode::E306)
727 .with_label(type_spec.span(), "missing type name")
728 })?;
729 let Some(base) = self.type_definitions.get(type_name.inner()) else {
731 return Err(
732 self.create_undefined_type_error(type_name, &format!("unknown type `{type_name}`"))
733 );
734 };
735
736 let attributes = &type_spec.attributes;
737 if attributes.is_empty() {
739 return Ok(base.clone());
740 }
741
742 let id = Id::from_anonymous();
744 let new_type = self.build_type_from_base(id, base, attributes)?;
745 self.insert_type_definition(new_type, type_name.span())
746 }
747
748 fn resolve_text_type_reference(
754 &self,
755 type_spec: &parser_types::TypeSpec,
756 current_text_rc: &Rc<draw::TextDefinition>,
757 ) -> Result<Rc<draw::TextDefinition>> {
758 let mut text_rc = if let Some(type_name) = &type_spec.type_name {
760 let base_type = self
761 .type_definitions
762 .get(type_name.inner())
763 .ok_or_else(|| {
764 Diagnostic::error(format!("undefined text type `{}`", type_name.inner()))
765 .with_code(ErrorCode::E300)
766 .with_label(type_spec.span(), "undefined type")
767 .with_help("type must be defined with `type` statement before use")
768 })?;
769
770 let base_text_rc = base_type.text_definition_from_draw().map_err(|err| {
771 Diagnostic::error(format!(
772 "type `{}` is not a text type: {}",
773 type_name.inner(),
774 err
775 ))
776 .with_code(ErrorCode::E307)
777 .with_label(type_spec.span(), "invalid type reference")
778 .with_help("only `Text` types can be used for text attributes")
779 })?;
780
781 Rc::clone(base_text_rc)
782 } else {
783 Rc::clone(current_text_rc)
784 };
785
786 if !type_spec.attributes.is_empty() {
788 let text_def_mut = Rc::make_mut(&mut text_rc);
789 elaborate_utils::TextAttributeExtractor::extract_text_attributes(
790 text_def_mut,
791 &type_spec.attributes,
792 )?;
793 }
794
795 Ok(text_rc)
796 }
797
798 fn resolve_stroke_type_reference(
804 &self,
805 type_spec: &parser_types::TypeSpec,
806 current_stroke_rc: &Rc<draw::StrokeDefinition>,
807 ) -> Result<Rc<draw::StrokeDefinition>> {
808 let mut stroke_rc = if let Some(type_name) = &type_spec.type_name {
810 let base_type = self
811 .type_definitions
812 .get(type_name.inner())
813 .ok_or_else(|| {
814 Diagnostic::error(format!("undefined stroke type `{}`", type_name.inner()))
815 .with_code(ErrorCode::E300)
816 .with_label(type_spec.span(), "undefined type")
817 .with_help("type must be defined with `type` statement before use")
818 })?;
819
820 let base_stroke_rc = base_type.stroke_definition().map_err(|err| {
821 Diagnostic::error(format!(
822 "type `{}` is not a stroke type: {}",
823 type_name.inner(),
824 err
825 ))
826 .with_code(ErrorCode::E307)
827 .with_label(type_spec.span(), "invalid type reference")
828 .with_help("only `Stroke` types can be used for stroke attributes")
829 })?;
830
831 Rc::clone(base_stroke_rc)
832 } else {
833 Rc::clone(current_stroke_rc)
834 };
835
836 if !type_spec.attributes.is_empty() {
838 let stroke_def_mut = Rc::make_mut(&mut stroke_rc);
839 elaborate_utils::StrokeAttributeExtractor::extract_stroke_attributes(
840 stroke_def_mut,
841 &type_spec.attributes,
842 )?;
843 }
844
845 Ok(stroke_rc)
846 }
847
848 fn build_type_from_base(
852 &self,
853 id: Id,
854 base: &elaborate_utils::TypeDefinition,
855 attributes: &[parser_types::Attribute],
856 ) -> Result<elaborate_utils::TypeDefinition> {
857 match base.draw_definition() {
858 elaborate_utils::DrawDefinition::Shape(shape_def) => {
859 let mut new_shape_def = Rc::clone(shape_def);
860 let shape_def_mut = Rc::make_mut(&mut new_shape_def);
861
862 for attr in attributes {
863 let name = attr.name.inner();
864
865 match *name {
866 "fill_color" => {
867 let color = Self::extract_color(attr, "fill_color")?;
868 shape_def_mut.set_fill_color(Some(color)).map_err(|err| {
869 Diagnostic::error(err.to_string())
870 .with_code(ErrorCode::E304)
871 .with_label(attr.span(), "unsupported attribute")
872 })?;
873 }
874 "stroke" => {
875 let type_spec = Self::extract_type_spec(attr, "stroke")?;
876 let stroke_rc = self
877 .resolve_stroke_type_reference(type_spec, shape_def_mut.stroke())?;
878 shape_def_mut.set_stroke(stroke_rc);
879 }
880 "rounded" => {
881 let val =
882 Self::extract_usize(attr, "rounded", "must be a positive number")?;
883 shape_def_mut.set_rounded(val).map_err(|err| {
884 Diagnostic::error(err.to_string())
885 .with_code(ErrorCode::E304)
886 .with_label(attr.span(), "unsupported attribute")
887 })?;
888 }
889 "text" => {
890 let type_spec = Self::extract_type_spec(attr, "text")?;
891 let text_rc =
892 self.resolve_text_type_reference(type_spec, shape_def_mut.text())?;
893 shape_def_mut.set_text(text_rc);
894 }
895 name => {
896 return Err(Diagnostic::error(format!(
897 "unknown shape attribute `{name}`"
898 ))
899 .with_code(ErrorCode::E303)
900 .with_label(attr.span(), "unknown attribute")
901 .with_help(
902 "valid shape attributes are: `fill_color`, `stroke`=[...], `rounded`, `text`=[...]",
903 ));
904 }
905 }
906 }
907
908 Ok(elaborate_utils::TypeDefinition::new_shape(
909 id,
910 new_shape_def,
911 ))
912 }
913 elaborate_utils::DrawDefinition::Arrow(arrow_def) => {
914 let mut new_arrow_def = Rc::clone(arrow_def);
915 let arrow_def_mut = Rc::make_mut(&mut new_arrow_def);
916
917 for attr in attributes {
918 let name = attr.name.inner();
919
920 match *name {
921 "stroke" => {
922 let type_spec = Self::extract_type_spec(attr, "stroke")?;
923 let stroke_rc = self
924 .resolve_stroke_type_reference(type_spec, arrow_def_mut.stroke())?;
925 arrow_def_mut.set_stroke(stroke_rc);
926 }
927 "style" => {
928 let style_str = Self::extract_string(attr, "style")?;
929 let val = draw::ArrowStyle::from_str(style_str).map_err(|_| {
930 Diagnostic::error("invalid arrow style")
931 .with_code(ErrorCode::E302)
932 .with_label(attr.span(), "invalid style")
933 .with_help(
934 "arrow style must be `straight`, `curved`, or `orthogonal`",
935 )
936 })?;
937 arrow_def_mut.set_style(val);
938 }
939 "text" => {
940 let type_spec = Self::extract_type_spec(attr, "text")?;
941 let text_rc =
942 self.resolve_text_type_reference(type_spec, arrow_def_mut.text())?;
943 arrow_def_mut.set_text(text_rc);
944 }
945 name => {
946 return Err(Diagnostic::error(format!(
947 "unknown arrow attribute `{name}`"
948 ))
949 .with_code(ErrorCode::E303)
950 .with_label(attr.span(), "unknown attribute")
951 .with_help(
952 "valid arrow attributes are: `stroke`=[...], `style`, `text`=[...]",
953 ));
954 }
955 }
956 }
957
958 Ok(elaborate_utils::TypeDefinition::new_arrow(
959 id,
960 new_arrow_def,
961 ))
962 }
963 elaborate_utils::DrawDefinition::Fragment(fragment_def) => {
964 let mut new_fragment_def = Rc::clone(fragment_def);
965 let fragment_def_mut = Rc::make_mut(&mut new_fragment_def);
966
967 for attr in attributes {
968 let name = attr.name.inner();
969
970 match *name {
971 "border_stroke" => {
972 let type_spec = Self::extract_type_spec(attr, "border_stroke")?;
973 let stroke_rc = self.resolve_stroke_type_reference(
974 type_spec,
975 fragment_def_mut.border_stroke(),
976 )?;
977 fragment_def_mut.set_border_stroke(stroke_rc);
978 }
979 "background_color" => {
980 let color = Self::extract_color(attr, "background_color")?;
981 fragment_def_mut.set_background_color(Some(color));
982 }
983 "separator_stroke" => {
984 let type_spec = Self::extract_type_spec(attr, "separator_stroke")?;
985 let stroke_rc = self.resolve_stroke_type_reference(
986 type_spec,
987 fragment_def_mut.separator_stroke(),
988 )?;
989 fragment_def_mut.set_separator_stroke(stroke_rc);
990 }
991 "operation_label_text" => {
992 let type_spec = Self::extract_type_spec(attr, "operation_label_text")?;
993 let text_rc = self.resolve_text_type_reference(
994 type_spec,
995 fragment_def_mut.operation_label_text(),
996 )?;
997 fragment_def_mut.set_operation_label_text(text_rc);
998 }
999 "section_title_text" => {
1000 let type_spec = Self::extract_type_spec(attr, "section_title_text")?;
1001 let text_rc = self.resolve_text_type_reference(
1002 type_spec,
1003 fragment_def_mut.section_title_text(),
1004 )?;
1005 fragment_def_mut.set_section_title_text(text_rc);
1006 }
1007 name => {
1008 return Err(Diagnostic::error(format!(
1009 "unknown fragment attribute `{name}`"
1010 ))
1011 .with_code(ErrorCode::E303)
1012 .with_label(attr.span(), "unknown attribute")
1013 .with_help("valid fragment attributes are: `border_stroke`=[...], `separator_stroke`=[...], `background_color`, `operation_label_text`=[...], `section_title_text`=[...]"));
1014 }
1015 }
1016 }
1017
1018 Ok(elaborate_utils::TypeDefinition::new_fragment(
1019 id,
1020 new_fragment_def,
1021 ))
1022 }
1023 elaborate_utils::DrawDefinition::Note(note_def) => {
1024 let mut new_note_def = Rc::clone(note_def);
1025 let note_def_mut = Rc::make_mut(&mut new_note_def);
1026
1027 for attr in attributes {
1028 let name = attr.name.inner();
1029
1030 match *name {
1031 "background_color" => {
1032 let color = Self::extract_color(attr, "background_color")?;
1033 note_def_mut.set_background_color(Some(color));
1034 }
1035 "stroke" => {
1036 let type_spec = Self::extract_type_spec(attr, "stroke")?;
1037 let stroke_rc = self
1038 .resolve_stroke_type_reference(type_spec, note_def_mut.stroke())?;
1039 note_def_mut.set_stroke(stroke_rc);
1040 }
1041 "text" => {
1042 let type_spec = Self::extract_type_spec(attr, "text")?;
1043 let text_rc =
1044 self.resolve_text_type_reference(type_spec, note_def_mut.text())?;
1045 note_def_mut.set_text(text_rc);
1046 }
1047 "on" | "align" => {
1048 }
1051 name => {
1052 return Err(Diagnostic::error(format!(
1053 "unknown note attribute `{name}`"
1054 ))
1055 .with_code(ErrorCode::E303)
1056 .with_label(attr.span(), "unknown attribute")
1057 .with_help(
1058 "valid note attributes are: `background_color`, `stroke`=[...], `text`=[...]",
1059 ));
1060 }
1061 }
1062 }
1063
1064 Ok(elaborate_utils::TypeDefinition::new_note(id, new_note_def))
1065 }
1066 elaborate_utils::DrawDefinition::ActivationBox(activation_box_def) => {
1067 let mut new_activation_box_def = Rc::clone(activation_box_def);
1068 let activation_box_def_mut = Rc::make_mut(&mut new_activation_box_def);
1069
1070 for attr in attributes {
1071 let name = attr.name.inner();
1072
1073 match *name {
1074 "width" => {
1075 let val = Self::extract_positive_float(attr, "width")?;
1076 activation_box_def_mut.set_width(val);
1077 }
1078 "nesting_offset" => {
1079 let val = Self::extract_positive_float(attr, "nesting_offset")?;
1080 activation_box_def_mut.set_nesting_offset(val);
1081 }
1082 "fill_color" => {
1083 let color = Self::extract_color(attr, "fill_color")?;
1084 activation_box_def_mut.set_fill_color(color);
1085 }
1086 "stroke" => {
1087 let type_spec = Self::extract_type_spec(attr, "stroke")?;
1088 let stroke_rc = self.resolve_stroke_type_reference(
1089 type_spec,
1090 activation_box_def_mut.stroke(),
1091 )?;
1092 activation_box_def_mut.set_stroke(stroke_rc);
1093 }
1094 name => {
1095 return Err(Diagnostic::error(format!(
1096 "unknown activation box attribute `{name}`"
1097 ))
1098 .with_code(ErrorCode::E303)
1099 .with_label(attr.span(), "unknown attribute")
1100 .with_help("valid activation box attributes are: `width`, `nesting_offset`, `fill_color`, `stroke`=[...]"));
1101 }
1102 }
1103 }
1104
1105 Ok(elaborate_utils::TypeDefinition::new_activation_box(
1106 id,
1107 new_activation_box_def,
1108 ))
1109 }
1110 elaborate_utils::DrawDefinition::Stroke(stroke_def) => {
1111 let mut new_stroke = (**stroke_def).clone();
1112 elaborate_utils::StrokeAttributeExtractor::extract_stroke_attributes(
1113 &mut new_stroke,
1114 attributes,
1115 )?;
1116 Ok(elaborate_utils::TypeDefinition::new_stroke(id, new_stroke))
1117 }
1118 elaborate_utils::DrawDefinition::Text(text_def) => {
1119 let mut new_text_def = (**text_def).clone();
1120 elaborate_utils::TextAttributeExtractor::extract_text_attributes(
1121 &mut new_text_def,
1122 attributes,
1123 )?;
1124 Ok(elaborate_utils::TypeDefinition::new_text(id, new_text_def))
1125 }
1126 }
1127 }
1128
1129 fn create_undefined_type_error(&self, span: &Spanned<Id>, message: &str) -> Diagnostic {
1131 Diagnostic::error(message)
1132 .with_code(ErrorCode::E300)
1133 .with_label(span.span(), "undefined type")
1134 .with_help(format!(
1135 "type `{}` must be a built-in type or defined with a `type` statement before it can be used as a base type",
1136 span.inner()
1137 ))
1138 }
1139
1140 fn extract_diagram_attributes(
1142 &self,
1143 kind: semantic::DiagramKind,
1144 attrs: &Vec<parser_types::Attribute<'_>>,
1145 ) -> Result<(
1146 semantic::LayoutEngine,
1147 Option<Color>,
1148 Option<Rc<draw::LifelineDefinition>>,
1149 )> {
1150 let mut layout_engine = match kind {
1152 semantic::DiagramKind::Component => self.cfg.component_layout,
1153 semantic::DiagramKind::Sequence => self.cfg.sequence_layout,
1154 };
1155
1156 let mut background_color = None;
1157 let mut lifeline_definition = None;
1158
1159 for attr in attrs {
1161 match *attr.name {
1162 "layout_engine" => {
1163 layout_engine = Self::determine_layout_engine(attr)?;
1164 }
1165 "background_color" => {
1166 let color = Self::extract_background_color(attr)?;
1167 background_color = Some(color);
1168 }
1169 "lifeline" => {
1170 if kind != semantic::DiagramKind::Sequence {
1172 return Err(Diagnostic::error(
1173 "`lifeline` attribute is only valid for sequence diagrams",
1174 )
1175 .with_code(ErrorCode::E304)
1176 .with_label(attr.span(), "invalid attribute"));
1177 }
1178 let definition = self.extract_lifeline_definition(attr)?;
1179 lifeline_definition = Some(Rc::new(definition));
1180 }
1181 _ => {
1182 return Err(Diagnostic::error(format!(
1183 "unsupported diagram attribute `{}`",
1184 attr.name
1185 ))
1186 .with_code(ErrorCode::E303)
1187 .with_label(attr.span(), "unsupported attribute"));
1188 }
1189 }
1190 }
1191
1192 Ok((layout_engine, background_color, lifeline_definition))
1193 }
1194
1195 fn extract_background_color(color_attr: &parser_types::Attribute<'_>) -> Result<Color> {
1197 Self::extract_color(color_attr, "background_color")
1198 }
1199
1200 fn extract_lifeline_definition(
1202 &self,
1203 lifeline_attr: &parser_types::Attribute<'_>,
1204 ) -> Result<draw::LifelineDefinition> {
1205 let type_spec = Self::extract_type_spec(lifeline_attr, "lifeline")?;
1206
1207 let default_stroke_rc = Rc::new(draw::StrokeDefinition::dashed(Color::default(), 1.0));
1209
1210 let stroke_rc =
1212 if let Some(stroke_attr) = type_spec.attributes.iter().find(|a| *a.name == "stroke") {
1213 let stroke_type_spec = Self::extract_type_spec(stroke_attr, "stroke")?;
1214
1215 self.resolve_stroke_type_reference(stroke_type_spec, &default_stroke_rc)?
1216 } else if !type_spec.attributes.is_empty() {
1217 return Err(Diagnostic::error(format!(
1218 "unknown lifeline attribute `{}`",
1219 type_spec.attributes[0].name
1220 ))
1221 .with_code(ErrorCode::E303)
1222 .with_label(type_spec.attributes[0].span(), "unknown attribute")
1223 .with_help("valid lifeline attributes are: `stroke`=[...]"));
1224 } else {
1225 default_stroke_rc
1226 };
1227
1228 Ok(draw::LifelineDefinition::new(stroke_rc))
1229 }
1230
1231 fn determine_layout_engine(
1233 engine_attr: &parser_types::Attribute<'_>,
1234 ) -> Result<semantic::LayoutEngine> {
1235 let engine_str = Self::extract_string(engine_attr, "layout_engine")?;
1236 semantic::LayoutEngine::from_str(engine_str).map_err(|_| {
1237 Diagnostic::error(format!("invalid `layout_engine` value: `{engine_str}`"))
1238 .with_code(ErrorCode::E302)
1239 .with_label(engine_attr.value.span(), "unsupported layout engine")
1240 .with_help("supported layout engines are: `basic`, `sugiyama`")
1241 })
1242 }
1243
1244 fn build_note_element(
1261 &mut self,
1262 note: &parser_types::Note,
1263 diagram_kind: semantic::DiagramKind,
1264 ) -> Result<semantic::Element> {
1265 let type_def = self.build_type_definition(¬e.type_spec)?;
1266
1267 let (on, align) = self.extract_note_attributes(¬e.type_spec.attributes, diagram_kind)?;
1269
1270 let content = note.content.inner().to_string();
1271
1272 let note_def_ref = type_def.note_definition().map_err(|err| {
1274 Diagnostic::error(err)
1275 .with_code(ErrorCode::E307)
1276 .with_label(note.content.span(), "invalid note type")
1277 })?;
1278 let note_def = Rc::clone(note_def_ref);
1279
1280 Ok(semantic::Element::Note(semantic::Note::new(
1281 on, align, content, note_def,
1282 )))
1283 }
1284
1285 fn extract_note_attributes(
1302 &mut self,
1303 attributes: &[parser_types::Attribute],
1304 diagram_kind: semantic::DiagramKind,
1305 ) -> Result<(Vec<Id>, semantic::NoteAlign)> {
1306 let mut on: Option<Vec<Id>> = None;
1307 let mut align: Option<semantic::NoteAlign> = None;
1308
1309 for attr in attributes {
1310 match *attr.name.inner() {
1311 "on" => {
1312 let ids = attr.value.as_identifiers().map_err(|_| {
1313 Diagnostic::error("`on` attribute must be a list of element identifiers")
1314 .with_code(ErrorCode::E302)
1315 .with_label(attr.value.span(), "invalid on value")
1316 .with_help("use syntax: `on=[element1, element2]`")
1317 })?;
1318
1319 on = Some(ids.iter().map(|id| *id.inner()).collect());
1320 }
1321 "align" => {
1322 let align_str = Self::extract_string(attr, "align")?;
1323
1324 let alignment = align_str.parse::<semantic::NoteAlign>().map_err(|_| {
1325 Diagnostic::error(format!("invalid alignment value: `{}`", align_str))
1326 .with_code(ErrorCode::E302)
1327 .with_label(attr.value.span(), "invalid alignment")
1328 .with_help("valid values: over, left, right, top, bottom")
1329 })?;
1330
1331 align = Some(alignment);
1332 }
1333 _ => {} }
1335 }
1336
1337 let on = on.unwrap_or_default();
1339 let align = align.unwrap_or(match diagram_kind {
1340 semantic::DiagramKind::Sequence => semantic::NoteAlign::Over,
1341 semantic::DiagramKind::Component => semantic::NoteAlign::Bottom,
1342 });
1343
1344 Ok((on, align))
1345 }
1346}
1347
1348#[cfg(test)]
1349mod tests {
1350 use std::cell::RefCell;
1351
1352 use super::*;
1353
1354 fn builder_with_builtins() -> Builder {
1356 let mut builder = Builder::new(ElaborateConfig::default());
1357 builder.type_definitions = Builder::builtin_type_definitions_map();
1358 builder
1359 }
1360
1361 #[test]
1362 #[should_panic(expected = "ActivateBlock should have been desugared")]
1363 fn test_activate_block_panics_in_elaboration() {
1364 let elements = vec![parser_types::Element::ActivateBlock {
1366 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1367 type_spec: parser_types::TypeSpec::default(),
1368 elements: vec![],
1369 }];
1370
1371 let diagram = parser_types::FileAst {
1372 header: parser_types::FileHeader::Diagram {
1373 kind: Spanned::new(semantic::DiagramKind::Component, Span::new(0..9)),
1374 attributes: vec![],
1375 },
1376 import_decls: vec![],
1377 type_definitions: vec![],
1378 elements,
1379 imports: vec![],
1380 };
1381
1382 let config = ElaborateConfig::default();
1383 let builder = Builder::new(config);
1384 let _ = builder.build(&diagram);
1386 }
1387
1388 #[test]
1389 fn test_explicit_activation_scoping_behavior() {
1390 let elements = vec![
1392 parser_types::Element::Activate {
1393 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1394 type_spec: parser_types::TypeSpec {
1395 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1396 attributes: vec![],
1397 },
1398 },
1399 parser_types::Element::Relation {
1400 source: Spanned::new(Id::new("user"), Span::new(0..4)),
1401 target: Spanned::new(Id::new("server"), Span::new(0..6)),
1402 relation_type: Spanned::new("->", Span::new(0..2)),
1403 type_spec: parser_types::TypeSpec {
1404 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1405 attributes: vec![],
1406 },
1407 label: Some(Spanned::new("Request".to_string(), Span::new(0..7))),
1408 },
1409 parser_types::Element::Relation {
1410 source: Spanned::new(Id::new("server"), Span::new(0..6)),
1411 target: Spanned::new(Id::new("database"), Span::new(0..8)),
1412 relation_type: Spanned::new("->", Span::new(0..2)),
1413 type_spec: parser_types::TypeSpec {
1414 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1415 attributes: vec![],
1416 },
1417 label: Some(Spanned::new("Query".to_string(), Span::new(0..5))),
1418 },
1419 parser_types::Element::Deactivate {
1420 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1421 },
1422 ];
1423
1424 let diagram = parser_types::FileAst {
1425 header: parser_types::FileHeader::Diagram {
1426 kind: Spanned::new(semantic::DiagramKind::Sequence, Span::new(0..8)),
1427 attributes: vec![],
1428 },
1429 import_decls: vec![],
1430 type_definitions: vec![],
1431 elements,
1432 imports: vec![],
1433 };
1434
1435 let config = ElaborateConfig::default();
1436 let builder = Builder::new(config);
1437 let result = builder.build(&diagram);
1438
1439 assert!(
1440 result.is_ok(),
1441 "Sequence diagram with activate block should work"
1442 );
1443
1444 let diagram = result.unwrap();
1445 for element in diagram.scope().elements() {
1447 if let semantic::Element::Relation(relation) = element {
1448 let source_str = relation.source().to_string();
1450 let target_str = relation.target().to_string();
1451 assert!(
1452 !source_str.starts_with("user::user::"),
1453 "Source should not be double-scoped: {}",
1454 source_str
1455 );
1456 assert!(
1457 !target_str.starts_with("user::server::"),
1458 "Target should not be double-scoped: {}",
1459 target_str
1460 );
1461 }
1462 }
1463 }
1464
1465 #[test]
1466 fn test_nested_explicit_activations_same_component() {
1467 let elements = vec![
1469 parser_types::Element::Activate {
1470 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1471 type_spec: parser_types::TypeSpec {
1472 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1473 attributes: vec![],
1474 },
1475 },
1476 parser_types::Element::Relation {
1477 source: Spanned::new(Id::new("user"), Span::new(0..4)),
1478 target: Spanned::new(Id::new("server"), Span::new(0..6)),
1479 relation_type: Spanned::new("->", Span::new(0..2)),
1480 type_spec: parser_types::TypeSpec {
1481 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1482 attributes: vec![],
1483 },
1484 label: Some(Spanned::new(
1485 "Initial request".to_string(),
1486 Span::new(0..16),
1487 )),
1488 },
1489 parser_types::Element::Activate {
1490 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1491 type_spec: parser_types::TypeSpec {
1492 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1493 attributes: vec![],
1494 },
1495 },
1496 parser_types::Element::Relation {
1497 source: Spanned::new(Id::new("user"), Span::new(0..4)),
1498 target: Spanned::new(Id::new("database"), Span::new(0..8)),
1499 relation_type: Spanned::new("->", Span::new(0..2)),
1500 type_spec: parser_types::TypeSpec {
1501 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1502 attributes: vec![],
1503 },
1504 label: Some(Spanned::new("Direct query".to_string(), Span::new(0..12))),
1505 },
1506 parser_types::Element::Deactivate {
1507 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1508 },
1509 parser_types::Element::Activate {
1510 component: Spanned::new(Id::new("server"), Span::new(0..6)),
1511 type_spec: parser_types::TypeSpec {
1512 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1513 attributes: vec![],
1514 },
1515 },
1516 parser_types::Element::Relation {
1517 source: Spanned::new(Id::new("server"), Span::new(0..6)),
1518 target: Spanned::new(Id::new("cache"), Span::new(0..5)),
1519 relation_type: Spanned::new("->", Span::new(0..2)),
1520 type_spec: parser_types::TypeSpec {
1521 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1522 attributes: vec![],
1523 },
1524 label: Some(Spanned::new("Cache lookup".to_string(), Span::new(0..12))),
1525 },
1526 parser_types::Element::Deactivate {
1527 component: Spanned::new(Id::new("server"), Span::new(0..6)),
1528 },
1529 parser_types::Element::Deactivate {
1530 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1531 },
1532 ];
1533
1534 let diagram = parser_types::FileAst {
1535 header: parser_types::FileHeader::Diagram {
1536 kind: Spanned::new(semantic::DiagramKind::Sequence, Span::new(0..8)),
1537 attributes: vec![],
1538 },
1539 import_decls: vec![],
1540 type_definitions: vec![],
1541 elements,
1542 imports: vec![],
1543 };
1544
1545 let config = ElaborateConfig::default();
1546 let builder = Builder::new(config);
1547 let result = builder.build(&diagram);
1548
1549 assert!(
1550 result.is_ok(),
1551 "Nested activate blocks should work: {:?}",
1552 result.err()
1553 );
1554
1555 let diagram = result.unwrap();
1556 let elems = diagram.scope().elements();
1557
1558 let activations: Vec<_> = elems
1559 .iter()
1560 .filter_map(|e| {
1561 if let semantic::Element::Activate(activate) = e {
1562 Some(activate.component().to_string())
1563 } else {
1564 None
1565 }
1566 })
1567 .collect();
1568 let deactivations: Vec<_> = elems
1569 .iter()
1570 .filter_map(|e| {
1571 if let semantic::Element::Deactivate(id) = e {
1572 Some(id.to_string())
1573 } else {
1574 None
1575 }
1576 })
1577 .collect();
1578 let relations: Vec<_> = elems
1579 .iter()
1580 .filter_map(|e| {
1581 if let semantic::Element::Relation(r) = e {
1582 Some((r.source().to_string(), r.target().to_string()))
1583 } else {
1584 None
1585 }
1586 })
1587 .collect();
1588
1589 assert_eq!(
1590 relations.len(),
1591 3,
1592 "Should have 3 relations after desugaring"
1593 );
1594 assert_eq!(
1595 activations.len(),
1596 3,
1597 "Should have 3 activation starts after desugaring"
1598 );
1599 assert_eq!(
1600 deactivations.len(),
1601 3,
1602 "Should have 3 activation ends after desugaring"
1603 );
1604
1605 assert_eq!(
1606 activations[0], "user",
1607 "First activation should be for 'user'"
1608 );
1609 assert_eq!(
1610 deactivations.last().unwrap(),
1611 "user",
1612 "Last deactivation should be for 'user'"
1613 );
1614 }
1615
1616 #[test]
1617 fn test_explicit_activate_in_sequence_diagram() {
1618 let config = ElaborateConfig::default();
1619 let builder = Builder::new(config);
1620
1621 let elements = vec![
1623 parser_types::Element::Component {
1625 name: Spanned::new(Id::new("user"), Span::new(0..4)),
1626 display_name: None,
1627 type_spec: parser_types::TypeSpec {
1628 type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(5..14))),
1629 attributes: vec![],
1630 },
1631 content: parser_types::ComponentContent::None,
1632 },
1633 parser_types::Element::Activate {
1635 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1636 type_spec: parser_types::TypeSpec {
1637 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1638 attributes: vec![],
1639 },
1640 },
1641 parser_types::Element::Deactivate {
1643 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1644 },
1645 ];
1646
1647 let diagram = parser_types::FileAst {
1648 header: parser_types::FileHeader::Diagram {
1649 kind: Spanned::new(semantic::DiagramKind::Sequence, Span::new(0..8)),
1650 attributes: vec![],
1651 },
1652 import_decls: vec![],
1653 type_definitions: vec![],
1654 elements,
1655 imports: vec![],
1656 };
1657
1658 let result = builder.build(&diagram);
1659 assert!(
1660 result.is_ok(),
1661 "Should successfully build sequence diagram with explicit activate/deactivate"
1662 );
1663
1664 let elaborate_diagram = result.unwrap();
1665 let elements = elaborate_diagram.scope().elements();
1666
1667 assert_eq!(
1669 elements.len(),
1670 3,
1671 "Should have 3 elements: component, activate, deactivate"
1672 );
1673
1674 if let semantic::Element::Activate(activate) = &elements[1] {
1676 assert_eq!(
1677 activate.component().to_string(),
1678 "user",
1679 "Activate should reference 'user' component"
1680 );
1681 } else {
1682 panic!("Second element should be Activate");
1683 }
1684
1685 if let semantic::Element::Deactivate(id) = &elements[2] {
1687 assert_eq!(
1688 id.to_string(),
1689 "user",
1690 "Deactivate should reference 'user' component"
1691 );
1692 } else {
1693 panic!("Third element should be Deactivate");
1694 }
1695 }
1696
1697 #[test]
1698 fn test_explicit_activate_not_allowed_in_component_diagram() {
1699 let config = ElaborateConfig::default();
1700 let builder = Builder::new(config);
1701
1702 let elements = vec![
1704 parser_types::Element::Component {
1706 name: Spanned::new(Id::new("user"), Span::new(0..4)),
1707 display_name: None,
1708 type_spec: parser_types::TypeSpec {
1709 type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(5..14))),
1710 attributes: vec![],
1711 },
1712 content: parser_types::ComponentContent::None,
1713 },
1714 parser_types::Element::Activate {
1716 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1717 type_spec: parser_types::TypeSpec::default(),
1718 },
1719 ];
1720
1721 let diagram = parser_types::FileAst {
1722 header: parser_types::FileHeader::Diagram {
1723 kind: Spanned::new(semantic::DiagramKind::Component, Span::new(0..9)),
1724 attributes: vec![],
1725 },
1726 import_decls: vec![],
1727 type_definitions: vec![],
1728 elements,
1729 imports: vec![],
1730 };
1731
1732 let result = builder.build(&diagram);
1733 assert!(
1734 result.is_err(),
1735 "Should fail to build component diagram with explicit activate"
1736 );
1737
1738 if let Err(err) = result {
1739 let error_message = format!("{}", err);
1740 assert!(
1741 error_message
1742 .contains("activate statements are only supported in sequence diagrams"),
1743 "Error should mention that activate is not allowed in component diagrams"
1744 );
1745 }
1746 }
1747
1748 #[test]
1749 fn test_explicit_activation_timing_and_nesting() {
1750 let elements = vec![
1753 parser_types::Element::Component {
1755 name: Spanned::new(Id::new("user"), Span::new(0..4)),
1756 display_name: None,
1757 type_spec: parser_types::TypeSpec {
1758 type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(0..9))),
1759 attributes: vec![],
1760 },
1761 content: parser_types::ComponentContent::None,
1762 },
1763 parser_types::Element::Component {
1764 name: Spanned::new(Id::new("server"), Span::new(0..6)),
1765 display_name: None,
1766 type_spec: parser_types::TypeSpec {
1767 type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(0..9))),
1768 attributes: vec![],
1769 },
1770 content: parser_types::ComponentContent::None,
1771 },
1772 parser_types::Element::Component {
1773 name: Spanned::new(Id::new("database"), Span::new(0..8)),
1774 display_name: None,
1775 type_spec: parser_types::TypeSpec {
1776 type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(0..9))),
1777 attributes: vec![],
1778 },
1779 content: parser_types::ComponentContent::None,
1780 },
1781 parser_types::Element::Activate {
1783 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1784 type_spec: parser_types::TypeSpec {
1785 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1786 attributes: vec![],
1787 },
1788 },
1789 parser_types::Element::Relation {
1790 source: Spanned::new(Id::new("user"), Span::new(0..4)),
1791 target: Spanned::new(Id::new("server"), Span::new(0..6)),
1792 relation_type: Spanned::new("->", Span::new(0..2)),
1793 type_spec: parser_types::TypeSpec {
1794 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1795 attributes: vec![],
1796 },
1797 label: Some(Spanned::new("First request".to_string(), Span::new(0..13))),
1798 },
1799 parser_types::Element::Activate {
1800 component: Spanned::new(Id::new("server"), Span::new(0..6)),
1801 type_spec: parser_types::TypeSpec {
1802 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1803 attributes: vec![],
1804 },
1805 },
1806 parser_types::Element::Relation {
1807 source: Spanned::new(Id::new("server"), Span::new(0..6)),
1808 target: Spanned::new(Id::new("database"), Span::new(0..8)),
1809 relation_type: Spanned::new("->", Span::new(0..2)),
1810 type_spec: parser_types::TypeSpec {
1811 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1812 attributes: vec![],
1813 },
1814 label: Some(Spanned::new("Nested query".to_string(), Span::new(0..12))),
1815 },
1816 parser_types::Element::Relation {
1817 source: Spanned::new(Id::new("database"), Span::new(0..8)),
1818 target: Spanned::new(Id::new("server"), Span::new(0..6)),
1819 relation_type: Spanned::new("->", Span::new(0..2)),
1820 type_spec: parser_types::TypeSpec {
1821 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1822 attributes: vec![],
1823 },
1824 label: Some(Spanned::new(
1825 "Nested response".to_string(),
1826 Span::new(0..15),
1827 )),
1828 },
1829 parser_types::Element::Deactivate {
1830 component: Spanned::new(Id::new("server"), Span::new(0..6)),
1831 },
1832 parser_types::Element::Relation {
1833 source: Spanned::new(Id::new("server"), Span::new(0..6)),
1834 target: Spanned::new(Id::new("user"), Span::new(0..4)),
1835 relation_type: Spanned::new("->", Span::new(0..2)),
1836 type_spec: parser_types::TypeSpec {
1837 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1838 attributes: vec![],
1839 },
1840 label: Some(Spanned::new("First response".to_string(), Span::new(0..14))),
1841 },
1842 parser_types::Element::Activate {
1843 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1844 type_spec: parser_types::TypeSpec {
1845 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1846 attributes: vec![],
1847 },
1848 },
1849 parser_types::Element::Relation {
1850 source: Spanned::new(Id::new("user"), Span::new(0..4)),
1851 target: Spanned::new(Id::new("server"), Span::new(0..6)),
1852 relation_type: Spanned::new("->", Span::new(0..2)),
1853 type_spec: parser_types::TypeSpec {
1854 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1855 attributes: vec![],
1856 },
1857 label: Some(Spanned::new("Second request".to_string(), Span::new(0..14))),
1858 },
1859 parser_types::Element::Deactivate {
1860 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1861 },
1862 parser_types::Element::Deactivate {
1863 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1864 },
1865 ];
1866
1867 let diagram = parser_types::FileAst {
1868 header: parser_types::FileHeader::Diagram {
1869 kind: Spanned::new(semantic::DiagramKind::Sequence, Span::new(0..8)),
1870 attributes: vec![],
1871 },
1872 import_decls: vec![],
1873 type_definitions: vec![],
1874 elements,
1875 imports: vec![],
1876 };
1877
1878 let config = ElaborateConfig::default();
1879 let builder = Builder::new(config);
1880 let result = builder.build(&diagram);
1881
1882 assert!(
1883 result.is_ok(),
1884 "Complex nested activate blocks should work: {:?}",
1885 result.err()
1886 );
1887
1888 let diagram = result.unwrap();
1889
1890 let elems = diagram.scope().elements();
1892 let relations = elems
1893 .iter()
1894 .filter(|e| matches!(e, semantic::Element::Relation(_)))
1895 .count();
1896 let activates = elems
1897 .iter()
1898 .filter(|e| matches!(e, semantic::Element::Activate(_)))
1899 .count();
1900 let deactivates = elems
1901 .iter()
1902 .filter(|e| matches!(e, semantic::Element::Deactivate(_)))
1903 .count();
1904
1905 assert!(
1906 relations >= 5,
1907 "Should have at least 5 relations after desugaring, found {}",
1908 relations
1909 );
1910 assert!(
1911 activates >= 3,
1912 "Should have at least 3 activates after desugaring, found {}",
1913 activates
1914 );
1915 assert!(
1916 deactivates >= 3,
1917 "Should have at least 3 deactivates after desugaring, found {}",
1918 deactivates
1919 );
1920 }
1921
1922 #[test]
1923 fn test_note_with_default_alignment_sequence() {
1924 let mut builder = builder_with_builtins();
1925
1926 let note = parser_types::Note {
1927 type_spec: parser_types::TypeSpec {
1928 type_name: Some(Spanned::new(Id::new("Note"), Span::new(0..4))),
1929 attributes: vec![],
1930 },
1931 content: Spanned::new("Test note".to_string(), Span::new(0..9)),
1932 };
1933
1934 let diagram_kind = semantic::DiagramKind::Sequence;
1935 let result = builder.build_note_element(¬e, diagram_kind);
1936
1937 assert!(result.is_ok());
1938 let element = result.unwrap();
1939 if let semantic::Element::Note(note_elem) = element {
1940 assert_eq!(note_elem.on().len(), 0); assert_eq!(note_elem.align(), semantic::NoteAlign::Over); assert_eq!(note_elem.content(), "Test note");
1943 } else {
1944 panic!("Expected Note element");
1945 }
1946 }
1947
1948 #[test]
1949 fn test_note_with_default_alignment_component() {
1950 let mut builder = builder_with_builtins();
1951
1952 let note = parser_types::Note {
1953 type_spec: parser_types::TypeSpec {
1954 type_name: Some(Spanned::new(Id::new("Note"), Span::new(0..4))),
1955 attributes: vec![],
1956 },
1957 content: Spanned::new("Test note".to_string(), Span::new(0..9)),
1958 };
1959
1960 let diagram_kind = semantic::DiagramKind::Component;
1961 let result = builder.build_note_element(¬e, diagram_kind);
1962
1963 assert!(result.is_ok());
1964 let element = result.unwrap();
1965 if let semantic::Element::Note(note_elem) = element {
1966 assert_eq!(note_elem.on().len(), 0); assert_eq!(note_elem.align(), semantic::NoteAlign::Bottom); assert_eq!(note_elem.content(), "Test note");
1969 } else {
1970 panic!("Expected Note element");
1971 }
1972 }
1973
1974 #[test]
1975 fn test_note_with_styling_attributes() {
1976 let mut builder = builder_with_builtins();
1977
1978 let attributes = vec![
1979 parser_types::Attribute {
1980 name: Spanned::new("background_color", Span::new(0..16)),
1981 value: parser_types::AttributeValue::String(Spanned::new(
1982 "lightyellow".to_string(),
1983 Span::new(0..11),
1984 )),
1985 },
1986 parser_types::Attribute {
1987 name: Spanned::new("stroke", Span::new(0..6)),
1988 value: parser_types::AttributeValue::TypeSpec(parser_types::TypeSpec {
1989 type_name: None,
1990 attributes: vec![
1991 parser_types::Attribute {
1992 name: Spanned::new("color", Span::new(0..5)),
1993 value: parser_types::AttributeValue::String(Spanned::new(
1994 "blue".to_string(),
1995 Span::new(0..4),
1996 )),
1997 },
1998 parser_types::Attribute {
1999 name: Spanned::new("width", Span::new(0..5)),
2000 value: parser_types::AttributeValue::Float(Spanned::new(
2001 2.0,
2002 Span::new(0..3),
2003 )),
2004 },
2005 ],
2006 }),
2007 },
2008 parser_types::Attribute {
2009 name: Spanned::new("text", Span::new(0..4)),
2010 value: parser_types::AttributeValue::TypeSpec(parser_types::TypeSpec {
2011 type_name: None,
2012 attributes: vec![parser_types::Attribute {
2013 name: Spanned::new("font_size", Span::new(0..9)),
2014 value: parser_types::AttributeValue::Float(Spanned::new(
2015 14.0,
2016 Span::new(0..2),
2017 )),
2018 }],
2019 }),
2020 },
2021 ];
2022
2023 let note = parser_types::Note {
2024 type_spec: parser_types::TypeSpec {
2025 type_name: Some(Spanned::new(Id::new("Note"), Span::new(0..4))),
2026 attributes,
2027 },
2028 content: Spanned::new("Styled note".to_string(), Span::new(0..11)),
2029 };
2030
2031 let diagram_kind = semantic::DiagramKind::Sequence;
2032 let result = builder.build_note_element(¬e, diagram_kind);
2033
2034 assert!(result.is_ok());
2035 let element = result.unwrap();
2036 if let semantic::Element::Note(note_elem) = element {
2037 assert_eq!(note_elem.content(), "Styled note");
2038 assert_eq!(note_elem.align(), semantic::NoteAlign::Over); assert_eq!(note_elem.on().len(), 0); } else {
2041 panic!("Expected Note element");
2042 }
2043 }
2044
2045 #[test]
2050 fn test_extract_type_spec_success() {
2051 use crate::parser_types::{Attribute, AttributeValue, TypeSpec};
2052
2053 let type_spec = TypeSpec {
2054 type_name: Some(Spanned::new(Id::new("BoldText"), Span::new(0..8))),
2055 attributes: vec![],
2056 };
2057 let attr = Attribute {
2058 name: Spanned::new("text", Span::new(0..4)),
2059 value: AttributeValue::TypeSpec(type_spec),
2060 };
2061
2062 let result = Builder::extract_type_spec(&attr, "text");
2063 assert!(result.is_ok());
2064 }
2065
2066 #[test]
2067 fn test_extract_type_spec_error() {
2068 use crate::parser_types::{Attribute, AttributeValue};
2069
2070 let attr = Attribute {
2071 name: Spanned::new("text", Span::new(0..4)),
2072 value: AttributeValue::String(Spanned::new(
2073 "not a type spec".to_string(),
2074 Span::new(5..20),
2075 )),
2076 };
2077
2078 let result = Builder::extract_type_spec(&attr, "text");
2079 assert!(result.is_err());
2080 let err = result.unwrap_err();
2081 assert!(err.to_string().contains("expected type spec"));
2082 }
2083
2084 #[test]
2085 fn test_extract_string_success() {
2086 use crate::parser_types::{Attribute, AttributeValue};
2087
2088 let attr = Attribute {
2089 name: Spanned::new("style", Span::new(0..5)),
2090 value: AttributeValue::String(Spanned::new("curved".to_string(), Span::new(6..14))),
2091 };
2092
2093 let result = Builder::extract_string(&attr, "style");
2094 assert!(result.is_ok());
2095 assert_eq!(result.unwrap(), "curved");
2096 }
2097
2098 #[test]
2099 fn test_extract_string_error() {
2100 use crate::parser_types::{Attribute, AttributeValue};
2101
2102 let attr = Attribute {
2103 name: Spanned::new("style", Span::new(0..5)),
2104 value: AttributeValue::Float(Spanned::new(42.0, Span::new(6..8))),
2105 };
2106
2107 let result = Builder::extract_string(&attr, "style");
2108 assert!(result.is_err());
2109 let err = result.unwrap_err();
2110 assert!(err.to_string().contains("expected string value"));
2111 }
2112
2113 #[test]
2114 fn test_extract_color_success() {
2115 use crate::parser_types::{Attribute, AttributeValue};
2116
2117 let attr = Attribute {
2118 name: Spanned::new("fill_color", Span::new(0..10)),
2119 value: AttributeValue::String(Spanned::new("red".to_string(), Span::new(11..16))),
2120 };
2121
2122 let result = Builder::extract_color(&attr, "fill_color");
2123 assert!(result.is_ok());
2124 }
2125
2126 #[test]
2127 fn test_extract_color_invalid_string() {
2128 use crate::parser_types::{Attribute, AttributeValue};
2129
2130 let attr = Attribute {
2131 name: Spanned::new("fill_color", Span::new(0..10)),
2132 value: AttributeValue::Float(Spanned::new(42.0, Span::new(11..13))),
2133 };
2134
2135 let result = Builder::extract_color(&attr, "fill_color");
2136 assert!(result.is_err());
2137 let err = result.unwrap_err();
2138 assert!(err.to_string().contains("expected string value"));
2139 }
2140
2141 #[test]
2142 fn test_extract_color_invalid_color() {
2143 use crate::parser_types::{Attribute, AttributeValue};
2144
2145 let attr = Attribute {
2146 name: Spanned::new("fill_color", Span::new(0..10)),
2147 value: AttributeValue::String(Spanned::new(
2148 "not-a-color-xyz".to_string(),
2149 Span::new(11..28),
2150 )),
2151 };
2152
2153 let result = Builder::extract_color(&attr, "fill_color");
2154 assert!(result.is_err());
2155 let err = result.unwrap_err();
2156 assert!(err.to_string().contains("invalid fill_color"));
2157 }
2158
2159 #[test]
2160 fn test_extract_positive_float_success() {
2161 use crate::parser_types::{Attribute, AttributeValue};
2162
2163 let attr = Attribute {
2164 name: Spanned::new("width", Span::new(0..5)),
2165 value: AttributeValue::Float(Spanned::new(42.5, Span::new(6..10))),
2166 };
2167
2168 let result = Builder::extract_positive_float(&attr, "width");
2169 assert!(result.is_ok());
2170 assert_eq!(result.unwrap(), 42.5);
2171 }
2172
2173 #[test]
2174 fn test_extract_positive_float_error() {
2175 use crate::parser_types::{Attribute, AttributeValue};
2176
2177 let attr = Attribute {
2178 name: Spanned::new("width", Span::new(0..5)),
2179 value: AttributeValue::String(Spanned::new(
2180 "not a number".to_string(),
2181 Span::new(6..20),
2182 )),
2183 };
2184
2185 let result = Builder::extract_positive_float(&attr, "width");
2186 assert!(result.is_err());
2187 let err = result.unwrap_err();
2188 assert!(err.to_string().contains("expected"));
2189 }
2190
2191 #[test]
2192 fn test_extract_usize_success() {
2193 use crate::parser_types::{Attribute, AttributeValue};
2194
2195 let attr = Attribute {
2196 name: Spanned::new("rounded", Span::new(0..7)),
2197 value: AttributeValue::Float(Spanned::new(10.0, Span::new(8..10))),
2198 };
2199
2200 let result = Builder::extract_usize(&attr, "rounded", "must be a positive number");
2201 assert!(result.is_ok());
2202 assert_eq!(result.unwrap(), 10);
2203 }
2204
2205 #[test]
2206 fn test_extract_usize_error() {
2207 use crate::parser_types::{Attribute, AttributeValue};
2208
2209 let attr = Attribute {
2210 name: Spanned::new("rounded", Span::new(0..7)),
2211 value: AttributeValue::String(Spanned::new(
2212 "not a number".to_string(),
2213 Span::new(8..22),
2214 )),
2215 };
2216
2217 let result = Builder::extract_usize(&attr, "rounded", "must be a positive number");
2218 assert!(result.is_err());
2219 let err = result.unwrap_err();
2220 assert!(err.to_string().contains("expected"));
2221 }
2222
2223 #[test]
2224 fn test_fragment_with_both_text_attributes() {
2225 use crate::parser_types::{Attribute, AttributeValue, TypeSpec};
2226
2227 let mut builder = builder_with_builtins();
2228
2229 let type_spec = TypeSpec {
2231 type_name: Some(Spanned::new(Id::new("Fragment"), Span::new(0..8))),
2232 attributes: vec![
2233 Attribute {
2234 name: Spanned::new("operation_label_text", Span::new(0..4)),
2235 value: AttributeValue::TypeSpec(TypeSpec {
2236 type_name: None,
2237 attributes: vec![Attribute {
2238 name: Spanned::new("font_size", Span::new(0..9)),
2239 value: AttributeValue::Float(Spanned::new(14.0, Span::new(0..2))),
2240 }],
2241 }),
2242 },
2243 Attribute {
2244 name: Spanned::new("section_title_text", Span::new(0..18)),
2245 value: AttributeValue::TypeSpec(TypeSpec {
2246 type_name: None,
2247 attributes: vec![Attribute {
2248 name: Spanned::new("font_size", Span::new(0..9)),
2249 value: AttributeValue::Float(Spanned::new(12.0, Span::new(0..2))),
2250 }],
2251 }),
2252 },
2253 ],
2254 };
2255
2256 let result = builder.build_type_definition(&type_spec);
2257 assert!(
2258 result.is_ok(),
2259 "Failed to build type definition with both operation_label_text and section_title_text: {:?}",
2260 result.err()
2261 );
2262
2263 let type_def = result.unwrap();
2264 match type_def.draw_definition() {
2266 elaborate_utils::DrawDefinition::Fragment(_) => {
2267 }
2269 _ => panic!("Expected Fragment draw definition"),
2270 }
2271 }
2272
2273 #[test]
2274 fn test_embedded_diagram_type_definitions_are_isolated() {
2275 let child_ast = parser_types::FileAst {
2286 header: parser_types::FileHeader::Diagram {
2287 kind: Spanned::new(semantic::DiagramKind::Sequence, Span::new(0..8)),
2288 attributes: vec![],
2289 },
2290 import_decls: vec![],
2291 type_definitions: vec![parser_types::TypeDefinition {
2292 name: Spanned::new(Id::new("Database"), Span::new(0..8)),
2293 type_spec: parser_types::TypeSpec {
2294 type_name: Some(Spanned::new(Id::new("Oval"), Span::new(0..4))),
2295 attributes: vec![parser_types::Attribute {
2296 name: Spanned::new("fill_color", Span::new(0..10)),
2297 value: parser_types::AttributeValue::String(Spanned::new(
2298 "#e0f0e0".to_string(),
2299 Span::new(0..7),
2300 )),
2301 }],
2302 },
2303 }],
2304 elements: vec![parser_types::Element::Component {
2305 name: Spanned::new(Id::new("database"), Span::new(0..8)),
2306 display_name: None,
2307 type_spec: parser_types::TypeSpec {
2308 type_name: Some(Spanned::new(Id::new("Database"), Span::new(0..8))),
2309 attributes: vec![],
2310 },
2311 content: parser_types::ComponentContent::None,
2312 }],
2313 imports: vec![],
2314 };
2315
2316 let parent_ast = parser_types::FileAst {
2323 header: parser_types::FileHeader::Diagram {
2324 kind: Spanned::new(semantic::DiagramKind::Component, Span::new(0..9)),
2325 attributes: vec![],
2326 },
2327 import_decls: vec![],
2328 type_definitions: vec![parser_types::TypeDefinition {
2329 name: Spanned::new(Id::new("Service"), Span::new(0..7)),
2330 type_spec: parser_types::TypeSpec {
2331 type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(0..9))),
2332 attributes: vec![parser_types::Attribute {
2333 name: Spanned::new("fill_color", Span::new(0..10)),
2334 value: parser_types::AttributeValue::String(Spanned::new(
2335 "#e6f3ff".to_string(),
2336 Span::new(0..7),
2337 )),
2338 }],
2339 },
2340 }],
2341 elements: vec![
2342 parser_types::Element::Component {
2343 name: Spanned::new(Id::new("gateway"), Span::new(0..7)),
2344 display_name: Some(Spanned::new("API Gateway".to_string(), Span::new(0..11))),
2345 type_spec: parser_types::TypeSpec {
2346 type_name: Some(Spanned::new(Id::new("Service"), Span::new(0..7))),
2347 attributes: vec![],
2348 },
2349 content: parser_types::ComponentContent::None,
2350 },
2351 parser_types::Element::Component {
2352 name: Spanned::new(Id::new("auth_overview"), Span::new(0..13)),
2353 display_name: Some(Spanned::new("Auth Overview".to_string(), Span::new(0..13))),
2354 type_spec: parser_types::TypeSpec {
2355 type_name: Some(Spanned::new(Id::new("Service"), Span::new(0..7))),
2356 attributes: vec![],
2357 },
2358 content: parser_types::ComponentContent::Diagram(
2359 parser_types::DiagramSource::Inline(Rc::new(RefCell::new(child_ast))),
2360 ),
2361 },
2362 parser_types::Element::Relation {
2363 source: Spanned::new(Id::new("gateway"), Span::new(0..7)),
2364 target: Spanned::new(Id::new("auth_overview"), Span::new(0..13)),
2365 relation_type: Spanned::new("->", Span::new(0..2)),
2366 type_spec: parser_types::TypeSpec {
2367 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
2368 attributes: vec![],
2369 },
2370 label: Some(Spanned::new("Auth detail".to_string(), Span::new(0..11))),
2371 },
2372 ],
2373 imports: vec![],
2374 };
2375
2376 let config = ElaborateConfig::default();
2377 let builder = Builder::new(config);
2378 let result = builder.build(&parent_ast);
2379
2380 assert!(
2381 result.is_ok(),
2382 "Embedded diagram with its own type definitions should build successfully, got: {:?}",
2383 result.err()
2384 );
2385
2386 let diagram = result.unwrap();
2387 assert_eq!(diagram.kind(), semantic::DiagramKind::Component);
2388
2389 let elements = diagram.scope().elements();
2391 assert_eq!(
2392 elements.len(),
2393 3,
2394 "Expected gateway, auth_overview, and a relation"
2395 );
2396
2397 if let semantic::Element::Node(node) = &elements[1] {
2399 assert!(
2400 matches!(node.block(), semantic::Block::Diagram(_)),
2401 "auth_overview should contain an embedded diagram"
2402 );
2403 } else {
2404 panic!("Expected second element to be a Node (auth_overview)");
2405 }
2406 }
2407}