smart_message 0.0.1

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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +3 -0
  3. data/.gitignore +8 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGELOG.md +100 -0
  6. data/COMMITS.md +196 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +71 -0
  9. data/README.md +303 -0
  10. data/Rakefile +10 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/docs/README.md +52 -0
  14. data/docs/architecture.md +370 -0
  15. data/docs/dispatcher.md +593 -0
  16. data/docs/examples.md +808 -0
  17. data/docs/getting-started.md +235 -0
  18. data/docs/ideas_to_think_about.md +329 -0
  19. data/docs/serializers.md +575 -0
  20. data/docs/transports.md +501 -0
  21. data/docs/troubleshooting.md +582 -0
  22. data/examples/01_point_to_point_orders.rb +200 -0
  23. data/examples/02_publish_subscribe_events.rb +364 -0
  24. data/examples/03_many_to_many_chat.rb +608 -0
  25. data/examples/README.md +335 -0
  26. data/examples/tmux_chat/README.md +283 -0
  27. data/examples/tmux_chat/bot_agent.rb +272 -0
  28. data/examples/tmux_chat/human_agent.rb +197 -0
  29. data/examples/tmux_chat/room_monitor.rb +158 -0
  30. data/examples/tmux_chat/shared_chat_system.rb +295 -0
  31. data/examples/tmux_chat/start_chat_demo.sh +190 -0
  32. data/examples/tmux_chat/stop_chat_demo.sh +22 -0
  33. data/lib/simple_stats.rb +57 -0
  34. data/lib/smart_message/base.rb +284 -0
  35. data/lib/smart_message/dispatcher/.keep +0 -0
  36. data/lib/smart_message/dispatcher.rb +146 -0
  37. data/lib/smart_message/errors.rb +29 -0
  38. data/lib/smart_message/header.rb +20 -0
  39. data/lib/smart_message/logger/base.rb +8 -0
  40. data/lib/smart_message/logger.rb +7 -0
  41. data/lib/smart_message/serializer/base.rb +23 -0
  42. data/lib/smart_message/serializer/json.rb +22 -0
  43. data/lib/smart_message/serializer.rb +10 -0
  44. data/lib/smart_message/transport/base.rb +85 -0
  45. data/lib/smart_message/transport/memory_transport.rb +69 -0
  46. data/lib/smart_message/transport/registry.rb +59 -0
  47. data/lib/smart_message/transport/stdout_transport.rb +62 -0
  48. data/lib/smart_message/transport.rb +41 -0
  49. data/lib/smart_message/version.rb +7 -0
  50. data/lib/smart_message/wrapper.rb +43 -0
  51. data/lib/smart_message.rb +54 -0
  52. data/smart_message.gemspec +53 -0
  53. metadata +252 -0
@@ -0,0 +1,575 @@
1
+ # Serializers
2
+
3
+ Serializers handle the encoding and decoding of message content, transforming Ruby objects into wire formats suitable for transmission and storage.
4
+
5
+ ## Overview
6
+
7
+ Serializers are responsible for:
8
+ - **Encoding**: Converting SmartMessage instances to transmittable formats
9
+ - **Decoding**: Converting received data back to Ruby objects
10
+ - **Format Support**: Handling different data formats (JSON, XML, MessagePack, etc.)
11
+ - **Type Safety**: Ensuring data integrity during conversion
12
+
13
+ ## Built-in Serializers
14
+
15
+ ### JSON Serializer
16
+
17
+ The default serializer that converts messages to/from JSON format.
18
+
19
+ **Features:**
20
+ - Human-readable output
21
+ - Wide compatibility
22
+ - Built on Ruby's standard JSON library
23
+ - Automatic property serialization
24
+
25
+ **Usage:**
26
+
27
+ ```ruby
28
+ # Basic usage
29
+ serializer = SmartMessage::Serializer::JSON.new
30
+
31
+ # Configure in message class
32
+ class UserMessage < SmartMessage::Base
33
+ property :user_id
34
+ property :email
35
+ property :preferences
36
+
37
+ config do
38
+ serializer SmartMessage::Serializer::JSON.new
39
+ end
40
+ end
41
+
42
+ # Manual encoding/decoding
43
+ message = UserMessage.new(user_id: 123, email: "user@example.com")
44
+ encoded = serializer.encode(message)
45
+ # => '{"user_id":123,"email":"user@example.com","preferences":null}'
46
+ ```
47
+
48
+ **Encoding Behavior:**
49
+ - All defined properties are included
50
+ - Nil values are preserved
51
+ - Internal `_sm_` properties are included in serialization
52
+ - Uses Ruby's `#to_json` method under the hood
53
+
54
+ ## Serializer Interface
55
+
56
+ All serializers must implement the `SmartMessage::Serializer::Base` interface:
57
+
58
+ ### Required Methods
59
+
60
+ ```ruby
61
+ class CustomSerializer < SmartMessage::Serializer::Base
62
+ def initialize(options = {})
63
+ @options = options
64
+ # Custom initialization
65
+ end
66
+
67
+ # Convert SmartMessage instance to wire format
68
+ def encode(message_instance)
69
+ # Transform message_instance to your format
70
+ # Return string or binary data
71
+ end
72
+
73
+ # Convert wire format back to hash
74
+ def decode(payload)
75
+ # Transform payload string back to hash
76
+ # Return hash suitable for SmartMessage.new(hash)
77
+ end
78
+ end
79
+ ```
80
+
81
+ ### Example: MessagePack Serializer
82
+
83
+ ```ruby
84
+ require 'msgpack'
85
+
86
+ class MessagePackSerializer < SmartMessage::Serializer::Base
87
+ def encode(message_instance)
88
+ message_instance.to_h.to_msgpack
89
+ end
90
+
91
+ def decode(payload)
92
+ MessagePack.unpack(payload)
93
+ end
94
+ end
95
+
96
+ # Usage
97
+ class BinaryMessage < SmartMessage::Base
98
+ property :data
99
+ property :timestamp
100
+
101
+ config do
102
+ serializer MessagePackSerializer.new
103
+ end
104
+ end
105
+ ```
106
+
107
+ ### Example: XML Serializer
108
+
109
+ ```ruby
110
+ require 'nokogiri'
111
+
112
+ class XMLSerializer < SmartMessage::Serializer::Base
113
+ def encode(message_instance)
114
+ data = message_instance.to_h
115
+ builder = Nokogiri::XML::Builder.new do |xml|
116
+ xml.message do
117
+ data.each do |key, value|
118
+ xml.send(key, value)
119
+ end
120
+ end
121
+ end
122
+ builder.to_xml
123
+ end
124
+
125
+ def decode(payload)
126
+ doc = Nokogiri::XML(payload)
127
+ hash = {}
128
+ doc.xpath('//message/*').each do |node|
129
+ hash[node.name] = node.text
130
+ end
131
+ hash
132
+ end
133
+ end
134
+ ```
135
+
136
+ ## Serialization Patterns
137
+
138
+ ### Type Coercion
139
+
140
+ Handle type conversions during serialization:
141
+
142
+ ```ruby
143
+ class TypedSerializer < SmartMessage::Serializer::Base
144
+ def encode(message_instance)
145
+ data = message_instance.to_h
146
+
147
+ # Convert specific types
148
+ data.transform_values do |value|
149
+ case value
150
+ when Time
151
+ value.iso8601
152
+ when Date
153
+ value.to_s
154
+ when BigDecimal
155
+ value.to_f
156
+ else
157
+ value
158
+ end
159
+ end.to_json
160
+ end
161
+
162
+ def decode(payload)
163
+ data = JSON.parse(payload)
164
+
165
+ # Convert back from strings
166
+ data.transform_values do |value|
167
+ case value
168
+ when /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
169
+ Time.parse(value)
170
+ else
171
+ value
172
+ end
173
+ end
174
+ end
175
+ end
176
+ ```
177
+
178
+ ### Nested Object Serialization
179
+
180
+ Handle complex nested structures:
181
+
182
+ ```ruby
183
+ class NestedSerializer < SmartMessage::Serializer::Base
184
+ def encode(message_instance)
185
+ data = deep_serialize(message_instance.to_h)
186
+ JSON.generate(data)
187
+ end
188
+
189
+ def decode(payload)
190
+ data = JSON.parse(payload)
191
+ deep_deserialize(data)
192
+ end
193
+
194
+ private
195
+
196
+ def deep_serialize(obj)
197
+ case obj
198
+ when Hash
199
+ obj.transform_values { |v| deep_serialize(v) }
200
+ when Array
201
+ obj.map { |v| deep_serialize(v) }
202
+ when SmartMessage::Base
203
+ # Serialize nested messages
204
+ obj.to_h
205
+ else
206
+ obj
207
+ end
208
+ end
209
+
210
+ def deep_deserialize(obj)
211
+ case obj
212
+ when Hash
213
+ obj.transform_values { |v| deep_deserialize(v) }
214
+ when Array
215
+ obj.map { |v| deep_deserialize(v) }
216
+ else
217
+ obj
218
+ end
219
+ end
220
+ end
221
+ ```
222
+
223
+ ## Serialization Options
224
+
225
+ ### Configurable Serializers
226
+
227
+ ```ruby
228
+ class ConfigurableJSONSerializer < SmartMessage::Serializer::Base
229
+ def initialize(options = {})
230
+ @pretty = options[:pretty] || false
231
+ @exclude_nil = options[:exclude_nil] || false
232
+ @date_format = options[:date_format] || :iso8601
233
+ end
234
+
235
+ def encode(message_instance)
236
+ data = message_instance.to_h
237
+
238
+ # Remove nil values if requested
239
+ data = data.compact if @exclude_nil
240
+
241
+ # Format dates
242
+ data = format_dates(data)
243
+
244
+ # Generate JSON
245
+ if @pretty
246
+ JSON.pretty_generate(data)
247
+ else
248
+ JSON.generate(data)
249
+ end
250
+ end
251
+
252
+ private
253
+
254
+ def format_dates(data)
255
+ data.transform_values do |value|
256
+ case value
257
+ when Time, Date
258
+ case @date_format
259
+ when :iso8601
260
+ value.iso8601
261
+ when :unix
262
+ value.to_i
263
+ when :rfc2822
264
+ value.rfc2822
265
+ else
266
+ value.to_s
267
+ end
268
+ else
269
+ value
270
+ end
271
+ end
272
+ end
273
+ end
274
+
275
+ # Usage with options
276
+ class TimestampMessage < SmartMessage::Base
277
+ property :event
278
+ property :timestamp
279
+
280
+ config do
281
+ serializer ConfigurableJSONSerializer.new(
282
+ pretty: true,
283
+ exclude_nil: true,
284
+ date_format: :unix
285
+ )
286
+ end
287
+ end
288
+ ```
289
+
290
+ ## Error Handling
291
+
292
+ ### Serialization Errors
293
+
294
+ Handle encoding/decoding failures gracefully:
295
+
296
+ ```ruby
297
+ class SafeSerializer < SmartMessage::Serializer::Base
298
+ def encode(message_instance)
299
+ JSON.generate(message_instance.to_h)
300
+ rescue JSON::GeneratorError => e
301
+ # Log the error
302
+ puts "Serialization failed: #{e.message}"
303
+
304
+ # Fallback to simple string representation
305
+ message_instance.to_h.to_s
306
+ end
307
+
308
+ def decode(payload)
309
+ JSON.parse(payload)
310
+ rescue JSON::ParserError => e
311
+ # Log the error
312
+ puts "Deserialization failed: #{e.message}"
313
+
314
+ # Return error indicator or empty hash
315
+ { "_error" => "Failed to deserialize: #{e.message}" }
316
+ end
317
+ end
318
+ ```
319
+
320
+ ### Validation During Serialization
321
+
322
+ ```ruby
323
+ class ValidatingSerializer < SmartMessage::Serializer::Base
324
+ def encode(message_instance)
325
+ validate_before_encoding(message_instance)
326
+ JSON.generate(message_instance.to_h)
327
+ end
328
+
329
+ def decode(payload)
330
+ data = JSON.parse(payload)
331
+ validate_after_decoding(data)
332
+ data
333
+ end
334
+
335
+ private
336
+
337
+ def validate_before_encoding(message)
338
+ required_fields = message.class.properties.select do |prop|
339
+ message.class.required?(prop)
340
+ end
341
+
342
+ missing = required_fields.select { |field| message[field].nil? }
343
+
344
+ if missing.any?
345
+ raise "Missing required fields: #{missing.join(', ')}"
346
+ end
347
+ end
348
+
349
+ def validate_after_decoding(data)
350
+ unless data.is_a?(Hash)
351
+ raise "Expected hash, got #{data.class}"
352
+ end
353
+
354
+ # Additional validation logic
355
+ end
356
+ end
357
+ ```
358
+
359
+ ## Performance Considerations
360
+
361
+ ### Binary Serialization
362
+
363
+ For high-performance scenarios, consider binary formats:
364
+
365
+ ```ruby
366
+ class ProtobufSerializer < SmartMessage::Serializer::Base
367
+ def initialize(proto_class)
368
+ @proto_class = proto_class
369
+ end
370
+
371
+ def encode(message_instance)
372
+ proto_obj = @proto_class.new(message_instance.to_h)
373
+ proto_obj.serialize_to_string
374
+ end
375
+
376
+ def decode(payload)
377
+ proto_obj = @proto_class.parse(payload)
378
+ proto_obj.to_h
379
+ end
380
+ end
381
+
382
+ # Usage
383
+ UserProto = Google::Protobuf::DescriptorPool.generated_pool.lookup("User").msgclass
384
+
385
+ class UserMessage < SmartMessage::Base
386
+ property :user_id
387
+ property :name
388
+
389
+ config do
390
+ serializer ProtobufSerializer.new(UserProto)
391
+ end
392
+ end
393
+ ```
394
+
395
+ ### Streaming Serialization
396
+
397
+ For large messages, consider streaming:
398
+
399
+ ```ruby
400
+ class StreamingSerializer < SmartMessage::Serializer::Base
401
+ def encode(message_instance)
402
+ StringIO.new.tap do |io|
403
+ JSON.dump(message_instance.to_h, io)
404
+ end.string
405
+ end
406
+
407
+ def decode(payload)
408
+ StringIO.new(payload).tap do |io|
409
+ JSON.load(io)
410
+ end
411
+ end
412
+ end
413
+ ```
414
+
415
+ ## Compression Support
416
+
417
+ ### Compressed Serialization
418
+
419
+ ```ruby
420
+ class CompressedJSONSerializer < SmartMessage::Serializer::Base
421
+ def encode(message_instance)
422
+ json_data = JSON.generate(message_instance.to_h)
423
+ Zlib::Deflate.deflate(json_data)
424
+ end
425
+
426
+ def decode(payload)
427
+ json_data = Zlib::Inflate.inflate(payload)
428
+ JSON.parse(json_data)
429
+ end
430
+ end
431
+
432
+ # Usage for large messages
433
+ class LargeDataMessage < SmartMessage::Base
434
+ property :dataset
435
+ property :metadata
436
+
437
+ config do
438
+ serializer CompressedJSONSerializer.new
439
+ end
440
+ end
441
+ ```
442
+
443
+ ## Testing Serializers
444
+
445
+ ### Serializer Testing Patterns
446
+
447
+ ```ruby
448
+ RSpec.describe CustomSerializer do
449
+ let(:serializer) { CustomSerializer.new }
450
+ let(:message) do
451
+ TestMessage.new(
452
+ user_id: 123,
453
+ email: "test@example.com",
454
+ created_at: Time.parse("2025-08-17T10:30:00Z")
455
+ )
456
+ end
457
+
458
+ describe "#encode" do
459
+ it "produces valid output" do
460
+ result = serializer.encode(message)
461
+ expect(result).to be_a(String)
462
+ expect(result).not_to be_empty
463
+ end
464
+
465
+ it "includes all properties" do
466
+ result = serializer.encode(message)
467
+ # Format-specific assertions
468
+ end
469
+ end
470
+
471
+ describe "#decode" do
472
+ it "roundtrips correctly" do
473
+ encoded = serializer.encode(message)
474
+ decoded = serializer.decode(encoded)
475
+
476
+ expect(decoded["user_id"]).to eq(123)
477
+ expect(decoded["email"]).to eq("test@example.com")
478
+ end
479
+ end
480
+
481
+ describe "error handling" do
482
+ it "handles invalid input gracefully" do
483
+ expect { serializer.decode("invalid") }.not_to raise_error
484
+ end
485
+ end
486
+ end
487
+ ```
488
+
489
+ ### Mock Serializer for Testing
490
+
491
+ ```ruby
492
+ class MockSerializer < SmartMessage::Serializer::Base
493
+ attr_reader :encoded_messages, :decoded_payloads
494
+
495
+ def initialize
496
+ @encoded_messages = []
497
+ @decoded_payloads = []
498
+ end
499
+
500
+ def encode(message_instance)
501
+ @encoded_messages << message_instance
502
+ "mock_encoded_#{message_instance.object_id}"
503
+ end
504
+
505
+ def decode(payload)
506
+ @decoded_payloads << payload
507
+ { "mock" => "decoded", "payload" => payload }
508
+ end
509
+
510
+ def clear
511
+ @encoded_messages.clear
512
+ @decoded_payloads.clear
513
+ end
514
+ end
515
+ ```
516
+
517
+ ## Common Serialization Issues
518
+
519
+ ### Handling Special Values
520
+
521
+ ```ruby
522
+ class RobustJSONSerializer < SmartMessage::Serializer::Base
523
+ def encode(message_instance)
524
+ data = sanitize_for_json(message_instance.to_h)
525
+ JSON.generate(data)
526
+ end
527
+
528
+ private
529
+
530
+ def sanitize_for_json(obj)
531
+ case obj
532
+ when Hash
533
+ obj.transform_values { |v| sanitize_for_json(v) }
534
+ when Array
535
+ obj.map { |v| sanitize_for_json(v) }
536
+ when Float
537
+ return nil if obj.nan? || obj.infinite?
538
+ obj
539
+ when BigDecimal
540
+ obj.to_f
541
+ when Symbol
542
+ obj.to_s
543
+ when Complex, Rational
544
+ obj.to_f
545
+ else
546
+ obj
547
+ end
548
+ end
549
+ end
550
+ ```
551
+
552
+ ### Character Encoding
553
+
554
+ ```ruby
555
+ class EncodingAwareSerializer < SmartMessage::Serializer::Base
556
+ def encode(message_instance)
557
+ data = message_instance.to_h
558
+ json = JSON.generate(data)
559
+ json.force_encoding('UTF-8')
560
+ end
561
+
562
+ def decode(payload)
563
+ # Ensure proper encoding
564
+ payload = payload.force_encoding('UTF-8')
565
+ JSON.parse(payload)
566
+ end
567
+ end
568
+ ```
569
+
570
+ ## Next Steps
571
+
572
+ - [Custom Serializers](custom-serializers.md) - Build your own serializer
573
+ - [Transports](transports.md) - How serializers work with transports
574
+ - [Message Headers](headers.md) - Understanding message metadata
575
+ - [Examples](examples.md) - Real-world serialization patterns