deimos-ruby 2.0.17 → 2.1.0

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +2 -0
  4. data/karafka.rb +1 -0
  5. data/lib/deimos/schema_class/enum.rb +1 -1
  6. data/lib/deimos/schema_class/record.rb +19 -2
  7. data/lib/deimos/test_helpers.rb +18 -6
  8. data/lib/deimos/utils/schema_class.rb +1 -1
  9. data/lib/deimos/version.rb +1 -1
  10. data/lib/generators/deimos/schema_class/templates/schema_record.rb.tt +3 -2
  11. data/lib/generators/deimos/schema_class_generator.rb +3 -3
  12. data/spec/batch_consumer_spec.rb +14 -12
  13. data/spec/consumer_spec.rb +51 -5
  14. data/spec/schemas/my_namespace/generated.rb +2 -2
  15. data/spec/schemas/my_namespace/my_nested_schema.rb +2 -2
  16. data/spec/schemas/my_namespace/my_schema.rb +2 -2
  17. data/spec/schemas/my_namespace/my_schema_with_circular_reference.rb +1 -1
  18. data/spec/schemas/my_namespace/my_schema_with_complex_type.rb +9 -9
  19. data/spec/schemas/my_namespace/my_schema_with_union_type.rb +38 -23
  20. data/spec/snapshots/consumers-no-nest.snap +16 -15
  21. data/spec/snapshots/consumers.snap +16 -15
  22. data/spec/snapshots/consumers_and_producers-no-nest.snap +18 -17
  23. data/spec/snapshots/consumers_and_producers.snap +18 -17
  24. data/spec/snapshots/consumers_circular-no-nest.snap +16 -15
  25. data/spec/snapshots/consumers_circular.snap +16 -15
  26. data/spec/snapshots/consumers_complex_types-no-nest.snap +16 -15
  27. data/spec/snapshots/consumers_complex_types.snap +16 -15
  28. data/spec/snapshots/consumers_nested-no-nest.snap +16 -15
  29. data/spec/snapshots/consumers_nested.snap +16 -15
  30. data/spec/snapshots/namespace_folders.snap +18 -17
  31. data/spec/snapshots/namespace_map.snap +18 -17
  32. data/spec/snapshots/producers_with_key-no-nest.snap +18 -17
  33. data/spec/snapshots/producers_with_key.snap +18 -17
  34. data/spec/spec_helper.rb +1 -1
  35. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6b4516c780cd6c4acfe46092cf4ff7342a3db20b5d259f6fee59e85c2dd6c01
4
- data.tar.gz: 02347f3bb35d9cd1c0435f097d453c6f7d90bccee6892d637954c862155164fd
3
+ metadata.gz: 4e9d857f4fd1fec065ad606bdda5aa5f157e91f69500708b60b3944636f178b8
4
+ data.tar.gz: 8652654cd562f1205713a485f3d6fd747e785dab31457643d0d3aaa8af8f9d20
5
5
  SHA512:
6
- metadata.gz: 8b1c2de2fa812f2385b07f3e9fde5135e734571f5bb34d96dd31536ebaf7576c3acdccc7df03098632d549e264d8ef1a5d8f2589995287dda4628dbd535a1840
7
- data.tar.gz: 600268d4111cb77b2f4c06a6e07ee95d7e7e4387c298a6099c4498f247a64a443426d639c09b1e30455e3f74ebca7a3790ca5b54abcf34d3ab8918811f58a765
6
+ metadata.gz: 112a711a7e4c6e596f0c78785b055538f0570296ccbc62aea233c18fb417127bf4ff63154f7555a1e155ed6435e359cc452d8748c041287e59e14ac84aa1dfae
7
+ data.tar.gz: 1a13b9ddfaeeca69a59b5be6a54cea67b5a5dd8088c13c4fa65d5e98df287a70f21a45ac2d5b4709a9583b3e901aec84d9250d435ee705032be5e461dd2d3781
data/CHANGELOG.md CHANGED
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## UNRELEASED
9
9
 
10
+ ## 2.1.0 - 2025-07-17
11
+
12
+ - ***BREAKING CHANGE***: Allow schema classes to be used with backwards-compatible Avro messages. In order to update to this version, you will need to regenerate your schema classes using the `deimos:generate_schema_classes` generator.
13
+
10
14
  ## 2.0.17 - 2025-07-03
11
15
 
12
16
  - Feature: Allow producers to specify `:message` and `:key` in the `publish` methods.
data/README.md CHANGED
@@ -1083,6 +1083,8 @@ decoded = Deimos.decode(schema: 'MySchema', namespace: 'com.my-namespace', paylo
1083
1083
 
1084
1084
  Bug reports and pull requests are welcome on GitHub at https://github.com/flipp-oss/deimos .
1085
1085
 
1086
+ If making changes to the generator, you should regenerate the test schema classes by running `./regenerate_test_schema_classes.rb` .
1087
+
1086
1088
  You can/should re-generate RBS types when methods or classes change by running the following:
1087
1089
 
1088
1090
  rbs collection install # if you haven't done it
data/karafka.rb ADDED
@@ -0,0 +1 @@
1
+ # blank load file, used in regenerate_test_schema_classes.rb
@@ -39,7 +39,7 @@ module Deimos
39
39
  end
40
40
 
41
41
  # @return [SchemaClass::Enum]
42
- def self.initialize_from_value(value)
42
+ def self.initialize_from_value(value, from_message: false)
43
43
  return nil if value.nil?
44
44
 
45
45
  value.is_a?(self) ? value : self.new(value)
@@ -72,11 +72,28 @@ module Deimos
72
72
  validator.schema_fields.map(&:name)
73
73
  end
74
74
 
75
+ # Used internally within Deimos so that we don't crash on unknown fields that come from
76
+ # a backwards compatible schema.
77
+ # @param kwargs [Hash] the attributes to set on the new object.
75
78
  # @return [SchemaClass::Record]
76
- def self.initialize_from_value(value)
79
+ def self.new_from_message(**kwargs)
80
+ @from_message = true
81
+ record = self.new
82
+ attrs = kwargs.select { |k, v| record.respond_to?("#{k}=") }
83
+ self.new(**attrs)
84
+ end
85
+
86
+ # @return [SchemaClass::Record]
87
+ # @param from_message [Boolean] whether it's being initialized from a real Avro message.
88
+ def self.initialize_from_value(value, from_message: false)
77
89
  return nil if value.nil?
78
90
 
79
- value.is_a?(self) ? value : self.new(**value.symbolize_keys)
91
+ return value if value.is_a?(self)
92
+ if from_message
93
+ self.new_from_message(**value.symbolize_keys)
94
+ else
95
+ self.new(**value.symbolize_keys)
96
+ end
80
97
  end
81
98
  end
82
99
  end
@@ -147,11 +147,17 @@ module Deimos
147
147
  payload,
148
148
  key: nil,
149
149
  call_original: nil,
150
- partition_key: nil)
150
+ partition_key: nil,
151
+ &block)
151
152
  unless call_original.nil?
152
153
  puts "test_consume_message(call_original: true) is deprecated and will be removed in the future. You can remove the call_original parameter."
153
154
  end
154
- test_consume_batch(handler_class_or_topic, [payload], keys: [key], partition_keys: [partition_key], single: true)
155
+ test_consume_batch(handler_class_or_topic,
156
+ [payload],
157
+ keys: [key],
158
+ partition_keys: [partition_key],
159
+ single: true,
160
+ &block)
155
161
  end
156
162
 
157
163
  # Test that a given handler will consume a given batch payload correctly,
@@ -171,7 +177,7 @@ module Deimos
171
177
  keys: [],
172
178
  call_original: nil,
173
179
  single: false,
174
- partition_keys: [])
180
+ partition_keys: [], &block)
175
181
  unless call_original.nil?
176
182
  puts "test_consume_batch(call_original: true) is deprecated and will be removed in the future. You can remove the call_original parameter."
177
183
  end
@@ -194,9 +200,15 @@ module Deimos
194
200
  karafka.produce(payload, {key: keys[i], partition_key: partition_keys[i], topic: consumer.topic.name})
195
201
  end
196
202
  if block_given?
197
- allow_any_instance_of(consumer_class).to receive(:consume_batch) do
198
- yield
199
- end
203
+ if single
204
+ allow(consumer).to receive(:consume_message) do
205
+ yield consumer.messages.first.payload, consumer.messages.first.as_json['metadata']
206
+ end
207
+ else
208
+ allow(consumer).to receive(:consume_batch) do
209
+ yield consumer.messages
210
+ end
211
+ end
200
212
  end
201
213
 
202
214
  # sent_messages should only include messages sent by application code, not this method
@@ -43,7 +43,7 @@ module Deimos
43
43
  klass = klass(schema, namespace)
44
44
  return payload if klass.nil? || payload.nil?
45
45
 
46
- klass.new(**payload.symbolize_keys)
46
+ klass.new_from_message(**payload.symbolize_keys)
47
47
  end
48
48
 
49
49
  # Determine and return the SchemaClass with the provided schema and namespace
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Deimos
4
- VERSION = '2.0.17'
4
+ VERSION = '2.1.0'
5
5
  end
@@ -49,8 +49,9 @@
49
49
  <%- @field_assignments.select{ |h| h[:is_complex_union] }.each do |method_definition| -%>
50
50
  # Helper method to determine which schema type to use for <%= method_definition[:field].name %>
51
51
  # @param value [Hash, nil]
52
+ # @param from_message [Boolean] whether this was initialized from a real Avro message
52
53
  # @return [Object, nil]
53
- def initialize_<%= method_definition[:field].name %>_type(value)
54
+ def initialize_<%= method_definition[:field].name %>_type(value, from_message: false)
54
55
  return nil if value.nil?
55
56
 
56
57
  klass = [<%= method_definition[:field].type.schemas.reject { |s| s.type_sym == :null }.select { |s| s.type_sym == :record }.map { |s| Deimos::SchemaBackends::AvroBase.schema_classname(s) }.join(', ') %>].find do |candidate|
@@ -58,7 +59,7 @@
58
59
  (value.keys - fields).empty?
59
60
  end
60
61
 
61
- klass.initialize_from_value(value)
62
+ klass.initialize_from_value(value, from_message: @from_message)
62
63
  end
63
64
 
64
65
  <%- end -%>
@@ -245,7 +245,7 @@ module Deimos
245
245
  "record.tombstone_key = key\n record.#{key_config[:field]} = key"
246
246
  elsif key_schema
247
247
  field_base_type = _field_type(key_schema)
248
- "record.tombstone_key = #{field_base_type}.initialize_from_value(key)\n record.payload_key = key"
248
+ "record.tombstone_key = #{field_base_type}.initialize_from_value(key, from_message: @from_message)\n record.payload_key = key"
249
249
  else
250
250
  ''
251
251
  end
@@ -299,9 +299,9 @@ module Deimos
299
299
  field_initialization = method_argument
300
300
 
301
301
  if _is_complex_union?(field)
302
- field_initialization = "initialize_#{field.name}_type(value)"
302
+ field_initialization = "initialize_#{field.name}_type(value, from_message: @from_message)"
303
303
  elsif is_schema_class
304
- field_initialization = "#{field_base_type}.initialize_from_value(value)"
304
+ field_initialization = "#{field_base_type}.initialize_from_value(value, from_message: @from_message)"
305
305
  end
306
306
 
307
307
  result << {
@@ -5,7 +5,7 @@ module ConsumerTest
5
5
  describe Deimos::Consumer, 'Batch Consumer' do
6
6
  let(:schema) { 'MySchema' }
7
7
  let(:use_schema_classes) { false }
8
- let(:reraise_errors) { false }
8
+ let(:should_reraise_errors) { false }
9
9
  let(:key_config) { { field: 'test_id' } }
10
10
  let(:consumer_class) do
11
11
  Class.new(described_class) do
@@ -22,12 +22,14 @@ module ConsumerTest
22
22
  route_schema = schema
23
23
  route_key = key_config
24
24
  route_use_classes = use_schema_classes
25
+ should_reraise = should_reraise_errors # for scoping
25
26
  Karafka::App.routes.redraw do
26
27
  topic 'my-topic' do
27
28
  consumer klass
28
29
  schema route_schema
29
30
  namespace 'com.my-namespace'
30
31
  key_config route_key
32
+ reraise_errors(should_reraise)
31
33
  use_schema_classes route_use_classes
32
34
  end
33
35
  end
@@ -77,7 +79,7 @@ module ConsumerTest
77
79
  end
78
80
 
79
81
  describe 'when reraising errors is disabled' do
80
- let(:reraise_errors) { false }
82
+ let(:should_reraise_errors) { false }
81
83
 
82
84
  it 'should not fail when before_consume_batch fails' do
83
85
  expect {
@@ -98,23 +100,23 @@ module ConsumerTest
98
100
  end
99
101
 
100
102
  it 'should decode payloads for all messages in the batch' do
101
- test_consume_batch('my-topic', batch) do
103
+ test_consume_batch('my-topic', batch) do |received|
102
104
  # Mock decoder simply returns the payload
103
- expect(messages.payloads).to eq(batch)
105
+ expect(received.payloads).to eq(batch)
104
106
  end
105
107
  end
106
108
 
107
109
  it 'should decode keys for all messages in the batch' do
108
- test_consume_batch('my-topic', batch, keys: keys) do
109
- expect(messages.map(&:key)).to eq([{'test_id' => 'foo'}, {'test_id' => 'bar'}])
110
+ test_consume_batch('my-topic', batch, keys: keys) do |received|
111
+ expect(received.map(&:key)).to eq(%w(foo bar))
110
112
  end
111
113
  end
112
114
 
113
115
  context 'with plain keys' do
114
116
  let(:key_config) { { plain: true } }
115
117
  it 'should decode plain keys for all messages in the batch' do
116
- test_consume_batch('my-topic', batch, keys: [1, 2]) do |_received, metadata|
117
- expect(metadata[:keys]).to eq([1, 2])
118
+ test_consume_batch('my-topic', batch, keys: [1, 2]) do |received|
119
+ expect(received.map(&:key)).to eq(["1", "2"])
118
120
  end
119
121
  end
120
122
  end
@@ -173,8 +175,8 @@ module ConsumerTest
173
175
  # a_kind_of(Numeric),
174
176
  # tags: %w(time:time_delayed topic:my-topic)).twice
175
177
 
176
- test_consume_batch('my-topic', batch_with_time) do
177
- expect(messages.payloads).to eq(batch_with_time)
178
+ test_consume_batch('my-topic', batch_with_time) do |received|
179
+ expect(received.payloads).to eq(batch_with_time)
178
180
  end
179
181
  end
180
182
 
@@ -186,8 +188,8 @@ module ConsumerTest
186
188
  # a_kind_of(Numeric),
187
189
  # tags: %w(time:time_delayed topic:my-topic)).twice
188
190
 
189
- test_consume_batch('my-topic', batch) do
190
- expect(messages.payloads).to eq(batch)
191
+ test_consume_batch('my-topic', batch) do |received|
192
+ expect(received.payloads).to eq(batch)
191
193
  end
192
194
  end
193
195
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'rails/generators'
4
+
3
5
  # :nodoc:
4
6
  # rubocop:disable Metrics/ModuleLength
5
7
  module ConsumerTest
@@ -12,7 +14,7 @@ module ConsumerTest
12
14
 
13
15
  # :nodoc:
14
16
  def fatal_error?(_exception, messages)
15
- messages.payloads.first&.dig(:test_id) == ['fatal']
17
+ messages.payloads.first&.to_h&.dig(:test_id) == ['fatal']
16
18
  end
17
19
 
18
20
  # :nodoc:
@@ -29,6 +31,7 @@ module ConsumerTest
29
31
  namespace 'com.my-namespace'
30
32
  key_config field: 'test_id'
31
33
  consumer consumer_class
34
+ reraise_errors true
32
35
  use_schema_classes route_usc
33
36
  reraise_errors route_rre
34
37
  end
@@ -43,6 +46,10 @@ module ConsumerTest
43
46
  before(:each) do
44
47
  Deimos.configure do |config|
45
48
  config.schema.use_full_namespace = true
49
+ config.schema.schema_namespace_map = {
50
+ 'com' => 'Schemas',
51
+ 'com.my-namespace.my-suborg' => %w(Schemas MyNamespace)
52
+ }
46
53
  end
47
54
  end
48
55
 
@@ -56,8 +63,8 @@ module ConsumerTest
56
63
  end
57
64
 
58
65
  it 'should consume a nil message' do
59
- test_consume_message(MyConsumer, nil, key: 'foo') do
60
- expect(messages).to be_empty
66
+ test_consume_message(MyConsumer, nil, key: 'foo') do |message|
67
+ expect(message).to be_nil
61
68
  end
62
69
  end
63
70
 
@@ -118,7 +125,7 @@ module ConsumerTest
118
125
  end
119
126
  end
120
127
 
121
- context 'with overriden schema classes' do
128
+ context 'with overridden schema classes' do
122
129
 
123
130
  before(:each) do
124
131
  set_karafka_config(:use_schema_classes, true)
@@ -142,6 +149,7 @@ module ConsumerTest
142
149
  namespace 'com.my-namespace'
143
150
  key_config field: 'test_id'
144
151
  consumer consumer_class
152
+ reraise_errors true
145
153
  end
146
154
  end
147
155
  end
@@ -160,8 +168,46 @@ module ConsumerTest
160
168
  end
161
169
 
162
170
  end
163
- end
164
171
 
172
+ context 'with backwards compatible schema classes' do
173
+ before(:each) do
174
+ set_karafka_config(:use_schema_classes, true)
175
+ Deimos.configure do |config|
176
+ config.schema.use_full_namespace = true
177
+ config.schema.schema_namespace_map = {
178
+ 'com' => 'Schemas',
179
+ 'com.my-namespace.my-suborg' => %w(Schemas MyNamespace)
180
+ }
181
+ end
182
+ # update the Avro to include a new field
183
+ generator.append_to_file(schema_file, additional_field_json, after: '"fields": [')
184
+ end
185
+
186
+ after(:each) do
187
+ generator.gsub_file(schema_file, additional_field_json, '')
188
+ end
189
+
190
+ let(:generator) { Rails::Generators::Base.new }
191
+ let(:schema_file) { File.join(__dir__, 'schemas/com/my-namespace/MySchema.avsc') }
192
+ let(:additional_field_json) do
193
+ '{"name": "additional_field", "type": "string", "default": ""},'
194
+ end
195
+
196
+ it 'should consume correctly and ignore the additional field' do
197
+ test_consume_message('my_consume_topic',
198
+ { 'test_id' => 'foo',
199
+ 'additional_field' => 'bar',
200
+ 'some_int' => 1 }) do |payload, _metadata|
201
+ expect(payload['test_id']).to eq('foo')
202
+ expect(payload['some_int']).to eq(1)
203
+ expect(payload.to_h).not_to have_key('additional_field')
204
+ end
205
+
206
+ end
207
+ end
208
+
209
+ end
165
210
  end
166
211
  end
212
+
167
213
  # rubocop:enable Metrics/ModuleLength
@@ -81,12 +81,12 @@ module Schemas; module MyNamespace
81
81
  ### Attribute Writers ###
82
82
  # @return [AnEnum]
83
83
  def an_enum=(value)
84
- @an_enum = AnEnum.initialize_from_value(value)
84
+ @an_enum = AnEnum.initialize_from_value(value, from_message: @from_message)
85
85
  end
86
86
 
87
87
  # @return [ARecord]
88
88
  def a_record=(value)
89
- @a_record = ARecord.initialize_from_value(value)
89
+ @a_record = ARecord.initialize_from_value(value, from_message: @from_message)
90
90
  end
91
91
 
92
92
  # @override
@@ -71,12 +71,12 @@ module Schemas; module MyNamespace
71
71
  ### Attribute Writers ###
72
72
  # @return [MyNestedRecord]
73
73
  def some_nested_record=(value)
74
- @some_nested_record = MyNestedRecord.initialize_from_value(value)
74
+ @some_nested_record = MyNestedRecord.initialize_from_value(value, from_message: @from_message)
75
75
  end
76
76
 
77
77
  # @return [nil, MyNestedRecord]
78
78
  def some_optional_record=(value)
79
- @some_optional_record = MyNestedRecord.initialize_from_value(value)
79
+ @some_optional_record = MyNestedRecord.initialize_from_value(value, from_message: @from_message)
80
80
  end
81
81
 
82
82
  # @override
@@ -19,7 +19,7 @@ module Schemas; module MyNamespace
19
19
  ### Attribute Writers ###
20
20
  # @return [MySchemaKey]
21
21
  def payload_key=(value)
22
- @payload_key = MySchemaKey.initialize_from_value(value)
22
+ @payload_key = MySchemaKey.initialize_from_value(value, from_message: @from_message)
23
23
  end
24
24
 
25
25
  # @override
@@ -44,7 +44,7 @@ module Schemas; module MyNamespace
44
44
 
45
45
  def self.tombstone(key)
46
46
  record = self.allocate
47
- record.tombstone_key = MySchemaKey.initialize_from_value(key)
47
+ record.tombstone_key = MySchemaKey.initialize_from_value(key, from_message: @from_message)
48
48
  record.payload_key = key
49
49
  record
50
50
  end
@@ -47,7 +47,7 @@ module Schemas; module MyNamespace
47
47
  # @return [Hash<String, Property>]
48
48
  def properties=(values)
49
49
  @properties = values&.transform_values do |value|
50
- Property.initialize_from_value(value)
50
+ Property.initialize_from_value(value, from_message: @from_message)
51
51
  end
52
52
  end
53
53
 
@@ -105,43 +105,43 @@ module Schemas; module MyNamespace
105
105
  ### Attribute Writers ###
106
106
  # @return [ARecord]
107
107
  def some_record=(value)
108
- @some_record = ARecord.initialize_from_value(value)
108
+ @some_record = ARecord.initialize_from_value(value, from_message: @from_message)
109
109
  end
110
110
 
111
111
  # @return [nil, ARecord]
112
112
  def some_optional_record=(value)
113
- @some_optional_record = ARecord.initialize_from_value(value)
113
+ @some_optional_record = ARecord.initialize_from_value(value, from_message: @from_message)
114
114
  end
115
115
 
116
116
  # @return [Array<ARecord>]
117
117
  def some_record_array=(values)
118
118
  @some_record_array = values&.map do |value|
119
- ARecord.initialize_from_value(value)
119
+ ARecord.initialize_from_value(value, from_message: @from_message)
120
120
  end
121
121
  end
122
122
 
123
123
  # @return [Hash<String, ARecord>]
124
124
  def some_record_map=(values)
125
125
  @some_record_map = values&.transform_values do |value|
126
- ARecord.initialize_from_value(value)
126
+ ARecord.initialize_from_value(value, from_message: @from_message)
127
127
  end
128
128
  end
129
129
 
130
130
  # @return [Array<AnEnum>]
131
131
  def some_enum_array=(values)
132
132
  @some_enum_array = values&.map do |value|
133
- AnEnum.initialize_from_value(value)
133
+ AnEnum.initialize_from_value(value, from_message: @from_message)
134
134
  end
135
135
  end
136
136
 
137
137
  # @return [nil, AnotherEnum]
138
138
  def some_optional_enum=(value)
139
- @some_optional_enum = AnotherEnum.initialize_from_value(value)
139
+ @some_optional_enum = AnotherEnum.initialize_from_value(value, from_message: @from_message)
140
140
  end
141
141
 
142
142
  # @return [YetAnotherEnum]
143
143
  def some_enum_with_default=(value)
144
- @some_enum_with_default = YetAnotherEnum.initialize_from_value(value)
144
+ @some_enum_with_default = YetAnotherEnum.initialize_from_value(value, from_message: @from_message)
145
145
  end
146
146
 
147
147
  # @override
@@ -150,8 +150,8 @@ module Schemas; module MyNamespace
150
150
  test_string_array: ["test"],
151
151
  test_int_array: [123],
152
152
  test_optional_int: 123,
153
- some_integer_map: { "abc"=>123 },
154
- some_record: { "a_record_field"=>"Test String" },
153
+ some_integer_map: {"abc"=>123},
154
+ some_record: {"a_record_field"=>"Test String"},
155
155
  some_optional_record: nil,
156
156
  some_record_array: nil,
157
157
  some_record_map: nil,