deimos-ruby 2.0.3 → 2.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 +8 -0
- data/docs/CONFIGURATION.md +6 -5
- data/lib/deimos/config/configuration.rb +4 -0
- data/lib/deimos/kafka_source.rb +14 -4
- data/lib/deimos/schema_backends/avro_schema_coercer.rb +39 -1
- data/lib/deimos/version.rb +1 -1
- data/lib/generators/deimos/schema_class/templates/schema_record.rb.tt +18 -0
- data/lib/generators/deimos/schema_class_generator.rb +17 -2
- data/spec/active_record_producer_spec.rb +111 -5
- data/spec/kafka_source_spec.rb +49 -1
- data/spec/schemas/com/my-namespace/MySchemaWithUnionType.avsc +91 -0
- data/spec/schemas/my_namespace/my_schema_with_union_type.rb +185 -0
- data/spec/snapshots/consumers-no-nest.snap +232 -0
- data/spec/snapshots/consumers.snap +202 -0
- data/spec/snapshots/consumers_and_producers-no-nest.snap +232 -0
- data/spec/snapshots/consumers_and_producers.snap +202 -0
- data/spec/snapshots/consumers_circular-no-nest.snap +232 -0
- data/spec/snapshots/consumers_circular.snap +202 -0
- data/spec/snapshots/consumers_complex_types-no-nest.snap +232 -0
- data/spec/snapshots/consumers_complex_types.snap +202 -0
- data/spec/snapshots/consumers_nested-no-nest.snap +232 -0
- data/spec/snapshots/consumers_nested.snap +202 -0
- data/spec/snapshots/namespace_folders.snap +202 -0
- data/spec/snapshots/namespace_map.snap +202 -0
- data/spec/snapshots/producers_with_key-no-nest.snap +232 -0
- data/spec/snapshots/producers_with_key.snap +202 -0
- data/spec/spec_helper.rb +25 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53cd09a846fed14b74b20cad18fc63374887c0a9deae4444f2d6548ea06b5b72
|
4
|
+
data.tar.gz: 95f7b6402d43d2c2332f5abbc8c0374655b1ac713b484d3f4b516321b4e2207d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 928b533075a3a98f5731f15e2e9436cd1ad0754006bcf18816bdc19933e0990478aa7cd06152e450f0041524cfcf04da839c1116a4895bf3c9e692e70aefa4f0
|
7
|
+
data.tar.gz: b363497c5b9f1e9ea6dd71bf2d4e4bd59e7b96fe229cc58ee9caccb3ca86a7cd1ef4bf0636e7a354d496c790aa867fcad3405bdd4b353e72fce1d1e22ba32876
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## UNRELEASED
|
9
9
|
|
10
|
+
## 2.0.5 - 2025-03-19
|
11
|
+
|
12
|
+
- Fix: Added support to handle producing union type of multiple records & data types.
|
13
|
+
|
14
|
+
## 2.0.4 - 2025-03-17
|
15
|
+
|
16
|
+
- Feature: Added `producers.truncate_columns` config.
|
17
|
+
|
10
18
|
## 2.0.3 - 2025-03-12
|
11
19
|
- Fix: `ActiveRecordProducer.config` could crash if there were non-producer configs.
|
12
20
|
|
data/docs/CONFIGURATION.md
CHANGED
@@ -43,11 +43,12 @@ things you need to reference into local variables before calling `configure`.
|
|
43
43
|
|
44
44
|
### Producer Configuration
|
45
45
|
|
46
|
-
| Config name
|
47
|
-
|
48
|
-
| producers.topic_prefix
|
49
|
-
| producers.disabled
|
50
|
-
| producers.backend
|
46
|
+
| Config name | Default | Description |
|
47
|
+
|----------------------------|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
48
|
+
| producers.topic_prefix | nil | Add a prefix to all topic names. This can be useful if you're using the same Kafka broker for different environments that are producing the same topics. |
|
49
|
+
| producers.disabled | false | Disable all actual message producing. Generally more useful to use the `disable_producers` method instead. |
|
50
|
+
| producers.backend | `:kafka_async` | Currently can be set to `:db`, `:kafka`, or `:kafka_async`. If using Kafka directly, a good pattern is to set to async in your user-facing app, and sync in your consumers or delayed workers. |
|
51
|
+
| producers.truncate_columns | false | If set to true, will truncate values to their database limits when using KafkaSource. |
|
51
52
|
|
52
53
|
### Schema Configuration
|
53
54
|
|
@@ -133,6 +133,10 @@ module Deimos # rubocop:disable Metrics/ModuleLength
|
|
133
133
|
# sync in your consumers or delayed workers.
|
134
134
|
# @return [Symbol]
|
135
135
|
setting :backend, :kafka_async
|
136
|
+
|
137
|
+
# If set to true, KafkaSource will automatically truncate fields to match the column
|
138
|
+
# length in the database.
|
139
|
+
setting :truncate_columns
|
136
140
|
end
|
137
141
|
|
138
142
|
setting :schema do
|
data/lib/deimos/kafka_source.rb
CHANGED
@@ -6,10 +6,6 @@ module Deimos
|
|
6
6
|
module KafkaSource
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
|
-
# @return [String]
|
10
|
-
DEPRECATION_WARNING = 'The kafka_producer interface will be deprecated ' \
|
11
|
-
'in future releases. Please use kafka_producers instead.'
|
12
|
-
|
13
9
|
included do
|
14
10
|
after_create(:send_kafka_event_on_create)
|
15
11
|
after_update(:send_kafka_event_on_update)
|
@@ -22,6 +18,7 @@ module Deimos
|
|
22
18
|
return unless self.persisted?
|
23
19
|
return unless self.class.kafka_config[:create]
|
24
20
|
|
21
|
+
self.truncate_columns if Deimos.config.producers.truncate_columns
|
25
22
|
self.class.kafka_producers.each { |p| p.send_event(self) }
|
26
23
|
end
|
27
24
|
|
@@ -39,6 +36,7 @@ module Deimos
|
|
39
36
|
field_change.present? && field_change[0] != field_change[1]
|
40
37
|
end
|
41
38
|
return unless any_changes
|
39
|
+
self.truncate_columns if Deimos.config.producers.truncate_columns
|
42
40
|
|
43
41
|
producers.each { |p| p.send_event(self) }
|
44
42
|
end
|
@@ -124,5 +122,17 @@ module Deimos
|
|
124
122
|
results
|
125
123
|
end
|
126
124
|
end
|
125
|
+
|
126
|
+
# check if any field has value longer than the field limit
|
127
|
+
def truncate_columns
|
128
|
+
self.class.columns.each do |col|
|
129
|
+
next unless col.type == :string
|
130
|
+
next if self[col.name].blank?
|
131
|
+
if self[col.name].to_s.length > col.limit
|
132
|
+
self[col.name] = self[col.name][0..col.limit - 1]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
false
|
136
|
+
end
|
127
137
|
end
|
128
138
|
end
|
@@ -18,10 +18,48 @@ module Deimos
|
|
18
18
|
union_types = type.schemas.map { |s| s.type.to_sym }
|
19
19
|
return nil if val.nil? && union_types.include?(:null)
|
20
20
|
|
21
|
-
schema_type = type
|
21
|
+
schema_type = find_schema_type(type, val)
|
22
22
|
coerce_type(schema_type, val)
|
23
23
|
end
|
24
24
|
|
25
|
+
# Find the right schema for val from a UnionSchema.
|
26
|
+
# @param type [Avro::Schema::UnionSchema]
|
27
|
+
# @param val [Object]
|
28
|
+
# @return [Avro::Schema::PrimitiveSchema]
|
29
|
+
def find_schema_type(type, val)
|
30
|
+
int_classes = [Time, ActiveSupport::TimeWithZone]
|
31
|
+
|
32
|
+
schema_type = type.schemas.find do |schema|
|
33
|
+
field_type = schema.type.to_sym
|
34
|
+
|
35
|
+
case field_type
|
36
|
+
when :int, :long
|
37
|
+
val.is_a?(Integer) ||
|
38
|
+
_is_integer_string?(val) ||
|
39
|
+
int_classes.any? { |klass| val.is_a?(klass) }
|
40
|
+
when :float, :double
|
41
|
+
val.is_a?(Numeric) || _is_float_string?(val)
|
42
|
+
when :array
|
43
|
+
val.is_a?(Array)
|
44
|
+
when :record
|
45
|
+
if val.is_a?(Hash)
|
46
|
+
schema_fields_set = Set.new(schema.fields.map(&:name))
|
47
|
+
Set.new(val.keys).subset?(schema_fields_set)
|
48
|
+
else
|
49
|
+
# If the value is not a hash, we can't coerce it to a record.
|
50
|
+
# Keep looking for another schema
|
51
|
+
false
|
52
|
+
end
|
53
|
+
else
|
54
|
+
schema.type.to_sym != :null
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
raise "No Schema type found for VALUE: #{val}\n TYPE: #{type}" if schema_type.nil?
|
59
|
+
|
60
|
+
schema_type
|
61
|
+
end
|
62
|
+
|
25
63
|
# Coerce sub-records in a payload to match the schema.
|
26
64
|
# @param type [Avro::Schema::RecordSchema]
|
27
65
|
# @param val [Object]
|
data/lib/deimos/version.rb
CHANGED
@@ -44,6 +44,24 @@
|
|
44
44
|
end
|
45
45
|
|
46
46
|
<%- end -%>
|
47
|
+
<% end -%>
|
48
|
+
<%- if @field_assignments.select{ |h| h[:is_complex_union] }.any? -%>
|
49
|
+
<%- @field_assignments.select{ |h| h[:is_complex_union] }.each do |method_definition| -%>
|
50
|
+
# Helper method to determine which schema type to use for <%= method_definition[:field].name %>
|
51
|
+
# @param value [Hash, nil]
|
52
|
+
# @return [Object, nil]
|
53
|
+
def initialize_<%= method_definition[:field].name %>_type(value)
|
54
|
+
return nil if value.nil?
|
55
|
+
|
56
|
+
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|
|
57
|
+
fields = candidate.new.as_json.keys
|
58
|
+
(value.keys - fields).empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
klass.initialize_from_value(value)
|
62
|
+
end
|
63
|
+
|
64
|
+
<%- end -%>
|
47
65
|
<% end -%>
|
48
66
|
# @override
|
49
67
|
<%= @initialization_definition %>
|
@@ -298,7 +298,9 @@ module Deimos
|
|
298
298
|
|
299
299
|
field_initialization = method_argument
|
300
300
|
|
301
|
-
if
|
301
|
+
if _is_complex_union?(field)
|
302
|
+
field_initialization = "initialize_#{field.name}_type(value)"
|
303
|
+
elsif is_schema_class
|
302
304
|
field_initialization = "#{field_base_type}.initialize_from_value(value)"
|
303
305
|
end
|
304
306
|
|
@@ -308,13 +310,26 @@ module Deimos
|
|
308
310
|
is_schema_class: is_schema_class,
|
309
311
|
method_argument: method_argument,
|
310
312
|
deimos_type: deimos_field_type(field),
|
311
|
-
field_initialization: field_initialization
|
313
|
+
field_initialization: field_initialization,
|
314
|
+
is_complex_union: _is_complex_union?(field)
|
312
315
|
}
|
313
316
|
end
|
314
317
|
|
315
318
|
result
|
316
319
|
end
|
317
320
|
|
321
|
+
# Helper method to detect if a field is a complex union type with multiple record schemas
|
322
|
+
# @param field [Deimos::SchemaField]
|
323
|
+
# @return [Boolean]
|
324
|
+
def _is_complex_union?(field)
|
325
|
+
return false unless field.type.type_sym == :union
|
326
|
+
|
327
|
+
non_null_schemas = field.type.schemas.reject { |s| s.type_sym == :null }
|
328
|
+
|
329
|
+
record_schemas = non_null_schemas.select { |s| s.type_sym == :record }
|
330
|
+
record_schemas.length > 1
|
331
|
+
end
|
332
|
+
|
318
333
|
# Converts Avro::Schema::NamedSchema's to String form for generated YARD docs.
|
319
334
|
# Recursively handles the typing for Arrays, Maps and Unions.
|
320
335
|
# @param avro_schema [Avro::Schema::NamedSchema]
|
@@ -3,6 +3,7 @@
|
|
3
3
|
describe Deimos::ActiveRecordProducer do
|
4
4
|
|
5
5
|
include_context 'with widgets'
|
6
|
+
include_context 'with widget_with_union_types'
|
6
7
|
|
7
8
|
prepend_before(:each) do
|
8
9
|
producer_class = Class.new(Deimos::ActiveRecordProducer)
|
@@ -40,6 +41,12 @@ describe Deimos::ActiveRecordProducer do
|
|
40
41
|
end
|
41
42
|
|
42
43
|
stub_const('MyProducerWithPostProcess', producer_class)
|
44
|
+
|
45
|
+
producer_class = Class.new(Deimos::ActiveRecordProducer) do
|
46
|
+
record_class WidgetWithUnionType
|
47
|
+
end
|
48
|
+
stub_const('MyProducerWithUnionType', producer_class)
|
49
|
+
|
43
50
|
Karafka::App.routes.redraw do
|
44
51
|
topic 'my-topic' do
|
45
52
|
schema 'MySchema'
|
@@ -71,6 +78,13 @@ describe Deimos::ActiveRecordProducer do
|
|
71
78
|
key_config none: true
|
72
79
|
producer_class MyProducerWithPostProcess
|
73
80
|
end
|
81
|
+
topic 'my-topic-with-union-type' do
|
82
|
+
schema 'MySchemaWithUnionType'
|
83
|
+
namespace 'com.my-namespace'
|
84
|
+
key_config none: true
|
85
|
+
producer_class MyProducerWithUnionType
|
86
|
+
end
|
87
|
+
|
74
88
|
end
|
75
89
|
|
76
90
|
end
|
@@ -90,6 +104,98 @@ describe Deimos::ActiveRecordProducer do
|
|
90
104
|
expect('my-topic').to have_sent(test_id: 'abc', some_int: 3)
|
91
105
|
end
|
92
106
|
|
107
|
+
it 'should coerce values for a UnionSchema' do
|
108
|
+
MyProducerWithUnionType.send_event(WidgetWithUnionType.new(
|
109
|
+
test_id: "abc",
|
110
|
+
test_long: 399999,
|
111
|
+
test_union_type: %w(hello world)
|
112
|
+
))
|
113
|
+
|
114
|
+
expect('my-topic-with-union-type').to have_sent(
|
115
|
+
test_id: "abc",
|
116
|
+
test_long: 399999,
|
117
|
+
test_union_type: %w(hello world)
|
118
|
+
)
|
119
|
+
|
120
|
+
MyProducerWithUnionType.send_event(WidgetWithUnionType.new(
|
121
|
+
test_id: "abc",
|
122
|
+
test_long: 399999,
|
123
|
+
test_union_type: {
|
124
|
+
record1_map:{ a:9999, b:234 },
|
125
|
+
record1_id: 567
|
126
|
+
}
|
127
|
+
))
|
128
|
+
|
129
|
+
expect('my-topic-with-union-type').to have_sent(
|
130
|
+
test_id: "abc",
|
131
|
+
test_long: 399999,
|
132
|
+
test_union_type:{
|
133
|
+
record1_map:{ a:9999, b:234 },
|
134
|
+
record1_id: 567
|
135
|
+
}
|
136
|
+
)
|
137
|
+
|
138
|
+
MyProducerWithUnionType.send_event(WidgetWithUnionType.new(
|
139
|
+
test_id: "abc",
|
140
|
+
test_long: 399999,
|
141
|
+
test_union_type: 1010101
|
142
|
+
))
|
143
|
+
|
144
|
+
expect('my-topic-with-union-type').to have_sent(
|
145
|
+
test_id: "abc",
|
146
|
+
test_long: 399999,
|
147
|
+
test_union_type:1010101
|
148
|
+
)
|
149
|
+
|
150
|
+
MyProducerWithUnionType.send_event(WidgetWithUnionType.new(
|
151
|
+
test_id: "abc",
|
152
|
+
test_long: 399999,
|
153
|
+
test_union_type: {
|
154
|
+
record2_id: "hello world"
|
155
|
+
}
|
156
|
+
))
|
157
|
+
|
158
|
+
expect('my-topic-with-union-type').to have_sent(
|
159
|
+
test_id: "abc",
|
160
|
+
test_long: 399999,
|
161
|
+
test_union_type: {
|
162
|
+
record2_id: "hello world"
|
163
|
+
}
|
164
|
+
)
|
165
|
+
|
166
|
+
MyProducerWithUnionType.send_event(WidgetWithUnionType.new(
|
167
|
+
test_id: "abc",
|
168
|
+
test_long: 399999,
|
169
|
+
test_union_type: {
|
170
|
+
record3_id:10.1010
|
171
|
+
}
|
172
|
+
))
|
173
|
+
|
174
|
+
expect('my-topic-with-union-type').to have_sent(
|
175
|
+
test_id: "abc",
|
176
|
+
test_long: 399999,
|
177
|
+
test_union_type: {
|
178
|
+
record3_id:10.1010
|
179
|
+
}
|
180
|
+
)
|
181
|
+
|
182
|
+
MyProducerWithUnionType.send_event(WidgetWithUnionType.new(
|
183
|
+
test_id: "abc",
|
184
|
+
test_long: 399999,
|
185
|
+
test_union_type: {
|
186
|
+
record4_id:101010
|
187
|
+
}
|
188
|
+
))
|
189
|
+
|
190
|
+
expect('my-topic-with-union-type').to have_sent(
|
191
|
+
test_id: "abc",
|
192
|
+
test_long: 399999,
|
193
|
+
test_union_type: {
|
194
|
+
record4_id:101010
|
195
|
+
}
|
196
|
+
)
|
197
|
+
end
|
198
|
+
|
93
199
|
it 'should coerce values' do
|
94
200
|
MyProducer.send_event(Widget.new(test_id: 'abc', some_int: '3'))
|
95
201
|
MyProducer.send_event(Widget.new(test_id: 'abc', some_int: 4.5))
|
@@ -109,11 +215,11 @@ describe Deimos::ActiveRecordProducer do
|
|
109
215
|
widget = Widget.create!(test_id: 'abc2', some_int: 3)
|
110
216
|
MyProducerWithID.send_event({id: widget.id, test_id: 'abc2', some_int: 3})
|
111
217
|
expect('my-topic-with-id').to have_sent(
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
218
|
+
test_id: 'abc2',
|
219
|
+
some_int: 3,
|
220
|
+
message_id: 'generated_id',
|
221
|
+
timestamp: anything
|
222
|
+
)
|
117
223
|
end
|
118
224
|
|
119
225
|
it 'should post process the batch of records in #send_events' do
|
data/spec/kafka_source_spec.rb
CHANGED
@@ -10,7 +10,7 @@ module KafkaSourceSpec
|
|
10
10
|
t.integer(:widget_id)
|
11
11
|
t.string(:description)
|
12
12
|
t.string(:model_id, default: '')
|
13
|
-
t.string(:name)
|
13
|
+
t.string(:name, limit: 100)
|
14
14
|
t.timestamps
|
15
15
|
end
|
16
16
|
ActiveRecord::Base.connection.add_index(:widgets, :widget_id)
|
@@ -105,6 +105,54 @@ module KafkaSourceSpec
|
|
105
105
|
expect('my-topic-the-second').to have_sent(nil, widget.id)
|
106
106
|
end
|
107
107
|
|
108
|
+
context 'with truncation off' do
|
109
|
+
before(:each) do
|
110
|
+
Deimos.config.producers.truncate_columns = false
|
111
|
+
end
|
112
|
+
it 'should not truncate values' do
|
113
|
+
widget = Widget.create!(widget_id: 1, name: 'a'*500)
|
114
|
+
expect('my-topic').to have_sent({
|
115
|
+
widget_id: 1,
|
116
|
+
name: 'a'*500,
|
117
|
+
id: widget.id,
|
118
|
+
created_at: anything,
|
119
|
+
updated_at: anything
|
120
|
+
}, 1)
|
121
|
+
widget.update_attribute(:name, 'b'*500)
|
122
|
+
expect('my-topic').to have_sent({
|
123
|
+
widget_id: 1,
|
124
|
+
name: 'b'*500,
|
125
|
+
id: widget.id,
|
126
|
+
created_at: anything,
|
127
|
+
updated_at: anything
|
128
|
+
}, 1)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'with truncation on' do
|
133
|
+
before(:each) do
|
134
|
+
Deimos.config.producers.truncate_columns = true
|
135
|
+
end
|
136
|
+
it 'should truncate values' do
|
137
|
+
widget = Widget.create!(widget_id: 1, name: 'a'*500)
|
138
|
+
expect('my-topic').to have_sent({
|
139
|
+
widget_id: 1,
|
140
|
+
name: 'a'*100,
|
141
|
+
id: widget.id,
|
142
|
+
created_at: anything,
|
143
|
+
updated_at: anything
|
144
|
+
}, 1)
|
145
|
+
widget.update_attribute(:name, 'b'*500)
|
146
|
+
expect('my-topic').to have_sent({
|
147
|
+
widget_id: 1,
|
148
|
+
name: 'b'*100,
|
149
|
+
id: widget.id,
|
150
|
+
created_at: anything,
|
151
|
+
updated_at: anything
|
152
|
+
}, 1)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
108
156
|
it 'should send events on import' do
|
109
157
|
widgets = (1..3).map do |i|
|
110
158
|
Widget.new(widget_id: i, name: "Widget #{i}")
|
@@ -0,0 +1,91 @@
|
|
1
|
+
{
|
2
|
+
"namespace": "com.my-namespace",
|
3
|
+
"name": "MySchemaWithUnionType",
|
4
|
+
"type": "record",
|
5
|
+
"doc": "Test schema",
|
6
|
+
"fields": [
|
7
|
+
{
|
8
|
+
"name": "test_id",
|
9
|
+
"type": "string",
|
10
|
+
"default": ""
|
11
|
+
},
|
12
|
+
{
|
13
|
+
"name": "test_long",
|
14
|
+
"type": [
|
15
|
+
"null",
|
16
|
+
"long"
|
17
|
+
],
|
18
|
+
"default": null
|
19
|
+
},
|
20
|
+
{
|
21
|
+
"name": "test_union_type",
|
22
|
+
"type": [
|
23
|
+
"null",
|
24
|
+
{
|
25
|
+
"type": "record",
|
26
|
+
"name": "Record1",
|
27
|
+
"namespace": "com.flipp.content",
|
28
|
+
"fields": [
|
29
|
+
{
|
30
|
+
"name": "record1_map",
|
31
|
+
"type": {
|
32
|
+
"type": "map",
|
33
|
+
"values": "long"
|
34
|
+
},
|
35
|
+
"default": {}
|
36
|
+
},
|
37
|
+
{
|
38
|
+
"name": "record1_id",
|
39
|
+
"type": "int",
|
40
|
+
"default": 0
|
41
|
+
}
|
42
|
+
]
|
43
|
+
},
|
44
|
+
{
|
45
|
+
"type": "record",
|
46
|
+
"name": "Record2",
|
47
|
+
"namespace": "com.flipp.content",
|
48
|
+
"fields": [
|
49
|
+
{
|
50
|
+
"name": "record2_id",
|
51
|
+
"type": "string",
|
52
|
+
"default": ""
|
53
|
+
}
|
54
|
+
]
|
55
|
+
},
|
56
|
+
{
|
57
|
+
"type": "record",
|
58
|
+
"name": "Record3",
|
59
|
+
"namespace": "com.flipp.content",
|
60
|
+
"fields": [
|
61
|
+
{
|
62
|
+
"name": "record3_id",
|
63
|
+
"type": "float",
|
64
|
+
"default": 0.0
|
65
|
+
}
|
66
|
+
]
|
67
|
+
},
|
68
|
+
{
|
69
|
+
"type": "record",
|
70
|
+
"name": "Record4",
|
71
|
+
"namespace": "com.flipp.content",
|
72
|
+
"fields": [
|
73
|
+
{
|
74
|
+
"name": "record4_id",
|
75
|
+
"type": "int",
|
76
|
+
"default": 0
|
77
|
+
}
|
78
|
+
]
|
79
|
+
},
|
80
|
+
"int",
|
81
|
+
{
|
82
|
+
"name": "test_array_of_strings",
|
83
|
+
"type": "array",
|
84
|
+
"default": [],
|
85
|
+
"items":"string"
|
86
|
+
}
|
87
|
+
],
|
88
|
+
"default": null
|
89
|
+
}
|
90
|
+
]
|
91
|
+
}
|