avro 1.8.2 → 1.9.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.
@@ -5,9 +5,9 @@
5
5
  # to you under the Apache License, Version 2.0 (the
6
6
  # "License"); you may not use this file except in compliance
7
7
  # with the License. You may obtain a copy of the License at
8
- #
8
+ #
9
9
  # http://www.apache.org/licenses/LICENSE-2.0
10
- #
10
+ #
11
11
  # Unless required by applicable law or agreed to in writing, software
12
12
  # distributed under the License is distributed on an "AS IS" BASIS,
13
13
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -139,7 +139,7 @@ EOS
139
139
 
140
140
  }
141
141
  EOS
142
- ExampleProtocol.new(<<-EOS, true)
142
+ ExampleProtocol.new(<<-EOS, true),
143
143
  {"namespace": "org.apache.avro.test",
144
144
  "protocol": "BulkData",
145
145
 
@@ -160,6 +160,29 @@ EOS
160
160
  }
161
161
 
162
162
  }
163
+ EOS
164
+ ExampleProtocol.new(<<-EOS, true),
165
+ {
166
+ "namespace": "com.acme",
167
+ "protocol": "HelloWorld",
168
+ "doc": "protocol_documentation",
169
+
170
+ "types": [
171
+ {"name": "Greeting", "type": "record", "fields": [
172
+ {"name": "message", "type": "string"}]},
173
+ {"name": "Curse", "type": "error", "fields": [
174
+ {"name": "message", "type": "string"}]}
175
+ ],
176
+
177
+ "messages": {
178
+ "hello": {
179
+ "doc": "message_documentation",
180
+ "request": [{"name": "greeting", "type": "Greeting" }],
181
+ "response": "Greeting",
182
+ "errors": ["Curse"]
183
+ }
184
+ }
185
+ }
163
186
  EOS
164
187
  ]
165
188
 
@@ -196,4 +219,14 @@ EOS
196
219
  assert_equal type.namespace, 'com.acme'
197
220
  end
198
221
  end
222
+
223
+ def test_protocol_doc_attribute
224
+ original = Protocol.parse(EXAMPLES.last.protocol_string)
225
+ assert_equal 'protocol_documentation', original.doc
226
+ end
227
+
228
+ def test_protocol_message_doc_attribute
229
+ original = Protocol.parse(EXAMPLES.last.protocol_string)
230
+ assert_equal 'message_documentation', original.messages['hello'].doc
231
+ end
199
232
  end
@@ -17,6 +17,10 @@
17
17
  require 'test_help'
18
18
 
19
19
  class TestSchema < Test::Unit::TestCase
20
+ def hash_to_schema(hash)
21
+ Avro::Schema.parse(hash.to_json)
22
+ end
23
+
20
24
  def test_default_namespace
21
25
  schema = Avro::Schema.parse <<-SCHEMA
22
26
  {"type": "record", "name": "OuterRecord", "fields": [
@@ -27,13 +31,13 @@ class TestSchema < Test::Unit::TestCase
27
31
  ]}
28
32
  SCHEMA
29
33
 
30
- assert_equal schema.name, 'OuterRecord'
31
- assert_equal schema.fullname, 'OuterRecord'
34
+ assert_equal 'OuterRecord', schema.name
35
+ assert_equal 'OuterRecord', schema.fullname
32
36
  assert_nil schema.namespace
33
37
 
34
38
  schema.fields.each do |field|
35
- assert_equal field.type.name, 'InnerRecord'
36
- assert_equal field.type.fullname, 'InnerRecord'
39
+ assert_equal 'InnerRecord', field.type.name
40
+ assert_equal 'InnerRecord', field.type.fullname
37
41
  assert_nil field.type.namespace
38
42
  end
39
43
  end
@@ -50,13 +54,13 @@ class TestSchema < Test::Unit::TestCase
50
54
  ]}
51
55
  SCHEMA
52
56
 
53
- assert_equal schema.name, 'OuterRecord'
54
- assert_equal schema.fullname, 'my.name.space.OuterRecord'
55
- assert_equal schema.namespace, 'my.name.space'
57
+ assert_equal 'OuterRecord', schema.name
58
+ assert_equal 'my.name.space.OuterRecord', schema.fullname
59
+ assert_equal 'my.name.space', schema.namespace
56
60
  schema.fields.each do |field|
57
- assert_equal field.type.name, 'InnerRecord'
58
- assert_equal field.type.fullname, 'my.name.space.InnerRecord'
59
- assert_equal field.type.namespace, 'my.name.space'
61
+ assert_equal 'InnerRecord', field.type.name
62
+ assert_equal 'my.name.space.InnerRecord', field.type.fullname
63
+ assert_equal 'my.name.space', field.type.namespace
60
64
  end
61
65
  end
62
66
 
@@ -71,13 +75,13 @@ class TestSchema < Test::Unit::TestCase
71
75
  ]}
72
76
  SCHEMA
73
77
 
74
- assert_equal schema.name, 'OuterRecord'
75
- assert_equal schema.fullname, 'my.name.space.OuterRecord'
76
- assert_equal schema.namespace, 'my.name.space'
78
+ assert_equal 'OuterRecord', schema.name
79
+ assert_equal 'my.name.space.OuterRecord', schema.fullname
80
+ assert_equal 'my.name.space', schema.namespace
77
81
  schema.fields.each do |field|
78
- assert_equal field.type.name, 'InnerEnum'
79
- assert_equal field.type.fullname, 'my.name.space.InnerEnum'
80
- assert_equal field.type.namespace, 'my.name.space'
82
+ assert_equal 'InnerEnum', field.type.name
83
+ assert_equal 'my.name.space.InnerEnum', field.type.fullname
84
+ assert_equal 'my.name.space', field.type.namespace
81
85
  end
82
86
  end
83
87
 
@@ -96,18 +100,18 @@ class TestSchema < Test::Unit::TestCase
96
100
  ]}
97
101
  SCHEMA
98
102
 
99
- assert_equal schema.name, 'OuterRecord'
100
- assert_equal schema.fullname, 'outer.OuterRecord'
101
- assert_equal schema.namespace, 'outer'
103
+ assert_equal 'OuterRecord', schema.name
104
+ assert_equal 'outer.OuterRecord', schema.fullname
105
+ assert_equal 'outer', schema.namespace
102
106
  middle = schema.fields.first.type
103
- assert_equal middle.name, 'MiddleRecord'
104
- assert_equal middle.fullname, 'middle.MiddleRecord'
105
- assert_equal middle.namespace, 'middle'
107
+ assert_equal 'MiddleRecord', middle.name
108
+ assert_equal 'middle.MiddleRecord', middle.fullname
109
+ assert_equal 'middle', middle.namespace
106
110
  inner = middle.fields.first.type
107
- assert_equal inner.name, 'InnerRecord'
108
- assert_equal inner.fullname, 'middle.InnerRecord'
109
- assert_equal inner.namespace, 'middle'
110
- assert_equal inner.fields.first.type, middle
111
+ assert_equal 'InnerRecord', inner.name
112
+ assert_equal 'middle.InnerRecord', inner.fullname
113
+ assert_equal 'middle', inner.namespace
114
+ assert_equal middle, inner.fields.first.type
111
115
  end
112
116
 
113
117
  def test_to_avro_includes_namespaces
@@ -120,7 +124,7 @@ class TestSchema < Test::Unit::TestCase
120
124
  ]}
121
125
  SCHEMA
122
126
 
123
- assert_equal schema.to_avro, {
127
+ assert_equal({
124
128
  'type' => 'record', 'name' => 'OuterRecord', 'namespace' => 'my.name.space',
125
129
  'fields' => [
126
130
  {'name' => 'definition', 'type' => {
@@ -129,6 +133,21 @@ class TestSchema < Test::Unit::TestCase
129
133
  }},
130
134
  {'name' => 'reference', 'type' => 'my.name.space.InnerFixed'}
131
135
  ]
136
+ }, schema.to_avro)
137
+ end
138
+
139
+ def test_to_avro_includes_logical_type
140
+ schema = Avro::Schema.parse <<-SCHEMA
141
+ {"type": "record", "name": "has_logical", "fields": [
142
+ {"name": "dt", "type": {"type": "int", "logicalType": "date"}}]
143
+ }
144
+ SCHEMA
145
+
146
+ assert_equal schema.to_avro, {
147
+ 'type' => 'record', 'name' => 'has_logical',
148
+ 'fields' => [
149
+ {'name' => 'dt', 'type' => {'type' => 'int', 'logicalType' => 'date'}}
150
+ ]
132
151
  }
133
152
  end
134
153
 
@@ -160,4 +179,281 @@ class TestSchema < Test::Unit::TestCase
160
179
  ]
161
180
  }
162
181
  end
182
+
183
+ def test_record_field_doc_attribute
184
+ field_schema_json = Avro::Schema.parse <<-SCHEMA
185
+ {
186
+ "type": "record",
187
+ "name": "Record",
188
+ "namespace": "my.name.space",
189
+ "fields": [
190
+ {
191
+ "name": "name",
192
+ "type": "boolean",
193
+ "doc": "documentation"
194
+ }
195
+ ]
196
+ }
197
+ SCHEMA
198
+
199
+ field_schema_hash =
200
+ {
201
+ 'type' => 'record',
202
+ 'name' => 'Record',
203
+ 'namespace' => 'my.name.space',
204
+ 'fields' => [
205
+ {
206
+ 'name' => 'name',
207
+ 'type' => 'boolean',
208
+ 'doc' => 'documentation'
209
+ }
210
+ ]
211
+ }
212
+
213
+ assert_equal field_schema_hash, field_schema_json.to_avro
214
+ end
215
+
216
+ def test_record_doc_attribute
217
+ record_schema_json = Avro::Schema.parse <<-SCHEMA
218
+ {
219
+ "type": "record",
220
+ "name": "Record",
221
+ "namespace": "my.name.space",
222
+ "doc": "documentation",
223
+ "fields": [
224
+ {
225
+ "name": "name",
226
+ "type": "boolean"
227
+ }
228
+ ]
229
+ }
230
+ SCHEMA
231
+
232
+ record_schema_hash =
233
+ {
234
+ 'type' => 'record',
235
+ 'name' => 'Record',
236
+ 'namespace' => 'my.name.space',
237
+ 'doc' => 'documentation',
238
+ 'fields' => [
239
+ {
240
+ 'name' => 'name',
241
+ 'type' => 'boolean'
242
+ }
243
+ ]
244
+ }
245
+
246
+ assert_equal record_schema_hash, record_schema_json.to_avro
247
+ end
248
+
249
+ def test_enum_doc_attribute
250
+ enum_schema_json = Avro::Schema.parse <<-SCHEMA
251
+ {
252
+ "type": "enum",
253
+ "name": "Enum",
254
+ "namespace": "my.name.space",
255
+ "doc": "documentation",
256
+ "symbols" : [
257
+ "SPADES",
258
+ "HEARTS",
259
+ "DIAMONDS",
260
+ "CLUBS"
261
+ ]
262
+ }
263
+ SCHEMA
264
+
265
+ enum_schema_hash =
266
+ {
267
+ 'type' => 'enum',
268
+ 'name' => 'Enum',
269
+ 'namespace' => 'my.name.space',
270
+ 'doc' => 'documentation',
271
+ 'symbols' => [
272
+ 'SPADES',
273
+ 'HEARTS',
274
+ 'DIAMONDS',
275
+ 'CLUBS'
276
+ ]
277
+ }
278
+ assert_equal enum_schema_hash, enum_schema_json.to_avro
279
+ end
280
+
281
+ def test_empty_record
282
+ schema = Avro::Schema.parse('{"type":"record", "name":"Empty"}')
283
+ assert_empty(schema.fields)
284
+ end
285
+
286
+ def test_empty_union
287
+ schema = Avro::Schema.parse('[]')
288
+ assert_equal(schema.to_s, '[]')
289
+ end
290
+
291
+ def test_read
292
+ schema = Avro::Schema.parse('"string"')
293
+ writer_schema = Avro::Schema.parse('"int"')
294
+ assert_false(schema.read?(writer_schema))
295
+ assert_true(schema.read?(schema))
296
+ end
297
+
298
+ def test_be_read
299
+ schema = Avro::Schema.parse('"string"')
300
+ writer_schema = Avro::Schema.parse('"int"')
301
+ assert_false(schema.be_read?(writer_schema))
302
+ assert_true(schema.be_read?(schema))
303
+ end
304
+
305
+ def test_mutual_read
306
+ schema = Avro::Schema.parse('"string"')
307
+ writer_schema = Avro::Schema.parse('"int"')
308
+ default1 = Avro::Schema.parse('{"type":"record", "name":"Default", "fields":[{"name":"i", "type":"int", "default": 1}]}')
309
+ default2 = Avro::Schema.parse('{"type":"record", "name":"Default", "fields":[{"name:":"s", "type":"string", "default": ""}]}')
310
+ assert_false(schema.mutual_read?(writer_schema))
311
+ assert_true(schema.mutual_read?(schema))
312
+ assert_true(default1.mutual_read?(default2))
313
+ end
314
+
315
+ def test_validate_defaults
316
+ exception = assert_raise(Avro::SchemaParseError) do
317
+ hash_to_schema(
318
+ type: 'record',
319
+ name: 'fruits',
320
+ fields: [
321
+ {
322
+ name: 'veggies',
323
+ type: 'string',
324
+ default: nil
325
+ }
326
+ ]
327
+ )
328
+ end
329
+ assert_equal('Error validating default for veggies: at . expected type string, got null',
330
+ exception.to_s)
331
+ end
332
+
333
+ def test_field_default_validation_disabled
334
+ Avro.disable_field_default_validation = true
335
+ assert_nothing_raised do
336
+ hash_to_schema(
337
+ type: 'record',
338
+ name: 'fruits',
339
+ fields: [
340
+ {
341
+ name: 'veggies',
342
+ type: 'string',
343
+ default: nil
344
+ }
345
+ ]
346
+ )
347
+ end
348
+ ensure
349
+ Avro.disable_field_default_validation = false
350
+ end
351
+
352
+ def test_field_default_validation_disabled_via_env
353
+ Avro.disable_field_default_validation = false
354
+ ENV['AVRO_DISABLE_FIELD_DEFAULT_VALIDATION'] = "1"
355
+
356
+ assert_nothing_raised do
357
+ hash_to_schema(
358
+ type: 'record',
359
+ name: 'fruits',
360
+ fields: [
361
+ {
362
+ name: 'veggies',
363
+ type: 'string',
364
+ default: nil
365
+ }
366
+ ]
367
+ )
368
+ end
369
+ ensure
370
+ ENV.delete('AVRO_DISABLE_FIELD_DEFAULT_VALIDATION')
371
+ Avro.disable_field_default_validation = false
372
+ end
373
+
374
+ def test_validate_record_valid_default
375
+ assert_nothing_raised(Avro::SchemaParseError) do
376
+ hash_to_schema(
377
+ type: 'record',
378
+ name: 'with_subrecord',
379
+ fields: [
380
+ {
381
+ name: 'sub',
382
+ type: {
383
+ name: 'subrecord',
384
+ type: 'record',
385
+ fields: [
386
+ { type: 'string', name: 'x' }
387
+ ]
388
+ },
389
+ default: {
390
+ x: "y"
391
+ }
392
+ }
393
+ ]
394
+ )
395
+ end
396
+ end
397
+
398
+ def test_validate_record_invalid_default
399
+ exception = assert_raise(Avro::SchemaParseError) do
400
+ hash_to_schema(
401
+ type: 'record',
402
+ name: 'with_subrecord',
403
+ fields: [
404
+ {
405
+ name: 'sub',
406
+ type: {
407
+ name: 'subrecord',
408
+ type: 'record',
409
+ fields: [
410
+ { type: 'string', name: 'x' }
411
+ ]
412
+ },
413
+ default: {
414
+ a: 1
415
+ }
416
+ }
417
+ ]
418
+ )
419
+ end
420
+ assert_equal('Error validating default for sub: at .x expected type string, got null',
421
+ exception.to_s)
422
+ end
423
+
424
+ def test_validate_union_defaults
425
+ exception = assert_raise(Avro::SchemaParseError) do
426
+ hash_to_schema(
427
+ type: 'record',
428
+ name: 'fruits',
429
+ fields: [
430
+ {
431
+ name: 'veggies',
432
+ type: %w(string null),
433
+ default: 5
434
+ }
435
+ ]
436
+ )
437
+ end
438
+ assert_equal('Error validating default for veggies: at . expected type string, got int with value 5',
439
+ exception.to_s)
440
+ end
441
+
442
+ def test_validate_union_default_first_type
443
+ exception = assert_raise(Avro::SchemaParseError) do
444
+ hash_to_schema(
445
+ type: 'record',
446
+ name: 'fruits',
447
+ fields: [
448
+ {
449
+ name: 'veggies',
450
+ type: %w(null string),
451
+ default: 'apple'
452
+ }
453
+ ]
454
+ )
455
+ end
456
+ assert_equal('Error validating default for veggies: at . expected type null, got string with value "apple"',
457
+ exception.to_s)
458
+ end
163
459
  end