smart_message 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: efdcf78330a702d97c6c59ec11495c04a42ef746fbe6ed12011711ba127a792c
4
- data.tar.gz: 395ae151a03376eab42036932e6ebbb437ddd0a42659a8ce8aca64fb80af45e7
3
+ metadata.gz: 946ebe27d52785f76921ac75921c49e9a9d6532f8714c4aed3b59a6a0eb7b92d
4
+ data.tar.gz: a40e2fba5460cb900b9ac3fd19689b5a596b069c41c6b17fde7edb7013ef027f
5
5
  SHA512:
6
- metadata.gz: d307903f836b3f1252bc8e6723c09112c4a89ace6dcb1fbf07581e1782b4ab92899ebaa4d00b74e1c615dd75b7e34a754c2adc84c9c1c894c1e5a679862742a7
7
- data.tar.gz: 966a81e21c5348b5c922f068a5edeb5709e73d6ae871eb0c653c76850b7f89ce4d1fde78ff5cd89df03e16cf8558fc6ce40c1949df179ba2f44f977ffe46b718
6
+ metadata.gz: '06813ab254720233a6136f960ddeb2f9b9e919627c8f4f8ff189a7d9749f0295d42ca8a2366021b7d037fe701db681036705b4b7442e32f1ff054c1e58e2487e'
7
+ data.tar.gz: 70d76f12b9fa4e0c2c09bfb2482fbd19acee4418fd173be6163de43e703ecc4d3b05329251bab2da5f9760603dafec2178b186ad10f5c779690160245e5f32ce
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ /*.log
1
2
  /.bundle/
2
3
  /.yardoc
3
4
  /_yardoc/
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- smart_message (0.0.1)
4
+ smart_message (0.0.2)
5
5
  activesupport
6
6
  concurrent-ruby
7
7
  hashie
data/README.md CHANGED
@@ -36,10 +36,10 @@ Or install it yourself as:
36
36
 
37
37
  ```ruby
38
38
  class OrderMessage < SmartMessage::Base
39
- property :order_id
40
- property :customer_id
41
- property :amount
42
- property :items
39
+ property :order_id, description: "Unique order identifier"
40
+ property :customer_id, description: "Customer's unique ID"
41
+ property :amount, description: "Total order amount in dollars"
42
+ property :items, description: "Array of ordered items"
43
43
 
44
44
  # Configure transport and serializer at class level
45
45
  config do
@@ -255,9 +255,9 @@ puts dispatcher.subscribers
255
255
 
256
256
  ```ruby
257
257
  class MyMessage < SmartMessage::Base
258
- property :user_id
259
- property :action
260
- property :timestamp, default: -> { Time.now }
258
+ property :user_id, description: "User's unique identifier"
259
+ property :action, description: "Action performed by the user"
260
+ property :timestamp, default: -> { Time.now }, description: "When the action occurred"
261
261
  end
262
262
 
263
263
  message = MyMessage.new(user_id: 123, action: "login")
@@ -266,6 +266,10 @@ message = MyMessage.new(user_id: 123, action: "login")
266
266
  puts message.user_id
267
267
  puts message.fields # Returns Set of property names (excluding internal _sm_ properties)
268
268
 
269
+ # Access property descriptions
270
+ puts MyMessage.property_description(:user_id) # => "User's unique identifier"
271
+ puts MyMessage.property_descriptions # => Hash of all descriptions
272
+
269
273
  # Access message header
270
274
  puts message._sm_header.uuid
271
275
  puts message._sm_header.message_class
data/docs/README.md CHANGED
@@ -8,14 +8,15 @@ Welcome to the comprehensive documentation for SmartMessage, a Ruby gem that abs
8
8
  - [Installation & Quick Start](getting-started.md)
9
9
  - [Basic Usage Examples](examples.md)
10
10
 
11
- ### Core Concepts
11
+ ### Core Concepts
12
12
  - [Architecture Overview](architecture.md)
13
13
  - [Message Lifecycle](message-lifecycle.md)
14
14
  - [Plugin System](plugin-system.md)
15
15
 
16
16
  ### Components
17
17
  - [SmartMessage::Base](base.md)
18
- - [Transport Layer](transports.md)
18
+ - [Property System](properties.md)
19
+ - [Transport Layer](transports.md)
19
20
  - [Serializers](serializers.md)
20
21
  - [Dispatcher & Routing](dispatcher.md)
21
22
  - [Message Headers](headers.md)
@@ -47,6 +48,6 @@ Welcome to the comprehensive documentation for SmartMessage, a Ruby gem that abs
47
48
 
48
49
  ## Version
49
50
 
50
- This documentation is for SmartMessage v0.0.1.
51
+ This documentation is for SmartMessage v0.0.2.
51
52
 
52
- For older versions, please check the git tags and corresponding documentation.
53
+ For older versions, please check the git tags and corresponding documentation.
@@ -0,0 +1,471 @@
1
+ # SmartMessage Property System
2
+
3
+ The SmartMessage property system builds on Hashie::Dash to provide a robust, declarative way to define message attributes. This document covers all available property options and features.
4
+
5
+ ## Table of Contents
6
+ - [Basic Property Definition](#basic-property-definition)
7
+ - [Class-Level Description](#class-level-description)
8
+ - [Property Options](#property-options)
9
+ - [Accessing Property Information](#accessing-property-information)
10
+ - [Hashie Extensions](#hashie-extensions)
11
+ - [Examples](#examples)
12
+
13
+ ## Basic Property Definition
14
+
15
+ Properties are defined using the `property` method in your message class:
16
+
17
+ ```ruby
18
+ class MyMessage < SmartMessage::Base
19
+ property :field_name
20
+ end
21
+ ```
22
+
23
+ ## Class-Level Description
24
+
25
+ In addition to property-level descriptions, you can add a description for the entire message class:
26
+
27
+ ```ruby
28
+ class OrderMessage < SmartMessage::Base
29
+ description "Handles order processing and fulfillment workflow"
30
+
31
+ property :order_id, description: "Unique order identifier"
32
+ property :amount, description: "Total amount in cents"
33
+ end
34
+
35
+ # Access the class description
36
+ OrderMessage.description # => "Handles order processing and fulfillment workflow"
37
+
38
+ # Class descriptions can also be set after class definition
39
+ class PaymentMessage < SmartMessage::Base
40
+ property :payment_id
41
+ end
42
+
43
+ PaymentMessage.description "Processes payment transactions"
44
+ PaymentMessage.description # => "Processes payment transactions"
45
+
46
+ # Can be set within config block
47
+ class NotificationMessage < SmartMessage::Base
48
+ config do
49
+ description "Sends notifications to users"
50
+ transport MyTransport.new
51
+ serializer MySerializer.new
52
+ end
53
+ end
54
+ ```
55
+
56
+ Class descriptions are useful for:
57
+ - Documenting the overall purpose of a message class
58
+ - Providing context for code generation tools
59
+ - Integration with documentation systems
60
+ - API documentation generation
61
+
62
+ Note: Class descriptions are not inherited by subclasses. Each class maintains its own description.
63
+
64
+ ## Property Options
65
+
66
+ SmartMessage supports all Hashie::Dash property options plus additional features:
67
+
68
+ ### 1. Default Values
69
+
70
+ Specify a default value for a property when not provided during initialization:
71
+
72
+ ```ruby
73
+ class OrderMessage < SmartMessage::Base
74
+ # Static default
75
+ property :status, default: 'pending'
76
+
77
+ # Dynamic default using a Proc
78
+ property :created_at, default: -> { Time.now }
79
+
80
+ # Default array
81
+ property :items, default: []
82
+ end
83
+
84
+ order = OrderMessage.new
85
+ order.status # => 'pending'
86
+ order.created_at # => Current time
87
+ order.items # => []
88
+ ```
89
+
90
+ ### 2. Required Properties
91
+
92
+ Mark properties as required to ensure they're provided during initialization:
93
+
94
+ ```ruby
95
+ class PaymentMessage < SmartMessage::Base
96
+ property :payment_id, required: true
97
+ property :amount, required: true
98
+ property :note # Optional
99
+ end
100
+
101
+ # This raises ArgumentError: The property 'payment_id' is required
102
+ PaymentMessage.new(amount: 100)
103
+
104
+ # This works
105
+ PaymentMessage.new(payment_id: 'PAY-123', amount: 100)
106
+ ```
107
+
108
+ ### 3. Property Transformation
109
+
110
+ Transform property values when they're set:
111
+
112
+ ```ruby
113
+ class UserMessage < SmartMessage::Base
114
+ property :email, transform_with: ->(v) { v.to_s.downcase }
115
+ property :name, transform_with: ->(v) { v.to_s.strip.capitalize }
116
+ property :tags, transform_with: ->(v) { Array(v).map(&:to_s) }
117
+ end
118
+
119
+ user = UserMessage.new(
120
+ email: 'USER@EXAMPLE.COM',
121
+ name: ' john ',
122
+ tags: 'admin'
123
+ )
124
+
125
+ user.email # => 'user@example.com'
126
+ user.name # => 'John'
127
+ user.tags # => ['admin']
128
+ ```
129
+
130
+ ### 4. Property Translation (from Hashie::Extensions::Dash::PropertyTranslation)
131
+
132
+ Map external field names to internal property names:
133
+
134
+ ```ruby
135
+ class ApiMessage < SmartMessage::Base
136
+ property :user_id, from: :userId
137
+ property :order_date, from: 'orderDate'
138
+ property :total_amount, from: [:totalAmount, :total, :amount]
139
+ end
140
+
141
+ # All of these work
142
+ msg1 = ApiMessage.new(userId: 123)
143
+ msg2 = ApiMessage.new(user_id: 123)
144
+ msg3 = ApiMessage.new('orderDate' => '2024-01-01')
145
+ msg4 = ApiMessage.new(totalAmount: 100) # or total: 100, or amount: 100
146
+
147
+ msg1.user_id # => 123
148
+ ```
149
+
150
+ ### 5. Type Coercion (from Hashie::Extensions::Coercion)
151
+
152
+ Automatically coerce property values to specific types:
153
+
154
+ ```ruby
155
+ class TypedMessage < SmartMessage::Base
156
+ property :count
157
+ property :price
158
+ property :active
159
+ property :tags
160
+ property :metadata
161
+
162
+ coerce_key :count, Integer
163
+ coerce_key :price, Float
164
+ coerce_key :active, ->(v) { v.to_s.downcase == 'true' }
165
+ coerce_key :tags, Array[String]
166
+ coerce_key :metadata, Hash
167
+ end
168
+
169
+ msg = TypedMessage.new(
170
+ count: '42',
171
+ price: '19.99',
172
+ active: 'yes',
173
+ tags: 'important',
174
+ metadata: nil
175
+ )
176
+
177
+ msg.count # => 42 (Integer)
178
+ msg.price # => 19.99 (Float)
179
+ msg.active # => false (Boolean logic)
180
+ msg.tags # => ['important'] (Array)
181
+ msg.metadata # => {} (Hash)
182
+ ```
183
+
184
+ ### 6. Property Descriptions (SmartMessage Enhancement)
185
+
186
+ Add human-readable descriptions to document your properties for dynamic LLM integration:
187
+
188
+ ```ruby
189
+ class DocumentedMessage < SmartMessage::Base
190
+ property :transaction_id,
191
+ required: true,
192
+ description: "Unique identifier for the transaction"
193
+
194
+ property :amount,
195
+ transform_with: ->(v) { BigDecimal(v.to_s) },
196
+ description: "Transaction amount in the smallest currency unit"
197
+
198
+ property :currency,
199
+ default: 'USD',
200
+ description: "ISO 4217 currency code"
201
+
202
+ property :status,
203
+ default: 'pending',
204
+ description: "Current transaction status: pending, completed, failed"
205
+
206
+ property :metadata,
207
+ default: {},
208
+ description: "Additional transaction metadata as key-value pairs"
209
+ end
210
+
211
+ # Access descriptions programmatically
212
+ DocumentedMessage.property_description(:amount)
213
+ # => "Transaction amount in the smallest currency unit"
214
+
215
+ DocumentedMessage.property_descriptions
216
+ # => {
217
+ # transaction_id: "Unique identifier for the transaction",
218
+ # amount: "Transaction amount in the smallest currency unit",
219
+ # currency: "ISO 4217 currency code",
220
+ # status: "Current transaction status: pending, completed, failed",
221
+ # metadata: "Additional transaction metadata as key-value pairs"
222
+ # }
223
+
224
+ DocumentedMessage.described_properties
225
+ # => [:transaction_id, :amount, :currency, :status, :metadata]
226
+ ```
227
+
228
+ ## Accessing Property Information
229
+
230
+ SmartMessage provides several methods to introspect properties:
231
+
232
+ ```ruby
233
+ class IntrospectionExample < SmartMessage::Base
234
+ property :id, required: true, description: "Unique identifier"
235
+ property :name, description: "Display name"
236
+ property :created_at, default: -> { Time.now }
237
+ property :tags
238
+ end
239
+
240
+ # Instance methods
241
+ instance = IntrospectionExample.new(id: 1, name: "Test")
242
+ instance.fields # => Set[:id, :name, :created_at, :tags]
243
+ instance.to_h # => Hash of all properties and values
244
+
245
+ # Class methods
246
+ IntrospectionExample.fields # => Set[:id, :name, :created_at, :tags]
247
+ IntrospectionExample.property_descriptions # => Hash of descriptions
248
+ IntrospectionExample.described_properties # => [:id, :name]
249
+ ```
250
+
251
+ ## Hashie Extensions
252
+
253
+ SmartMessage::Base automatically includes these Hashie extensions:
254
+
255
+ ### 1. DeepMerge
256
+ Allows deep merging of nested hash properties:
257
+
258
+ ```ruby
259
+ msg = MyMessage.new(config: { a: 1, b: { c: 2 } })
260
+ msg.deep_merge(config: { b: { d: 3 } })
261
+ # => config: { a: 1, b: { c: 2, d: 3 } }
262
+ ```
263
+
264
+ ### 2. IgnoreUndeclared
265
+ Silently ignores properties that haven't been declared:
266
+
267
+ ```ruby
268
+ # Won't raise an error for unknown properties
269
+ msg = MyMessage.new(known: 'value', unknown: 'ignored')
270
+ ```
271
+
272
+ ### 3. IndifferentAccess
273
+ Access properties with strings or symbols:
274
+
275
+ ```ruby
276
+ msg = MyMessage.new('name' => 'John')
277
+ msg[:name] # => 'John'
278
+ msg['name'] # => 'John'
279
+ msg.name # => 'John'
280
+ ```
281
+
282
+ ### 4. MethodAccess
283
+ Access properties as methods:
284
+
285
+ ```ruby
286
+ msg = MyMessage.new(name: 'John')
287
+ msg.name # => 'John'
288
+ msg.name = 'Jane'
289
+ msg.name # => 'Jane'
290
+ ```
291
+
292
+ ### 5. MergeInitializer
293
+ Allows initializing with merged hash values:
294
+
295
+ ```ruby
296
+ defaults = { status: 'active', retries: 3 }
297
+ msg = MyMessage.new(defaults.merge(status: 'pending'))
298
+ # => status: 'pending', retries: 3
299
+ ```
300
+
301
+ ## Examples
302
+
303
+ ### Complete Example: Order Processing Message
304
+
305
+ ```ruby
306
+ class OrderProcessingMessage < SmartMessage::Base
307
+ description "Manages the complete order lifecycle from placement to delivery"
308
+
309
+ # Required fields with descriptions
310
+ property :order_id,
311
+ required: true,
312
+ description: "Unique order identifier from the ordering system"
313
+
314
+ property :customer_id,
315
+ required: true,
316
+ description: "Customer who placed the order"
317
+
318
+ # Amount with transformation and description
319
+ property :total_amount,
320
+ transform_with: ->(v) { BigDecimal(v.to_s) },
321
+ description: "Total order amount including tax and shipping"
322
+
323
+ # Status with default and validation description
324
+ property :status,
325
+ default: 'pending',
326
+ description: "Order status: pending, processing, shipped, delivered, cancelled"
327
+
328
+ # Items with coercion
329
+ property :items,
330
+ default: [],
331
+ description: "Array of order line items"
332
+
333
+ # Timestamps with dynamic defaults
334
+ property :created_at,
335
+ default: -> { Time.now },
336
+ description: "When the order was created"
337
+
338
+ property :updated_at,
339
+ default: -> { Time.now },
340
+ description: "Last modification timestamp"
341
+
342
+ # Optional fields
343
+ property :notes,
344
+ description: "Optional order notes or special instructions"
345
+
346
+ property :shipping_address,
347
+ description: "Shipping address as a nested hash"
348
+
349
+ # Field translation for external APIs
350
+ property :external_ref,
351
+ from: [:externalReference, :ext_ref],
352
+ description: "Reference ID from external system"
353
+ end
354
+
355
+ # Usage
356
+ order = OrderProcessingMessage.new(
357
+ order_id: 'ORD-2024-001',
358
+ customer_id: 'CUST-123',
359
+ total_amount: '149.99',
360
+ items: [
361
+ { sku: 'WIDGET-A', quantity: 2, price: 49.99 },
362
+ { sku: 'WIDGET-B', quantity: 1, price: 50.01 }
363
+ ],
364
+ shipping_address: {
365
+ street: '123 Main St',
366
+ city: 'Springfield',
367
+ state: 'IL',
368
+ zip: '62701'
369
+ },
370
+ externalReference: 'EXT-789' # Note: uses translated field name
371
+ )
372
+
373
+ # Access properties
374
+ order.order_id # => 'ORD-2024-001'
375
+ order.total_amount # => BigDecimal('149.99')
376
+ order.status # => 'pending' (default)
377
+ order.external_ref # => 'EXT-789' (translated)
378
+ order.created_at # => Time object
379
+
380
+ # Get class and property information
381
+ OrderProcessingMessage.description
382
+ # => "Manages the complete order lifecycle from placement to delivery"
383
+
384
+ OrderProcessingMessage.property_description(:total_amount)
385
+ # => "Total order amount including tax and shipping"
386
+
387
+ OrderProcessingMessage.property_descriptions.keys
388
+ # => [:order_id, :customer_id, :total_amount, :status, :items, ...]
389
+ ```
390
+
391
+ ### Example: API Integration Message
392
+
393
+ ```ruby
394
+ class ApiWebhookMessage < SmartMessage::Base
395
+ # Handle different API naming conventions
396
+ property :event_type,
397
+ from: [:eventType, :event, :type],
398
+ required: true,
399
+ description: "Type of webhook event"
400
+
401
+ property :payload,
402
+ required: true,
403
+ description: "Event payload data"
404
+
405
+ property :timestamp,
406
+ from: [:timestamp, :created_at, :occurredAt],
407
+ transform_with: ->(v) { Time.parse(v.to_s) },
408
+ description: "When the event occurred"
409
+
410
+ property :retry_count,
411
+ from: :retryCount,
412
+ default: 0,
413
+ transform_with: ->(v) { v.to_i },
414
+ description: "Number of delivery attempts"
415
+
416
+ property :signature,
417
+ description: "HMAC signature for webhook validation"
418
+ end
419
+
420
+ # Can initialize with various field names
421
+ webhook1 = ApiWebhookMessage.new(
422
+ eventType: 'order.completed',
423
+ payload: { order_id: 123 },
424
+ occurredAt: '2024-01-01T10:00:00Z',
425
+ retryCount: '2'
426
+ )
427
+
428
+ webhook2 = ApiWebhookMessage.new(
429
+ type: 'order.completed', # Alternative field name
430
+ payload: { order_id: 123 },
431
+ timestamp: Time.now, # Alternative field name
432
+ retry_count: 2 # Internal field name
433
+ )
434
+ ```
435
+
436
+ ## Best Practices
437
+
438
+ 1. **Always add descriptions** to document the purpose and format of each property
439
+ 2. **Use required properties** for fields that must be present for valid messages
440
+ 3. **Provide sensible defaults** for optional fields to reduce boilerplate
441
+ 4. **Use transformations** to ensure data consistency and type safety
442
+ 5. **Leverage field translation** when integrating with external APIs that use different naming conventions
443
+ 6. **Document valid values** in descriptions for enum-like fields (e.g., status fields)
444
+ 7. **Use type coercion** for fields that may come from untrusted sources (like HTTP parameters)
445
+
446
+ ## Property Option Compatibility
447
+
448
+ Multiple options can be combined on a single property:
449
+
450
+ ```ruby
451
+ property :amount,
452
+ required: true,
453
+ from: [:amount, :total, :value],
454
+ transform_with: ->(v) { BigDecimal(v.to_s) },
455
+ description: "Transaction amount in cents"
456
+ ```
457
+
458
+ The processing order is:
459
+ 1. Field translation (from)
460
+ 2. Default value (if not provided)
461
+ 3. Required validation
462
+ 4. Type coercion
463
+ 5. Transformation
464
+ 6. Value assignment
465
+
466
+ ## Limitations
467
+
468
+ - Property names must be valid Ruby method names
469
+ - The `_sm_` prefix is reserved for internal SmartMessage properties
470
+ - Descriptions are metadata only and don't affect runtime behavior
471
+ - Some Hashie options may conflict if used incorrectly (e.g., required with default)
@@ -5,6 +5,7 @@
5
5
  require 'securerandom' # STDLIB
6
6
 
7
7
  require_relative './wrapper.rb'
8
+ require_relative './property_descriptions.rb'
8
9
 
9
10
  module SmartMessage
10
11
  # The foundation class for the smart message
@@ -18,6 +19,8 @@ module SmartMessage
18
19
 
19
20
  include Hashie::Extensions::Dash::PropertyTranslation
20
21
 
22
+ include SmartMessage::PropertyDescriptions
23
+
21
24
  include Hashie::Extensions::Coercion
22
25
  include Hashie::Extensions::DeepMerge
23
26
  include Hashie::Extensions::IgnoreUndeclared
@@ -162,6 +165,17 @@ module SmartMessage
162
165
 
163
166
  class << self
164
167
 
168
+ #########################################################
169
+ ## class-level description
170
+
171
+ def description(desc = nil)
172
+ if desc.nil?
173
+ @description
174
+ else
175
+ @description = desc.to_s
176
+ end
177
+ end
178
+
165
179
  #########################################################
166
180
  ## class-level configuration
167
181
 
@@ -0,0 +1,41 @@
1
+ # lib/smart_message/property_descriptions.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ module SmartMessage
6
+ module PropertyDescriptions
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ base.class_eval do
10
+ @property_descriptions = {}
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+ def property(property_name, options = {})
16
+ description = options.delete(:description)
17
+
18
+ # Store description if provided
19
+ if description
20
+ @property_descriptions ||= {}
21
+ @property_descriptions[property_name.to_sym] = description
22
+ end
23
+
24
+ # Call original property method
25
+ super(property_name, options)
26
+ end
27
+
28
+ def property_description(property_name)
29
+ @property_descriptions&.[](property_name.to_sym)
30
+ end
31
+
32
+ def property_descriptions
33
+ @property_descriptions&.dup || {}
34
+ end
35
+
36
+ def described_properties
37
+ @property_descriptions&.keys || []
38
+ end
39
+ end
40
+ end
41
+ end
@@ -3,5 +3,5 @@
3
3
  # frozen_string_literal: true
4
4
 
5
5
  module SmartMessage
6
- VERSION = "0.0.1"
6
+ VERSION = "0.0.2"
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_message
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
@@ -190,6 +190,7 @@ files:
190
190
  - docs/examples.md
191
191
  - docs/getting-started.md
192
192
  - docs/ideas_to_think_about.md
193
+ - docs/properties.md
193
194
  - docs/serializers.md
194
195
  - docs/transports.md
195
196
  - docs/troubleshooting.md
@@ -213,6 +214,7 @@ files:
213
214
  - lib/smart_message/header.rb
214
215
  - lib/smart_message/logger.rb
215
216
  - lib/smart_message/logger/base.rb
217
+ - lib/smart_message/property_descriptions.rb
216
218
  - lib/smart_message/serializer.rb
217
219
  - lib/smart_message/serializer/base.rb
218
220
  - lib/smart_message/serializer/json.rb