smart_message 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.irbrc +24 -0
  4. data/CHANGELOG.md +119 -0
  5. data/Gemfile.lock +6 -1
  6. data/README.md +389 -17
  7. data/docs/README.md +3 -1
  8. data/docs/addressing.md +119 -13
  9. data/docs/architecture.md +184 -46
  10. data/docs/dead_letter_queue.md +673 -0
  11. data/docs/dispatcher.md +87 -0
  12. data/docs/examples.md +59 -1
  13. data/docs/getting-started.md +8 -1
  14. data/docs/logging.md +382 -326
  15. data/docs/message_deduplication.md +488 -0
  16. data/docs/message_filtering.md +451 -0
  17. data/examples/01_point_to_point_orders.rb +54 -53
  18. data/examples/02_publish_subscribe_events.rb +14 -10
  19. data/examples/03_many_to_many_chat.rb +16 -8
  20. data/examples/04_redis_smart_home_iot.rb +20 -10
  21. data/examples/05_proc_handlers.rb +12 -11
  22. data/examples/06_custom_logger_example.rb +95 -100
  23. data/examples/07_error_handling_scenarios.rb +4 -2
  24. data/examples/08_entity_addressing_basic.rb +18 -6
  25. data/examples/08_entity_addressing_with_filtering.rb +27 -9
  26. data/examples/09_dead_letter_queue_demo.rb +559 -0
  27. data/examples/09_regex_filtering_microservices.rb +407 -0
  28. data/examples/10_header_block_configuration.rb +263 -0
  29. data/examples/10_message_deduplication.rb +209 -0
  30. data/examples/11_global_configuration_example.rb +219 -0
  31. data/examples/README.md +102 -0
  32. data/examples/dead_letters.jsonl +12 -0
  33. data/examples/performance_metrics/benchmark_results_ractor_20250818_205603.json +135 -0
  34. data/examples/performance_metrics/benchmark_results_ractor_20250818_205831.json +135 -0
  35. data/examples/performance_metrics/benchmark_results_test_20250818_204942.json +130 -0
  36. data/examples/performance_metrics/benchmark_results_threadpool_20250818_204942.json +130 -0
  37. data/examples/performance_metrics/benchmark_results_threadpool_20250818_204959.json +130 -0
  38. data/examples/performance_metrics/benchmark_results_threadpool_20250818_205044.json +130 -0
  39. data/examples/performance_metrics/benchmark_results_threadpool_20250818_205109.json +130 -0
  40. data/examples/performance_metrics/benchmark_results_threadpool_20250818_205252.json +130 -0
  41. data/examples/performance_metrics/benchmark_results_unknown_20250819_172852.json +130 -0
  42. data/examples/performance_metrics/compare_benchmarks.rb +519 -0
  43. data/examples/performance_metrics/dead_letters.jsonl +3100 -0
  44. data/examples/performance_metrics/performance_benchmark.rb +344 -0
  45. data/examples/show_logger.rb +367 -0
  46. data/examples/show_me.rb +145 -0
  47. data/examples/temp.txt +94 -0
  48. data/examples/tmux_chat/bot_agent.rb +4 -2
  49. data/examples/tmux_chat/human_agent.rb +4 -2
  50. data/examples/tmux_chat/room_monitor.rb +4 -2
  51. data/examples/tmux_chat/shared_chat_system.rb +6 -3
  52. data/lib/smart_message/addressing.rb +259 -0
  53. data/lib/smart_message/base.rb +123 -599
  54. data/lib/smart_message/circuit_breaker.rb +2 -1
  55. data/lib/smart_message/configuration.rb +199 -0
  56. data/lib/smart_message/ddq/base.rb +71 -0
  57. data/lib/smart_message/ddq/memory.rb +109 -0
  58. data/lib/smart_message/ddq/redis.rb +168 -0
  59. data/lib/smart_message/ddq.rb +31 -0
  60. data/lib/smart_message/dead_letter_queue.rb +27 -10
  61. data/lib/smart_message/deduplication.rb +174 -0
  62. data/lib/smart_message/dispatcher.rb +259 -61
  63. data/lib/smart_message/header.rb +5 -0
  64. data/lib/smart_message/logger/base.rb +21 -1
  65. data/lib/smart_message/logger/default.rb +88 -138
  66. data/lib/smart_message/logger/lumberjack.rb +324 -0
  67. data/lib/smart_message/logger/null.rb +81 -0
  68. data/lib/smart_message/logger.rb +17 -9
  69. data/lib/smart_message/messaging.rb +100 -0
  70. data/lib/smart_message/plugins.rb +132 -0
  71. data/lib/smart_message/serializer/base.rb +25 -8
  72. data/lib/smart_message/serializer/json.rb +5 -4
  73. data/lib/smart_message/subscription.rb +196 -0
  74. data/lib/smart_message/transport/base.rb +72 -41
  75. data/lib/smart_message/transport/memory_transport.rb +7 -5
  76. data/lib/smart_message/transport/redis_transport.rb +15 -45
  77. data/lib/smart_message/transport/stdout_transport.rb +18 -8
  78. data/lib/smart_message/transport.rb +1 -34
  79. data/lib/smart_message/utilities.rb +142 -0
  80. data/lib/smart_message/version.rb +1 -1
  81. data/lib/smart_message/versioning.rb +85 -0
  82. data/lib/smart_message/wrapper.rb.bak +132 -0
  83. data/lib/smart_message.rb +74 -28
  84. data/smart_message.gemspec +3 -0
  85. metadata +83 -3
  86. data/lib/smart_message/serializer.rb +0 -10
  87. data/lib/smart_message/wrapper.rb +0 -43
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env ruby
2
+ # show_me.rb - Demonstrates the pretty_print method on SmartMessage instances
3
+
4
+ require_relative '../lib/smart_message'
5
+
6
+ # Define a sample order message
7
+ class OrderMessage < SmartMessage::Base
8
+ version 1
9
+ description "A message representing an e-commerce order"
10
+
11
+ from 'e-commerce-api'
12
+ to 'order-processor'
13
+
14
+ property :order_id, required: true
15
+ property :customer_name, required: true
16
+ property :customer_email
17
+ property :items, required: true
18
+ property :total_amount, required: true
19
+ property :shipping_address
20
+ property :payment_info
21
+ property :order_date
22
+ end
23
+
24
+ # Define a sample user notification message
25
+ class UserNotificationMessage < SmartMessage::Base
26
+ version 2
27
+ description "A notification message for users"
28
+
29
+ from 'notification-service'
30
+ to 'user-app'
31
+
32
+ property :user_id, required: true
33
+ property :notification_type, required: true
34
+ property :title, required: true
35
+ property :message_body, required: true
36
+ property :metadata
37
+ property :priority
38
+ property :expires_at
39
+ end
40
+
41
+ puts "=" * 60
42
+ puts "SmartMessage pretty_print Method Demonstration"
43
+ puts "=" * 60
44
+
45
+ # Create an order message with complex nested data
46
+ puts "\n1. Order Message Example:"
47
+ puts "-" * 30
48
+
49
+ order = OrderMessage.new(
50
+ order_id: "ORD-2024-001",
51
+ customer_name: "Jane Smith",
52
+ customer_email: "jane.smith@example.com",
53
+ items: [
54
+ {
55
+ product_id: "PROD-123",
56
+ name: "Wireless Headphones",
57
+ quantity: 2,
58
+ unit_price: 99.99,
59
+ subtotal: 199.98
60
+ },
61
+ {
62
+ product_id: "PROD-456",
63
+ name: "Phone Case",
64
+ quantity: 1,
65
+ unit_price: 24.99,
66
+ subtotal: 24.99
67
+ }
68
+ ],
69
+ total_amount: 224.97,
70
+ shipping_address: {
71
+ street: "123 Main St",
72
+ city: "Anytown",
73
+ state: "CA",
74
+ zip: "90210",
75
+ country: "USA"
76
+ },
77
+ payment_info: {
78
+ method: "credit_card",
79
+ last_four: "1234",
80
+ status: "authorized"
81
+ },
82
+ order_date: Time.now
83
+ )
84
+
85
+ puts ">"*22
86
+ ap order.to_h
87
+ puts "<"*22
88
+
89
+ order.pretty_print
90
+
91
+ # Create a user notification message
92
+ puts "\n\n2. User Notification Message Example:"
93
+ puts "-" * 40
94
+
95
+ notification = UserNotificationMessage.new(
96
+ user_id: "user_789",
97
+ notification_type: "order_update",
98
+ title: "Your Order Has Shipped!",
99
+ message_body: "Great news! Your order #ORD-2024-001 has been shipped and is on its way.",
100
+ metadata: {
101
+ tracking_number: "1Z999AA1234567890",
102
+ carrier: "UPS",
103
+ estimated_delivery: "2024-08-21",
104
+ order_id: "ORD-2024-001",
105
+ push_notification: true,
106
+ email_notification: true
107
+ },
108
+ priority: "high",
109
+ expires_at: Time.now + (7 * 24 * 60 * 60) # 7 days from now
110
+ )
111
+
112
+ notification.pretty_print
113
+
114
+ # Create a simple message to show minimal data
115
+ puts "\n\n3. Simple Message Example (Content Only):"
116
+ puts "-" * 40
117
+
118
+ class SimpleMessage < SmartMessage::Base
119
+ from 'greeting-service'
120
+
121
+ property :greeting, required: true
122
+ property :recipient
123
+ end
124
+
125
+ simple = SimpleMessage.new(
126
+ greeting: "Hello, World!",
127
+ recipient: "Everyone"
128
+ )
129
+
130
+ simple.pretty_print
131
+
132
+ puts "\n\n4. Message with Header Information:"
133
+ puts "-" * 40
134
+ puts "Using pretty_print(include_header: true) to show both header and content:\n"
135
+
136
+ simple.pretty_print(include_header: true)
137
+
138
+ puts "\n" + "=" * 60
139
+ puts "Demonstration Complete!"
140
+ puts "=" * 60
141
+ puts "\nThe pretty_print method has two modes:"
142
+ puts "• pretty_print() - Shows only message content (default)"
143
+ puts "• pretty_print(include_header: true) - Shows header + content"
144
+ puts "\nThis filters out internal SmartMessage properties and presents"
145
+ puts "the data in a beautifully formatted, readable way using amazing_print."
data/examples/temp.txt ADDED
@@ -0,0 +1,94 @@
1
+ === SmartMessage Example: Point-to-Point Order Processing ===
2
+
3
+ 🚀 Starting Order Processing Demo
4
+ 🏪 OrderService: Starting up...
5
+ 💳 PaymentService: Starting up...
6
+
7
+ ============================================================
8
+ Processing Sample Orders
9
+ ============================================================
10
+
11
+ --- Order 1 ---
12
+
13
+ 🏪 OrderService: Creating order ORD-1001
14
+ 🏪 OrderService: Sending order to payment processing...
15
+
16
+ ===================================================
17
+ == SmartMessage Published via STDOUT Transport
18
+ == Single-Tier Serialization:
19
+ == Message Class: OrderMessage
20
+ == Serialized Message: {"_sm_header":{"uuid":"d9a7f410-b902-4a67-9e22-694d3bf01b5f","message_class":"OrderMessage","published_at":"2025-08-20 12:02:21 -0500","publisher_pid":50033,"version":1,"from":"OrderService","serializer":"SmartMessage::Serializer::Json"},"_sm_payload":{"order_id":"ORD-1001","customer_id":"CUST-001","amount":99.99,"currency":"USD","payment_method":"credit_card","items":["Widget A","Widget B"]}}
21
+ ===================================================
22
+
23
+ 💳 PaymentService: Starting up...
24
+ 💳 PaymentService: Processing payment for order ORD-1001
25
+ 💳 PaymentService: Sending payment response...
26
+
27
+ ===================================================
28
+ == SmartMessage Published via STDOUT Transport
29
+ == Single-Tier Serialization:
30
+ == Message Class: PaymentResponseMessage
31
+ == Serialized Message: {"_sm_header":{"uuid":"ca09a331-d5b9-4250-ab4c-542a1b1db9c7","message_class":"PaymentResponseMessage","published_at":"2025-08-20 12:02:21 -0500","publisher_pid":50033,"version":1,"from":"PaymentService","serializer":"SmartMessage::Serializer::Json"},"_sm_payload":{"order_id":"ORD-1001","payment_id":"PAY-5001","status":"success","message":"Payment processed successfully","processed_at":"2025-08-20T12:02:21-05:00"}}
32
+ ===================================================
33
+
34
+ ✅ Payment success: Order ORD-1001 - Payment processed successfully
35
+
36
+ --- Order 2 ---
37
+
38
+ 🏪 OrderService: Creating order ORD-1002
39
+ 🏪 OrderService: Sending order to payment processing...
40
+
41
+ ===================================================
42
+ == SmartMessage Published via STDOUT Transport
43
+ == Single-Tier Serialization:
44
+ == Message Class: OrderMessage
45
+ == Serialized Message: {"_sm_header":{"uuid":"85ee7a93-ad19-48b3-9366-b6ed6dbb9e0e","message_class":"OrderMessage","published_at":"2025-08-20 12:02:22 -0500","publisher_pid":50033,"version":1,"from":"OrderService","serializer":"SmartMessage::Serializer::Json"},"_sm_payload":{"order_id":"ORD-1002","customer_id":"CUST-002","amount":1299.99,"currency":"USD","payment_method":"debit_card","items":["Premium Widget","Extended Warranty"]}}
46
+ ===================================================
47
+
48
+ 💳 PaymentService: Starting up...
49
+ 💳 PaymentService: Processing payment for order ORD-1002
50
+ 💳 PaymentService: Sending payment response...
51
+
52
+ ===================================================
53
+ == SmartMessage Published via STDOUT Transport
54
+ == Single-Tier Serialization:
55
+ == Message Class: PaymentResponseMessage
56
+ == Serialized Message: {"_sm_header":{"uuid":"773c1dc9-5ee2-4866-a5d0-95701f436250","message_class":"PaymentResponseMessage","published_at":"2025-08-20 12:02:22 -0500","publisher_pid":50033,"version":1,"from":"PaymentService","serializer":"SmartMessage::Serializer::Json"},"_sm_payload":{"order_id":"ORD-1002","payment_id":"PAY-5001","status":"failed","message":"Insufficient funds","processed_at":"2025-08-20T12:02:22-05:00"}}
57
+ ===================================================
58
+
59
+ ❌ Payment failed: Order ORD-1002 - Insufficient funds
60
+
61
+ --- Order 3 ---
62
+
63
+ 🏪 OrderService: Creating order ORD-1003
64
+ 🏪 OrderService: Sending order to payment processing...
65
+
66
+ ===================================================
67
+ == SmartMessage Published via STDOUT Transport
68
+ == Single-Tier Serialization:
69
+ == Message Class: OrderMessage
70
+ == Serialized Message: {"_sm_header":{"uuid":"5b3cb763-8c3e-470f-b6f9-dd5e0929f926","message_class":"OrderMessage","published_at":"2025-08-20 12:02:22 -0500","publisher_pid":50033,"version":1,"from":"OrderService","serializer":"SmartMessage::Serializer::Json"},"_sm_payload":{"order_id":"ORD-1003","customer_id":"CUST-003","amount":45.5,"currency":"USD","payment_method":"paypal","items":["Small Widget"]}}
71
+ ===================================================
72
+
73
+ 💳 PaymentService: Starting up...
74
+ 💳 PaymentService: Processing payment for order ORD-1003
75
+ 💳 PaymentService: Sending payment response...
76
+
77
+ ===================================================
78
+ == SmartMessage Published via STDOUT Transport
79
+ == Single-Tier Serialization:
80
+ == Message Class: PaymentResponseMessage
81
+ == Serialized Message: {"_sm_header":{"uuid":"dc35ea12-28a5-4926-854a-d2cc52f6c91b","message_class":"PaymentResponseMessage","published_at":"2025-08-20 12:02:22 -0500","publisher_pid":50033,"version":1,"from":"PaymentService","serializer":"SmartMessage::Serializer::Json"},"_sm_payload":{"order_id":"ORD-1003","payment_id":"PAY-5001","status":"success","message":"Payment processed successfully","processed_at":"2025-08-20T12:02:22-05:00"}}
82
+ ===================================================
83
+
84
+ ✅ Payment success: Order ORD-1003 - Payment processed successfully
85
+
86
+ ⏳ Waiting for all payments to process...
87
+
88
+ ✨ Demo completed!
89
+
90
+ This example demonstrated:
91
+ • Point-to-point messaging between OrderService and PaymentService
92
+ • Bidirectional communication with request/response pattern
93
+ • JSON serialization of complex message data
94
+ • STDOUT transport with loopback for local demonstration
@@ -53,7 +53,8 @@ class BotChatAgent < BaseAgent
53
53
  end
54
54
  end
55
55
 
56
- def handle_bot_command(message_header, message_payload)
56
+ def handle_bot_command(wrapper)
57
+ message_header, message_payload = wrapper.split
57
58
  command_data = JSON.parse(message_payload)
58
59
 
59
60
  # Only handle commands in rooms we're in and commands we can handle
@@ -66,7 +67,8 @@ class BotChatAgent < BaseAgent
66
67
  process_command(command_data)
67
68
  end
68
69
 
69
- def handle_chat_message(message_header, message_payload)
70
+ def handle_chat_message(wrapper)
71
+ message_header, message_payload = wrapper.split
70
72
  chat_data = JSON.parse(message_payload)
71
73
 
72
74
  # Only process messages from rooms we're in and not our own messages
@@ -69,7 +69,8 @@ class HumanChatAgent < BaseAgent
69
69
  end
70
70
  end
71
71
 
72
- def handle_chat_message(message_header, message_payload)
72
+ def handle_chat_message(wrapper)
73
+ message_header, message_payload = wrapper.split
73
74
  chat_data = JSON.parse(message_payload)
74
75
 
75
76
  # Only process messages from rooms we're in and not our own messages
@@ -90,7 +91,8 @@ class HumanChatAgent < BaseAgent
90
91
  end
91
92
  end
92
93
 
93
- def handle_system_notification(message_header, message_payload)
94
+ def handle_system_notification(wrapper)
95
+ message_header, message_payload = wrapper.split
94
96
  notif_data = JSON.parse(message_payload)
95
97
 
96
98
  # Only process notifications from rooms we're in
@@ -62,7 +62,8 @@ class RoomMonitor < BaseAgent
62
62
  end
63
63
  end
64
64
 
65
- def handle_chat_message(message_header, message_payload)
65
+ def handle_chat_message(wrapper)
66
+ message_header, message_payload = wrapper.split
66
67
  chat_data = JSON.parse(message_payload)
67
68
 
68
69
  return unless chat_data['room_id'] == @room_id
@@ -93,7 +94,8 @@ class RoomMonitor < BaseAgent
93
94
  end
94
95
  end
95
96
 
96
- def handle_system_notification(message_header, message_payload)
97
+ def handle_system_notification(wrapper)
98
+ message_header, message_payload = wrapper.split
97
99
  notif_data = JSON.parse(message_payload)
98
100
 
99
101
  return unless notif_data['room_id'] == @room_id
@@ -128,7 +128,8 @@ class ChatMessage < SmartMessage::Base
128
128
  serializer SmartMessage::Serializer::JSON.new
129
129
  end
130
130
 
131
- def self.process(message_header, message_payload)
131
+ def self.process(wrapper)
132
+ message_header, message_payload = wrapper.split
132
133
  # Default processing - agents will override this
133
134
  end
134
135
  end
@@ -157,7 +158,8 @@ class BotCommandMessage < SmartMessage::Base
157
158
  serializer SmartMessage::Serializer::JSON.new
158
159
  end
159
160
 
160
- def self.process(message_header, message_payload)
161
+ def self.process(wrapper)
162
+ message_header, message_payload = wrapper.split
161
163
  # Default processing - bots will override this
162
164
  end
163
165
  end
@@ -184,7 +186,8 @@ class SystemNotificationMessage < SmartMessage::Base
184
186
  serializer SmartMessage::Serializer::JSON.new
185
187
  end
186
188
 
187
- def self.process(message_header, message_payload)
189
+ def self.process(wrapper)
190
+ message_header, message_payload = wrapper.split
188
191
  # Default processing
189
192
  end
190
193
  end
@@ -0,0 +1,259 @@
1
+ # lib/smart_message/addressing.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ module SmartMessage
6
+ # Addressing configuration module for SmartMessage::Base
7
+ # Handles from, to, and reply_to addressing at both class and instance levels
8
+ module Addressing
9
+
10
+ # DSL context for header block processing
11
+ class HeaderDSL
12
+ def initialize(message_class)
13
+ @message_class = message_class
14
+ end
15
+
16
+ # Set default from value for this message class
17
+ def from(entity_id)
18
+ @message_class.from(entity_id)
19
+ end
20
+
21
+ # Set default to value for this message class
22
+ def to(entity_id)
23
+ @message_class.to(entity_id)
24
+ end
25
+
26
+ # Set default reply_to value for this message class
27
+ def reply_to(entity_id)
28
+ @message_class.reply_to(entity_id)
29
+ end
30
+ end
31
+ def self.included(base)
32
+ base.extend(ClassMethods)
33
+ base.class_eval do
34
+ # Class-level addressing configuration - use a registry for per-class isolation
35
+ class_variable_set(:@@addressing_registry, {}) unless class_variable_defined?(:@@addressing_registry)
36
+ end
37
+ end
38
+
39
+ #########################################################
40
+ ## instance-level addressing configuration
41
+
42
+ def from(entity_id = nil)
43
+ if entity_id.nil?
44
+ @from || self.class.from
45
+ else
46
+ @from = entity_id
47
+ # Update the header with the new value
48
+ _sm_header.from = entity_id if _sm_header
49
+ self # Return self for method chaining
50
+ end
51
+ end
52
+
53
+ # Setter shortcut for cleaner assignment syntax
54
+ def from=(entity_id)
55
+ @from = entity_id
56
+ _sm_header.from = entity_id if _sm_header
57
+ end
58
+
59
+ def from_configured?; !from.nil?; end
60
+ def from_missing?; from.nil?; end
61
+ def reset_from;
62
+ @from = nil
63
+ _sm_header.from = nil if _sm_header
64
+ end
65
+
66
+ def to(entity_id = nil)
67
+ if entity_id.nil?
68
+ @to || self.class.to
69
+ else
70
+ @to = entity_id
71
+ # Update the header with the new value
72
+ _sm_header.to = entity_id if _sm_header
73
+ self # Return self for method chaining
74
+ end
75
+ end
76
+
77
+ # Setter shortcut for cleaner assignment syntax
78
+ def to=(entity_id)
79
+ @to = entity_id
80
+ _sm_header.to = entity_id if _sm_header
81
+ end
82
+
83
+ def to_configured?; !to.nil?; end
84
+ def to_missing?; to.nil?; end
85
+ def reset_to;
86
+ @to = nil
87
+ _sm_header.to = nil if _sm_header
88
+ end
89
+
90
+ def reply_to(entity_id = nil)
91
+ if entity_id.nil?
92
+ @reply_to || self.class.reply_to
93
+ else
94
+ @reply_to = entity_id
95
+ # Update the header with the new value
96
+ _sm_header.reply_to = entity_id if _sm_header
97
+ self # Return self for method chaining
98
+ end
99
+ end
100
+
101
+ # Setter shortcut for cleaner assignment syntax
102
+ def reply_to=(entity_id)
103
+ @reply_to = entity_id
104
+ _sm_header.reply_to = entity_id if _sm_header
105
+ end
106
+
107
+ def reply_to_configured?; !reply_to.nil?; end
108
+ def reply_to_missing?; reply_to.nil?; end
109
+ def reset_reply_to;
110
+ @reply_to = nil
111
+ _sm_header.reply_to = nil if _sm_header
112
+ end
113
+
114
+ module ClassMethods
115
+ #########################################################
116
+ ## Header DSL support
117
+
118
+ # Header DSL block processor
119
+ # Allows: header do; from "service"; to "target"; end
120
+ def header(*args, &block)
121
+ # Handle the case where this might be called with unexpected arguments
122
+ # This helps with IRB compatibility issues
123
+ if args.length > 0
124
+ # If called with arguments, this might be an IRB inspection issue
125
+ # Try to delegate gracefully or return nil
126
+ return nil
127
+ end
128
+
129
+ if block_given?
130
+ # Create a DSL context to capture header configuration
131
+ dsl = HeaderDSL.new(self)
132
+ dsl.instance_eval(&block)
133
+ else
134
+ # No block provided - this is an error for class-level usage
135
+ raise ArgumentError, "header() at class level requires a block. Use: header do; from 'value'; end"
136
+ end
137
+ end
138
+
139
+ #########################################################
140
+ ## class-level addressing configuration
141
+
142
+ # Helper method to normalize filter values (string -> array, nil -> nil)
143
+ private def normalize_filter_value(value)
144
+ case value
145
+ when nil
146
+ nil
147
+ when String, Regexp
148
+ [value]
149
+ when Array
150
+ # Validate that array contains only Strings and Regexps
151
+ value.each do |item|
152
+ unless item.is_a?(String) || item.is_a?(Regexp)
153
+ raise ArgumentError, "Array filter values must be Strings or Regexps, got: #{item.class}"
154
+ end
155
+ end
156
+ value
157
+ else
158
+ raise ArgumentError, "Filter value must be a String, Regexp, Array, or nil, got: #{value.class}"
159
+ end
160
+ end
161
+
162
+ # Helper method to find addressing values in the inheritance chain
163
+ private def find_addressing_value(field)
164
+ # Start with current class
165
+ current_class = self
166
+ addressing_registry = class_variable_get(:@@addressing_registry)
167
+
168
+ while current_class && current_class.respond_to?(:name)
169
+ class_name = current_class.name || current_class.to_s
170
+
171
+ # Check registry for this class
172
+ result = addressing_registry.dig(class_name, field)
173
+ return result if result
174
+
175
+ # If we have a proper name but no result, also check the to_s version
176
+ if current_class.name
177
+ alternative_key = current_class.to_s
178
+ result = addressing_registry.dig(alternative_key, field)
179
+ return result if result
180
+ end
181
+
182
+ # Move up the inheritance chain
183
+ current_class = current_class.superclass
184
+
185
+ # Stop if we reach SmartMessage::Base or above
186
+ break if current_class == SmartMessage::Base || current_class.nil?
187
+ end
188
+
189
+ nil
190
+ end
191
+
192
+ def from(entity_id = nil)
193
+ class_name = self.name || self.to_s
194
+ addressing_registry = class_variable_get(:@@addressing_registry)
195
+ if entity_id.nil?
196
+ # Try to find the value, checking inheritance chain
197
+ result = find_addressing_value(:from)
198
+ result
199
+ else
200
+ addressing_registry[class_name] ||= {}
201
+ addressing_registry[class_name][:from] = entity_id
202
+ end
203
+ end
204
+
205
+ def from_configured?; !from.nil?; end
206
+ def from_missing?; from.nil?; end
207
+ def reset_from;
208
+ class_name = self.name || self.to_s
209
+ addressing_registry = class_variable_get(:@@addressing_registry)
210
+ addressing_registry[class_name] ||= {}
211
+ addressing_registry[class_name][:from] = nil
212
+ end
213
+
214
+ def to(entity_id = nil)
215
+ class_name = self.name || self.to_s
216
+ addressing_registry = class_variable_get(:@@addressing_registry)
217
+ if entity_id.nil?
218
+ # Try to find the value, checking inheritance chain
219
+ result = find_addressing_value(:to)
220
+ result
221
+ else
222
+ addressing_registry[class_name] ||= {}
223
+ addressing_registry[class_name][:to] = entity_id
224
+ end
225
+ end
226
+
227
+ def to_configured?; !to.nil?; end
228
+ def to_missing?; to.nil?; end
229
+ def reset_to;
230
+ class_name = self.name || self.to_s
231
+ addressing_registry = class_variable_get(:@@addressing_registry)
232
+ addressing_registry[class_name] ||= {}
233
+ addressing_registry[class_name][:to] = nil
234
+ end
235
+
236
+ def reply_to(entity_id = nil)
237
+ class_name = self.name || self.to_s
238
+ addressing_registry = class_variable_get(:@@addressing_registry)
239
+ if entity_id.nil?
240
+ # Try to find the value, checking inheritance chain
241
+ result = find_addressing_value(:reply_to)
242
+ result
243
+ else
244
+ addressing_registry[class_name] ||= {}
245
+ addressing_registry[class_name][:reply_to] = entity_id
246
+ end
247
+ end
248
+
249
+ def reply_to_configured?; !reply_to.nil?; end
250
+ def reply_to_missing?; reply_to.nil?; end
251
+ def reset_reply_to;
252
+ class_name = self.name || self.to_s
253
+ addressing_registry = class_variable_get(:@@addressing_registry)
254
+ addressing_registry[class_name] ||= {}
255
+ addressing_registry[class_name][:reply_to] = nil
256
+ end
257
+ end
258
+ end
259
+ end