1414use crate :: logic_parser:: build_tree;
1515use resolver_traits:: DiagramResolver ;
1616use sequence_logic:: SequenceTree ;
17+ use sequence_parser:: syntax_ast:: { MessageContent , ParticipantIdentifier , Statement } ;
1718use sequence_parser:: SeqPumlDocument ;
19+ use std:: collections:: HashSet ;
1820
1921/// Resolver for sequence diagrams.
2022///
@@ -24,27 +26,82 @@ use sequence_parser::SeqPumlDocument;
2426pub struct SequenceResolver ;
2527
2628/// Error type for `SequenceResolver`.
27- ///
28- /// `build_tree` is currently infallible, so this enum has no variants.
29- /// It satisfies the `std::error::Error` bound required by the CLI's generic
30- /// `puml_resolver<R>` helper.
3129#[ derive( Debug ) ]
32- pub enum SequenceResolverError { }
30+ pub enum SequenceResolverError {
31+ /// A message references a participant that was not declared in a
32+ /// `participant` (or actor/boundary/…) statement.
33+ UndeclaredParticipant { name : String , role : & ' static str } ,
34+ }
3335
3436impl std:: fmt:: Display for SequenceResolverError {
35- fn fmt ( & self , _f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
36- match * self { }
37+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
38+ match self {
39+ SequenceResolverError :: UndeclaredParticipant { name, role } => {
40+ write ! ( f, "{role} '{name}' is not declared as a participant" )
41+ }
42+ }
3743 }
3844}
3945
4046impl std:: error:: Error for SequenceResolverError { }
4147
48+ /// Collect all identifiers that a `ParticipantIdentifier` makes available.
49+ fn collect_participant_names ( id : & ParticipantIdentifier , out : & mut HashSet < String > ) {
50+ match id {
51+ ParticipantIdentifier :: QuotedAsId { quoted, id } => {
52+ out. insert ( quoted. clone ( ) ) ;
53+ out. insert ( id. clone ( ) ) ;
54+ }
55+ ParticipantIdentifier :: IdAsQuoted { id, quoted } => {
56+ out. insert ( id. clone ( ) ) ;
57+ out. insert ( quoted. clone ( ) ) ;
58+ }
59+ ParticipantIdentifier :: IdAsId { id1, id2 } => {
60+ out. insert ( id1. clone ( ) ) ;
61+ out. insert ( id2. clone ( ) ) ;
62+ }
63+ ParticipantIdentifier :: Quoted ( s) | ParticipantIdentifier :: Id ( s) => {
64+ out. insert ( s. clone ( ) ) ;
65+ }
66+ }
67+ }
68+
4269impl DiagramResolver for SequenceResolver {
4370 type Document = SeqPumlDocument ;
4471 type Output = SequenceTree ;
4572 type Error = SequenceResolverError ;
4673
4774 fn resolve ( & mut self , document : & SeqPumlDocument ) -> Result < SequenceTree , Self :: Error > {
75+ // 1. Collect declared participants.
76+ let mut declared = HashSet :: new ( ) ;
77+ for stmt in & document. statements {
78+ if let Statement :: ParticipantDef ( p) = stmt {
79+ collect_participant_names ( & p. identifier , & mut declared) ;
80+ }
81+ }
82+
83+ // 2. Validate message targets only when participants are declared.
84+ if !declared. is_empty ( ) {
85+ for stmt in & document. statements {
86+ if let Statement :: Message ( msg) = stmt {
87+ let MessageContent :: WithTargets { left, right, .. } = & msg. content ;
88+ if !left. is_empty ( ) && !declared. contains ( left) {
89+ return Err ( SequenceResolverError :: UndeclaredParticipant {
90+ name : left. clone ( ) ,
91+ role : "caller" ,
92+ } ) ;
93+ }
94+ if !right. is_empty ( ) && !declared. contains ( right) {
95+ return Err ( SequenceResolverError :: UndeclaredParticipant {
96+ name : right. clone ( ) ,
97+ role : "callee" ,
98+ } ) ;
99+ }
100+ }
101+ }
102+ }
103+
104+ // 3. Build the tree.
48105 let root_interactions = build_tree ( & document. statements ) ;
49106 Ok ( SequenceTree {
50107 name : document. name . clone ( ) ,
@@ -58,7 +115,9 @@ mod sequence_resolver_tests {
58115 use super :: * ;
59116 use parser_core:: common_ast:: { Arrow , ArrowDecor , ArrowLine } ;
60117 use resolver_traits:: DiagramResolver ;
61- use sequence_parser:: syntax_ast:: { Message , MessageContent , Statement } ;
118+ use sequence_parser:: syntax_ast:: {
119+ Message , MessageContent , ParticipantDef , ParticipantIdentifier , ParticipantType , Statement ,
120+ } ;
62121
63122 fn solid_arrow ( ) -> Arrow {
64123 Arrow {
@@ -169,4 +228,77 @@ mod sequence_resolver_tests {
169228
170229 assert_eq ! ( tree1. root_interactions. len( ) , tree2. root_interactions. len( ) ) ;
171230 }
231+
232+ fn make_participant ( name : & str ) -> Statement {
233+ Statement :: ParticipantDef ( ParticipantDef {
234+ participant_type : ParticipantType :: Participant ,
235+ identifier : ParticipantIdentifier :: Id ( name. to_string ( ) ) ,
236+ stereotype : None ,
237+ } )
238+ }
239+
240+ /// When participants are declared, all message targets must be among them.
241+ #[ test]
242+ fn test_declared_participants_pass_validation ( ) {
243+ let stmts = vec ! [
244+ make_participant( "A" ) ,
245+ make_participant( "B" ) ,
246+ make_call( "A" , "B" , "doWork" ) ,
247+ make_return( "B" , "A" , "result" ) ,
248+ ] ;
249+ let mut resolver = SequenceResolver ;
250+ let doc = SeqPumlDocument {
251+ name : Some ( "valid" . to_string ( ) ) ,
252+ statements : stmts,
253+ } ;
254+ assert ! ( resolver. resolve( & doc) . is_ok( ) ) ;
255+ }
256+
257+ /// An undeclared callee should cause an error.
258+ #[ test]
259+ fn test_undeclared_callee_raises_error ( ) {
260+ let stmts = vec ! [ make_participant( "A" ) , make_call( "A" , "B" , "doWork" ) ] ;
261+ let mut resolver = SequenceResolver ;
262+ let doc = SeqPumlDocument {
263+ name : Some ( "bad_callee" . to_string ( ) ) ,
264+ statements : stmts,
265+ } ;
266+ let err = resolver. resolve ( & doc) . unwrap_err ( ) ;
267+ let msg = err. to_string ( ) ;
268+ assert ! (
269+ msg. contains( "B" ) ,
270+ "error should name the undeclared participant"
271+ ) ;
272+ assert ! ( msg. contains( "callee" ) , "error should indicate the role" ) ;
273+ }
274+
275+ /// An undeclared caller should cause an error.
276+ #[ test]
277+ fn test_undeclared_caller_raises_error ( ) {
278+ let stmts = vec ! [ make_participant( "B" ) , make_call( "A" , "B" , "doWork" ) ] ;
279+ let mut resolver = SequenceResolver ;
280+ let doc = SeqPumlDocument {
281+ name : Some ( "bad_caller" . to_string ( ) ) ,
282+ statements : stmts,
283+ } ;
284+ let err = resolver. resolve ( & doc) . unwrap_err ( ) ;
285+ let msg = err. to_string ( ) ;
286+ assert ! (
287+ msg. contains( "A" ) ,
288+ "error should name the undeclared participant"
289+ ) ;
290+ assert ! ( msg. contains( "caller" ) , "error should indicate the role" ) ;
291+ }
292+
293+ /// When no participants are declared, messages are allowed freely (no validation).
294+ #[ test]
295+ fn test_no_participants_declared_skips_validation ( ) {
296+ let stmts = vec ! [ make_call( "X" , "Y" , "hello" ) ] ;
297+ let mut resolver = SequenceResolver ;
298+ let doc = SeqPumlDocument {
299+ name : Some ( "implicit" . to_string ( ) ) ,
300+ statements : stmts,
301+ } ;
302+ assert ! ( resolver. resolve( & doc) . is_ok( ) ) ;
303+ }
172304}
0 commit comments