smart_message 0.0.3 → 0.0.5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -0
- data/Gemfile.lock +1 -1
- data/README.md +244 -9
- data/docs/README.md +1 -0
- data/docs/architecture.md +2 -0
- data/docs/examples.md +2 -0
- data/docs/getting-started.md +11 -0
- data/docs/logging.md +452 -0
- data/docs/properties.md +213 -7
- data/examples/.gitignore +2 -0
- data/examples/01_point_to_point_orders.rb +27 -11
- data/examples/02_publish_subscribe_events.rb +16 -7
- data/examples/03_many_to_many_chat.rb +56 -22
- data/examples/04_redis_smart_home_iot.rb +48 -21
- data/examples/05_proc_handlers.rb +12 -5
- data/examples/06_custom_logger_example.rb +641 -0
- data/examples/07_error_handling_scenarios.rb +477 -0
- data/examples/tmux_chat/bot_agent.rb +4 -1
- data/examples/tmux_chat/shared_chat_system.rb +50 -22
- data/lib/smart_message/base.rb +105 -8
- data/lib/smart_message/errors.rb +3 -0
- data/lib/smart_message/header.rb +32 -5
- data/lib/smart_message/logger/default.rb +217 -0
- data/lib/smart_message/logger.rb +9 -1
- data/lib/smart_message/property_descriptions.rb +5 -4
- data/lib/smart_message/property_validations.rb +141 -0
- data/lib/smart_message/version.rb +1 -1
- metadata +7 -1
data/docs/logging.md
ADDED
@@ -0,0 +1,452 @@
|
|
1
|
+
# Logging in SmartMessage
|
2
|
+
|
3
|
+
SmartMessage provides flexible logging capabilities through its plugin architecture. This document covers the built-in default logger as well as how to create custom loggers.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
|
7
|
+
- [Default Logger](#default-logger)
|
8
|
+
- [Configuration Options](#configuration-options)
|
9
|
+
- [Output Destinations](#output-destinations)
|
10
|
+
- [Log Levels](#log-levels)
|
11
|
+
- [Message Lifecycle Logging](#message-lifecycle-logging)
|
12
|
+
- [Rails Integration](#rails-integration)
|
13
|
+
- [Custom Loggers](#custom-loggers)
|
14
|
+
- [Examples](#examples)
|
15
|
+
|
16
|
+
## Default Logger
|
17
|
+
|
18
|
+
SmartMessage includes a built-in `SmartMessage::Logger::Default` class that automatically detects your environment and chooses the best logging approach:
|
19
|
+
|
20
|
+
- **Rails Applications**: Uses `Rails.logger` with tagged logging
|
21
|
+
- **Standalone Ruby**: Uses Ruby's standard `Logger` class with file output
|
22
|
+
|
23
|
+
### Quick Start
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class MyMessage < SmartMessage::Base
|
27
|
+
property :content
|
28
|
+
|
29
|
+
config do
|
30
|
+
transport SmartMessage::Transport::StdoutTransport.new
|
31
|
+
serializer SmartMessage::Serializer::JSON.new
|
32
|
+
logger SmartMessage::Logger::Default.new # Zero configuration!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
## Configuration Options
|
38
|
+
|
39
|
+
The default logger accepts several configuration options:
|
40
|
+
|
41
|
+
### Basic Configuration
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
# Use defaults (Rails.logger or log/smart_message.log)
|
45
|
+
logger SmartMessage::Logger::Default.new
|
46
|
+
|
47
|
+
# Custom log file path
|
48
|
+
logger SmartMessage::Logger::Default.new(
|
49
|
+
log_file: '/var/log/my_app/messages.log'
|
50
|
+
)
|
51
|
+
|
52
|
+
# Custom log level
|
53
|
+
logger SmartMessage::Logger::Default.new(
|
54
|
+
level: Logger::DEBUG
|
55
|
+
)
|
56
|
+
|
57
|
+
# Both custom file and level
|
58
|
+
logger SmartMessage::Logger::Default.new(
|
59
|
+
log_file: 'logs/custom.log',
|
60
|
+
level: Logger::WARN
|
61
|
+
)
|
62
|
+
```
|
63
|
+
|
64
|
+
## Output Destinations
|
65
|
+
|
66
|
+
The default logger supports multiple output destinations:
|
67
|
+
|
68
|
+
### File Logging (Default)
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# Default file location (Rails convention)
|
72
|
+
logger SmartMessage::Logger::Default.new
|
73
|
+
# → log/smart_message.log
|
74
|
+
|
75
|
+
# Custom file path
|
76
|
+
logger SmartMessage::Logger::Default.new(
|
77
|
+
log_file: '/var/log/application/messages.log'
|
78
|
+
)
|
79
|
+
```
|
80
|
+
|
81
|
+
**Features:**
|
82
|
+
- Automatic log rotation (10 files, 10MB each)
|
83
|
+
- Directory creation if needed
|
84
|
+
- Timestamped entries with clean formatting
|
85
|
+
|
86
|
+
### STDOUT Logging
|
87
|
+
|
88
|
+
Perfect for containerized applications (Docker, Kubernetes):
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
logger SmartMessage::Logger::Default.new(
|
92
|
+
log_file: STDOUT,
|
93
|
+
level: Logger::INFO
|
94
|
+
)
|
95
|
+
```
|
96
|
+
|
97
|
+
### STDERR Logging
|
98
|
+
|
99
|
+
For error-focused logging:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
logger SmartMessage::Logger::Default.new(
|
103
|
+
log_file: STDERR,
|
104
|
+
level: Logger::WARN
|
105
|
+
)
|
106
|
+
```
|
107
|
+
|
108
|
+
### In-Memory Logging (Testing)
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
require 'stringio'
|
112
|
+
|
113
|
+
logger SmartMessage::Logger::Default.new(
|
114
|
+
log_file: StringIO.new,
|
115
|
+
level: Logger::DEBUG
|
116
|
+
)
|
117
|
+
```
|
118
|
+
|
119
|
+
## Log Levels
|
120
|
+
|
121
|
+
The default logger supports all standard Ruby log levels:
|
122
|
+
|
123
|
+
| Level | Numeric Value | Description |
|
124
|
+
|-------|--------------|-------------|
|
125
|
+
| `Logger::DEBUG` | 0 | Detailed debugging information |
|
126
|
+
| `Logger::INFO` | 1 | General information messages |
|
127
|
+
| `Logger::WARN` | 2 | Warning messages |
|
128
|
+
| `Logger::ERROR` | 3 | Error messages |
|
129
|
+
| `Logger::FATAL` | 4 | Fatal error messages |
|
130
|
+
|
131
|
+
### Environment-Based Defaults
|
132
|
+
|
133
|
+
The default logger automatically sets appropriate log levels based on your environment:
|
134
|
+
|
135
|
+
- **Rails Production**: `Logger::INFO`
|
136
|
+
- **Rails Test**: `Logger::ERROR`
|
137
|
+
- **Rails Development**: `Logger::DEBUG`
|
138
|
+
- **Non-Rails**: `Logger::INFO`
|
139
|
+
|
140
|
+
## Message Lifecycle Logging
|
141
|
+
|
142
|
+
The default logger automatically logs key events in the message lifecycle:
|
143
|
+
|
144
|
+
### Message Creation
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
# Logged at DEBUG level
|
148
|
+
message = MyMessage.new(content: "Hello")
|
149
|
+
# → [DEBUG] [SmartMessage] Created: MyMessage - {content: "Hello"}
|
150
|
+
```
|
151
|
+
|
152
|
+
### Message Publishing
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
# Logged at INFO level
|
156
|
+
message.publish
|
157
|
+
# → [INFO] [SmartMessage] Published: MyMessage via StdoutTransport
|
158
|
+
```
|
159
|
+
|
160
|
+
### Message Reception
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
# Logged at INFO level when message is received
|
164
|
+
# → [INFO] [SmartMessage] Received: MyMessage (45 bytes)
|
165
|
+
```
|
166
|
+
|
167
|
+
### Message Processing
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
# Logged at INFO level after processing
|
171
|
+
# → [INFO] [SmartMessage] Processed: MyMessage - Success
|
172
|
+
```
|
173
|
+
|
174
|
+
### Subscription Management
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
# Logged at INFO level
|
178
|
+
MyMessage.subscribe
|
179
|
+
# → [INFO] [SmartMessage] Subscribed: MyMessage
|
180
|
+
|
181
|
+
MyMessage.unsubscribe
|
182
|
+
# → [INFO] [SmartMessage] Unsubscribed: MyMessage
|
183
|
+
```
|
184
|
+
|
185
|
+
### Error Logging
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
# Logged at ERROR level with full stack trace (DEBUG level)
|
189
|
+
# → [ERROR] [SmartMessage] Error in message processing: RuntimeError - Something went wrong
|
190
|
+
# → [DEBUG] [SmartMessage] Backtrace: ...
|
191
|
+
```
|
192
|
+
|
193
|
+
## Rails Integration
|
194
|
+
|
195
|
+
When running in a Rails application, the default logger provides enhanced integration:
|
196
|
+
|
197
|
+
### Automatic Detection
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
# Automatically uses Rails.logger when available
|
201
|
+
logger SmartMessage::Logger::Default.new
|
202
|
+
```
|
203
|
+
|
204
|
+
### Tagged Logging
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
# In Rails, all SmartMessage logs are tagged
|
208
|
+
Rails.logger.tagged('SmartMessage') do
|
209
|
+
# All SmartMessage logging happens here
|
210
|
+
end
|
211
|
+
```
|
212
|
+
|
213
|
+
### Rails Log File Location
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
# Uses Rails.root/log/smart_message.log when Rails is detected
|
217
|
+
logger SmartMessage::Logger::Default.new
|
218
|
+
# → Rails.root.join('log', 'smart_message.log')
|
219
|
+
```
|
220
|
+
|
221
|
+
### Rails Environment Handling
|
222
|
+
|
223
|
+
The logger respects Rails environment settings:
|
224
|
+
|
225
|
+
- **Production**: INFO level, structured logging
|
226
|
+
- **Development**: DEBUG level, verbose output
|
227
|
+
- **Test**: ERROR level, minimal output
|
228
|
+
|
229
|
+
## Custom Loggers
|
230
|
+
|
231
|
+
You can create custom loggers by inheriting from `SmartMessage::Logger::Base`:
|
232
|
+
|
233
|
+
### Basic Custom Logger
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
class SmartMessage::Logger::MyCustomLogger < SmartMessage::Logger::Base
|
237
|
+
def initialize(external_logger)
|
238
|
+
@logger = external_logger
|
239
|
+
end
|
240
|
+
|
241
|
+
def log_message_created(message)
|
242
|
+
@logger.debug "Created message: #{message.class.name}"
|
243
|
+
end
|
244
|
+
|
245
|
+
def log_message_published(message, transport)
|
246
|
+
@logger.info "Published #{message.class.name} via #{transport.class.name}"
|
247
|
+
end
|
248
|
+
|
249
|
+
# Implement other lifecycle methods as needed...
|
250
|
+
end
|
251
|
+
```
|
252
|
+
|
253
|
+
### Wrapper for Third-Party Loggers
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
# Semantic Logger example
|
257
|
+
class SmartMessage::Logger::SemanticLogger < SmartMessage::Logger::Base
|
258
|
+
def initialize(semantic_logger = nil)
|
259
|
+
@logger = semantic_logger || SemanticLogger['SmartMessage']
|
260
|
+
end
|
261
|
+
|
262
|
+
def log_message_created(message)
|
263
|
+
@logger.debug "Message created", message_class: message.class.name
|
264
|
+
end
|
265
|
+
|
266
|
+
def log_error(context, error)
|
267
|
+
@logger.error "Error in #{context}", exception: error
|
268
|
+
end
|
269
|
+
end
|
270
|
+
```
|
271
|
+
|
272
|
+
### Multi-Logger (Broadcast)
|
273
|
+
|
274
|
+
```ruby
|
275
|
+
class SmartMessage::Logger::MultiLogger < SmartMessage::Logger::Base
|
276
|
+
def initialize(*loggers)
|
277
|
+
@loggers = loggers
|
278
|
+
end
|
279
|
+
|
280
|
+
def log_message_created(message)
|
281
|
+
@loggers.each { |logger| logger.log_message_created(message) }
|
282
|
+
end
|
283
|
+
|
284
|
+
# Other methods follow same pattern...
|
285
|
+
end
|
286
|
+
|
287
|
+
# Usage
|
288
|
+
logger SmartMessage::Logger::MultiLogger.new(
|
289
|
+
SmartMessage::Logger::Default.new(log_file: 'app.log'),
|
290
|
+
SmartMessage::Logger::Default.new(log_file: STDOUT),
|
291
|
+
SmartMessage::Logger::MyCustomLogger.new(external_system)
|
292
|
+
)
|
293
|
+
```
|
294
|
+
|
295
|
+
## Examples
|
296
|
+
|
297
|
+
### Development Setup
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
class OrderMessage < SmartMessage::Base
|
301
|
+
property :order_id, String, required: true
|
302
|
+
property :customer_id, String, required: true
|
303
|
+
property :amount, Float, required: true
|
304
|
+
|
305
|
+
config do
|
306
|
+
transport SmartMessage::Transport::StdoutTransport.new(loopback: true)
|
307
|
+
serializer SmartMessage::Serializer::JSON.new
|
308
|
+
|
309
|
+
# Verbose logging for development
|
310
|
+
logger SmartMessage::Logger::Default.new(
|
311
|
+
log_file: STDOUT,
|
312
|
+
level: Logger::DEBUG
|
313
|
+
)
|
314
|
+
end
|
315
|
+
|
316
|
+
def self.process(message_header, message_payload)
|
317
|
+
data = JSON.parse(message_payload)
|
318
|
+
|
319
|
+
# Logger is available in process method
|
320
|
+
logger.info "Processing order #{data['order_id']}"
|
321
|
+
|
322
|
+
# Business logic here
|
323
|
+
result = process_order(data)
|
324
|
+
|
325
|
+
logger.info "Order #{data['order_id']} completed: #{result}"
|
326
|
+
result
|
327
|
+
end
|
328
|
+
end
|
329
|
+
```
|
330
|
+
|
331
|
+
### Production Setup
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
class NotificationMessage < SmartMessage::Base
|
335
|
+
property :recipient, String, required: true
|
336
|
+
property :subject, String, required: true
|
337
|
+
property :body, String, required: true
|
338
|
+
|
339
|
+
config do
|
340
|
+
transport SmartMessage::Transport::RedisTransport.new
|
341
|
+
serializer SmartMessage::Serializer::JSON.new
|
342
|
+
|
343
|
+
# Production logging with file rotation
|
344
|
+
logger SmartMessage::Logger::Default.new(
|
345
|
+
log_file: '/var/log/app/notifications.log',
|
346
|
+
level: Logger::INFO
|
347
|
+
)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
```
|
351
|
+
|
352
|
+
### Docker/Kubernetes Setup
|
353
|
+
|
354
|
+
```ruby
|
355
|
+
class EventMessage < SmartMessage::Base
|
356
|
+
property :event_type, String, required: true
|
357
|
+
property :data, Hash, required: true
|
358
|
+
|
359
|
+
config do
|
360
|
+
transport SmartMessage::Transport::RedisTransport.new(
|
361
|
+
redis_url: ENV['REDIS_URL']
|
362
|
+
)
|
363
|
+
serializer SmartMessage::Serializer::JSON.new
|
364
|
+
|
365
|
+
# Container-friendly STDOUT logging
|
366
|
+
logger SmartMessage::Logger::Default.new(
|
367
|
+
log_file: STDOUT,
|
368
|
+
level: ENV['LOG_LEVEL']&.upcase&.to_sym || Logger::INFO
|
369
|
+
)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
```
|
373
|
+
|
374
|
+
### Testing Setup
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
# In test_helper.rb or similar
|
378
|
+
class TestMessage < SmartMessage::Base
|
379
|
+
property :test_data, Hash
|
380
|
+
|
381
|
+
config do
|
382
|
+
transport SmartMessage::Transport::MemoryTransport.new
|
383
|
+
serializer SmartMessage::Serializer::JSON.new
|
384
|
+
|
385
|
+
# Minimal logging for tests
|
386
|
+
logger SmartMessage::Logger::Default.new(
|
387
|
+
log_file: StringIO.new,
|
388
|
+
level: Logger::FATAL # Only fatal errors in tests
|
389
|
+
)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
```
|
393
|
+
|
394
|
+
### Instance-Level Logger Override
|
395
|
+
|
396
|
+
```ruby
|
397
|
+
# Different logger for specific instances
|
398
|
+
class PriorityMessage < SmartMessage::Base
|
399
|
+
property :priority, String
|
400
|
+
property :data, Hash
|
401
|
+
|
402
|
+
config do
|
403
|
+
transport SmartMessage::Transport::RedisTransport.new
|
404
|
+
serializer SmartMessage::Serializer::JSON.new
|
405
|
+
logger SmartMessage::Logger::Default.new # Default logger
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# Override logger for high-priority messages
|
410
|
+
priority_logger = SmartMessage::Logger::Default.new(
|
411
|
+
log_file: '/var/log/priority.log',
|
412
|
+
level: Logger::DEBUG
|
413
|
+
)
|
414
|
+
|
415
|
+
urgent_message = PriorityMessage.new(priority: 'urgent', data: {...})
|
416
|
+
urgent_message.logger(priority_logger) # Override for this instance
|
417
|
+
urgent_message.publish
|
418
|
+
```
|
419
|
+
|
420
|
+
## Best Practices
|
421
|
+
|
422
|
+
1. **Use the default logger** unless you have specific requirements
|
423
|
+
2. **Log to STDOUT** in containerized environments
|
424
|
+
3. **Use appropriate log levels** - avoid DEBUG in production
|
425
|
+
4. **Tag your logs** for better searchability
|
426
|
+
5. **Consider structured logging** for production systems
|
427
|
+
6. **Test your logging** - ensure logs are helpful for debugging
|
428
|
+
7. **Monitor log volume** - excessive logging can impact performance
|
429
|
+
8. **Rotate log files** to prevent disk space issues (default logger handles this)
|
430
|
+
|
431
|
+
## Troubleshooting
|
432
|
+
|
433
|
+
### No logs appearing
|
434
|
+
- Check log level settings
|
435
|
+
- Verify file permissions
|
436
|
+
- Ensure logger is configured
|
437
|
+
|
438
|
+
### Too much logging
|
439
|
+
- Increase log level (DEBUG → INFO → WARN → ERROR)
|
440
|
+
- Consider filtering in production
|
441
|
+
|
442
|
+
### Performance issues
|
443
|
+
- Lower log level in production
|
444
|
+
- Use asynchronous logging for high-volume systems
|
445
|
+
- Consider structured logging formats
|
446
|
+
|
447
|
+
### Rails integration not working
|
448
|
+
- Ensure Rails is loaded before SmartMessage
|
449
|
+
- Check that `Rails.logger` is available
|
450
|
+
- Verify Rails environment is set correctly
|
451
|
+
|
452
|
+
For more troubleshooting tips, see the [Troubleshooting Guide](troubleshooting.md).
|
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
|
-
#
|
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
|
-
|
44
|
-
|
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
|
-
#
|
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
|
-
|
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
|
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
|
|
data/examples/.gitignore
ADDED