1use std::{collections::HashMap, mem, rc::Rc, str::FromStr};
8
9use log::{debug, info, trace};
10
11use orrery_core::{
12 color::Color,
13 draw::{ArrowDirection, ArrowStyle, LifelineDefinition, StrokeDefinition, TextDefinition},
14 identifier::Id,
15 semantic::{
16 Activate, Block, Diagram, DiagramKind, Element, Fragment, FragmentSection, LayoutEngine,
17 Node, Note, NoteAlign, Relation, Scope,
18 },
19};
20
21use crate::{
22 builtin_types,
23 elaborate_utils::{self, StrokeAttributeExtractor, TextAttributeExtractor},
24 error::{Diagnostic, ErrorCode, Result},
25 parser_types,
26 span::{Span, Spanned},
27};
28
29#[derive(Debug, Clone, Default)]
34pub struct ElaborateConfig {
35 pub component_layout: LayoutEngine,
37 pub sequence_layout: LayoutEngine,
39}
40
41impl ElaborateConfig {
42 pub fn new(component_layout: LayoutEngine, sequence_layout: LayoutEngine) -> Self {
44 Self {
45 component_layout,
46 sequence_layout,
47 }
48 }
49}
50
51pub struct Builder {
65 cfg: ElaborateConfig,
66 type_definitions: HashMap<Id, elaborate_utils::TypeDefinition>,
67}
68
69impl Builder {
70 pub fn new(cfg: ElaborateConfig) -> Self {
80 Self {
81 cfg,
82 type_definitions: HashMap::new(),
83 }
84 }
85
86 pub fn build(mut self, ast: &parser_types::FileAst) -> Result<Diagram> {
110 debug!("Building elaborated diagram");
111 self.build_diagram_from_file_ast(ast)
112 }
113
114 fn build_diagram_from_file_ast(&mut self, file_ast: &parser_types::FileAst) -> Result<Diagram> {
129 let (kind_spanned, attributes) = match &file_ast.header {
130 parser_types::FileHeader::Diagram { kind, attributes } => (kind, attributes),
131 parser_types::FileHeader::Library { span } => {
132 return Err(Diagnostic::error("expected diagram, found library")
133 .with_code(ErrorCode::E306)
134 .with_label(*span, "expected diagram"));
135 }
136 };
137
138 info!("Processing diagram of kind: {kind_spanned}");
139 trace!("Type definitions: {:?}", file_ast.type_definitions);
140 trace!("Elements count: {}", file_ast.elements.len());
141
142 let saved_type_defs = mem::replace(
143 &mut self.type_definitions,
144 Self::builtin_type_definitions_map(),
145 );
146
147 debug!("Updating type definitions");
148 self.update_type_direct_definitions(&file_ast.type_definitions)?;
149
150 let kind = **kind_spanned;
151
152 debug!("Building block from elements");
153 let block = self.build_block_from_elements(&file_ast.elements, kind)?;
154
155 let scope = match block {
156 Block::None => {
157 debug!("Empty block, using default scope");
158 Scope::default()
159 }
160 Block::Scope(scope) => {
161 debug!(
162 elements_len = scope.elements().len();
163 "Using scope from block",
164 );
165 scope
166 }
167 Block::Diagram(_) => {
168 return Err(Diagnostic::error("nested diagram not allowed")
169 .with_code(ErrorCode::E305)
170 .with_label(kind_spanned.span(), "nested diagram")
171 .with_help("diagrams cannot be nested inside other diagrams"));
172 }
173 };
174
175 let (layout_engine, background_color, lifeline_definition) =
176 self.extract_diagram_attributes(kind, attributes)?;
177
178 info!(kind:?; "Diagram elaboration completed successfully");
179
180 self.type_definitions = saved_type_defs;
182
183 Ok(Diagram::new(
184 kind,
185 scope,
186 layout_engine,
187 background_color,
188 lifeline_definition,
189 ))
190 }
191
192 fn build_diagram_from_diagram_source(
207 &mut self,
208 source: &parser_types::DiagramSource,
209 ) -> Result<Diagram> {
210 match source {
211 parser_types::DiagramSource::Inline(rc) => {
212 let file_ast = rc.borrow();
213 self.build_diagram_from_file_ast(&file_ast)
214 }
215 parser_types::DiagramSource::Ref(id) => Err(Diagnostic::error(format!(
218 "unresolved embed reference `{id}`",
219 ))
220 .with_code(ErrorCode::E309)
221 .with_label(id.span(), "expected inlined embedded diagram")),
222 }
223 }
224
225 fn extract_type_spec<'b>(
237 attr: &'b parser_types::Attribute<'b>,
238 key: &str,
239 ) -> Result<&'b parser_types::TypeSpec<'b>> {
240 attr.value.as_type_spec().map_err(|err| {
241 Diagnostic::error(err.to_string())
242 .with_code(ErrorCode::E302)
243 .with_label(attr.span(), format!("invalid {key} attribute value"))
244 .with_help(format!(
245 "{key} attribute must be a type reference or inline attributes"
246 ))
247 })
248 }
249
250 fn extract_string<'b>(attr: &'b parser_types::Attribute<'b>, key: &str) -> Result<&'b str> {
256 attr.value.as_str().map_err(|err| {
257 Diagnostic::error(err.to_string())
258 .with_code(ErrorCode::E302)
259 .with_label(attr.span(), format!("invalid {key} value"))
260 .with_help(format!("{key} values must be strings"))
261 })
262 }
263
264 fn extract_color(attr: &parser_types::Attribute<'_>, key: &str) -> Result<Color> {
271 let color_str = attr.value.as_str().map_err(|err| {
272 Diagnostic::error(err.to_string())
273 .with_code(ErrorCode::E302)
274 .with_label(attr.span(), "invalid color value")
275 .with_help("color values must be strings")
276 })?;
277
278 Color::new(color_str).map_err(|err| {
279 Diagnostic::error(format!("invalid {key} `{color_str}`: {err}"))
280 .with_code(ErrorCode::E302)
281 .with_label(attr.span(), "invalid color")
282 .with_help("use a valid CSS color")
283 })
284 }
285
286 fn extract_positive_float(attr: &parser_types::Attribute<'_>, key: &str) -> Result<f32> {
292 attr.value.as_float().map_err(|err| {
293 Diagnostic::error(err.to_string())
294 .with_code(ErrorCode::E302)
295 .with_label(attr.span(), format!("invalid {key} value"))
296 .with_help(format!("{key} must be a positive number"))
297 })
298 }
299
300 fn extract_usize(attr: &parser_types::Attribute<'_>, key: &str, hint: &str) -> Result<usize> {
307 attr.value.as_usize().map_err(|err| {
308 Diagnostic::error(err.to_string())
309 .with_code(ErrorCode::E302)
310 .with_label(attr.span(), format!("invalid {key} value"))
311 .with_help(format!("{key} {hint}"))
312 })
313 }
314
315 fn builtin_type_definitions_map() -> HashMap<Id, elaborate_utils::TypeDefinition> {
321 builtin_types::defaults()
322 .into_iter()
323 .map(|def| (def.id(), def))
324 .collect()
325 }
326
327 fn insert_type_definition(
329 &mut self,
330 type_def: elaborate_utils::TypeDefinition,
331 span: Span,
332 ) -> Result<elaborate_utils::TypeDefinition> {
333 let id = type_def.id();
334
335 if self.type_definitions.insert(id, type_def.clone()).is_none() {
337 Ok(type_def)
338 } else {
339 Err(Diagnostic::error(format!("cannot override type `{id}`"))
340 .with_code(ErrorCode::E301)
341 .with_label(span, "type override not supported")
342 .with_help("built-in types cannot be redefined"))
343 }
344 }
345
346 fn update_type_direct_definitions(
347 &mut self,
348 type_definitions: &Vec<parser_types::TypeDefinition>,
349 ) -> Result<()> {
350 for type_def in type_definitions {
351 let base_type_name = type_def
352 .type_spec
353 .type_name
354 .as_ref()
355 .expect("TypeDefinition should always have a type_name in TypeSpec");
356
357 let base = self
358 .type_definitions
359 .get(base_type_name.inner())
360 .ok_or_else(|| {
361 self.create_undefined_type_error(
363 base_type_name,
364 &format!("base type `{}` not found", base_type_name.inner()),
365 )
366 })?;
367
368 let new_type_def = self.build_type_from_base(
370 *type_def.name.inner(),
371 base,
372 &type_def.type_spec.attributes,
373 )?;
374 self.insert_type_definition(new_type_def, type_def.span())?;
375 }
376 Ok(())
377 }
378
379 fn build_block_from_elements(
391 &mut self,
392 parser_elements: &[parser_types::Element],
393 diagram_kind: DiagramKind,
394 ) -> Result<Block> {
395 if parser_elements.is_empty() {
396 Ok(Block::None)
397 } else {
398 Ok(Block::Scope(self.build_scope_from_elements(
399 parser_elements,
400 diagram_kind,
401 )?))
402 }
403 }
404
405 fn build_scope_from_elements(
416 &mut self,
417 parser_elements: &[parser_types::Element],
418 diagram_kind: DiagramKind,
419 ) -> Result<Scope> {
420 let mut elements = Vec::new();
421
422 for parser_elm in parser_elements {
423 let element = match parser_elm {
424 parser_types::Element::Component {
425 name,
426 display_name,
427 type_spec,
428 content,
429 } => self.build_component_element(
430 name,
431 display_name,
432 type_spec,
433 content,
434 parser_elm,
435 diagram_kind,
436 )?,
437 parser_types::Element::Relation {
438 source,
439 target,
440 relation_type,
441 type_spec,
442 label,
443 } => {
444 self.build_relation_element(source, target, relation_type, type_spec, label)?
445 }
446 parser_types::Element::ActivateBlock { .. } => {
447 unreachable!(
448 "ActivateBlock should have been desugared into explicit activate/deactivate statements before elaboration"
449 );
450 }
451 parser_types::Element::Activate {
452 component,
453 type_spec,
454 } => self.build_activate_element(component, type_spec, diagram_kind)?,
455 parser_types::Element::Deactivate { component } => {
456 self.build_deactivate_element(component, diagram_kind)?
457 }
458 parser_types::Element::Fragment(fragment) => {
459 self.build_fragment_element(fragment, diagram_kind)?
460 }
461 parser_types::Element::AltElseBlock { .. }
462 | parser_types::Element::OptBlock { .. }
463 | parser_types::Element::LoopBlock { .. }
464 | parser_types::Element::ParBlock { .. }
465 | parser_types::Element::BreakBlock { .. }
466 | parser_types::Element::CriticalBlock { .. } => {
467 unreachable!(
468 "Fragment sugar syntax should have been desugared into Fragment elements before elaboration"
469 );
470 }
471 parser_types::Element::Note(note) => self.build_note_element(note, diagram_kind)?,
472 };
473 elements.push(element);
474 }
475 Ok(Scope::new(elements))
476 }
477
478 fn build_component_element(
496 &mut self,
497 name: &Spanned<Id>,
498 display_name: &Option<Spanned<String>>,
499 type_spec: &parser_types::TypeSpec,
500 content: &parser_types::ComponentContent,
501 parser_elm: &parser_types::Element,
502 diagram_kind: DiagramKind,
503 ) -> Result<Element> {
504 let type_def = self.build_type_definition(type_spec)?;
505
506 let shape_def = type_def.shape_definition().map_err(|err| {
507 Diagnostic::error(err)
508 .with_code(ErrorCode::E307)
509 .with_label(type_spec.span(), "invalid shape type")
510 })?;
511
512 if !matches!(content, parser_types::ComponentContent::None) && !shape_def.supports_content()
513 {
514 let type_name = type_spec
515 .type_name
516 .as_ref()
517 .map_or(type_def.id(), |name| *name.inner());
518 return Err(Diagnostic::error(format!(
519 "shape type `{type_name}` does not support nested content"
520 ))
521 .with_code(ErrorCode::E308)
522 .with_label(parser_elm.span(), "content not supported")
523 .with_help(format!(
524 "shape `{type_name}` is content-free and cannot contain nested elements or embedded diagrams"
525 )));
526 }
527
528 let block = match content {
529 parser_types::ComponentContent::None => Block::None,
530 parser_types::ComponentContent::Scope(elements) => {
531 self.build_block_from_elements(elements, diagram_kind)?
532 }
533 parser_types::ComponentContent::Diagram(source) => {
534 Block::Diagram(self.build_diagram_from_diagram_source(source)?)
535 }
536 };
537
538 let node = Node::new(
539 *name.inner(),
540 display_name.as_ref().map(|n| n.to_string()),
541 block,
542 Rc::clone(shape_def),
543 );
544
545 Ok(Element::Node(node))
546 }
547
548 fn build_relation_element(
559 &mut self,
560 source: &Spanned<Id>,
561 target: &Spanned<Id>,
562 relation_type: &Spanned<&str>,
563 type_spec: &parser_types::TypeSpec,
564 label: &Option<Spanned<String>>,
565 ) -> Result<Element> {
566 let relation_type_def = self.build_type_definition(type_spec)?;
568
569 let arrow_def = relation_type_def.arrow_definition().map_err(|err| {
570 Diagnostic::error(err)
571 .with_code(ErrorCode::E307)
572 .with_label(type_spec.span(), "invalid arrow type")
573 })?;
574
575 let arrow_direction = ArrowDirection::from_str(relation_type).map_err(|_| {
576 Diagnostic::error(format!("invalid arrow direction `{relation_type}`"))
577 .with_code(ErrorCode::E302)
578 .with_label(relation_type.span(), "invalid direction")
579 .with_help("arrow direction must be `->`, `<-`, `<->`, or `-`")
580 })?;
581
582 Ok(Element::Relation(Relation::new(
583 *source.inner(),
584 *target.inner(),
585 arrow_direction,
586 label.as_ref().map(|l| l.to_string()),
587 Rc::clone(arrow_def),
588 )))
589 }
590
591 fn build_activate_element(
602 &mut self,
603 component: &Spanned<Id>,
604 type_spec: &parser_types::TypeSpec,
605 diagram_kind: DiagramKind,
606 ) -> Result<Element> {
607 if diagram_kind != DiagramKind::Sequence {
609 return Err(Diagnostic::error(
610 "activate statements are only supported in sequence diagrams",
611 )
612 .with_code(ErrorCode::E304)
613 .with_label(component.span(), "activate not allowed here")
614 .with_help("activate statements are used for temporal grouping in sequence diagrams"));
615 }
616
617 let activate_type_def = self.build_type_definition(type_spec)?;
618
619 let activation_box_def = activate_type_def
620 .activation_box_definition()
621 .map_err(|err| {
622 Diagnostic::error(err)
623 .with_code(ErrorCode::E307)
624 .with_label(type_spec.span(), "invalid activation box type")
625 })?;
626
627 Ok(Element::Activate(Activate::new(
628 *component.inner(),
629 Rc::clone(activation_box_def),
630 )))
631 }
632
633 fn build_deactivate_element(
642 &mut self,
643 component: &Spanned<Id>,
644 diagram_kind: DiagramKind,
645 ) -> Result<Element> {
646 if diagram_kind != DiagramKind::Sequence {
648 return Err(Diagnostic::error(
649 "deactivate statements are only supported in sequence diagrams",
650 )
651 .with_code(ErrorCode::E304)
652 .with_label(component.span(), "deactivate not allowed here")
653 .with_help(
654 "deactivate statements are used for temporal grouping in sequence diagrams",
655 ));
656 }
657
658 Ok(Element::Deactivate(*component.inner()))
659 }
660
661 fn build_fragment_element(
672 &mut self,
673 fragment: &parser_types::Fragment,
674 diagram_kind: DiagramKind,
675 ) -> Result<Element> {
676 if diagram_kind != DiagramKind::Sequence {
678 return Err(Diagnostic::error(
679 "fragment blocks are only supported in sequence diagrams",
680 )
681 .with_code(ErrorCode::E304)
682 .with_label(fragment.span(), "fragment not allowed here")
683 .with_help("fragment blocks are used for grouping in sequence diagrams"));
684 }
685
686 let type_def = self
688 .build_type_definition(&fragment.type_spec)
689 .map_err(|_| {
690 Diagnostic::error(format!(
691 "invalid fragment type for operation `{}`",
692 fragment.operation.inner()
693 ))
694 .with_code(ErrorCode::E300)
695 .with_label(fragment.operation.span(), "invalid fragment type")
696 .with_help("fragment types must be defined in the type system")
697 })?;
698
699 let fragment_def = type_def.fragment_definition().map_err(|err| {
700 Diagnostic::error(err)
701 .with_code(ErrorCode::E307)
702 .with_label(fragment.type_spec.span(), "invalid fragment type")
703 })?;
704
705 let mut sections = Vec::new();
706 for parser_section in &fragment.sections {
707 let scope = self.build_scope_from_elements(&parser_section.elements, diagram_kind)?;
708 let elements_vec = scope.elements().to_vec();
709
710 sections.push(FragmentSection::new(
711 parser_section.title.as_ref().map(|t| t.inner().to_string()),
712 elements_vec,
713 ));
714 }
715
716 Ok(Element::Fragment(Fragment::new(
717 fragment.operation.inner().to_string(),
718 sections,
719 Rc::clone(fragment_def),
720 )))
721 }
722
723 fn build_type_definition(
724 &mut self,
725 type_spec: &parser_types::TypeSpec,
726 ) -> Result<elaborate_utils::TypeDefinition> {
727 let type_name = type_spec.type_name.as_ref().ok_or_else(|| {
728 Diagnostic::error("base type `type_spec` must have a type name")
729 .with_code(ErrorCode::E306)
730 .with_label(type_spec.span(), "missing type name")
731 })?;
732 let Some(base) = self.type_definitions.get(type_name.inner()) else {
734 return Err(
735 self.create_undefined_type_error(type_name, &format!("unknown type `{type_name}`"))
736 );
737 };
738
739 let attributes = &type_spec.attributes;
740 if attributes.is_empty() {
742 return Ok(base.clone());
743 }
744
745 let id = Id::from_anonymous();
747 let new_type = self.build_type_from_base(id, base, attributes)?;
748 self.insert_type_definition(new_type, type_name.span())
749 }
750
751 fn resolve_text_type_reference(
757 &self,
758 type_spec: &parser_types::TypeSpec,
759 current_text_rc: &Rc<TextDefinition>,
760 ) -> Result<Rc<TextDefinition>> {
761 let mut text_rc = if let Some(type_name) = &type_spec.type_name {
763 let base_type = self
764 .type_definitions
765 .get(type_name.inner())
766 .ok_or_else(|| {
767 Diagnostic::error(format!("undefined text type `{}`", type_name.inner()))
768 .with_code(ErrorCode::E300)
769 .with_label(type_spec.span(), "undefined type")
770 .with_help("type must be defined with `type` statement before use")
771 })?;
772
773 let base_text_rc = base_type.text_definition_from_draw().map_err(|err| {
774 Diagnostic::error(format!(
775 "type `{}` is not a text type: {}",
776 type_name.inner(),
777 err
778 ))
779 .with_code(ErrorCode::E307)
780 .with_label(type_spec.span(), "invalid type reference")
781 .with_help("only `Text` types can be used for text attributes")
782 })?;
783
784 Rc::clone(base_text_rc)
785 } else {
786 Rc::clone(current_text_rc)
787 };
788
789 if !type_spec.attributes.is_empty() {
791 let text_def_mut = Rc::make_mut(&mut text_rc);
792 TextAttributeExtractor::extract_text_attributes(text_def_mut, &type_spec.attributes)?;
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<StrokeDefinition>,
807 ) -> Result<Rc<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 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 = 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 StrokeAttributeExtractor::extract_stroke_attributes(&mut new_stroke, attributes)?;
1113 Ok(elaborate_utils::TypeDefinition::new_stroke(id, new_stroke))
1114 }
1115 elaborate_utils::DrawDefinition::Text(text_def) => {
1116 let mut new_text_def = (**text_def).clone();
1117 TextAttributeExtractor::extract_text_attributes(&mut new_text_def, attributes)?;
1118 Ok(elaborate_utils::TypeDefinition::new_text(id, new_text_def))
1119 }
1120 }
1121 }
1122
1123 fn create_undefined_type_error(&self, span: &Spanned<Id>, message: &str) -> Diagnostic {
1125 Diagnostic::error(message)
1126 .with_code(ErrorCode::E300)
1127 .with_label(span.span(), "undefined type")
1128 .with_help(format!(
1129 "type `{}` must be a built-in type or defined with a `type` statement before it can be used as a base type",
1130 span.inner()
1131 ))
1132 }
1133
1134 fn extract_diagram_attributes(
1136 &self,
1137 kind: DiagramKind,
1138 attrs: &Vec<parser_types::Attribute<'_>>,
1139 ) -> Result<(LayoutEngine, Option<Color>, Option<Rc<LifelineDefinition>>)> {
1140 let mut layout_engine = match kind {
1142 DiagramKind::Component => self.cfg.component_layout,
1143 DiagramKind::Sequence => self.cfg.sequence_layout,
1144 };
1145
1146 let mut background_color = None;
1147 let mut lifeline_definition = None;
1148
1149 for attr in attrs {
1151 match *attr.name {
1152 "layout_engine" => {
1153 layout_engine = Self::determine_layout_engine(attr)?;
1154 }
1155 "background_color" => {
1156 let color = Self::extract_background_color(attr)?;
1157 background_color = Some(color);
1158 }
1159 "lifeline" => {
1160 if kind != DiagramKind::Sequence {
1162 return Err(Diagnostic::error(
1163 "`lifeline` attribute is only valid for sequence diagrams",
1164 )
1165 .with_code(ErrorCode::E304)
1166 .with_label(attr.span(), "invalid attribute"));
1167 }
1168 let definition = self.extract_lifeline_definition(attr)?;
1169 lifeline_definition = Some(Rc::new(definition));
1170 }
1171 _ => {
1172 return Err(Diagnostic::error(format!(
1173 "unsupported diagram attribute `{}`",
1174 attr.name
1175 ))
1176 .with_code(ErrorCode::E303)
1177 .with_label(attr.span(), "unsupported attribute"));
1178 }
1179 }
1180 }
1181
1182 Ok((layout_engine, background_color, lifeline_definition))
1183 }
1184
1185 fn extract_background_color(color_attr: &parser_types::Attribute<'_>) -> Result<Color> {
1187 Self::extract_color(color_attr, "background_color")
1188 }
1189
1190 fn extract_lifeline_definition(
1192 &self,
1193 lifeline_attr: &parser_types::Attribute<'_>,
1194 ) -> Result<LifelineDefinition> {
1195 let type_spec = Self::extract_type_spec(lifeline_attr, "lifeline")?;
1196
1197 let default_stroke_rc = Rc::new(StrokeDefinition::dashed(Color::default(), 1.0));
1199
1200 let stroke_rc =
1202 if let Some(stroke_attr) = type_spec.attributes.iter().find(|a| *a.name == "stroke") {
1203 let stroke_type_spec = Self::extract_type_spec(stroke_attr, "stroke")?;
1204
1205 self.resolve_stroke_type_reference(stroke_type_spec, &default_stroke_rc)?
1206 } else if !type_spec.attributes.is_empty() {
1207 return Err(Diagnostic::error(format!(
1208 "unknown lifeline attribute `{}`",
1209 type_spec.attributes[0].name
1210 ))
1211 .with_code(ErrorCode::E303)
1212 .with_label(type_spec.attributes[0].span(), "unknown attribute")
1213 .with_help("valid lifeline attributes are: `stroke`=[...]"));
1214 } else {
1215 default_stroke_rc
1216 };
1217
1218 Ok(LifelineDefinition::new(stroke_rc))
1219 }
1220
1221 fn determine_layout_engine(engine_attr: &parser_types::Attribute<'_>) -> Result<LayoutEngine> {
1223 let engine_str = Self::extract_string(engine_attr, "layout_engine")?;
1224 LayoutEngine::from_str(engine_str).map_err(|_| {
1225 Diagnostic::error(format!("invalid `layout_engine` value: `{engine_str}`"))
1226 .with_code(ErrorCode::E302)
1227 .with_label(engine_attr.value.span(), "unsupported layout engine")
1228 .with_help("supported layout engines are: `basic`, `sugiyama`")
1229 })
1230 }
1231
1232 fn build_note_element(
1249 &mut self,
1250 note: &parser_types::Note,
1251 diagram_kind: DiagramKind,
1252 ) -> Result<Element> {
1253 let type_def = self.build_type_definition(¬e.type_spec)?;
1254
1255 let (on, align) = self.extract_note_attributes(¬e.type_spec.attributes, diagram_kind)?;
1257
1258 let content = note.content.inner().to_string();
1259
1260 let note_def_ref = type_def.note_definition().map_err(|err| {
1262 Diagnostic::error(err)
1263 .with_code(ErrorCode::E307)
1264 .with_label(note.content.span(), "invalid note type")
1265 })?;
1266 let note_def = Rc::clone(note_def_ref);
1267
1268 Ok(Element::Note(Note::new(on, align, content, note_def)))
1269 }
1270
1271 fn extract_note_attributes(
1288 &mut self,
1289 attributes: &[parser_types::Attribute],
1290 diagram_kind: DiagramKind,
1291 ) -> Result<(Vec<Id>, NoteAlign)> {
1292 let mut on: Option<Vec<Id>> = None;
1293 let mut align: Option<NoteAlign> = None;
1294
1295 for attr in attributes {
1296 match *attr.name.inner() {
1297 "on" => {
1298 let ids = attr.value.as_identifiers().map_err(|_| {
1299 Diagnostic::error("`on` attribute must be a list of element identifiers")
1300 .with_code(ErrorCode::E302)
1301 .with_label(attr.value.span(), "invalid on value")
1302 .with_help("use syntax: `on=[element1, element2]`")
1303 })?;
1304
1305 on = Some(ids.iter().map(|id| *id.inner()).collect());
1306 }
1307 "align" => {
1308 let align_str = Self::extract_string(attr, "align")?;
1309
1310 let alignment = align_str.parse::<NoteAlign>().map_err(|_| {
1311 Diagnostic::error(format!("invalid alignment value: `{}`", align_str))
1312 .with_code(ErrorCode::E302)
1313 .with_label(attr.value.span(), "invalid alignment")
1314 .with_help("valid values: over, left, right, top, bottom")
1315 })?;
1316
1317 align = Some(alignment);
1318 }
1319 _ => {} }
1321 }
1322
1323 let on = on.unwrap_or_default();
1325 let align = align.unwrap_or(match diagram_kind {
1326 DiagramKind::Sequence => NoteAlign::Over,
1327 DiagramKind::Component => NoteAlign::Bottom,
1328 });
1329
1330 Ok((on, align))
1331 }
1332}
1333
1334#[cfg(test)]
1335mod tests {
1336 use std::cell::RefCell;
1337
1338 use super::*;
1339
1340 fn builder_with_builtins() -> Builder {
1342 let mut builder = Builder::new(ElaborateConfig::default());
1343 builder.type_definitions = Builder::builtin_type_definitions_map();
1344 builder
1345 }
1346
1347 #[test]
1348 #[should_panic(expected = "ActivateBlock should have been desugared")]
1349 fn test_activate_block_panics_in_elaboration() {
1350 let elements = vec![parser_types::Element::ActivateBlock {
1352 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1353 type_spec: parser_types::TypeSpec::default(),
1354 elements: vec![],
1355 }];
1356
1357 let diagram = parser_types::FileAst {
1358 header: parser_types::FileHeader::Diagram {
1359 kind: Spanned::new(DiagramKind::Component, Span::new(0..9)),
1360 attributes: vec![],
1361 },
1362 import_decls: vec![],
1363 type_definitions: vec![],
1364 elements,
1365 imports: vec![],
1366 };
1367
1368 let config = ElaborateConfig::default();
1369 let builder = Builder::new(config);
1370 let _ = builder.build(&diagram);
1372 }
1373
1374 #[test]
1375 fn test_explicit_activation_scoping_behavior() {
1376 let elements = vec![
1378 parser_types::Element::Activate {
1379 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1380 type_spec: parser_types::TypeSpec {
1381 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1382 attributes: vec![],
1383 },
1384 },
1385 parser_types::Element::Relation {
1386 source: Spanned::new(Id::new("user"), Span::new(0..4)),
1387 target: Spanned::new(Id::new("server"), Span::new(0..6)),
1388 relation_type: Spanned::new("->", Span::new(0..2)),
1389 type_spec: parser_types::TypeSpec {
1390 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1391 attributes: vec![],
1392 },
1393 label: Some(Spanned::new("Request".to_string(), Span::new(0..7))),
1394 },
1395 parser_types::Element::Relation {
1396 source: Spanned::new(Id::new("server"), Span::new(0..6)),
1397 target: Spanned::new(Id::new("database"), Span::new(0..8)),
1398 relation_type: Spanned::new("->", Span::new(0..2)),
1399 type_spec: parser_types::TypeSpec {
1400 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1401 attributes: vec![],
1402 },
1403 label: Some(Spanned::new("Query".to_string(), Span::new(0..5))),
1404 },
1405 parser_types::Element::Deactivate {
1406 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1407 },
1408 ];
1409
1410 let diagram = parser_types::FileAst {
1411 header: parser_types::FileHeader::Diagram {
1412 kind: Spanned::new(DiagramKind::Sequence, Span::new(0..8)),
1413 attributes: vec![],
1414 },
1415 import_decls: vec![],
1416 type_definitions: vec![],
1417 elements,
1418 imports: vec![],
1419 };
1420
1421 let config = ElaborateConfig::default();
1422 let builder = Builder::new(config);
1423 let result = builder.build(&diagram);
1424
1425 assert!(
1426 result.is_ok(),
1427 "Sequence diagram with activate block should work"
1428 );
1429
1430 let diagram = result.unwrap();
1431 for element in diagram.scope().elements() {
1433 if let Element::Relation(relation) = element {
1434 let source_str = relation.source().to_string();
1436 let target_str = relation.target().to_string();
1437 assert!(
1438 !source_str.starts_with("user::user::"),
1439 "Source should not be double-scoped: {}",
1440 source_str
1441 );
1442 assert!(
1443 !target_str.starts_with("user::server::"),
1444 "Target should not be double-scoped: {}",
1445 target_str
1446 );
1447 }
1448 }
1449 }
1450
1451 #[test]
1452 fn test_nested_explicit_activations_same_component() {
1453 let elements = vec![
1455 parser_types::Element::Activate {
1456 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1457 type_spec: parser_types::TypeSpec {
1458 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1459 attributes: vec![],
1460 },
1461 },
1462 parser_types::Element::Relation {
1463 source: Spanned::new(Id::new("user"), Span::new(0..4)),
1464 target: Spanned::new(Id::new("server"), Span::new(0..6)),
1465 relation_type: Spanned::new("->", Span::new(0..2)),
1466 type_spec: parser_types::TypeSpec {
1467 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1468 attributes: vec![],
1469 },
1470 label: Some(Spanned::new(
1471 "Initial request".to_string(),
1472 Span::new(0..16),
1473 )),
1474 },
1475 parser_types::Element::Activate {
1476 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1477 type_spec: parser_types::TypeSpec {
1478 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1479 attributes: vec![],
1480 },
1481 },
1482 parser_types::Element::Relation {
1483 source: Spanned::new(Id::new("user"), Span::new(0..4)),
1484 target: Spanned::new(Id::new("database"), Span::new(0..8)),
1485 relation_type: Spanned::new("->", Span::new(0..2)),
1486 type_spec: parser_types::TypeSpec {
1487 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1488 attributes: vec![],
1489 },
1490 label: Some(Spanned::new("Direct query".to_string(), Span::new(0..12))),
1491 },
1492 parser_types::Element::Deactivate {
1493 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1494 },
1495 parser_types::Element::Activate {
1496 component: Spanned::new(Id::new("server"), Span::new(0..6)),
1497 type_spec: parser_types::TypeSpec {
1498 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1499 attributes: vec![],
1500 },
1501 },
1502 parser_types::Element::Relation {
1503 source: Spanned::new(Id::new("server"), Span::new(0..6)),
1504 target: Spanned::new(Id::new("cache"), Span::new(0..5)),
1505 relation_type: Spanned::new("->", Span::new(0..2)),
1506 type_spec: parser_types::TypeSpec {
1507 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1508 attributes: vec![],
1509 },
1510 label: Some(Spanned::new("Cache lookup".to_string(), Span::new(0..12))),
1511 },
1512 parser_types::Element::Deactivate {
1513 component: Spanned::new(Id::new("server"), Span::new(0..6)),
1514 },
1515 parser_types::Element::Deactivate {
1516 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1517 },
1518 ];
1519
1520 let diagram = parser_types::FileAst {
1521 header: parser_types::FileHeader::Diagram {
1522 kind: Spanned::new(DiagramKind::Sequence, Span::new(0..8)),
1523 attributes: vec![],
1524 },
1525 import_decls: vec![],
1526 type_definitions: vec![],
1527 elements,
1528 imports: vec![],
1529 };
1530
1531 let config = ElaborateConfig::default();
1532 let builder = Builder::new(config);
1533 let result = builder.build(&diagram);
1534
1535 assert!(
1536 result.is_ok(),
1537 "Nested activate blocks should work: {:?}",
1538 result.err()
1539 );
1540
1541 let diagram = result.unwrap();
1542 let elems = diagram.scope().elements();
1543
1544 let activations: Vec<_> = elems
1545 .iter()
1546 .filter_map(|e| {
1547 if let Element::Activate(activate) = e {
1548 Some(activate.component().to_string())
1549 } else {
1550 None
1551 }
1552 })
1553 .collect();
1554 let deactivations: Vec<_> = elems
1555 .iter()
1556 .filter_map(|e| {
1557 if let Element::Deactivate(id) = e {
1558 Some(id.to_string())
1559 } else {
1560 None
1561 }
1562 })
1563 .collect();
1564 let relations: Vec<_> = elems
1565 .iter()
1566 .filter_map(|e| {
1567 if let Element::Relation(r) = e {
1568 Some((r.source().to_string(), r.target().to_string()))
1569 } else {
1570 None
1571 }
1572 })
1573 .collect();
1574
1575 assert_eq!(
1576 relations.len(),
1577 3,
1578 "Should have 3 relations after desugaring"
1579 );
1580 assert_eq!(
1581 activations.len(),
1582 3,
1583 "Should have 3 activation starts after desugaring"
1584 );
1585 assert_eq!(
1586 deactivations.len(),
1587 3,
1588 "Should have 3 activation ends after desugaring"
1589 );
1590
1591 assert_eq!(
1592 activations[0], "user",
1593 "First activation should be for 'user'"
1594 );
1595 assert_eq!(
1596 deactivations.last().unwrap(),
1597 "user",
1598 "Last deactivation should be for 'user'"
1599 );
1600 }
1601
1602 #[test]
1603 fn test_explicit_activate_in_sequence_diagram() {
1604 let config = ElaborateConfig::default();
1605 let builder = Builder::new(config);
1606
1607 let elements = vec![
1609 parser_types::Element::Component {
1611 name: Spanned::new(Id::new("user"), Span::new(0..4)),
1612 display_name: None,
1613 type_spec: parser_types::TypeSpec {
1614 type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(5..14))),
1615 attributes: vec![],
1616 },
1617 content: parser_types::ComponentContent::None,
1618 },
1619 parser_types::Element::Activate {
1621 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1622 type_spec: parser_types::TypeSpec {
1623 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1624 attributes: vec![],
1625 },
1626 },
1627 parser_types::Element::Deactivate {
1629 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1630 },
1631 ];
1632
1633 let diagram = parser_types::FileAst {
1634 header: parser_types::FileHeader::Diagram {
1635 kind: Spanned::new(DiagramKind::Sequence, Span::new(0..8)),
1636 attributes: vec![],
1637 },
1638 import_decls: vec![],
1639 type_definitions: vec![],
1640 elements,
1641 imports: vec![],
1642 };
1643
1644 let result = builder.build(&diagram);
1645 assert!(
1646 result.is_ok(),
1647 "Should successfully build sequence diagram with explicit activate/deactivate"
1648 );
1649
1650 let elaborate_diagram = result.unwrap();
1651 let elements = elaborate_diagram.scope().elements();
1652
1653 assert_eq!(
1655 elements.len(),
1656 3,
1657 "Should have 3 elements: component, activate, deactivate"
1658 );
1659
1660 if let Element::Activate(activate) = &elements[1] {
1662 assert_eq!(
1663 activate.component().to_string(),
1664 "user",
1665 "Activate should reference 'user' component"
1666 );
1667 } else {
1668 panic!("Second element should be Activate");
1669 }
1670
1671 if let Element::Deactivate(id) = &elements[2] {
1673 assert_eq!(
1674 id.to_string(),
1675 "user",
1676 "Deactivate should reference 'user' component"
1677 );
1678 } else {
1679 panic!("Third element should be Deactivate");
1680 }
1681 }
1682
1683 #[test]
1684 fn test_explicit_activate_not_allowed_in_component_diagram() {
1685 let config = ElaborateConfig::default();
1686 let builder = Builder::new(config);
1687
1688 let elements = vec![
1690 parser_types::Element::Component {
1692 name: Spanned::new(Id::new("user"), Span::new(0..4)),
1693 display_name: None,
1694 type_spec: parser_types::TypeSpec {
1695 type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(5..14))),
1696 attributes: vec![],
1697 },
1698 content: parser_types::ComponentContent::None,
1699 },
1700 parser_types::Element::Activate {
1702 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1703 type_spec: parser_types::TypeSpec::default(),
1704 },
1705 ];
1706
1707 let diagram = parser_types::FileAst {
1708 header: parser_types::FileHeader::Diagram {
1709 kind: Spanned::new(DiagramKind::Component, Span::new(0..9)),
1710 attributes: vec![],
1711 },
1712 import_decls: vec![],
1713 type_definitions: vec![],
1714 elements,
1715 imports: vec![],
1716 };
1717
1718 let result = builder.build(&diagram);
1719 assert!(
1720 result.is_err(),
1721 "Should fail to build component diagram with explicit activate"
1722 );
1723
1724 if let Err(err) = result {
1725 let error_message = format!("{}", err);
1726 assert!(
1727 error_message
1728 .contains("activate statements are only supported in sequence diagrams"),
1729 "Error should mention that activate is not allowed in component diagrams"
1730 );
1731 }
1732 }
1733
1734 #[test]
1735 fn test_explicit_activation_timing_and_nesting() {
1736 let elements = vec![
1739 parser_types::Element::Component {
1741 name: Spanned::new(Id::new("user"), Span::new(0..4)),
1742 display_name: None,
1743 type_spec: parser_types::TypeSpec {
1744 type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(0..9))),
1745 attributes: vec![],
1746 },
1747 content: parser_types::ComponentContent::None,
1748 },
1749 parser_types::Element::Component {
1750 name: Spanned::new(Id::new("server"), Span::new(0..6)),
1751 display_name: None,
1752 type_spec: parser_types::TypeSpec {
1753 type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(0..9))),
1754 attributes: vec![],
1755 },
1756 content: parser_types::ComponentContent::None,
1757 },
1758 parser_types::Element::Component {
1759 name: Spanned::new(Id::new("database"), Span::new(0..8)),
1760 display_name: None,
1761 type_spec: parser_types::TypeSpec {
1762 type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(0..9))),
1763 attributes: vec![],
1764 },
1765 content: parser_types::ComponentContent::None,
1766 },
1767 parser_types::Element::Activate {
1769 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1770 type_spec: parser_types::TypeSpec {
1771 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1772 attributes: vec![],
1773 },
1774 },
1775 parser_types::Element::Relation {
1776 source: Spanned::new(Id::new("user"), Span::new(0..4)),
1777 target: Spanned::new(Id::new("server"), Span::new(0..6)),
1778 relation_type: Spanned::new("->", Span::new(0..2)),
1779 type_spec: parser_types::TypeSpec {
1780 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1781 attributes: vec![],
1782 },
1783 label: Some(Spanned::new("First request".to_string(), Span::new(0..13))),
1784 },
1785 parser_types::Element::Activate {
1786 component: Spanned::new(Id::new("server"), Span::new(0..6)),
1787 type_spec: parser_types::TypeSpec {
1788 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1789 attributes: vec![],
1790 },
1791 },
1792 parser_types::Element::Relation {
1793 source: Spanned::new(Id::new("server"), Span::new(0..6)),
1794 target: Spanned::new(Id::new("database"), Span::new(0..8)),
1795 relation_type: Spanned::new("->", Span::new(0..2)),
1796 type_spec: parser_types::TypeSpec {
1797 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1798 attributes: vec![],
1799 },
1800 label: Some(Spanned::new("Nested query".to_string(), Span::new(0..12))),
1801 },
1802 parser_types::Element::Relation {
1803 source: Spanned::new(Id::new("database"), Span::new(0..8)),
1804 target: Spanned::new(Id::new("server"), Span::new(0..6)),
1805 relation_type: Spanned::new("->", Span::new(0..2)),
1806 type_spec: parser_types::TypeSpec {
1807 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1808 attributes: vec![],
1809 },
1810 label: Some(Spanned::new(
1811 "Nested response".to_string(),
1812 Span::new(0..15),
1813 )),
1814 },
1815 parser_types::Element::Deactivate {
1816 component: Spanned::new(Id::new("server"), Span::new(0..6)),
1817 },
1818 parser_types::Element::Relation {
1819 source: Spanned::new(Id::new("server"), Span::new(0..6)),
1820 target: Spanned::new(Id::new("user"), Span::new(0..4)),
1821 relation_type: Spanned::new("->", Span::new(0..2)),
1822 type_spec: parser_types::TypeSpec {
1823 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1824 attributes: vec![],
1825 },
1826 label: Some(Spanned::new("First response".to_string(), Span::new(0..14))),
1827 },
1828 parser_types::Element::Activate {
1829 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1830 type_spec: parser_types::TypeSpec {
1831 type_name: Some(Spanned::new(Id::new("Activate"), Span::new(0..8))),
1832 attributes: vec![],
1833 },
1834 },
1835 parser_types::Element::Relation {
1836 source: Spanned::new(Id::new("user"), Span::new(0..4)),
1837 target: Spanned::new(Id::new("server"), Span::new(0..6)),
1838 relation_type: Spanned::new("->", Span::new(0..2)),
1839 type_spec: parser_types::TypeSpec {
1840 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
1841 attributes: vec![],
1842 },
1843 label: Some(Spanned::new("Second request".to_string(), Span::new(0..14))),
1844 },
1845 parser_types::Element::Deactivate {
1846 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1847 },
1848 parser_types::Element::Deactivate {
1849 component: Spanned::new(Id::new("user"), Span::new(0..4)),
1850 },
1851 ];
1852
1853 let diagram = parser_types::FileAst {
1854 header: parser_types::FileHeader::Diagram {
1855 kind: Spanned::new(DiagramKind::Sequence, Span::new(0..8)),
1856 attributes: vec![],
1857 },
1858 import_decls: vec![],
1859 type_definitions: vec![],
1860 elements,
1861 imports: vec![],
1862 };
1863
1864 let config = ElaborateConfig::default();
1865 let builder = Builder::new(config);
1866 let result = builder.build(&diagram);
1867
1868 assert!(
1869 result.is_ok(),
1870 "Complex nested activate blocks should work: {:?}",
1871 result.err()
1872 );
1873
1874 let diagram = result.unwrap();
1875
1876 let elems = diagram.scope().elements();
1878 let relations = elems
1879 .iter()
1880 .filter(|e| matches!(e, Element::Relation(_)))
1881 .count();
1882 let activates = elems
1883 .iter()
1884 .filter(|e| matches!(e, Element::Activate(_)))
1885 .count();
1886 let deactivates = elems
1887 .iter()
1888 .filter(|e| matches!(e, Element::Deactivate(_)))
1889 .count();
1890
1891 assert!(
1892 relations >= 5,
1893 "Should have at least 5 relations after desugaring, found {}",
1894 relations
1895 );
1896 assert!(
1897 activates >= 3,
1898 "Should have at least 3 activates after desugaring, found {}",
1899 activates
1900 );
1901 assert!(
1902 deactivates >= 3,
1903 "Should have at least 3 deactivates after desugaring, found {}",
1904 deactivates
1905 );
1906 }
1907
1908 #[test]
1909 fn test_note_with_default_alignment_sequence() {
1910 let mut builder = builder_with_builtins();
1911
1912 let note = parser_types::Note {
1913 type_spec: parser_types::TypeSpec {
1914 type_name: Some(Spanned::new(Id::new("Note"), Span::new(0..4))),
1915 attributes: vec![],
1916 },
1917 content: Spanned::new("Test note".to_string(), Span::new(0..9)),
1918 };
1919
1920 let diagram_kind = DiagramKind::Sequence;
1921 let result = builder.build_note_element(¬e, diagram_kind);
1922
1923 assert!(result.is_ok());
1924 let element = result.unwrap();
1925 if let Element::Note(note_elem) = element {
1926 assert_eq!(note_elem.on().len(), 0); assert_eq!(note_elem.align(), NoteAlign::Over); assert_eq!(note_elem.content(), "Test note");
1929 } else {
1930 panic!("Expected Note element");
1931 }
1932 }
1933
1934 #[test]
1935 fn test_note_with_default_alignment_component() {
1936 let mut builder = builder_with_builtins();
1937
1938 let note = parser_types::Note {
1939 type_spec: parser_types::TypeSpec {
1940 type_name: Some(Spanned::new(Id::new("Note"), Span::new(0..4))),
1941 attributes: vec![],
1942 },
1943 content: Spanned::new("Test note".to_string(), Span::new(0..9)),
1944 };
1945
1946 let diagram_kind = DiagramKind::Component;
1947 let result = builder.build_note_element(¬e, diagram_kind);
1948
1949 assert!(result.is_ok());
1950 let element = result.unwrap();
1951 if let Element::Note(note_elem) = element {
1952 assert_eq!(note_elem.on().len(), 0); assert_eq!(note_elem.align(), NoteAlign::Bottom); assert_eq!(note_elem.content(), "Test note");
1955 } else {
1956 panic!("Expected Note element");
1957 }
1958 }
1959
1960 #[test]
1961 fn test_note_with_styling_attributes() {
1962 let mut builder = builder_with_builtins();
1963
1964 let attributes = vec![
1965 parser_types::Attribute {
1966 name: Spanned::new("background_color", Span::new(0..16)),
1967 value: parser_types::AttributeValue::String(Spanned::new(
1968 "lightyellow".to_string(),
1969 Span::new(0..11),
1970 )),
1971 },
1972 parser_types::Attribute {
1973 name: Spanned::new("stroke", Span::new(0..6)),
1974 value: parser_types::AttributeValue::TypeSpec(parser_types::TypeSpec {
1975 type_name: None,
1976 attributes: vec![
1977 parser_types::Attribute {
1978 name: Spanned::new("color", Span::new(0..5)),
1979 value: parser_types::AttributeValue::String(Spanned::new(
1980 "blue".to_string(),
1981 Span::new(0..4),
1982 )),
1983 },
1984 parser_types::Attribute {
1985 name: Spanned::new("width", Span::new(0..5)),
1986 value: parser_types::AttributeValue::Float(Spanned::new(
1987 2.0,
1988 Span::new(0..3),
1989 )),
1990 },
1991 ],
1992 }),
1993 },
1994 parser_types::Attribute {
1995 name: Spanned::new("text", Span::new(0..4)),
1996 value: parser_types::AttributeValue::TypeSpec(parser_types::TypeSpec {
1997 type_name: None,
1998 attributes: vec![parser_types::Attribute {
1999 name: Spanned::new("font_size", Span::new(0..9)),
2000 value: parser_types::AttributeValue::Float(Spanned::new(
2001 14.0,
2002 Span::new(0..2),
2003 )),
2004 }],
2005 }),
2006 },
2007 ];
2008
2009 let note = parser_types::Note {
2010 type_spec: parser_types::TypeSpec {
2011 type_name: Some(Spanned::new(Id::new("Note"), Span::new(0..4))),
2012 attributes,
2013 },
2014 content: Spanned::new("Styled note".to_string(), Span::new(0..11)),
2015 };
2016
2017 let diagram_kind = DiagramKind::Sequence;
2018 let result = builder.build_note_element(¬e, diagram_kind);
2019
2020 assert!(result.is_ok());
2021 let element = result.unwrap();
2022 if let Element::Note(note_elem) = element {
2023 assert_eq!(note_elem.content(), "Styled note");
2024 assert_eq!(note_elem.align(), NoteAlign::Over); assert_eq!(note_elem.on().len(), 0); } else {
2027 panic!("Expected Note element");
2028 }
2029 }
2030
2031 #[test]
2036 fn test_extract_type_spec_success() {
2037 use crate::parser_types::{Attribute, AttributeValue, TypeSpec};
2038
2039 let type_spec = TypeSpec {
2040 type_name: Some(Spanned::new(Id::new("BoldText"), Span::new(0..8))),
2041 attributes: vec![],
2042 };
2043 let attr = Attribute {
2044 name: Spanned::new("text", Span::new(0..4)),
2045 value: AttributeValue::TypeSpec(type_spec),
2046 };
2047
2048 let result = Builder::extract_type_spec(&attr, "text");
2049 assert!(result.is_ok());
2050 }
2051
2052 #[test]
2053 fn test_extract_type_spec_error() {
2054 use crate::parser_types::{Attribute, AttributeValue};
2055
2056 let attr = Attribute {
2057 name: Spanned::new("text", Span::new(0..4)),
2058 value: AttributeValue::String(Spanned::new(
2059 "not a type spec".to_string(),
2060 Span::new(5..20),
2061 )),
2062 };
2063
2064 let result = Builder::extract_type_spec(&attr, "text");
2065 assert!(result.is_err());
2066 let err = result.unwrap_err();
2067 assert!(err.to_string().contains("expected type spec"));
2068 }
2069
2070 #[test]
2071 fn test_extract_string_success() {
2072 use crate::parser_types::{Attribute, AttributeValue};
2073
2074 let attr = Attribute {
2075 name: Spanned::new("style", Span::new(0..5)),
2076 value: AttributeValue::String(Spanned::new("curved".to_string(), Span::new(6..14))),
2077 };
2078
2079 let result = Builder::extract_string(&attr, "style");
2080 assert!(result.is_ok());
2081 assert_eq!(result.unwrap(), "curved");
2082 }
2083
2084 #[test]
2085 fn test_extract_string_error() {
2086 use crate::parser_types::{Attribute, AttributeValue};
2087
2088 let attr = Attribute {
2089 name: Spanned::new("style", Span::new(0..5)),
2090 value: AttributeValue::Float(Spanned::new(42.0, Span::new(6..8))),
2091 };
2092
2093 let result = Builder::extract_string(&attr, "style");
2094 assert!(result.is_err());
2095 let err = result.unwrap_err();
2096 assert!(err.to_string().contains("expected string value"));
2097 }
2098
2099 #[test]
2100 fn test_extract_color_success() {
2101 use crate::parser_types::{Attribute, AttributeValue};
2102
2103 let attr = Attribute {
2104 name: Spanned::new("fill_color", Span::new(0..10)),
2105 value: AttributeValue::String(Spanned::new("red".to_string(), Span::new(11..16))),
2106 };
2107
2108 let result = Builder::extract_color(&attr, "fill_color");
2109 assert!(result.is_ok());
2110 }
2111
2112 #[test]
2113 fn test_extract_color_invalid_string() {
2114 use crate::parser_types::{Attribute, AttributeValue};
2115
2116 let attr = Attribute {
2117 name: Spanned::new("fill_color", Span::new(0..10)),
2118 value: AttributeValue::Float(Spanned::new(42.0, Span::new(11..13))),
2119 };
2120
2121 let result = Builder::extract_color(&attr, "fill_color");
2122 assert!(result.is_err());
2123 let err = result.unwrap_err();
2124 assert!(err.to_string().contains("expected string value"));
2125 }
2126
2127 #[test]
2128 fn test_extract_color_invalid_color() {
2129 use crate::parser_types::{Attribute, AttributeValue};
2130
2131 let attr = Attribute {
2132 name: Spanned::new("fill_color", Span::new(0..10)),
2133 value: AttributeValue::String(Spanned::new(
2134 "not-a-color-xyz".to_string(),
2135 Span::new(11..28),
2136 )),
2137 };
2138
2139 let result = Builder::extract_color(&attr, "fill_color");
2140 assert!(result.is_err());
2141 let err = result.unwrap_err();
2142 assert!(err.to_string().contains("invalid fill_color"));
2143 }
2144
2145 #[test]
2146 fn test_extract_positive_float_success() {
2147 use crate::parser_types::{Attribute, AttributeValue};
2148
2149 let attr = Attribute {
2150 name: Spanned::new("width", Span::new(0..5)),
2151 value: AttributeValue::Float(Spanned::new(42.5, Span::new(6..10))),
2152 };
2153
2154 let result = Builder::extract_positive_float(&attr, "width");
2155 assert!(result.is_ok());
2156 assert_eq!(result.unwrap(), 42.5);
2157 }
2158
2159 #[test]
2160 fn test_extract_positive_float_error() {
2161 use crate::parser_types::{Attribute, AttributeValue};
2162
2163 let attr = Attribute {
2164 name: Spanned::new("width", Span::new(0..5)),
2165 value: AttributeValue::String(Spanned::new(
2166 "not a number".to_string(),
2167 Span::new(6..20),
2168 )),
2169 };
2170
2171 let result = Builder::extract_positive_float(&attr, "width");
2172 assert!(result.is_err());
2173 let err = result.unwrap_err();
2174 assert!(err.to_string().contains("expected"));
2175 }
2176
2177 #[test]
2178 fn test_extract_usize_success() {
2179 use crate::parser_types::{Attribute, AttributeValue};
2180
2181 let attr = Attribute {
2182 name: Spanned::new("rounded", Span::new(0..7)),
2183 value: AttributeValue::Float(Spanned::new(10.0, Span::new(8..10))),
2184 };
2185
2186 let result = Builder::extract_usize(&attr, "rounded", "must be a positive number");
2187 assert!(result.is_ok());
2188 assert_eq!(result.unwrap(), 10);
2189 }
2190
2191 #[test]
2192 fn test_extract_usize_error() {
2193 use crate::parser_types::{Attribute, AttributeValue};
2194
2195 let attr = Attribute {
2196 name: Spanned::new("rounded", Span::new(0..7)),
2197 value: AttributeValue::String(Spanned::new(
2198 "not a number".to_string(),
2199 Span::new(8..22),
2200 )),
2201 };
2202
2203 let result = Builder::extract_usize(&attr, "rounded", "must be a positive number");
2204 assert!(result.is_err());
2205 let err = result.unwrap_err();
2206 assert!(err.to_string().contains("expected"));
2207 }
2208
2209 #[test]
2210 fn test_fragment_with_both_text_attributes() {
2211 use crate::parser_types::{Attribute, AttributeValue, TypeSpec};
2212
2213 let mut builder = builder_with_builtins();
2214
2215 let type_spec = TypeSpec {
2217 type_name: Some(Spanned::new(Id::new("Fragment"), Span::new(0..8))),
2218 attributes: vec![
2219 Attribute {
2220 name: Spanned::new("operation_label_text", Span::new(0..4)),
2221 value: AttributeValue::TypeSpec(TypeSpec {
2222 type_name: None,
2223 attributes: vec![Attribute {
2224 name: Spanned::new("font_size", Span::new(0..9)),
2225 value: AttributeValue::Float(Spanned::new(14.0, Span::new(0..2))),
2226 }],
2227 }),
2228 },
2229 Attribute {
2230 name: Spanned::new("section_title_text", Span::new(0..18)),
2231 value: AttributeValue::TypeSpec(TypeSpec {
2232 type_name: None,
2233 attributes: vec![Attribute {
2234 name: Spanned::new("font_size", Span::new(0..9)),
2235 value: AttributeValue::Float(Spanned::new(12.0, Span::new(0..2))),
2236 }],
2237 }),
2238 },
2239 ],
2240 };
2241
2242 let result = builder.build_type_definition(&type_spec);
2243 assert!(
2244 result.is_ok(),
2245 "Failed to build type definition with both operation_label_text and section_title_text: {:?}",
2246 result.err()
2247 );
2248
2249 let type_def = result.unwrap();
2250 match type_def.draw_definition() {
2252 elaborate_utils::DrawDefinition::Fragment(_) => {
2253 }
2255 _ => panic!("Expected Fragment draw definition"),
2256 }
2257 }
2258
2259 #[test]
2260 fn test_embedded_diagram_type_definitions_are_isolated() {
2261 let child_ast = parser_types::FileAst {
2272 header: parser_types::FileHeader::Diagram {
2273 kind: Spanned::new(DiagramKind::Sequence, Span::new(0..8)),
2274 attributes: vec![],
2275 },
2276 import_decls: vec![],
2277 type_definitions: vec![parser_types::TypeDefinition {
2278 name: Spanned::new(Id::new("Database"), Span::new(0..8)),
2279 type_spec: parser_types::TypeSpec {
2280 type_name: Some(Spanned::new(Id::new("Oval"), Span::new(0..4))),
2281 attributes: vec![parser_types::Attribute {
2282 name: Spanned::new("fill_color", Span::new(0..10)),
2283 value: parser_types::AttributeValue::String(Spanned::new(
2284 "#e0f0e0".to_string(),
2285 Span::new(0..7),
2286 )),
2287 }],
2288 },
2289 }],
2290 elements: vec![parser_types::Element::Component {
2291 name: Spanned::new(Id::new("database"), Span::new(0..8)),
2292 display_name: None,
2293 type_spec: parser_types::TypeSpec {
2294 type_name: Some(Spanned::new(Id::new("Database"), Span::new(0..8))),
2295 attributes: vec![],
2296 },
2297 content: parser_types::ComponentContent::None,
2298 }],
2299 imports: vec![],
2300 };
2301
2302 let parent_ast = parser_types::FileAst {
2309 header: parser_types::FileHeader::Diagram {
2310 kind: Spanned::new(DiagramKind::Component, Span::new(0..9)),
2311 attributes: vec![],
2312 },
2313 import_decls: vec![],
2314 type_definitions: vec![parser_types::TypeDefinition {
2315 name: Spanned::new(Id::new("Service"), Span::new(0..7)),
2316 type_spec: parser_types::TypeSpec {
2317 type_name: Some(Spanned::new(Id::new("Rectangle"), Span::new(0..9))),
2318 attributes: vec![parser_types::Attribute {
2319 name: Spanned::new("fill_color", Span::new(0..10)),
2320 value: parser_types::AttributeValue::String(Spanned::new(
2321 "#e6f3ff".to_string(),
2322 Span::new(0..7),
2323 )),
2324 }],
2325 },
2326 }],
2327 elements: vec![
2328 parser_types::Element::Component {
2329 name: Spanned::new(Id::new("gateway"), Span::new(0..7)),
2330 display_name: Some(Spanned::new("API Gateway".to_string(), Span::new(0..11))),
2331 type_spec: parser_types::TypeSpec {
2332 type_name: Some(Spanned::new(Id::new("Service"), Span::new(0..7))),
2333 attributes: vec![],
2334 },
2335 content: parser_types::ComponentContent::None,
2336 },
2337 parser_types::Element::Component {
2338 name: Spanned::new(Id::new("auth_overview"), Span::new(0..13)),
2339 display_name: Some(Spanned::new("Auth Overview".to_string(), Span::new(0..13))),
2340 type_spec: parser_types::TypeSpec {
2341 type_name: Some(Spanned::new(Id::new("Service"), Span::new(0..7))),
2342 attributes: vec![],
2343 },
2344 content: parser_types::ComponentContent::Diagram(
2345 parser_types::DiagramSource::Inline(Rc::new(RefCell::new(child_ast))),
2346 ),
2347 },
2348 parser_types::Element::Relation {
2349 source: Spanned::new(Id::new("gateway"), Span::new(0..7)),
2350 target: Spanned::new(Id::new("auth_overview"), Span::new(0..13)),
2351 relation_type: Spanned::new("->", Span::new(0..2)),
2352 type_spec: parser_types::TypeSpec {
2353 type_name: Some(Spanned::new(Id::new("Arrow"), Span::new(0..5))),
2354 attributes: vec![],
2355 },
2356 label: Some(Spanned::new("Auth detail".to_string(), Span::new(0..11))),
2357 },
2358 ],
2359 imports: vec![],
2360 };
2361
2362 let config = ElaborateConfig::default();
2363 let builder = Builder::new(config);
2364 let result = builder.build(&parent_ast);
2365
2366 assert!(
2367 result.is_ok(),
2368 "Embedded diagram with its own type definitions should build successfully, got: {:?}",
2369 result.err()
2370 );
2371
2372 let diagram = result.unwrap();
2373 assert_eq!(diagram.kind(), DiagramKind::Component);
2374
2375 let elements = diagram.scope().elements();
2377 assert_eq!(
2378 elements.len(),
2379 3,
2380 "Expected gateway, auth_overview, and a relation"
2381 );
2382
2383 if let Element::Node(node) = &elements[1] {
2385 assert!(
2386 matches!(node.block(), Block::Diagram(_)),
2387 "auth_overview should contain an embedded diagram"
2388 );
2389 } else {
2390 panic!("Expected second element to be a Node (auth_overview)");
2391 }
2392 }
2393}