diff --git a/plantuml/parser/integration_test/sequence_diagram/BUILD b/plantuml/parser/integration_test/sequence_diagram/BUILD index de021bf1..600d3f64 100644 --- a/plantuml/parser/integration_test/sequence_diagram/BUILD +++ b/plantuml/parser/integration_test/sequence_diagram/BUILD @@ -10,33 +10,6 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -filegroup( - name = "simple_sequence_test", - srcs = [ - "simple_sequence.json", - "simple_sequence.puml", - ], - visibility = ["//visibility:public"], -) - -filegroup( - name = "comprehensive_sequence_test", - srcs = [ - "comprehensive_sequence_test.json", - "comprehensive_sequence_test.puml", - ], - visibility = ["//visibility:public"], -) - -filegroup( - name = "sequence_diagram_tests", - srcs = [ - ":comprehensive_sequence_test", - ":simple_sequence_test", - ], - visibility = ["//visibility:public"], -) - filegroup( name = "sequence_diagram_files", srcs = glob( diff --git a/plantuml/parser/integration_test/sequence_diagram/participant_identifier_examples/output.json b/plantuml/parser/integration_test/sequence_diagram/participant_identifier_examples/output.json index a5b5e02f..fff2a905 100644 --- a/plantuml/parser/integration_test/sequence_diagram/participant_identifier_examples/output.json +++ b/plantuml/parser/integration_test/sequence_diagram/participant_identifier_examples/output.json @@ -23,8 +23,8 @@ "stereotype": "component" }, { - "display_name": "RightId", - "alias": "LeftId", + "display_name": "LeftId", + "alias": "RightId", "participant_type": "Participant", "source_location": { "file": "", diff --git a/plantuml/parser/integration_test/sequence_diagram/participant_identifier_examples/participant_identifier_examples.puml b/plantuml/parser/integration_test/sequence_diagram/participant_identifier_examples/participant_identifier_examples.puml index 29be3254..b27eb63d 100644 --- a/plantuml/parser/integration_test/sequence_diagram/participant_identifier_examples/participant_identifier_examples.puml +++ b/plantuml/parser/integration_test/sequence_diagram/participant_identifier_examples/participant_identifier_examples.puml @@ -13,19 +13,19 @@ @startuml participant_identifier_examples -' QuotedAsId +' quoted_display_with_alias participant "Quoted As Id" as QuotedAsId <> -' IdAsQuoted +' alias_with_quoted_display participant IdAsQuoted as "Id As Quoted" <> -' IdAsId +' display_with_alias participant LeftId as RightId <> -' Quoted +' quoted_display participant "Quoted Only" <> -' Id +' alias_only participant IdOnly <> QuotedAsId -> IdAsQuoted : callQuotedAsId() diff --git a/plantuml/parser/integration_test/src/test_error_view.rs b/plantuml/parser/integration_test/src/test_error_view.rs index 196b52cd..a11f522f 100644 --- a/plantuml/parser/integration_test/src/test_error_view.rs +++ b/plantuml/parser/integration_test/src/test_error_view.rs @@ -15,7 +15,7 @@ use std::path::Path; use puml_parser::{ ActivityParserError, BaseParseError, ClassError, ComponentError, IncludeExpandError, - IncludeParseError, PreprocessError, ProcedureExpandError, ProcedureParseError, + IncludeParseError, PreprocessError, ProcedureExpandError, ProcedureParseError, SequenceError, }; use puml_resolver::{ ActivityResolverError, ClassPumlResolverError, ComponentResolverError, SequenceResolverError, @@ -223,6 +223,18 @@ impl ErrorView for ComponentError { } } +impl ErrorView for SequenceError { + fn project(&self, base_dir: &Path) -> ProjectedError { + match self { + SequenceError::Base(e) => e.project(base_dir), + SequenceError::InvalidStatement(message) => { + let _ = base_dir; + ProjectedError::new("InvalidStatement").with_field("message", message.to_string()) + } + } + } +} + impl ErrorView for ComponentResolverError { fn project(&self, _base_dir: &Path) -> ProjectedError { match self { diff --git a/plantuml/parser/puml_parser/src/grammar/sequence.pest b/plantuml/parser/puml_parser/src/grammar/sequence.pest index 4ec92bf5..46f0f467 100644 --- a/plantuml/parser/puml_parser/src/grammar/sequence.pest +++ b/plantuml/parser/puml_parser/src/grammar/sequence.pest @@ -106,9 +106,10 @@ stereotype_elem = { // Participant definitions participant_def = { - create_kw? ~ participant_type ~ - (quoted_participant_as_id | participant_id_as_quoted | participant_id_as_id | quoted_participant | participant_id) ~ - color_spec? ~ stereotype? ~ order_clause? + create_kw? + ~ participant_type + ~ participant_identifier + ~ stereotype? ~ order_clause? ~ color_spec? } create_kw = { ^"create" } @@ -117,14 +118,30 @@ participant_type = @{ ^"entity" | ^"queue" | ^"actor") ~ &(" " | "\t" | "\n" | "\r") } -quoted_participant_as_id = { quoted_string ~ alias_clause } -participant_id_as_quoted = { participant_id ~ alias_clause } -participant_id_as_id = { participant_id ~ alias_clause } -quoted_participant = { quoted_string } -participant_id = { CNAME } +participant_identifier = { + quoted_display_with_alias + | display_with_alias + | alias_with_quoted_display + | quoted_display + | alias_only +} -color_spec = @{ BASIC_COLOR } +// participant "Display" as Alias +quoted_display_with_alias = { quoted_string ~ ^"as" ~ NAME } + +// participant Display as Alias +display_with_alias = { NAME ~ ^"as" ~ NAME } + +// participant Alias as "Display" +alias_with_quoted_display = { NAME ~ ^"as" ~ quoted_string } +// participant "Display" +quoted_display = { quoted_string } + +// participant Alias +alias_only = { NAME } + +color_spec = @{ BASIC_COLOR } order_clause = { ^"order" ~ signed_number } signed_number = { "-"? ~ NUMBER } @@ -159,10 +176,11 @@ message = { async_marker = { "&" } message_participant = { lost_found_marker | + quoted_display_with_alias | + display_with_alias | + alias_with_quoted_display | participant_ref | - quoted_string | - quoted_participant_as_id | - participant_id_as_quoted + quoted_string } lost_found_marker = { "[" ~ ("o" | "x")? | ("o" | "x")? ~ ("[" | "]") } participant_ref = { CNAME | quoted_string } diff --git a/plantuml/parser/puml_parser/src/sequence_diagram/BUILD b/plantuml/parser/puml_parser/src/sequence_diagram/BUILD index 3eaad808..47be2567 100644 --- a/plantuml/parser/puml_parser/src/sequence_diagram/BUILD +++ b/plantuml/parser/puml_parser/src/sequence_diagram/BUILD @@ -10,7 +10,7 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test") +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") rust_library( name = "puml_parser_sequence", @@ -46,14 +46,17 @@ rust_test( ], ) -# Job 1: Parse PUML file with syntax parser, output JSON -rust_binary( - name = "syntax_parse", - srcs = ["src/syntax_parse_main.rs"], - visibility = ["//visibility:public"], +rust_test( + name = "sequence_integration_test", + srcs = ["test/sequence_integration_test.rs"], + args = [ + "--nocapture", + ], + data = ["//plantuml/parser/puml_parser/tests/sequence_diagram:sequence_diagram_files"], deps = [ ":puml_parser_sequence", - "@crates//:serde", - "@crates//:serde_json", + "//plantuml/parser/integration_test:test_framework", + "//plantuml/parser/puml_parser:parser_core", + "//plantuml/parser/puml_utils", ], ) diff --git a/plantuml/parser/puml_parser/src/sequence_diagram/src/lib.rs b/plantuml/parser/puml_parser/src/sequence_diagram/src/lib.rs index 63181803..d53a69e2 100644 --- a/plantuml/parser/puml_parser/src/sequence_diagram/src/lib.rs +++ b/plantuml/parser/puml_parser/src/sequence_diagram/src/lib.rs @@ -11,16 +11,16 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -pub mod syntax_ast; -mod syntax_parser; +pub mod sequence_ast; +mod sequence_parser; -pub use syntax_ast::{ +pub use sequence_ast::{ ActivateCmd, Arrow, CreateCmd, DeactivateCmd, DestroyCmd, ExternalEndpoint, GroupCmd, GroupType, Message, MessageContent, ParticipantDef, ParticipantIdentifier, ParticipantType, SeqPumlDocument, Statement, }; -pub use syntax_parser::{PumlSequenceParser, SequenceError}; +pub use sequence_parser::{PumlSequenceParser, SequenceError}; /// Parse a PlantUML sequence diagram and return the document name and statements /// This is a convenience function for backwards compatibility with tests diff --git a/plantuml/parser/puml_parser/src/sequence_diagram/src/syntax_ast.rs b/plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_ast.rs similarity index 66% rename from plantuml/parser/puml_parser/src/sequence_diagram/src/syntax_ast.rs rename to plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_ast.rs index 7a5e362c..70cc2682 100644 --- a/plantuml/parser/puml_parser/src/sequence_diagram/src/syntax_ast.rs +++ b/plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_ast.rs @@ -19,14 +19,14 @@ use std::str::FromStr; pub use parser_core::common_ast::Arrow; // Document structure representing a complete PlantUML sequence diagram -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct SeqPumlDocument { pub name: Option, pub statements: Vec, } // Statement types used during parsing -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Statement { DestroyCmd(DestroyCmd), CreateCmd(CreateCmd), @@ -38,20 +38,16 @@ pub enum Statement { } // Participant definitions -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ParticipantDef { + #[serde(default)] + pub is_create: bool, pub participant_type: ParticipantType, pub identifier: ParticipantIdentifier, pub stereotype: Option, pub source_location: SourceLocation, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct ParticipantDisplay { - pub display_name: String, - pub alias: Option, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct ExternalEndpoint; @@ -84,66 +80,36 @@ pub enum ParticipantType { Collections, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ParticipantIdentifier { - QuotedAsId { quoted: String, id: String }, - IdAsQuoted { id: String, quoted: String }, - IdAsId { id1: String, id2: String }, - Quoted(String), - Id(String), -} - -impl ParticipantIdentifier { - pub fn display(&self) -> ParticipantDisplay { - match self { - ParticipantIdentifier::QuotedAsId { quoted, id } => ParticipantDisplay { - display_name: quoted.clone(), - alias: Some(id.clone()), - }, - ParticipantIdentifier::IdAsQuoted { id, quoted } => ParticipantDisplay { - display_name: quoted.clone(), - alias: Some(id.clone()), - }, - ParticipantIdentifier::IdAsId { id1, id2 } => ParticipantDisplay { - display_name: id1.clone(), - alias: Some(id2.clone()), - }, - ParticipantIdentifier::Quoted(quoted) => ParticipantDisplay { - display_name: quoted.clone(), - alias: None, - }, - ParticipantIdentifier::Id(id) => ParticipantDisplay { - display_name: id.clone(), - alias: None, - }, - } - } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ParticipantIdentifier { + pub display_name: String, + pub alias: Option, } // Destroy/Create commands -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct DestroyCmd { pub participant: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct CreateCmd { pub participant: String, } // Activate/Deactivate commands -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ActivateCmd { pub participant: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct DeactivateCmd { pub participant: Option, } // Messages (internal parsing structure) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Message { pub content: MessageContent, #[serde(skip_serializing_if = "Option::is_none")] @@ -152,7 +118,7 @@ pub struct Message { pub source_location: SourceLocation, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum MessageContent { WithTargets { left: String, @@ -168,7 +134,7 @@ pub enum ActivationType { } // Group commands (alt, opt, loop, etc.) - internal parsing structure -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct GroupCmd { pub group_type: GroupType, pub text: Option, diff --git a/plantuml/parser/puml_parser/src/sequence_diagram/src/syntax_parser.rs b/plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_parser.rs similarity index 78% rename from plantuml/parser/puml_parser/src/sequence_diagram/src/syntax_parser.rs rename to plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_parser.rs index 864b4d25..bab79d0d 100644 --- a/plantuml/parser/puml_parser/src/sequence_diagram/src/syntax_parser.rs +++ b/plantuml/parser/puml_parser/src/sequence_diagram/src/sequence_parser.rs @@ -10,7 +10,7 @@ // // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -use log::{debug, trace}; +use log::{debug, trace, warn}; use parser_core::common_parser::parse_arrow as common_parse_arrow; use parser_core::common_parser::{PlantUmlCommonParser, Rule}; use parser_core::{ @@ -22,7 +22,7 @@ use std::path::PathBuf; use std::rc::Rc; use thiserror::Error; -use crate::syntax_ast::*; +use crate::sequence_ast::*; #[derive(Debug, Error)] pub enum SequenceError { @@ -96,6 +96,7 @@ impl PumlSequenceParser { pair: pest::iterators::Pair, source_location: SourceLocation, ) -> Result { + let mut is_create = false; let mut participant_type: Option = None; let mut identifier: Option = None; let mut stereotype: Option = None; @@ -103,78 +104,13 @@ impl PumlSequenceParser { for inner in pair.into_inner() { match inner.as_rule() { Rule::create_kw => { - // Handle create keyword if needed + is_create = true; } Rule::participant_type => { - participant_type = Self::parse_participant_type(inner); + participant_type = Some(Self::parse_participant_type(inner)?); } - Rule::quoted_participant_as_id => { - let mut parts = inner.into_inner(); - let quoted = parts - .next() - .map(|p| Self::extract_quoted_string(p.as_str())) - .ok_or_else(|| { - SequenceError::InvalidStatement( - "missing quoted participant".to_string(), - ) - })?; - let alias_clause = parts.next().ok_or_else(|| { - SequenceError::InvalidStatement("missing alias clause".to_string()) - })?; - let id_pair = alias_clause.into_inner().next().ok_or_else(|| { - SequenceError::InvalidStatement("missing alias id".to_string()) - })?; - let id = match id_pair.as_rule() { - Rule::quoted_string => Self::extract_quoted_string(id_pair.as_str()), - _ => id_pair.as_str().trim().to_string(), - }; - identifier = Some(ParticipantIdentifier::QuotedAsId { quoted, id }); - } - Rule::participant_id_as_quoted => { - let mut parts = inner.into_inner(); - let id = parts - .next() - .ok_or_else(|| { - SequenceError::InvalidStatement("missing participant id".to_string()) - })? - .as_str() - .trim() - .to_string(); - let alias_clause = parts.next().ok_or_else(|| { - SequenceError::InvalidStatement("missing alias clause".to_string()) - })?; - let quoted_pair = alias_clause.into_inner().next().ok_or_else(|| { - SequenceError::InvalidStatement("missing quoted alias".to_string()) - })?; - let quoted = Self::extract_quoted_string(quoted_pair.as_str()); - identifier = Some(ParticipantIdentifier::IdAsQuoted { id, quoted }); - } - Rule::participant_id_as_id => { - let mut parts = inner.into_inner(); - let id1 = parts - .next() - .ok_or_else(|| { - SequenceError::InvalidStatement("missing participant id1".to_string()) - })? - .as_str() - .trim() - .to_string(); - let alias_clause = parts.next().ok_or_else(|| { - SequenceError::InvalidStatement("missing alias clause".to_string()) - })?; - let id2_pair = alias_clause.into_inner().next().ok_or_else(|| { - SequenceError::InvalidStatement("missing alias id2".to_string()) - })?; - let id2 = id2_pair.as_str().trim().to_string(); - identifier = Some(ParticipantIdentifier::IdAsId { id1, id2 }); - } - Rule::quoted_participant => { - let quoted = Self::extract_quoted_string(inner.as_str()); - identifier = Some(ParticipantIdentifier::Quoted(quoted)); - } - Rule::participant_id => { - let id = inner.as_str().trim().to_string(); - identifier = Some(ParticipantIdentifier::Id(id)); + Rule::participant_identifier => { + identifier = Some(Self::parse_participant_identifier(inner)?); } Rule::stereotype => { stereotype = Some(Self::extract_stereotype(inner.as_str())); @@ -187,6 +123,7 @@ impl PumlSequenceParser { } Ok(ParticipantDef { + is_create, participant_type: participant_type.ok_or_else(|| { SequenceError::InvalidStatement("missing participant type".to_string()) })?, @@ -198,19 +135,108 @@ impl PumlSequenceParser { }) } - fn parse_participant_type(pair: pest::iterators::Pair) -> Option { + fn parse_participant_identifier( + pair: pest::iterators::Pair, + ) -> Result { + let participant = pair + .into_inner() + .next() + .expect("participant_identifier must contain a participant identifier"); + let participant_rule = participant.as_rule(); + + Ok(match participant_rule { + Rule::quoted_display_with_alias => { + match Self::participant_parts(participant).as_slice() { + [display_name, alias] => ParticipantIdentifier { + display_name: Self::extract_quoted_string(display_name), + alias: Some(alias.to_string()), + }, + _ => { + return Self::invalid_participant_identifier( + "quoted_display_with_alias grammar shape changed", + ); + } + } + } + Rule::display_with_alias => match Self::participant_parts(participant).as_slice() { + [display_name, alias] => ParticipantIdentifier { + display_name: display_name.to_string(), + alias: Some(alias.to_string()), + }, + _ => { + return Self::invalid_participant_identifier( + "display_with_alias grammar shape changed", + ); + } + }, + Rule::alias_with_quoted_display => { + match Self::participant_parts(participant).as_slice() { + [alias, display_name] => ParticipantIdentifier { + display_name: Self::extract_quoted_string(display_name), + alias: Some(alias.to_string()), + }, + _ => { + return Self::invalid_participant_identifier( + "alias_with_quoted_display grammar shape changed", + ); + } + } + } + Rule::quoted_display => ParticipantIdentifier { + display_name: Self::extract_quoted_string(participant.as_str()), + alias: None, + }, + Rule::alias_only => ParticipantIdentifier { + display_name: participant.as_str().trim().to_string(), + alias: None, + }, + _ => { + warn!( + "participant_identifier grammar produced unsupported value: {:?}", + participant_rule + ); + return Err(SequenceError::InvalidStatement(format!( + "unsupported participant_identifier grammar value: {:?}", + participant_rule + ))); + } + }) + } + + fn invalid_participant_identifier( + reason: &str, + ) -> Result { + warn!("{reason}"); + Err(SequenceError::InvalidStatement(reason.to_string())) + } + + fn participant_parts(participant: pest::iterators::Pair) -> Vec { + participant + .into_inner() + .map(|part| part.as_str().trim().to_string()) + .collect() + } + + fn parse_participant_type( + pair: pest::iterators::Pair, + ) -> Result { let text = pair.as_str().to_lowercase(); - match text.as_str() { - "participant" => Some(ParticipantType::Participant), - "actor" => Some(ParticipantType::Actor), - "boundary" => Some(ParticipantType::Boundary), - "control" => Some(ParticipantType::Control), - "entity" => Some(ParticipantType::Entity), - "queue" => Some(ParticipantType::Queue), - "database" => Some(ParticipantType::Database), - "collections" => Some(ParticipantType::Collections), - _ => None, - } + Ok(match text.as_str() { + "participant" => ParticipantType::Participant, + "actor" => ParticipantType::Actor, + "boundary" => ParticipantType::Boundary, + "control" => ParticipantType::Control, + "entity" => ParticipantType::Entity, + "queue" => ParticipantType::Queue, + "database" => ParticipantType::Database, + "collections" => ParticipantType::Collections, + _ => { + warn!("participant_type grammar produced unsupported value: {text}"); + return Err(SequenceError::InvalidStatement(format!( + "unsupported participant type: {text}" + ))); + } + }) } fn parse_message( @@ -383,8 +409,6 @@ impl PumlSequenceParser { s.trim() .trim_start_matches('"') .trim_end_matches('"') - .trim_start_matches('«') - .trim_end_matches('»') .to_string() } @@ -428,22 +452,17 @@ impl PumlSequenceParser { Rule::CNAME => Self::normalize_participant_name(pair.as_str()), - Rule::quoted_participant_as_id - | Rule::participant_id_as_quoted - | Rule::participant_id_as_id => { - let mut inner = pair.into_inner(); - - inner.next(); // skip lhs - - let alias_clause = inner.next().unwrap(); - - let target = alias_clause.into_inner().next().unwrap(); + Rule::quoted_display_with_alias | Rule::display_with_alias => pair + .into_inner() + .nth(1) + .map(|p| p.as_str().trim().to_string()) + .unwrap_or_default(), - match target.as_rule() { - Rule::quoted_string => Self::extract_quoted_string(target.as_str()), - _ => target.as_str().trim().to_string(), - } - } + Rule::alias_with_quoted_display => pair + .into_inner() + .next() + .map(|p| p.as_str().trim().to_string()) + .unwrap_or_default(), _ => pair.as_str().trim().to_string(), } diff --git a/plantuml/parser/puml_parser/src/sequence_diagram/src/syntax_parse_main.rs b/plantuml/parser/puml_parser/src/sequence_diagram/src/syntax_parse_main.rs deleted file mode 100644 index 43943b8f..00000000 --- a/plantuml/parser/puml_parser/src/sequence_diagram/src/syntax_parse_main.rs +++ /dev/null @@ -1,69 +0,0 @@ -// ******************************************************************************* -// Copyright (c) 2026 Contributors to the Eclipse Foundation -// -// See the NOTICE file(s) distributed with this work for additional -// information regarding copyright ownership. -// -// This program and the accompanying materials are made available under the -// terms of the Apache License Version 2.0 which is available at -// -// -// SPDX-License-Identifier: Apache-2.0 -// ******************************************************************************* - -//! Syntax parser job: Parse PUML file and output JSON - -use sequence_parser::parse_sequence_diagram; -use std::env; -use std::fs; - -fn main() { - let args: Vec = env::args().collect(); - - if args.len() < 3 { - eprintln!("Usage: {} ", args[0]); - std::process::exit(1); - } - - let input_file = &args[1]; - let output_file = &args[2]; - - // Read the PUML file - let puml_content = match fs::read_to_string(input_file) { - Ok(content) => content, - Err(e) => { - eprintln!("Error reading file '{}': {}", input_file, e); - std::process::exit(1); - } - }; - - // Parse the sequence diagram - let (_doc_name, statements) = match parse_sequence_diagram(&puml_content) { - Ok(result) => result, - Err(e) => { - eprintln!("Error parsing sequence diagram: {}", e); - std::process::exit(1); - } - }; - - // Serialize to JSON (use IDs as-is, don't translate to quoted names) - let json = match serde_json::to_string_pretty(&statements) { - Ok(json) => json, - Err(e) => { - eprintln!("Error serializing to JSON: {}", e); - std::process::exit(1); - } - }; - - // Write to output file - if let Err(e) = fs::write(output_file, json) { - eprintln!("Error writing to '{}': {}", output_file, e); - std::process::exit(1); - } - - println!( - "✓ Syntax parse complete: {} statements → {}", - statements.len(), - output_file - ); -} diff --git a/plantuml/parser/puml_parser/src/sequence_diagram/test/sequence_integration_test.rs b/plantuml/parser/puml_parser/src/sequence_diagram/test/sequence_integration_test.rs new file mode 100644 index 00000000..971a1b88 --- /dev/null +++ b/plantuml/parser/puml_parser/src/sequence_diagram/test/sequence_integration_test.rs @@ -0,0 +1,67 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* +use std::collections::{HashMap, HashSet}; +use std::fs; +use std::path::PathBuf; +use std::rc::Rc; + +use parser_core::{BaseParseError, DiagramParser}; +use puml_utils::LogLevel; +use sequence_parser::{PumlSequenceParser, SeqPumlDocument, SequenceError}; +use test_framework::{run_case, DefaultExpectationChecker, DiagramProcessor}; + +const TEST_MODULE: &str = "puml_parser/tests/sequence_diagram"; + +struct SequenceRunner; + +impl DiagramProcessor for SequenceRunner { + type Output = SeqPumlDocument; + type Error = SequenceError; + + fn run( + &self, + files: &HashSet>, + ) -> Result, SeqPumlDocument>, SequenceError> { + let mut results = HashMap::new(); + let mut parser = PumlSequenceParser; + + for puml_path in files { + let content = fs::read_to_string(&**puml_path).map_err(|e| { + SequenceError::Base(BaseParseError::IoError { + path: puml_path.as_ref().to_path_buf(), + error: Box::new(e), + }) + })?; + + let sequence_ast = parser.parse_file(puml_path, &content, LogLevel::Error)?; + + results.insert(Rc::clone(puml_path), sequence_ast); + } + + Ok(results) + } +} + +fn run_sequence_diagram_parser_case(case_name: &str) { + run_case( + TEST_MODULE, + case_name, + SequenceRunner, + DefaultExpectationChecker, + ); +} + +#[test] +fn test_participant_identifiers() { + run_sequence_diagram_parser_case("participant_identifiers"); +} diff --git a/plantuml/parser/puml_parser/src/sequence_diagram/tests/syntax_parse_test.rs b/plantuml/parser/puml_parser/src/sequence_diagram/tests/syntax_parse_test.rs deleted file mode 100644 index b39627c2..00000000 --- a/plantuml/parser/puml_parser/src/sequence_diagram/tests/syntax_parse_test.rs +++ /dev/null @@ -1,79 +0,0 @@ -// ******************************************************************************* -// Copyright (c) 2026 Contributors to the Eclipse Foundation -// -// See the NOTICE file(s) distributed with this work for additional -// information regarding copyright ownership. -// -// This program and the accompanying materials are made available under the -// terms of the Apache License Version 2.0 which is available at -// -// -// SPDX-License-Identifier: Apache-2.0 -// ******************************************************************************* - -//! Syntax parser test suite: Compare parsed output with expected JSON for each test pair - -use parser_core::DiagramParser; -use puml_utils::LogLevel; -use sequence_parser::PumlSequenceParser; -use std::fs; -use std::path::PathBuf; -use std::rc::Rc; - -fn test_file_pair(puml_file: &str, json_file: &str) { - // Read and parse the PUML file - let puml_content = fs::read_to_string(puml_file) - .unwrap_or_else(|e| panic!("Error reading input file '{}': {}", puml_file, e)); - - // Use DiagramParser trait directly - let mut parser = PumlSequenceParser; - let path = Rc::new(PathBuf::from(puml_file)); - let document = parser - .parse_file(&path, &puml_content, LogLevel::Error) - .unwrap_or_else(|e| panic!("Error parsing sequence diagram '{}': {}", puml_file, e)); - - // Serialize parsed statements to JSON (not the full document with name) - let actual_json = serde_json::to_string_pretty(&document.statements) - .expect("Error serializing parsed result to JSON"); - - // Read expected JSON - let expected_json = fs::read_to_string(json_file) - .unwrap_or_else(|e| panic!("Error reading expected file '{}': {}", json_file, e)); - - // Parse both JSONs to normalize formatting - let actual_value: serde_json::Value = - serde_json::from_str(&actual_json).expect("Error parsing actual JSON"); - - let expected_value: serde_json::Value = - serde_json::from_str(&expected_json).expect("Error parsing expected JSON"); - - // Compare the values - if actual_value != expected_value { - eprintln!( - "\nExpected JSON:\n{}", - serde_json::to_string_pretty(&expected_value).unwrap() - ); - eprintln!( - "\nActual JSON:\n{}", - serde_json::to_string_pretty(&actual_value).unwrap() - ); - - panic!("Parsed output does not match expected JSON"); - } -} - -#[test] -fn test_comprehensive_sequence() { - test_file_pair( - "plantuml/parser/integration_test/sequence_diagram/comprehensive_sequence_test.puml", - "plantuml/parser/integration_test/sequence_diagram/comprehensive_sequence_test.json", - ); -} - -#[test] -fn test_simple_sequence() { - test_file_pair( - "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "plantuml/parser/integration_test/sequence_diagram/simple_sequence.json", - ); -} diff --git a/plantuml/parser/puml_parser/src/sequence_diagram/tests/BUILD b/plantuml/parser/puml_parser/tests/sequence_diagram/BUILD similarity index 51% rename from plantuml/parser/puml_parser/src/sequence_diagram/tests/BUILD rename to plantuml/parser/puml_parser/tests/sequence_diagram/BUILD index 9b655c02..5b4169f2 100644 --- a/plantuml/parser/puml_parser/src/sequence_diagram/tests/BUILD +++ b/plantuml/parser/puml_parser/tests/sequence_diagram/BUILD @@ -10,20 +10,11 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("@rules_rust//rust:defs.bzl", "rust_test") - -# Test suite: Compare parsed output with expected JSON for each test pair -rust_test( - name = "syntax_parse_test", - srcs = ["syntax_parse_test.rs"], - data = [ - "//plantuml/parser/integration_test/sequence_diagram:sequence_diagram_tests", - ], - deps = [ - "//plantuml/parser/puml_parser:parser_core", - "//plantuml/parser/puml_parser:sequence_diagram", - "//plantuml/parser/puml_utils", - "@crates//:serde", - "@crates//:serde_json", - ], +filegroup( + name = "sequence_diagram_files", + srcs = glob([ + "**/*.json", + "**/*.puml", + ]), + visibility = ["//plantuml/parser:__subpackages__"], ) diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/output.json b/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/output.json new file mode 100644 index 00000000..beaab559 --- /dev/null +++ b/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/output.json @@ -0,0 +1,106 @@ +{ + "participant_identifiers.puml": { + "name": "participant_identifiers", + "statements": [ + { + "ParticipantDef": { + "participant_type": "Actor", + "identifier": { + "display_name": "External User", + "alias": "User" + }, + "stereotype": null, + "source_location": { + "file": "", + "line": 16 + } + } + }, + { + "ParticipantDef": { + "participant_type": "Participant", + "identifier": { + "display_name": "Sample Service", + "alias": "Service" + }, + "stereotype": null, + "source_location": { + "file": "", + "line": 17 + } + } + }, + { + "ParticipantDef": { + "participant_type": "Boundary", + "identifier": { + "display_name": "Gateway", + "alias": "GatewayAlias" + }, + "stereotype": null, + "source_location": { + "file": "", + "line": 18 + } + } + }, + { + "ParticipantDef": { + "participant_type": "Control", + "identifier": { + "display_name": "Controller", + "alias": null + }, + "stereotype": null, + "source_location": { + "file": "", + "line": 19 + } + } + }, + { + "ParticipantDef": { + "participant_type": "Queue", + "identifier": { + "display_name": "Work Queue", + "alias": null + }, + "stereotype": null, + "source_location": { + "file": "", + "line": 20 + } + } + }, + { + "ParticipantDef": { + "is_create": true, + "participant_type": "Participant", + "identifier": { + "display_name": "Created Worker", + "alias": "Worker" + }, + "stereotype": null, + "source_location": { + "file": "", + "line": 23 + } + } + }, + { + "ParticipantDef": { + "participant_type": "Participant", + "identifier": { + "display_name": "LastOrder", + "alias": null + }, + "stereotype": "component", + "source_location": { + "file": "", + "line": 26 + } + } + } + ] + } +} diff --git a/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/participant_identifiers.puml b/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/participant_identifiers.puml new file mode 100644 index 00000000..aa5397d2 --- /dev/null +++ b/plantuml/parser/puml_parser/tests/sequence_diagram/participant_identifiers/participant_identifiers.puml @@ -0,0 +1,28 @@ +' ******************************************************************************* +' Copyright (c) 2026 Contributors to the Eclipse Foundation +' +' See the NOTICE file(s) distributed with this work for additional +' information regarding copyright ownership. +' +' This program and the accompanying materials are made available under the +' terms of the Apache License Version 2.0 which is available at +' https://www.apache.org/licenses/LICENSE-2.0 +' +' SPDX-License-Identifier: Apache-2.0 +' ******************************************************************************* +@startuml participant_identifiers + +' normal participant declaration +actor "External User" as User +participant Service as "Sample Service" +boundary Gateway as GatewayAlias +control Controller +queue "Work Queue" + +' create participant declaration +create participant "Created Worker" as Worker + +' participant declaration with stereotype and order +participant LastOrder <> order 1 + +@enduml diff --git a/plantuml/parser/puml_resolver/src/sequence_diagram/BUILD b/plantuml/parser/puml_resolver/src/sequence_diagram/BUILD index 8f69092c..038b6306 100644 --- a/plantuml/parser/puml_resolver/src/sequence_diagram/BUILD +++ b/plantuml/parser/puml_resolver/src/sequence_diagram/BUILD @@ -51,23 +51,6 @@ rust_binary( ], ) -# Test: Compare logic_parse output with expected JSON -rust_test( - name = "logic_parse_test", - srcs = ["test/logic_parse_test.rs"], - data = [ - "test/logic.json", - "//plantuml/parser/integration_test/sequence_diagram:simple_sequence_test", - ], - deps = [ - ":puml_resolver_sequence", - "//plantuml/parser/puml_parser:sequence_diagram", - "//tools/metamodel/sequence:sequence_diagram", - "@crates//:serde", - "@crates//:serde_json", - ], -) - rust_test( name = "sequence_resolver_it", srcs = ["tests/sequence_resolver_test.rs"], diff --git a/plantuml/parser/puml_resolver/src/sequence_diagram/src/logic_parse_main.rs b/plantuml/parser/puml_resolver/src/sequence_diagram/src/logic_parse_main.rs index 25e680e3..7db33158 100644 --- a/plantuml/parser/puml_resolver/src/sequence_diagram/src/logic_parse_main.rs +++ b/plantuml/parser/puml_resolver/src/sequence_diagram/src/logic_parse_main.rs @@ -13,7 +13,7 @@ //! Logic parser job: Build hierarchical tree from syntax JSON -use sequence_parser::syntax_ast::Statement; +use sequence_parser::sequence_ast::Statement; use sequence_resolver::logic_parser::build_tree; use std::env; diff --git a/plantuml/parser/puml_resolver/src/sequence_diagram/src/sequence_resolver.rs b/plantuml/parser/puml_resolver/src/sequence_diagram/src/sequence_resolver.rs index 8b3169a5..ff7b38ae 100644 --- a/plantuml/parser/puml_resolver/src/sequence_diagram/src/sequence_resolver.rs +++ b/plantuml/parser/puml_resolver/src/sequence_diagram/src/sequence_resolver.rs @@ -12,11 +12,10 @@ // ******************************************************************************* use crate::logic_parser::build_tree; -use log::warn; use resolver_traits::DiagramResolver; use sequence_logic::{ParticipantType as LogicParticipantType, SequenceParticipant, SequenceTree}; -use sequence_parser::syntax_ast::{ - ExternalEndpoint, MessageContent, ParticipantDef, ParticipantDisplay, +use sequence_parser::sequence_ast::{ + ExternalEndpoint, MessageContent, ParticipantIdentifier, ParticipantType as SyntaxParticipantType, Statement, }; use sequence_parser::SeqPumlDocument; @@ -66,19 +65,11 @@ fn is_special_endpoint_marker(name: &str) -> bool { name.parse::().is_ok() } -fn participant_from_def(def: &ParticipantDef, display: ParticipantDisplay) -> SequenceParticipant { - warn!( - "resolving sequence participant: display_name='{}', alias={:?}", - display.display_name, display.alias - ); - - SequenceParticipant { - display_name: display.display_name, - alias: display.alias, - participant_type: map_parser_participant_type(&def.participant_type), - source_location: def.source_location.clone(), - stereotype: def.stereotype.clone(), - } +fn participant_reference_name(identifier: &ParticipantIdentifier) -> &str { + identifier + .alias + .as_deref() + .unwrap_or(&identifier.display_name) } impl DiagramResolver for SequenceResolver { @@ -92,18 +83,19 @@ impl DiagramResolver for SequenceResolver { let mut participants = Vec::new(); for stmt in &document.statements { if let Statement::ParticipantDef(p) = stmt { - let display = p.identifier.display(); - declared.insert(display.display_name.clone()); - if let Some(alias) = display.alias.clone() { - declared.insert(alias); - } - participants.push(participant_from_def(p, display)); + declared.insert(participant_reference_name(&p.identifier).to_string()); + participants.push(SequenceParticipant { + display_name: p.identifier.display_name.clone(), + alias: p.identifier.alias.clone(), + participant_type: map_parser_participant_type(&p.participant_type), + source_location: p.source_location.clone(), + stereotype: p.stereotype.clone(), + }); } } // 2. Validate message targets only when participants are declared. if !declared.is_empty() { - warn!("no validation is performed"); for stmt in &document.statements { if let Statement::Message(msg) = stmt { let MessageContent::WithTargets { left, right, .. } = &msg.content; @@ -145,7 +137,7 @@ mod sequence_resolver_tests { use parser_core::common_ast::{Arrow, ArrowDecor, ArrowLine}; use resolver_traits::DiagramResolver; use sequence_logic::SourceLocation; - use sequence_parser::syntax_ast::{ + use sequence_parser::sequence_ast::{ Message, MessageContent, ParticipantDef, ParticipantIdentifier, ParticipantType as SyntaxParticipantType, Statement, }; @@ -268,8 +260,25 @@ mod sequence_resolver_tests { fn make_participant(name: &str) -> Statement { Statement::ParticipantDef(ParticipantDef { + is_create: false, + participant_type: SyntaxParticipantType::Participant, + identifier: ParticipantIdentifier { + display_name: name.to_string(), + alias: None, + }, + stereotype: None, + source_location: dummy_source_location(), + }) + } + + fn make_participant_with_alias(display_name: &str, alias: &str) -> Statement { + Statement::ParticipantDef(ParticipantDef { + is_create: false, participant_type: SyntaxParticipantType::Participant, - identifier: ParticipantIdentifier::Id(name.to_string()), + identifier: ParticipantIdentifier { + display_name: display_name.to_string(), + alias: Some(alias.to_string()), + }, stereotype: None, source_location: dummy_source_location(), }) @@ -292,6 +301,39 @@ mod sequence_resolver_tests { assert!(resolver.resolve(&doc).is_ok()); } + #[test] + fn test_aliased_participant_reference_passes_validation() { + let stmts = vec![ + make_participant("A"), + make_participant_with_alias("Display B", "B"), + make_call("A", "B", "doWork"), + ]; + let mut resolver = SequenceResolver; + let doc = SeqPumlDocument { + name: Some("valid_alias".to_string()), + statements: stmts, + }; + assert!(resolver.resolve(&doc).is_ok()); + } + + #[test] + fn test_aliased_participant_display_name_reference_raises_error() { + let stmts = vec![ + make_participant("A"), + make_participant_with_alias("Display B", "B"), + make_call("A", "Display B", "doWork"), + ]; + let mut resolver = SequenceResolver; + let doc = SeqPumlDocument { + name: Some("invalid_display_reference".to_string()), + statements: stmts, + }; + let err = resolver.resolve(&doc).unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("Display B")); + assert!(msg.contains("callee")); + } + /// An undeclared callee should cause an error. #[test] fn test_undeclared_callee_raises_error() { diff --git a/plantuml/parser/puml_resolver/src/sequence_diagram/test/logic.json b/plantuml/parser/puml_resolver/src/sequence_diagram/test/logic.json deleted file mode 100644 index 5289ec01..00000000 --- a/plantuml/parser/puml_resolver/src/sequence_diagram/test/logic.json +++ /dev/null @@ -1,360 +0,0 @@ -[ - { - "event": { - "Interaction": { - "caller": "ComponentA", - "callee": "ComponentB", - "method": "callMethod1()" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 20 - }, - "branches_node": [ - { - "event": { - "Condition": { - "condition_type": "Alt", - "condition_value": "condition1" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 21 - }, - "branches_node": [ - { - "event": { - "Return": { - "caller": "ComponentA", - "callee": "ComponentB", - "return_content": "Return Result" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 22 - }, - "branches_node": [] - } - ] - }, - { - "event": { - "Condition": { - "condition_type": "Else", - "condition_value": "" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 23 - }, - "branches_node": [ - { - "event": { - "Interaction": { - "caller": "ComponentB", - "callee": "ComponentC", - "method": "method2(1)" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 24 - }, - "branches_node": [ - { - "event": { - "Return": { - "caller": "ComponentB", - "callee": "ComponentC", - "return_content": "Return Result" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 25 - }, - "branches_node": [] - } - ] - }, - { - "event": { - "Condition": { - "condition_type": "Loop", - "condition_value": "for i = 0; i < 3; ++i" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 27 - }, - "branches_node": [ - { - "event": { - "Interaction": { - "caller": "ComponentB", - "callee": "ComponentC", - "method": "method2(i)" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 28 - }, - "branches_node": [ - { - "event": { - "Return": { - "caller": "ComponentB", - "callee": "ComponentC", - "return_content": "Return Result" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 29 - }, - "branches_node": [] - } - ] - }, - { - "event": { - "Condition": { - "condition_type": "Alt", - "condition_value": "innerConditionA" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 30 - }, - "branches_node": [ - { - "event": { - "Interaction": { - "caller": "ComponentB", - "callee": "ComponentC", - "method": "method2(extra)" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 31 - }, - "branches_node": [ - { - "event": { - "Return": { - "caller": "ComponentB", - "callee": "ComponentC", - "return_content": "Return Result" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 32 - }, - "branches_node": [] - } - ] - } - ] - } - ] - }, - { - "event": { - "Condition": { - "condition_type": "Loop", - "condition_value": "while count > 0" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 36 - }, - "branches_node": [ - { - "event": { - "Interaction": { - "caller": "ComponentB", - "callee": "ComponentC", - "method": "method2(count)" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 37 - }, - "branches_node": [ - { - "event": { - "Return": { - "caller": "ComponentB", - "callee": "ComponentC", - "return_content": "Return Result" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 38 - }, - "branches_node": [] - } - ] - }, - { - "event": { - "Condition": { - "condition_type": "Alt", - "condition_value": "innerConditionB" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 39 - }, - "branches_node": [ - { - "event": { - "Interaction": { - "caller": "ComponentB", - "callee": "ComponentC", - "method": "method2(fallback)" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 40 - }, - "branches_node": [ - { - "event": { - "Return": { - "caller": "ComponentB", - "callee": "ComponentC", - "return_content": "Return Result" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 41 - }, - "branches_node": [] - } - ] - } - ] - }, - { - "event": { - "Condition": { - "condition_type": "Else", - "condition_value": "" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 42 - }, - "branches_node": [ - { - "event": { - "Interaction": { - "caller": "ComponentB", - "callee": "ComponentC", - "method": "method2(default)" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 43 - }, - "branches_node": [ - { - "event": { - "Return": { - "caller": "ComponentB", - "callee": "ComponentC", - "return_content": "Return Result" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 44 - }, - "branches_node": [] - } - ] - } - ] - } - ] - }, - { - "event": { - "Condition": { - "condition_type": "Alt", - "condition_value": "condition2" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 48 - }, - "branches_node": [ - { - "event": { - "Interaction": { - "caller": "ComponentB", - "callee": "ComponentC", - "method": "method2(result)" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 49 - }, - "branches_node": [ - { - "event": { - "Return": { - "caller": "ComponentB", - "callee": "ComponentC", - "return_content": "Return Result" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 50 - }, - "branches_node": [] - } - ] - } - ] - }, - { - "event": { - "Return": { - "caller": "ComponentA", - "callee": "ComponentB", - "return_content": "Return Result" - } - }, - "source_location": { - "file": "plantuml/parser/integration_test/sequence_diagram/simple_sequence.puml", - "line": 53 - }, - "branches_node": [] - } - ] - } - ] - } -] diff --git a/plantuml/parser/puml_resolver/src/sequence_diagram/test/logic_parse_test.rs b/plantuml/parser/puml_resolver/src/sequence_diagram/test/logic_parse_test.rs deleted file mode 100644 index 8bc9dec1..00000000 --- a/plantuml/parser/puml_resolver/src/sequence_diagram/test/logic_parse_test.rs +++ /dev/null @@ -1,132 +0,0 @@ -// ******************************************************************************* -// Copyright (c) 2026 Contributors to the Eclipse Foundation -// -// See the NOTICE file(s) distributed with this work for additional -// information regarding copyright ownership. -// -// This program and the accompanying materials are made available under the -// terms of the Apache License Version 2.0 which is available at -// -// -// SPDX-License-Identifier: Apache-2.0 -// ******************************************************************************* -//! Logic parser test suite: Compare logic_parse output with expected JSON - -use sequence_logic::{ConditionType, Event}; -use sequence_parser::syntax_ast::Statement; -use sequence_resolver::logic_parser::build_tree; -use std::fs; - -#[test] -fn test_logic_parse_output() { - // Read the syntax.json file - let syntax_file = "plantuml/parser/integration_test/sequence_diagram/simple_sequence.json"; - let expected_file = "plantuml/parser/puml_resolver/src/sequence_diagram/test/logic.json"; - - let json_content = fs::read_to_string(syntax_file) - .unwrap_or_else(|e| panic!("Error reading input file '{}': {}", syntax_file, e)); - - // Deserialize the statements - let statements: Vec = serde_json::from_str(&json_content) - .unwrap_or_else(|e| panic!("Error parsing JSON from '{}': {}", syntax_file, e)); - - // Build the tree (same logic as logic_parse_main.rs) - let tree = build_tree(&statements); - - // Serialize the tree to JSON - let actual_json = serde_json::to_string_pretty(&tree).expect("Error serializing tree to JSON"); - - // Read expected JSON - let expected_json = fs::read_to_string(expected_file) - .unwrap_or_else(|e| panic!("Error reading expected file '{}': {}", expected_file, e)); - - // Parse both JSONs to normalize formatting - let actual_value: serde_json::Value = - serde_json::from_str(&actual_json).expect("Error parsing actual JSON"); - - let expected_value: serde_json::Value = - serde_json::from_str(&expected_json).expect("Error parsing expected JSON"); - - // Compare the values - if actual_value != expected_value { - eprintln!( - "\nExpected JSON:\n{}", - serde_json::to_string_pretty(&expected_value).unwrap() - ); - eprintln!( - "\nActual JSON:\n{}", - serde_json::to_string_pretty(&actual_value).unwrap() - ); - panic!("Logic parse output does not match expected output"); - } -} - -#[test] -fn test_logic_parse_nested_loops_match_branch_nesting_rules() { - let syntax_file = "plantuml/parser/integration_test/sequence_diagram/simple_sequence.json"; - - let json_content = fs::read_to_string(syntax_file) - .unwrap_or_else(|e| panic!("Error reading input file '{}': {}", syntax_file, e)); - let statements: Vec = serde_json::from_str(&json_content) - .unwrap_or_else(|e| panic!("Error parsing JSON from '{}': {}", syntax_file, e)); - - let tree = build_tree(&statements); - assert_eq!(tree.len(), 1, "expected a single root interaction"); - - let root = &tree[0]; - assert_eq!(root.branches_node.len(), 2, "expected alt and else arms"); - - let else_branch = &root.branches_node[1]; - match &else_branch.event { - Event::Condition(condition) => assert_eq!(condition.condition_type, ConditionType::Else), - other => panic!("expected else condition node, got {:?}", other), - } - - assert!( - else_branch.branches_node.len() >= 4, - "expected direct call, two loops, and trailing branch inside else arm" - ); - - let for_loop = &else_branch.branches_node[1]; - match &for_loop.event { - Event::Condition(condition) => { - assert_eq!(condition.condition_type, ConditionType::Loop); - assert_eq!(condition.condition_value, "for i = 0; i < 3; ++i"); - } - other => panic!("expected for-loop condition node, got {:?}", other), - } - assert_eq!( - for_loop.branches_node.len(), - 2, - "expected call and nested branch in for-loop" - ); - match &for_loop.branches_node[1].event { - Event::Condition(condition) => { - assert_eq!(condition.condition_type, ConditionType::Alt); - assert_eq!(condition.condition_value, "innerConditionA"); - } - other => panic!("expected nested alt inside for-loop, got {:?}", other), - } - - let while_loop = &else_branch.branches_node[2]; - match &while_loop.event { - Event::Condition(condition) => { - assert_eq!(condition.condition_type, ConditionType::Loop); - assert_eq!(condition.condition_value, "while count > 0"); - } - other => panic!("expected while-loop condition node, got {:?}", other), - } - assert_eq!( - while_loop.branches_node.len(), - 3, - "expected call plus alt/else arms inside while-loop" - ); - match &while_loop.branches_node[1].event { - Event::Condition(condition) => assert_eq!(condition.condition_type, ConditionType::Alt), - other => panic!("expected nested alt inside while-loop, got {:?}", other), - } - match &while_loop.branches_node[2].event { - Event::Condition(condition) => assert_eq!(condition.condition_type, ConditionType::Else), - other => panic!("expected nested else inside while-loop, got {:?}", other), - } -}