@@ -11,32 +11,91 @@ defmodule EngineSystem.Engine.DSL.InterfaceBuilder do
11
11
"""
12
12
defmacro interface ( do: block ) do
13
13
quote do
14
- # Temporarily store current interface
14
+ # Temporarily store current interface and all message definitions (including duplicates)
15
15
Module . put_attribute ( __MODULE__ , :current_interface , [ ] )
16
+ Module . put_attribute ( __MODULE__ , :all_message_definitions , [ ] )
16
17
unquote ( block )
17
18
19
+ # Validate for duplicates before updating spec
20
+ all_definitions =
21
+ Module . get_attribute ( __MODULE__ , :all_message_definitions ) |> Enum . reverse ( )
22
+
23
+ current_interface = Module . get_attribute ( __MODULE__ , :current_interface )
24
+
25
+ case EngineSystem.Engine.DSL.InterfaceBuilder . validate_duplicate_tags ( all_definitions ) do
26
+ :ok ->
27
+ :ok
28
+
29
+ { :error , duplicate_info } ->
30
+ { tag , first_location , duplicate_location } = duplicate_info
31
+
32
+ raise CompileError ,
33
+ file: duplicate_location . file ,
34
+ line: duplicate_location . line ,
35
+ description: """
36
+ duplicate message tag #{ inspect ( tag ) }
37
+ First definition at #{ first_location . file } :#{ first_location . line }
38
+ Duplicate definition at #{ duplicate_location . file } :#{ duplicate_location . line }
39
+
40
+ Suggestion: Use different tag names like #{ inspect ( :"#{ tag } _by_key" ) } and #{ inspect ( :"#{ tag } _by_id" ) }
41
+ """
42
+ end
43
+
18
44
# Update spec with collected interface
19
45
spec_data = Module . get_attribute ( __MODULE__ , :engine_spec_data )
20
- interface = Module . get_attribute ( __MODULE__ , : current_interface) |> Enum . reverse ( )
46
+ interface = current_interface |> Enum . reverse ( )
21
47
updated_spec = % { spec_data | interface: interface }
22
48
Module . put_attribute ( __MODULE__ , :engine_spec_data , updated_spec )
23
49
Module . delete_attribute ( __MODULE__ , :current_interface )
50
+ Module . delete_attribute ( __MODULE__ , :all_message_definitions )
24
51
end
25
52
end
26
53
27
54
@ doc """
28
55
I define a message type in the interface.
29
56
"""
30
57
defmacro message ( tag , fields \\ [ ] ) do
58
+ location = % {
59
+ file: __CALLER__ . file ,
60
+ line: __CALLER__ . line
61
+ }
62
+
31
63
quote do
32
64
current_interface = Module . get_attribute ( __MODULE__ , :current_interface )
65
+ all_definitions = Module . get_attribute ( __MODULE__ , :all_message_definitions )
66
+
67
+ # Add this definition to our tracking list
68
+ Module . put_attribute ( __MODULE__ , :all_message_definitions , [
69
+ { unquote ( tag ) , unquote ( Macro . escape ( location ) ) } | all_definitions
70
+ ] )
33
71
72
+ # Add to interface as well
34
73
Module . put_attribute ( __MODULE__ , :current_interface , [
35
74
{ unquote ( tag ) , unquote ( fields ) } | current_interface
36
75
] )
37
76
end
38
77
end
39
78
79
+ @ doc """
80
+ I validate for duplicate message tags and return detailed error information.
81
+ """
82
+ def validate_duplicate_tags ( all_definitions ) do
83
+ find_first_duplicate ( all_definitions , % { } )
84
+ end
85
+
86
+ # Helper function to find the first duplicate and its locations
87
+ defp find_first_duplicate ( [ ] , _seen ) , do: :ok
88
+
89
+ defp find_first_duplicate ( [ { tag , location } | rest ] , seen ) do
90
+ case Map . get ( seen , tag ) do
91
+ nil ->
92
+ find_first_duplicate ( rest , Map . put ( seen , tag , location ) )
93
+
94
+ first_location ->
95
+ { :error , { tag , first_location , location } }
96
+ end
97
+ end
98
+
40
99
@ doc """
41
100
I validate a message interface definition.
42
101
0 commit comments