smart_message 0.0.4 → 0.0.6

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.
data/docs/properties.md CHANGED
@@ -4,6 +4,7 @@ The SmartMessage property system builds on Hashie::Dash to provide a robust, dec
4
4
 
5
5
  ## Table of Contents
6
6
  - [Basic Property Definition](#basic-property-definition)
7
+ - [Schema Versioning](#schema-versioning)
7
8
  - [Class-Level Description](#class-level-description)
8
9
  - [Property Options](#property-options)
9
10
  - [Accessing Property Information](#accessing-property-information)
@@ -20,9 +21,115 @@ class MyMessage < SmartMessage::Base
20
21
  end
21
22
  ```
22
23
 
24
+ ## Schema Versioning
25
+
26
+ SmartMessage supports schema versioning to enable message evolution while maintaining compatibility:
27
+
28
+ ```ruby
29
+ class OrderMessage < SmartMessage::Base
30
+ version 2 # Declare schema version
31
+
32
+ property :order_id, required: true
33
+ property :customer_email # Added in version 2
34
+ end
35
+ ```
36
+
37
+ ### Version Management
38
+
39
+ ```ruby
40
+ # Version 1 message
41
+ class V1OrderMessage < SmartMessage::Base
42
+ version 1 # or omit for default version 1
43
+
44
+ property :order_id, required: true
45
+ property :amount, required: true
46
+ end
47
+
48
+ # Version 2 message with additional field
49
+ class V2OrderMessage < SmartMessage::Base
50
+ version 2
51
+
52
+ property :order_id, required: true
53
+ property :amount, required: true
54
+ property :customer_email # New in version 2
55
+ end
56
+
57
+ # Version 3 message with validation
58
+ class V3OrderMessage < SmartMessage::Base
59
+ version 3
60
+
61
+ property :order_id,
62
+ required: true,
63
+ validate: ->(v) { v.is_a?(String) && v.length > 0 }
64
+
65
+ property :amount,
66
+ required: true,
67
+ validate: ->(v) { v.is_a?(Numeric) && v > 0 }
68
+
69
+ property :customer_email,
70
+ validate: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
71
+ end
72
+ ```
73
+
74
+ ### Version Validation
75
+
76
+ The framework automatically validates version compatibility:
77
+
78
+ ```ruby
79
+ message = V2OrderMessage.new(order_id: "123", amount: 99.99)
80
+ # Header automatically gets version: 2
81
+
82
+ # Version validation happens automatically
83
+ message.validate! # Validates message + header + version compatibility
84
+
85
+ # Manual version validation
86
+ message.validate_header_version! # Checks header version matches class version
87
+
88
+ # Check version information
89
+ V2OrderMessage.version # => 2
90
+ V2OrderMessage.expected_header_version # => 2
91
+ message._sm_header.version # => 2
92
+ ```
93
+
94
+ ### Version Evolution Patterns
95
+
96
+ ```ruby
97
+ # Pattern 1: Additive changes (safe)
98
+ class UserMessageV1 < SmartMessage::Base
99
+ version 1
100
+ property :user_id, required: true
101
+ property :name, required: true
102
+ end
103
+
104
+ class UserMessageV2 < SmartMessage::Base
105
+ version 2
106
+ property :user_id, required: true
107
+ property :name, required: true
108
+ property :email # Optional addition - backward compatible
109
+ end
110
+
111
+ # Pattern 2: Field validation evolution
112
+ class ProductMessageV1 < SmartMessage::Base
113
+ version 1
114
+ property :product_id, required: true
115
+ property :price, required: true
116
+ end
117
+
118
+ class ProductMessageV2 < SmartMessage::Base
119
+ version 2
120
+ property :product_id,
121
+ required: true,
122
+ validate: ->(v) { v.is_a?(String) && v.match?(/\APROD-\d+\z/) }
123
+
124
+ property :price,
125
+ required: true,
126
+ validate: ->(v) { v.is_a?(Numeric) && v > 0 }
127
+ end
128
+ ```
129
+
23
130
  ## Class-Level Description
24
131
 
25
- In addition to property-level descriptions, you can add a description for the entire message class:
132
+ In addition to property-level descriptions, you can add a description for the entire message class using the `description` DSL method:
26
133
 
27
134
  ```ruby
28
135
  class OrderMessage < SmartMessage::Base
@@ -35,15 +142,29 @@ end
35
142
  # Access the class description
36
143
  OrderMessage.description # => "Handles order processing and fulfillment workflow"
37
144
 
38
- # Class descriptions can also be set after class definition
145
+ # Instance access to class description
146
+ order = OrderMessage.new(order_id: "123", amount: 9999)
147
+ order.description # => "Handles order processing and fulfillment workflow"
148
+ ```
149
+
150
+ ### Setting Descriptions
151
+
152
+ Class descriptions can be set in multiple ways:
153
+
154
+ ```ruby
155
+ # 1. During class definition
39
156
  class PaymentMessage < SmartMessage::Base
157
+ description "Processes payment transactions"
40
158
  property :payment_id
41
159
  end
42
160
 
43
- PaymentMessage.description "Processes payment transactions"
44
- PaymentMessage.description # => "Processes payment transactions"
161
+ # 2. After class definition
162
+ class RefundMessage < SmartMessage::Base
163
+ property :refund_id
164
+ end
165
+ RefundMessage.description "Handles payment refunds and reversals"
45
166
 
46
- # Can be set within config block
167
+ # 3. Within config block
47
168
  class NotificationMessage < SmartMessage::Base
48
169
  config do
49
170
  description "Sends notifications to users"
@@ -53,13 +174,41 @@ class NotificationMessage < SmartMessage::Base
53
174
  end
54
175
  ```
55
176
 
177
+ ### Default Descriptions
178
+
179
+ Classes without explicit descriptions automatically receive a default description:
180
+
181
+ ```ruby
182
+ class MyMessage < SmartMessage::Base
183
+ property :data
184
+ end
185
+
186
+ MyMessage.description # => "MyMessage is a SmartMessage"
187
+
188
+ # This applies to all unnamed message classes
189
+ class SomeModule::ComplexMessage < SmartMessage::Base
190
+ property :info
191
+ end
192
+
193
+ SomeModule::ComplexMessage.description
194
+ # => "SomeModule::ComplexMessage is a SmartMessage"
195
+ ```
196
+
197
+ ### Use Cases
198
+
56
199
  Class descriptions are useful for:
57
200
  - Documenting the overall purpose of a message class
58
201
  - Providing context for code generation tools
59
202
  - Integration with documentation systems
60
203
  - API documentation generation
204
+ - Dynamic message introspection in gateway applications
61
205
 
62
- Note: Class descriptions are not inherited by subclasses. Each class maintains its own description.
206
+ ### Important Notes
207
+
208
+ - Class descriptions are not inherited by subclasses - each class maintains its own description
209
+ - Setting a description to `nil` will revert to the default description
210
+ - Descriptions are stored as strings and can include multiline content
211
+ - Both class and instance methods are available to access descriptions
63
212
 
64
213
  ## Property Options
65
214
 
@@ -181,7 +330,64 @@ msg.tags # => ['important'] (Array)
181
330
  msg.metadata # => {} (Hash)
182
331
  ```
183
332
 
184
- ### 6. Property Descriptions (SmartMessage Enhancement)
333
+ ### 6. Property Validation (SmartMessage Enhancement)
334
+
335
+ Add custom validation logic to ensure data integrity:
336
+
337
+ ```ruby
338
+ class ValidatedMessage < SmartMessage::Base
339
+ # Lambda validation
340
+ property :age,
341
+ validate: ->(v) { v.is_a?(Integer) && v.between?(1, 120) },
342
+ validation_message: "Age must be an integer between 1 and 120"
343
+
344
+ # Regex validation for email
345
+ property :email,
346
+ validate: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i,
347
+ validation_message: "Must be a valid email address"
348
+
349
+ # Array inclusion validation
350
+ property :status,
351
+ validate: ['active', 'inactive', 'pending'],
352
+ validation_message: "Status must be active, inactive, or pending"
353
+
354
+ # Range validation
355
+ property :score,
356
+ validate: (0..100),
357
+ validation_message: "Score must be between 0 and 100"
358
+
359
+ # Class type validation
360
+ property :created_at,
361
+ validate: Time,
362
+ validation_message: "Must be a Time object"
363
+
364
+ # Symbol method validation
365
+ property :username,
366
+ validate: :valid_username?,
367
+ validation_message: "Username contains invalid characters"
368
+
369
+ private
370
+
371
+ def valid_username?(value)
372
+ value.to_s.match?(/\A[a-zA-Z0-9_]+\z/)
373
+ end
374
+ end
375
+
376
+ # Validation usage
377
+ message = ValidatedMessage.new(age: 25, email: "test@example.com")
378
+
379
+ # Validate entire message
380
+ message.validate! # Raises SmartMessage::Errors::ValidationError on failure
381
+ message.valid? # Returns true/false
382
+
383
+ # Get validation errors
384
+ errors = message.validation_errors
385
+ errors.each do |error|
386
+ puts "#{error[:property]}: #{error[:message]}"
387
+ end
388
+ ```
389
+
390
+ ### 7. Property Descriptions (SmartMessage Enhancement)
185
391
 
186
392
  Add human-readable descriptions to document your properties for dynamic LLM integration:
187
393
 
@@ -13,12 +13,21 @@ puts
13
13
 
14
14
  # Define the Order Message
15
15
  class OrderMessage < SmartMessage::Base
16
- property :order_id
17
- property :customer_id
18
- property :amount
19
- property :currency, default: 'USD'
20
- property :payment_method
21
- property :items
16
+ description "Represents customer orders for processing through the payment system"
17
+
18
+ property :order_id,
19
+ description: "Unique identifier for the order (e.g., ORD-1001)"
20
+ property :customer_id,
21
+ description: "Unique identifier for the customer placing the order"
22
+ property :amount,
23
+ description: "Total order amount in decimal format (e.g., 99.99)"
24
+ property :currency,
25
+ default: 'USD',
26
+ description: "ISO currency code for the order (defaults to USD)"
27
+ property :payment_method,
28
+ description: "Payment method selected by customer (credit_card, debit_card, paypal, etc.)"
29
+ property :items,
30
+ description: "Array of item names or descriptions included in the order"
22
31
 
23
32
  # Configure to use memory transport for this example
24
33
  config do
@@ -35,11 +44,18 @@ end
35
44
 
36
45
  # Define the Payment Response Message
37
46
  class PaymentResponseMessage < SmartMessage::Base
38
- property :order_id
39
- property :payment_id
40
- property :status # 'success', 'failed', 'pending'
41
- property :message
42
- property :processed_at
47
+ description "Contains payment processing results sent back to the order system"
48
+
49
+ property :order_id,
50
+ description: "Reference to the original order being processed"
51
+ property :payment_id,
52
+ description: "Unique identifier for the payment transaction (e.g., PAY-5001)"
53
+ property :status,
54
+ description: "Payment processing status: 'success', 'failed', or 'pending'"
55
+ property :message,
56
+ description: "Human-readable description of the payment result"
57
+ property :processed_at,
58
+ description: "ISO8601 timestamp when the payment was processed"
43
59
 
44
60
  config do
45
61
  transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
@@ -13,13 +13,22 @@ puts
13
13
 
14
14
  # Define the User Event Message
15
15
  class UserEventMessage < SmartMessage::Base
16
- property :event_id
17
- property :event_type # 'user_registered', 'user_login', 'password_changed', etc.
18
- property :user_id
19
- property :user_email
20
- property :user_name
21
- property :timestamp
22
- property :metadata # Additional event-specific data
16
+ description "Broadcasts user activity events to multiple notification services"
17
+
18
+ property :event_id,
19
+ description: "Unique identifier for this event (e.g., EVT-1001)"
20
+ property :event_type,
21
+ description: "Type of user event: 'user_registered', 'user_login', 'password_changed', etc."
22
+ property :user_id,
23
+ description: "Unique identifier for the user performing the action"
24
+ property :user_email,
25
+ description: "Email address of the user for notification purposes"
26
+ property :user_name,
27
+ description: "Display name of the user"
28
+ property :timestamp,
29
+ description: "ISO8601 timestamp when the event occurred"
30
+ property :metadata,
31
+ description: "Additional event-specific data (source, location, IP, etc.)"
23
32
 
24
33
  config do
25
34
  transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
@@ -14,15 +14,26 @@ puts
14
14
 
15
15
  # Define the Chat Message
16
16
  class ChatMessage < SmartMessage::Base
17
- property :message_id
18
- property :room_id
19
- property :sender_id
20
- property :sender_name
21
- property :content
22
- property :message_type # 'user', 'bot', 'system'
23
- property :timestamp
24
- property :mentions # Array of user IDs mentioned
25
- property :metadata
17
+ description "Represents chat messages exchanged between users in chat rooms"
18
+
19
+ property :message_id,
20
+ description: "Unique identifier for this chat message"
21
+ property :room_id,
22
+ description: "Identifier of the chat room where message was sent"
23
+ property :sender_id,
24
+ description: "Unique identifier of the user or bot sending the message"
25
+ property :sender_name,
26
+ description: "Display name of the message sender"
27
+ property :content,
28
+ description: "The actual text content of the chat message"
29
+ property :message_type,
30
+ description: "Type of message: 'user', 'bot', or 'system'"
31
+ property :timestamp,
32
+ description: "ISO8601 timestamp when message was sent"
33
+ property :mentions,
34
+ description: "Array of user IDs mentioned in the message (@username)"
35
+ property :metadata,
36
+ description: "Additional message data (reactions, edit history, etc.)"
26
37
 
27
38
  config do
28
39
  transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
@@ -37,12 +48,20 @@ end
37
48
 
38
49
  # Define Bot Command Message
39
50
  class BotCommandMessage < SmartMessage::Base
40
- property :command_id
41
- property :room_id
42
- property :user_id
43
- property :command
44
- property :parameters
45
- property :timestamp
51
+ description "Represents commands sent to chat bots for processing"
52
+
53
+ property :command_id,
54
+ description: "Unique identifier for this bot command"
55
+ property :room_id,
56
+ description: "Chat room where the command was issued"
57
+ property :user_id,
58
+ description: "User who issued the bot command"
59
+ property :command,
60
+ description: "The bot command name (e.g., 'weather', 'help', 'joke')"
61
+ property :parameters,
62
+ description: "Array of parameters passed to the bot command"
63
+ property :timestamp,
64
+ description: "ISO8601 timestamp when command was issued"
46
65
 
47
66
  config do
48
67
  transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
@@ -57,12 +76,20 @@ end
57
76
 
58
77
  # Define System Notification Message
59
78
  class SystemNotificationMessage < SmartMessage::Base
60
- property :notification_id
61
- property :room_id
62
- property :notification_type # 'user_joined', 'user_left', 'room_created'
63
- property :content
64
- property :timestamp
65
- property :metadata
79
+ description "System-generated notifications about chat room events and status changes"
80
+
81
+ property :notification_id,
82
+ description: "Unique identifier for this system notification"
83
+ property :room_id,
84
+ description: "Chat room affected by this notification"
85
+ property :notification_type,
86
+ description: "Type of notification: 'user_joined', 'user_left', 'room_created', etc."
87
+ property :content,
88
+ description: "Human-readable description of the system event"
89
+ property :timestamp,
90
+ description: "ISO8601 timestamp when the system event occurred"
91
+ property :metadata,
92
+ description: "Additional system event data and context"
66
93
 
67
94
  config do
68
95
  transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
@@ -329,7 +356,11 @@ class BotAgent
329
356
  end
330
357
 
331
358
  def process_chat_message(chat_data)
332
- # Bot can respond to certain keywords or patterns
359
+ # Don't respond to other bots to avoid infinite loops
360
+ return if chat_data['message_type'] == 'bot'
361
+ return if chat_data['sender_id'] == @bot_id # Don't respond to our own messages
362
+
363
+ # Bot can respond to certain keywords or patterns from human users only
333
364
  content = chat_data['content'].downcase
334
365
 
335
366
  if content.include?('hello') || content.include?('hi')
@@ -598,6 +629,9 @@ class DistributedChatDemo
598
629
  puts "• Dynamic subscription and unsubscription"
599
630
  puts "• Event-driven responses and interactions"
600
631
  puts "• Service discovery and capability advertisement"
632
+
633
+ puts "\n🛑 Shutting down demo..."
634
+ sleep(1) # Give any pending messages time to process
601
635
  end
602
636
  end
603
637
 
@@ -58,14 +58,24 @@ SHARED_TRANSPORT = create_transport
58
58
 
59
59
  # Sensor Data Message - Published by IoT devices
60
60
  class SensorDataMessage < SmartMessage::Base
61
- property :device_id
62
- property :device_type # 'thermostat', 'security_camera', 'door_lock', 'smoke_detector'
63
- property :location # 'living_room', 'kitchen', 'bedroom', 'garage'
64
- property :sensor_type # 'temperature', 'humidity', 'motion', 'status'
65
- property :value
66
- property :unit # 'celsius', 'percent', 'boolean', 'lux'
67
- property :timestamp
68
- property :battery_level # For battery-powered devices
61
+ description "Real-time sensor data from smart home IoT devices"
62
+
63
+ property :device_id,
64
+ description: "Unique identifier for the IoT device (e.g., THERM-001)"
65
+ property :device_type,
66
+ description: "Type of device: 'thermostat', 'security_camera', 'door_lock', 'smoke_detector'"
67
+ property :location,
68
+ description: "Physical location of device: 'living_room', 'kitchen', 'bedroom', 'garage'"
69
+ property :sensor_type,
70
+ description: "Type of sensor reading: 'temperature', 'humidity', 'motion', 'status'"
71
+ property :value,
72
+ description: "Numeric or boolean sensor reading value"
73
+ property :unit,
74
+ description: "Unit of measurement: 'celsius', 'percent', 'boolean', 'lux'"
75
+ property :timestamp,
76
+ description: "ISO8601 timestamp when sensor reading was taken"
77
+ property :battery_level,
78
+ description: "Battery percentage for battery-powered devices (0-100)"
69
79
 
70
80
  config do
71
81
  transport SHARED_TRANSPORT
@@ -88,11 +98,18 @@ end
88
98
 
89
99
  # Device Command Message - Sent to control IoT devices
90
100
  class DeviceCommandMessage < SmartMessage::Base
91
- property :device_id
92
- property :command # 'set_temperature', 'lock_door', 'start_recording', 'test_alarm'
93
- property :parameters # Command-specific parameters
94
- property :requested_by # Who/what requested this command
95
- property :timestamp
101
+ description "Commands sent to control and configure smart home IoT devices"
102
+
103
+ property :device_id,
104
+ description: "Target device identifier to receive the command"
105
+ property :command,
106
+ description: "Command to execute: 'set_temperature', 'lock_door', 'start_recording', 'test_alarm'"
107
+ property :parameters,
108
+ description: "Hash of command-specific parameters and values"
109
+ property :requested_by,
110
+ description: "Identifier of user, system, or automation that requested this command"
111
+ property :timestamp,
112
+ description: "ISO8601 timestamp when command was issued"
96
113
 
97
114
  config do
98
115
  transport SHARED_TRANSPORT
@@ -107,14 +124,24 @@ end
107
124
 
108
125
  # Alert Message - For critical notifications
109
126
  class AlertMessage < SmartMessage::Base
110
- property :alert_id
111
- property :severity # 'low', 'medium', 'high', 'critical'
112
- property :alert_type # 'security_breach', 'fire_detected', 'device_offline', 'battery_low'
113
- property :device_id
114
- property :location
115
- property :message
116
- property :timestamp
117
- property :requires_action # Boolean indicating if immediate action needed
127
+ description "Critical alerts and notifications from smart home monitoring systems"
128
+
129
+ property :alert_id,
130
+ description: "Unique identifier for this alert event"
131
+ property :severity,
132
+ description: "Alert severity level: 'low', 'medium', 'high', 'critical'"
133
+ property :alert_type,
134
+ description: "Type of alert: 'security_breach', 'fire_detected', 'device_offline', 'battery_low'"
135
+ property :device_id,
136
+ description: "Device that triggered the alert (if applicable)"
137
+ property :location,
138
+ description: "Physical location where alert was triggered"
139
+ property :message,
140
+ description: "Human-readable description of the alert condition"
141
+ property :timestamp,
142
+ description: "ISO8601 timestamp when alert was generated"
143
+ property :requires_action,
144
+ description: "Boolean indicating if immediate human action is required"
118
145
 
119
146
  config do
120
147
  transport SHARED_TRANSPORT
@@ -14,11 +14,18 @@ puts
14
14
 
15
15
  # Define a simple notification message
16
16
  class NotificationMessage < SmartMessage::Base
17
- property :type # 'info', 'warning', 'error'
18
- property :title
19
- property :message
20
- property :user_id
21
- property :timestamp
17
+ description "System notifications processed by different types of handlers"
18
+
19
+ property :type,
20
+ description: "Notification type: 'info', 'warning', 'error'"
21
+ property :title,
22
+ description: "Short title or subject of the notification"
23
+ property :message,
24
+ description: "Detailed notification message content"
25
+ property :user_id,
26
+ description: "Target user ID for the notification (optional)"
27
+ property :timestamp,
28
+ description: "ISO8601 timestamp when notification was created"
22
29
 
23
30
  config do
24
31
  transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
@@ -285,11 +285,18 @@ end
285
285
 
286
286
  # Sample message class with comprehensive logging
287
287
  class OrderProcessingMessage < SmartMessage::Base
288
- property :order_id
289
- property :customer_id
290
- property :amount
291
- property :status
292
- property :items
288
+ description "Order processing messages with comprehensive multi-logger configuration"
289
+
290
+ property :order_id,
291
+ description: "Unique identifier for the customer order"
292
+ property :customer_id,
293
+ description: "Identifier of the customer placing the order"
294
+ property :amount,
295
+ description: "Total monetary amount of the order"
296
+ property :status,
297
+ description: "Current processing status of the order"
298
+ property :items,
299
+ description: "Array of items included in the order"
293
300
 
294
301
  config do
295
302
  transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
@@ -350,10 +357,16 @@ end
350
357
 
351
358
  # Notification message with different logger configuration
352
359
  class NotificationMessage < SmartMessage::Base
353
- property :recipient
354
- property :subject
355
- property :body
356
- property :priority
360
+ description "User notifications with file-based logging configuration"
361
+
362
+ property :recipient,
363
+ description: "Target recipient for the notification"
364
+ property :subject,
365
+ description: "Subject line or title of the notification"
366
+ property :body,
367
+ description: "Main content body of the notification"
368
+ property :priority,
369
+ description: "Priority level of the notification (low, normal, high, urgent)"
357
370
 
358
371
  config do
359
372
  transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
@@ -384,8 +397,12 @@ end
384
397
  # Example: Message class using standard Ruby logger
385
398
  # This demonstrates how to use Ruby's standard Logger in production code
386
399
  class StandardLoggerMessage < SmartMessage::Base
387
- property :content
388
- property :level
400
+ description "Demonstrates integration with standard Ruby Logger for production logging"
401
+
402
+ property :content,
403
+ description: "Main content of the message to be logged"
404
+ property :level,
405
+ description: "Logging level for the message (debug, info, warn, error)"
389
406
 
390
407
  config do
391
408
  transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
@@ -417,8 +434,12 @@ end
417
434
 
418
435
  # Example: Message using the built-in Default Logger
419
436
  class DefaultLoggerMessage < SmartMessage::Base
420
- property :message
421
- property :level
437
+ description "Demonstrates SmartMessage's built-in default logger with auto-detection"
438
+
439
+ property :message,
440
+ description: "The message content to be logged using default logger"
441
+ property :level,
442
+ description: "Log level (debug, info, warn, error, fatal)"
422
443
 
423
444
  config do
424
445
  transport SmartMessage::Transport::StdoutTransport.new(loopback: true)