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,69 @@
1
+ # lib/smart_message/transport/memory_transport.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ module SmartMessage
6
+ module Transport
7
+ # In-memory transport for testing and local development
8
+ # Messages are stored in memory and can be synchronously processed
9
+ class MemoryTransport < Base
10
+ attr_reader :messages
11
+
12
+ def default_options
13
+ {
14
+ auto_process: true,
15
+ max_messages: 1000
16
+ }
17
+ end
18
+
19
+ def configure
20
+ @messages = []
21
+ @message_mutex = Mutex.new
22
+ end
23
+
24
+ # Publish message to memory queue
25
+ def publish(message_header, message_payload)
26
+ @message_mutex.synchronize do
27
+ # Prevent memory overflow
28
+ @messages.shift if @messages.size >= @options[:max_messages]
29
+
30
+ @messages << {
31
+ header: message_header,
32
+ payload: message_payload,
33
+ published_at: Time.now
34
+ }
35
+ end
36
+
37
+ # Auto-process if enabled
38
+ receive(message_header, message_payload) if @options[:auto_process]
39
+ end
40
+
41
+ # Get all stored messages
42
+ def all_messages
43
+ @message_mutex.synchronize { @messages.dup }
44
+ end
45
+
46
+ # Get message count
47
+ def message_count
48
+ @message_mutex.synchronize { @messages.size }
49
+ end
50
+
51
+ # Clear all messages
52
+ def clear_messages
53
+ @message_mutex.synchronize { @messages.clear }
54
+ end
55
+
56
+ # Process all pending messages
57
+ def process_all
58
+ messages_to_process = @message_mutex.synchronize { @messages.dup }
59
+ messages_to_process.each do |msg|
60
+ receive(msg[:header], msg[:payload])
61
+ end
62
+ end
63
+
64
+ def connected?
65
+ true
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,59 @@
1
+ # lib/smart_message/transport/registry.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ module SmartMessage
6
+ module Transport
7
+ # Registry for transport implementations
8
+ # Provides discovery and management of available transport adapters
9
+ class Registry
10
+ def initialize
11
+ @transports = {}
12
+ register_builtin_transports
13
+ end
14
+
15
+ # Register a transport class
16
+ # @param name [Symbol, String] Transport identifier
17
+ # @param transport_class [Class] Transport implementation class
18
+ def register(name, transport_class)
19
+ unless transport_class.ancestors.include?(SmartMessage::Transport::Base)
20
+ raise ArgumentError, "Transport must inherit from SmartMessage::Transport::Base"
21
+ end
22
+ @transports[name.to_sym] = transport_class
23
+ end
24
+
25
+ # Get a transport class by name
26
+ # @param name [Symbol, String] Transport identifier
27
+ # @return [Class, nil] Transport class or nil if not found
28
+ def get(name)
29
+ @transports[name.to_sym]
30
+ end
31
+
32
+ # Check if a transport is registered
33
+ # @param name [Symbol, String] Transport identifier
34
+ # @return [Boolean]
35
+ def registered?(name)
36
+ @transports.key?(name.to_sym)
37
+ end
38
+
39
+ # List all registered transport names
40
+ # @return [Array<Symbol>] List of transport identifiers
41
+ def list
42
+ @transports.keys
43
+ end
44
+
45
+ # Clear all registered transports
46
+ def clear
47
+ @transports.clear
48
+ end
49
+
50
+ private
51
+
52
+ def register_builtin_transports
53
+ # Register built-in transports
54
+ register(:stdout, SmartMessage::Transport::StdoutTransport)
55
+ register(:memory, SmartMessage::Transport::MemoryTransport)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,62 @@
1
+ # lib/smart_message/transport/stdout_transport.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ module SmartMessage
6
+ module Transport
7
+ # STDOUT transport for testing and development
8
+ # This transport outputs messages to STDOUT and optionally loops them back
9
+ class StdoutTransport < Base
10
+ def default_options
11
+ {
12
+ loopback: false,
13
+ output: $stdout
14
+ }
15
+ end
16
+
17
+ def configure
18
+ @output = @options[:output].is_a?(String) ? File.open(@options[:output], 'w') : @options[:output]
19
+ end
20
+
21
+ # Enable/disable loopback mode
22
+ def loopback=(enabled)
23
+ @options[:loopback] = enabled
24
+ end
25
+
26
+ def loopback?
27
+ @options[:loopback]
28
+ end
29
+
30
+ # Publish message to STDOUT
31
+ def publish(message_header, message_payload)
32
+ @output.puts format_message(message_header, message_payload)
33
+ @output.flush
34
+
35
+ # If loopback is enabled, route the message back through the dispatcher
36
+ receive(message_header, message_payload) if loopback?
37
+ end
38
+
39
+ def connected?
40
+ !@output.closed?
41
+ end
42
+
43
+ def disconnect
44
+ @output.close if @output.respond_to?(:close) && @output != $stdout && @output != $stderr
45
+ end
46
+
47
+ private
48
+
49
+ def format_message(message_header, message_payload)
50
+ <<~MESSAGE
51
+
52
+ ===================================================
53
+ == SmartMessage Published via STDOUT Transport
54
+ == Header: #{message_header.inspect}
55
+ == Payload: #{message_payload}
56
+ ===================================================
57
+
58
+ MESSAGE
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,41 @@
1
+ # lib/smart_message/transport.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'transport/base'
6
+ require_relative 'transport/registry'
7
+ require_relative 'transport/stdout_transport'
8
+ require_relative 'transport/memory_transport'
9
+
10
+ module SmartMessage
11
+ # Transport layer abstraction for SmartMessage
12
+ module Transport
13
+ class << self
14
+ # Get the transport registry instance
15
+ def registry
16
+ @registry ||= Registry.new
17
+ end
18
+
19
+ # Register a transport adapter
20
+ def register(name, transport_class)
21
+ registry.register(name, transport_class)
22
+ end
23
+
24
+ # Get a transport by name
25
+ def get(name)
26
+ registry.get(name)
27
+ end
28
+
29
+ # Create a transport instance with options
30
+ def create(name, **options)
31
+ transport_class = get(name)
32
+ transport_class&.new(**options)
33
+ end
34
+
35
+ # List all registered transports
36
+ def available
37
+ registry.list
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,7 @@
1
+ # lib/smart_message/version.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ module SmartMessage
6
+ VERSION = "0.0.1"
7
+ end
@@ -0,0 +1,43 @@
1
+ # lib/smart_message/wrapper.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ # TODO: consider having a serializer plugin for the wrapper that is
6
+ # different than that used for the payload.
7
+
8
+ require_relative './header.rb'
9
+
10
+ module SmartMessage
11
+ module Wrapper
12
+ # Every smart message has a common wrapper format that contains
13
+ # information used to support the dispatching of subscribed
14
+ # messages upon receipt from a transport as well as the serialized
15
+ # payload.
16
+ class Base < Hashie::Dash
17
+ include Hashie::Extensions::IndifferentAccess
18
+ include Hashie::Extensions::MergeInitializer
19
+ include Hashie::Extensions::MethodAccess
20
+
21
+ # Common attributes of the smart message standard header
22
+ # Using '_sm_' as a prefix to avoid potential collision with
23
+ # a user's message definition.
24
+ property :_sm_header
25
+ property :_sm_payload
26
+
27
+ def initialize(**props, &block)
28
+ attributes = {
29
+ _sm_header: SmartMessage::Header.new(
30
+ uuid: SecureRandom.uuid,
31
+ message_class: self.class.to_s,
32
+ published_at: 2,
33
+ publisher_pid: 3
34
+ )
35
+ }.merge(props)
36
+
37
+ super(attributes, &block)
38
+ end
39
+
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,54 @@
1
+ # lib/smart_message.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+
5
+ # FIXME: handle this better
6
+ class MilClass # IMO nil is the same as an empty String
7
+ def to_s
8
+ ''
9
+ end
10
+ end
11
+
12
+
13
+ require 'active_support/core_ext/string/inflections'
14
+ require 'date' # STDLIB
15
+
16
+ # Production logging should use the logger framework, not debug_me
17
+
18
+ require 'hashie' # Your friendly neighborhood hash library.
19
+
20
+ require_relative './simple_stats'
21
+
22
+ require_relative './smart_message/version'
23
+ require_relative './smart_message/errors'
24
+
25
+ require_relative './smart_message/dispatcher.rb'
26
+ require_relative './smart_message/transport.rb'
27
+ require_relative './smart_message/base.rb'
28
+
29
+ # SmartMessage abstracts messages from the backend transport process
30
+ module SmartMessage
31
+ # The super class of all smart messages
32
+ # class Base < Dash from the Hashie gem plus mixins
33
+ # end
34
+
35
+ # encapsulates the message transport plugin
36
+ # module Transport is defined in transport.rb
37
+
38
+ # encapsulates the message code/decode serializer
39
+ module Serializer
40
+ # the super class of the message serializer
41
+ class Base
42
+ end
43
+ end
44
+
45
+ # encapsulates the message logging capability
46
+ module Logger
47
+ # the super class of the message logger
48
+ class Base
49
+ end
50
+ end
51
+ end # module SmartMessage
52
+
53
+ require_relative './smart_message/serializer'
54
+ require_relative './smart_message/logger'
@@ -0,0 +1,53 @@
1
+ # smart_message.gemspec
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'smart_message/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'smart_message'
10
+ spec.version = SmartMessage::VERSION
11
+ spec.authors = ['Dewayne VanHoozer']
12
+ spec.email = ['dvanhoozer@gmail.com']
13
+
14
+ spec.homepage = "https://github.com/MadBomber/smart_message"
15
+ spec.summary = %q{An abstraction to protect message content from the backend delivery transport.}
16
+ spec.description = <<~DESCRIPTION
17
+ Much like ActiveRecord abstracts the model as an ORM from the
18
+ backend data-store, SmartMessage abstracts the message from
19
+ its backend transport processes.
20
+ DESCRIPTION
21
+
22
+ spec.metadata = {
23
+ "homepage_uri" => spec.homepage,
24
+ "source_code_uri" => "https://github.com/MadBomber/smart_message",
25
+ "changelog_uri" => "https://github.com/MadBomber/smart_message/blob/master/CHANGELOG.md",
26
+ "bug_tracker_uri" => "https://github.com/MadBomber/smart_message/issues",
27
+ "documentation_uri" => "https://github.com/MadBomber/smart_message/blob/master/README.md"
28
+ }
29
+
30
+ # Specify which files should be added to the gem when it is released.
31
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
32
+ spec.files = Dir.chdir(File.expand_path(__dir__, __FILE__)) do
33
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
34
+ end
35
+ spec.require_paths = ['lib']
36
+
37
+ spec.required_ruby_version = '>= 3.0.0'
38
+
39
+ spec.add_dependency 'hashie'
40
+ spec.add_dependency 'activesupport'
41
+ spec.add_dependency 'concurrent-ruby'
42
+
43
+ spec.add_development_dependency 'bundler'
44
+ spec.add_development_dependency 'rake'
45
+ spec.add_development_dependency 'minitest'
46
+ spec.add_development_dependency 'mutex_m'
47
+ spec.add_development_dependency 'shoulda'
48
+ spec.add_development_dependency 'minitest-power_assert'
49
+
50
+ spec.add_development_dependency 'amazing_print'
51
+ spec.add_development_dependency 'debug_me'
52
+
53
+ end
metadata ADDED
@@ -0,0 +1,252 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smart_message
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Dewayne VanHoozer
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: hashie
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: activesupport
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: concurrent-ruby
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: bundler
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rake
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: minitest
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: mutex_m
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: shoulda
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: minitest-power_assert
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ - !ruby/object:Gem::Dependency
139
+ name: amazing_print
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ - !ruby/object:Gem::Dependency
153
+ name: debug_me
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ description: |
167
+ Much like ActiveRecord abstracts the model as an ORM from the
168
+ backend data-store, SmartMessage abstracts the message from
169
+ its backend transport processes.
170
+ email:
171
+ - dvanhoozer@gmail.com
172
+ executables: []
173
+ extensions: []
174
+ extra_rdoc_files: []
175
+ files:
176
+ - ".envrc"
177
+ - ".gitignore"
178
+ - ".travis.yml"
179
+ - CHANGELOG.md
180
+ - COMMITS.md
181
+ - Gemfile
182
+ - Gemfile.lock
183
+ - README.md
184
+ - Rakefile
185
+ - bin/console
186
+ - bin/setup
187
+ - docs/README.md
188
+ - docs/architecture.md
189
+ - docs/dispatcher.md
190
+ - docs/examples.md
191
+ - docs/getting-started.md
192
+ - docs/ideas_to_think_about.md
193
+ - docs/serializers.md
194
+ - docs/transports.md
195
+ - docs/troubleshooting.md
196
+ - examples/01_point_to_point_orders.rb
197
+ - examples/02_publish_subscribe_events.rb
198
+ - examples/03_many_to_many_chat.rb
199
+ - examples/README.md
200
+ - examples/tmux_chat/README.md
201
+ - examples/tmux_chat/bot_agent.rb
202
+ - examples/tmux_chat/human_agent.rb
203
+ - examples/tmux_chat/room_monitor.rb
204
+ - examples/tmux_chat/shared_chat_system.rb
205
+ - examples/tmux_chat/start_chat_demo.sh
206
+ - examples/tmux_chat/stop_chat_demo.sh
207
+ - lib/simple_stats.rb
208
+ - lib/smart_message.rb
209
+ - lib/smart_message/base.rb
210
+ - lib/smart_message/dispatcher.rb
211
+ - lib/smart_message/dispatcher/.keep
212
+ - lib/smart_message/errors.rb
213
+ - lib/smart_message/header.rb
214
+ - lib/smart_message/logger.rb
215
+ - lib/smart_message/logger/base.rb
216
+ - lib/smart_message/serializer.rb
217
+ - lib/smart_message/serializer/base.rb
218
+ - lib/smart_message/serializer/json.rb
219
+ - lib/smart_message/transport.rb
220
+ - lib/smart_message/transport/base.rb
221
+ - lib/smart_message/transport/memory_transport.rb
222
+ - lib/smart_message/transport/registry.rb
223
+ - lib/smart_message/transport/stdout_transport.rb
224
+ - lib/smart_message/version.rb
225
+ - lib/smart_message/wrapper.rb
226
+ - smart_message.gemspec
227
+ homepage: https://github.com/MadBomber/smart_message
228
+ licenses: []
229
+ metadata:
230
+ homepage_uri: https://github.com/MadBomber/smart_message
231
+ source_code_uri: https://github.com/MadBomber/smart_message
232
+ changelog_uri: https://github.com/MadBomber/smart_message/blob/master/CHANGELOG.md
233
+ bug_tracker_uri: https://github.com/MadBomber/smart_message/issues
234
+ documentation_uri: https://github.com/MadBomber/smart_message/blob/master/README.md
235
+ rdoc_options: []
236
+ require_paths:
237
+ - lib
238
+ required_ruby_version: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - ">="
241
+ - !ruby/object:Gem::Version
242
+ version: 3.0.0
243
+ required_rubygems_version: !ruby/object:Gem::Requirement
244
+ requirements:
245
+ - - ">="
246
+ - !ruby/object:Gem::Version
247
+ version: '0'
248
+ requirements: []
249
+ rubygems_version: 3.7.1
250
+ specification_version: 4
251
+ summary: An abstraction to protect message content from the backend delivery transport.
252
+ test_files: []