avro-builder 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 77208340502bdb2f785d8dfec4074ed2ad29c368
4
- data.tar.gz: 8597a93d23ceabc2bb2df55fa3407eaa0afc1077
3
+ metadata.gz: 927dca1e52261de526556bb1a692ac90406d10bf
4
+ data.tar.gz: 89e990b81db93413c7f055c1ec95eb23a237b8f3
5
5
  SHA512:
6
- metadata.gz: 739f0bb19f344589693a8afee826a23b61119941ee6f241a107773f151a97ef64654bf62cffee15429b5a1516622b6aa210eeb846cf166cfa67b6b1d7b4fdb0f
7
- data.tar.gz: ee228210b7daaed7973b5c56980f30cea2166f3420cff62dfeaf21fd4ea0787617cbd6722b92bcc5774c71226e8a5a926f866ae4ecaeb66c839bc68203189df7
6
+ metadata.gz: c69e346811dfbd2574602bd6724b83dc2421a822123c5973b8b6def6b8b9aa552231af78c7778cf2feaac9054ea72145263f2ce41ce5b43eb7351f099dfb480e
7
+ data.tar.gz: aeb7b896dbe7146a7b4e8d0c4e7eb5533f48c7e15179ff3899ef74f733a91230ec4b9693268bad38792cca40739e7a68265abfcf91cae7c033ebda99d5d51f69
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ /gemfiles/*.lock
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --format documentation
2
2
  --color
3
+ --require spec_helper
@@ -2,6 +2,9 @@ language: ruby
2
2
  rvm:
3
3
  - 2.2.4
4
4
  - 2.3.1
5
+ before_script:
6
+ - bundle exec appraisal install --jobs=3
5
7
  script:
6
8
  - bundle exec rubocop
7
- - bundle exec rspec
9
+ - bundle exec appraisal avro-official rspec
10
+ - bundle exec appraisal avro-salsify-fork rspec
@@ -0,0 +1,7 @@
1
+ appraise 'avro-official' do
2
+ gem 'avro', '1.8.1'
3
+ end
4
+
5
+ appraise 'avro-salsify-fork' do
6
+ gem 'avro-salsify-fork', '1.9.0.0', require: 'avro'
7
+ end
@@ -1,5 +1,15 @@
1
1
  # avro-builder changelog
2
2
 
3
+ ## v0.11.0
4
+ - Add support for generating schemas that contain logical types. The official
5
+ Ruby `avro` gem does not yet support logical types. The `avro-salsify-fork` gem
6
+ can be used to encode and decode logical types.
7
+ - Add support for `union`, `map`, and `array` methods to embed those types
8
+ within other complex types.
9
+ - Add methods for primitives types (`string`, `int`, `long`, `float`, etc)
10
+ that allow those types, including a logical type attribute, to be embedded
11
+ within complex types.
12
+
3
13
  ## v0.10.0
4
14
  - Include DSL file line numbers in stack traces.
5
15
  - Add optional `filename` keyword argument to `Avro::Builder.build` and
data/README.md CHANGED
@@ -173,6 +173,20 @@ record :my_record_with_named do
173
173
  end
174
174
  ```
175
175
 
176
+ ### Complex Types
177
+
178
+ Array, maps and unions can each be embedded within another complex type using
179
+ methods that match the type name:
180
+
181
+ ```ruby
182
+ record :complex_types do
183
+ required :array_of_unions, :array, items: union(:int, :string)
184
+ required :array_or_map, :union, types: [array(:int), map(:int)]
185
+ end
186
+ ```
187
+
188
+ For more on unions see [below](#unions).
189
+
176
190
  ### Nested Records
177
191
 
178
192
  Nested records may be created by referring to the name of the previously
@@ -240,6 +254,48 @@ end
240
254
  For an optional union, `null` is automatically added as the first type for
241
255
  the union and the field defaults to `null`.
242
256
 
257
+ ### Logical Types
258
+
259
+ The DSL supports setting a logical type on any type except a union. The logical
260
+ types defined in the Avro [spec](https://avro.apache.org/docs/1.8.1/spec.html#Logical+Types)
261
+ are more limited.
262
+
263
+ The official Ruby `avro` gem does not yet support logical types:
264
+ [AVRO-1695](https://issues.apache.org/jira/browse/AVRO-1695).
265
+
266
+ There is a `avro-salsify-fork` gem released from this
267
+ [fork](https://github.com/salsify/avro) that includes changes to support
268
+ encoding and decoding logical types. To use this gem, reference it in your Gemfile:
269
+
270
+ ```ruby
271
+ gem 'avro-salsify-fork', require: 'avro'
272
+ ```
273
+
274
+ A logical type can be specified for a field using the `logical_type` attribute:
275
+
276
+ ```ruby
277
+ record :with_timestamp
278
+ required :created_at, :long, logical_type: 'timestamp-micros'
279
+ end
280
+ ```
281
+
282
+ Primitive types with a logical type can also be embedded within complex types
283
+ using either the generic `type` method:
284
+
285
+ ```ruby
286
+ record :with_date_array
287
+ required :date_array, :array, type(:int, logical_type: date)
288
+ end
289
+ ```
290
+
291
+ Or using a primitive type specific method:
292
+
293
+ ```ruby
294
+ record :with_date_array
295
+ required :date_array, :array, int(logical_type: date)
296
+ end
297
+ ```
298
+
243
299
  ### Auto-loading and Imports
244
300
 
245
301
  Specify paths to search for definitions:
data/Rakefile CHANGED
@@ -1,6 +1,14 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
+ require 'appraisal/task'
3
4
 
4
- RSpec::Core::RakeTask.new(:spec)
5
+ RSpec::Core::RakeTask.new(:default_spec)
5
6
 
6
- task default: :spec
7
+ Appraisal::Task.new
8
+
9
+ if !ENV['APPRAISAL_INITIALIZED']
10
+ task default: :appraisal
11
+ task spec: :appraisal
12
+ else
13
+ task default: :default_spec
14
+ end
@@ -21,6 +21,7 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.add_runtime_dependency 'avro', '>= 1.7.0'
23
23
 
24
+ spec.add_development_dependency 'appraisal'
24
25
  spec.add_development_dependency 'bundler', '~> 1.11'
25
26
  spec.add_development_dependency 'rake', '~> 10.0'
26
27
  spec.add_development_dependency 'rspec', '~> 3.0'
data/bin/setup CHANGED
@@ -4,4 +4,5 @@ IFS=$'\n\t'
4
4
  set -vx
5
5
 
6
6
  bundle install
7
+ appraisal install
7
8
  overcommit --install
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "avro", "1.8.1"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "avro-salsify-fork", "1.9.0.0", :require => "avro"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,34 @@
1
+ module Avro
2
+ module Builder
3
+
4
+ # This concern is included by contexts where anonymous types can be defined.
5
+ module AnonymousTypes
6
+ include Avro::Builder::TypeFactory
7
+
8
+ Avro::Schema::PRIMITIVE_TYPES_SYM.each do |type_name|
9
+ define_method(type_name) do |options = {}, &block|
10
+ type(type_name, options, &block)
11
+ end
12
+ end
13
+
14
+ def union(*types, &block)
15
+ type(__method__, { types: types }, &block)
16
+ end
17
+
18
+ def array(items, options = {}, &block)
19
+ type(__method__, { items: items }.merge(options), &block)
20
+ end
21
+
22
+ def map(values, options = {}, &block)
23
+ type(__method__, { values: values }.merge(options), &block)
24
+ end
25
+
26
+ def type(type_name, options = {}, &block)
27
+ create_and_configure_builtin_type(type_name,
28
+ cache: cache,
29
+ internal: options,
30
+ &block)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -5,6 +5,7 @@ require 'avro/builder/dsl_attributes'
5
5
  require 'avro/builder/namespaceable'
6
6
  require 'avro/builder/definition_cache'
7
7
  require 'avro/builder/type_factory'
8
+ require 'avro/builder/anonymous_types'
8
9
  require 'avro/builder/types'
9
10
  require 'avro/builder/field'
10
11
  require 'avro/builder/record'
@@ -89,15 +90,12 @@ module Avro
89
90
  end
90
91
 
91
92
  def create_named_type(name, avro_type_name, options = {}, &block)
92
- create_and_configure_builtin_type(avro_type_name,
93
- cache: cache,
94
- internal: { _name: name,
95
- namespace: namespace },
96
- options: options,
97
- &block).tap do |type|
98
- type.validate!
99
- @last_object = type
100
- end
93
+ @last_object = create_and_configure_builtin_type(avro_type_name,
94
+ cache: cache,
95
+ internal: { _name: name,
96
+ namespace: namespace },
97
+ options: options,
98
+ &block)
101
99
  end
102
100
 
103
101
  def eval_file(name)
@@ -9,8 +9,8 @@ module Avro
9
9
  class Field
10
10
  include Avro::Builder::DslOptions
11
11
  include Avro::Builder::DslAttributes
12
- include Avro::Builder::TypeFactory
13
12
  include Avro::Builder::Aliasable
13
+ include Avro::Builder::AnonymousTypes
14
14
 
15
15
  INTERNAL_ATTRIBUTES = %i(optional_field).to_set.freeze
16
16
 
@@ -31,29 +31,33 @@ module Avro
31
31
  send(key, type_options.delete(key)) if dsl_attribute?(key)
32
32
  end
33
33
 
34
- @type = if builtin_type?(avro_type_name)
35
- create_and_configure_builtin_type(avro_type_name,
36
- field: self,
37
- cache: cache,
38
- internal: internal,
39
- options: type_options)
40
- else
41
- cache.lookup_named_type(avro_type_name, namespace)
42
- end
34
+ @field_type = if builtin_type?(avro_type_name)
35
+ create_and_configure_builtin_type(avro_type_name,
36
+ field: self,
37
+ cache: cache,
38
+ internal: internal,
39
+ validate_type: false,
40
+ options: type_options)
41
+ elsif avro_type_name.is_a?(Avro::Builder::Types::Type)
42
+ raise 'Type name must be an Avro builtin type '\
43
+ "or a previously defined type name. Got #{avro_type_name}"
44
+ else
45
+ cache.lookup_named_type(avro_type_name, namespace)
46
+ end
43
47
 
44
48
  # DSL calls must be evaluated after the type has been constructed
45
49
  instance_eval(&block) if block_given?
46
- @type.validate!
50
+ @field_type.validate!
47
51
  end
48
52
 
49
53
  ## Delegate additional DSL calls to the type
50
54
 
51
55
  def respond_to_missing?(id, _include_all)
52
- type.dsl_respond_to?(id) || super
56
+ field_type.dsl_respond_to?(id) || super
53
57
  end
54
58
 
55
59
  def method_missing(id, *args, &block)
56
- type.dsl_respond_to?(id) ? type.send(id, *args, &block) : super
60
+ field_type.dsl_respond_to?(id) ? field_type.send(id, *args, &block) : super
57
61
  end
58
62
 
59
63
  def name_fragment
@@ -64,7 +68,7 @@ module Avro
64
68
  # and return the namespace value from the enclosing record.
65
69
  def namespace(value = nil)
66
70
  if value
67
- type.namespace(value)
71
+ field_type.namespace(value)
68
72
  else
69
73
  record.namespace
70
74
  end
@@ -73,7 +77,7 @@ module Avro
73
77
  # Delegate setting name explicitly via DSL to type
74
78
  def name(value = nil)
75
79
  if value
76
- type.name(value)
80
+ field_type.name(value)
77
81
  else
78
82
  # Return the name of the field
79
83
  @name
@@ -95,12 +99,12 @@ module Avro
95
99
 
96
100
  private
97
101
 
98
- attr_accessor :type, :optional_field, :cache, :record
102
+ attr_accessor :field_type, :optional_field, :cache, :record
99
103
 
100
104
  # Optional fields must be serialized as a union -- an array of types.
101
105
  def serialized_type(reference_state)
102
- result = type.serialize(reference_state)
103
- optional_field ? type.class.union_with_null(result) : result
106
+ result = field_type.serialize(reference_state)
107
+ optional_field ? field_type.class.union_with_null(result) : result
104
108
  end
105
109
  end
106
110
  end
@@ -31,11 +31,13 @@ module Avro
31
31
  cache: nil,
32
32
  internal: {},
33
33
  options: {},
34
+ validate_type: true,
34
35
  &block)
35
36
  create_builtin_type(avro_type_name, field: field, cache: cache).tap do |type|
36
37
  type.configure_options(internal.merge(options))
37
38
  type.cache!
38
39
  type.instance_eval(&block) if block_given?
40
+ type.validate! if validate_type
39
41
  end
40
42
  end
41
43
 
@@ -1,6 +1,5 @@
1
1
  require 'avro/builder/types/type'
2
2
  require 'avro/builder/types/complex_type'
3
- require 'avro/builder/types/configurable_type'
4
3
  require 'avro/builder/types/type_referencer'
5
4
  require 'avro/builder/types/named_type'
6
5
  require 'avro/builder/types/record_type'
@@ -3,7 +3,6 @@ module Avro
3
3
  module Types
4
4
  class ArrayType < Type
5
5
  include Avro::Builder::Types::ComplexType
6
- include Avro::Builder::Types::ConfigurableType
7
6
  include Avro::Builder::Types::TypeReferencer
8
7
 
9
8
  dsl_attribute :items do |items_type = nil|
@@ -19,10 +18,8 @@ module Avro
19
18
  end
20
19
 
21
20
  def serialize(referenced_state)
22
- {
23
- type: avro_type_name,
24
- items: items.serialize(referenced_state)
25
- }
21
+ super(referenced_state,
22
+ overrides: { items: items.serialize(referenced_state) })
26
23
  end
27
24
  end
28
25
  end
@@ -19,6 +19,13 @@ module Avro
19
19
  field.namespace
20
20
  end
21
21
 
22
+ def serialize(_reference_state, overrides: {})
23
+ {
24
+ type: avro_type_name,
25
+ logicalType: logical_type
26
+ }.merge(overrides).reject { |_, v| v.nil? }
27
+ end
28
+
22
29
  module ClassMethods
23
30
 
24
31
  # Infer avro_type_name based on class
@@ -3,7 +3,6 @@ module Avro
3
3
  module Types
4
4
  class MapType < Type
5
5
  include Avro::Builder::Types::ComplexType
6
- include Avro::Builder::Types::ConfigurableType
7
6
  include Avro::Builder::Types::TypeReferencer
8
7
 
9
8
  dsl_attribute :values do |value_type = nil|
@@ -15,10 +14,8 @@ module Avro
15
14
  end
16
15
 
17
16
  def serialize(referenced_state)
18
- {
19
- type: avro_type_name,
20
- values: values.serialize(referenced_state)
21
- }
17
+ super(referenced_state,
18
+ overrides: { values: values.serialize(referenced_state) })
22
19
  end
23
20
 
24
21
  def validate!
@@ -1,4 +1,3 @@
1
- require 'avro/builder/types/configurable_type'
2
1
  require 'avro/builder/namespaceable'
3
2
  require 'avro/builder/aliasable'
4
3
  require 'avro/builder/types/named_error_handling'
@@ -12,7 +11,6 @@ module Avro
12
11
  class NamedType < Type
13
12
  include Avro::Builder::Types::ComplexType
14
13
  include Avro::Builder::Namespaceable
15
- include Avro::Builder::Types::ConfigurableType
16
14
  include Avro::Builder::Aliasable
17
15
 
18
16
  dsl_option :name, dsl_name: :type_name
@@ -59,11 +57,7 @@ module Avro
59
57
  # to the serialized value.
60
58
  def serialize(reference_state, overrides: {})
61
59
  reference_state.definition_or_reference(fullname) do
62
- {
63
- name: name,
64
- type: avro_type_name,
65
- namespace: namespace
66
- }.merge(overrides).reject { |_, v| v.nil? }
60
+ serialized_attribute_hash.merge(overrides).reject { |_, v| v.nil? }
67
61
  end
68
62
  end
69
63
 
@@ -71,12 +65,21 @@ module Avro
71
65
  # Subclasses may call super with additional overrides to be added
72
66
  # to the hash representation.
73
67
  def to_h(_reference_state, overrides: {})
68
+ serialized_attribute_hash
69
+ .merge(aliases: aliases)
70
+ .merge(overrides)
71
+ .reject { |_, v| v.nil? }
72
+ end
73
+
74
+ private
75
+
76
+ def serialized_attribute_hash
74
77
  {
75
78
  name: name,
76
79
  type: avro_type_name,
77
80
  namespace: namespace,
78
- aliases: aliases
79
- }.merge(overrides).reject { |_, v| v.nil? }
81
+ logicalType: logical_type
82
+ }
80
83
  end
81
84
  end
82
85
  end
@@ -4,6 +4,7 @@ module Avro
4
4
  # This class represents a record in an Avro schema. Records may be defined
5
5
  # at the top-level or as the type for a field in a record.
6
6
  class RecordType < Avro::Builder::Types::NamedType
7
+ include Avro::Builder::AnonymousTypes
7
8
 
8
9
  DSL_METHODS = %i(required optional extends).to_set.freeze
9
10
 
@@ -64,6 +65,7 @@ module Avro
64
65
  namespace: namespace,
65
66
  doc: doc,
66
67
  aliases: aliases,
68
+ logicalType: logical_type,
67
69
  fields: fields.values.map { |field| field.serialize(reference_state) }
68
70
  }.reject { |_, v| v.nil? }
69
71
  end
@@ -8,6 +8,8 @@ module Avro
8
8
  include Avro::Builder::DslOptions
9
9
  include Avro::Builder::DslAttributes
10
10
 
11
+ dsl_attribute :logical_type
12
+
11
13
  attr_reader :avro_type_name
12
14
 
13
15
  def initialize(avro_type_name, cache:, field: nil)
@@ -17,15 +19,21 @@ module Avro
17
19
  end
18
20
 
19
21
  def serialize(_reference_state)
20
- avro_type_name
22
+ if logical_type
23
+ { type: avro_type_name, logicalType: logical_type }
24
+ else
25
+ avro_type_name
26
+ end
21
27
  end
22
28
 
23
29
  def namespace
24
30
  nil
25
31
  end
26
32
 
27
- def configure_options(_options = {})
28
- # No-op
33
+ def configure_options(options = {})
34
+ options.each do |key, value|
35
+ send("#{key}=", value) if dsl_option?(key)
36
+ end
29
37
  end
30
38
 
31
39
  # Optional fields are represented as a union of the type with :null.
@@ -8,11 +8,13 @@ module Avro
8
8
  module TypeReferencer
9
9
  include Avro::Builder::TypeFactory
10
10
 
11
- def create_builtin_or_lookup_named_type(avro_type_name)
12
- if builtin_type?(avro_type_name)
13
- create_builtin_type(avro_type_name, field: field, cache: cache)
11
+ def create_builtin_or_lookup_named_type(avro_type_or_name)
12
+ if avro_type_or_name.is_a?(Avro::Builder::Types::Type)
13
+ avro_type_or_name
14
+ elsif builtin_type?(avro_type_or_name)
15
+ create_builtin_type(avro_type_or_name, field: field, cache: cache)
14
16
  else
15
- cache.lookup_named_type(avro_type_name)
17
+ cache.lookup_named_type(avro_type_or_name)
16
18
  end
17
19
  end
18
20
  end
@@ -3,7 +3,6 @@ module Avro
3
3
  module Types
4
4
  class UnionType < Type
5
5
  include Avro::Builder::Types::ComplexType
6
- include Avro::Builder::Types::ConfigurableType
7
6
  include Avro::Builder::Types::TypeReferencer
8
7
 
9
8
  NULL_TYPE = 'null'.freeze
@@ -30,6 +29,10 @@ module Avro
30
29
  def validate!
31
30
  validate_required_attribute!(:types)
32
31
  end
32
+
33
+ def logical_type=(value)
34
+ raise AttributeError.new("Logical types are not supported for unions: #{value}.")
35
+ end
33
36
  end
34
37
  end
35
38
  end
@@ -1,5 +1,5 @@
1
1
  module Avro
2
2
  module Builder
3
- VERSION = '0.10.0'.freeze
3
+ VERSION = '0.11.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: avro-builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Salsify Engineering
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.7.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: appraisal
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -135,6 +149,7 @@ files:
135
149
  - ".rubocop.yml"
136
150
  - ".ruby-version"
137
151
  - ".travis.yml"
152
+ - Appraisals
138
153
  - CHANGELOG.md
139
154
  - Gemfile
140
155
  - LICENSE.txt
@@ -143,8 +158,11 @@ files:
143
158
  - avro-builder.gemspec
144
159
  - bin/console
145
160
  - bin/setup
161
+ - gemfiles/avro_official.gemfile
162
+ - gemfiles/avro_salsify_fork.gemfile
146
163
  - lib/avro/builder.rb
147
164
  - lib/avro/builder/aliasable.rb
165
+ - lib/avro/builder/anonymous_types.rb
148
166
  - lib/avro/builder/definition_cache.rb
149
167
  - lib/avro/builder/dsl.rb
150
168
  - lib/avro/builder/dsl_attributes.rb
@@ -165,7 +183,6 @@ files:
165
183
  - lib/avro/builder/types.rb
166
184
  - lib/avro/builder/types/array_type.rb
167
185
  - lib/avro/builder/types/complex_type.rb
168
- - lib/avro/builder/types/configurable_type.rb
169
186
  - lib/avro/builder/types/enum_type.rb
170
187
  - lib/avro/builder/types/fixed_type.rb
171
188
  - lib/avro/builder/types/map_type.rb
@@ -1,16 +0,0 @@
1
- module Avro
2
- module Builder
3
- module Types
4
-
5
- # This concern is used by Types that can be configured via the DSL.
6
- # Only attributes that can be set via options are configured here.
7
- module ConfigurableType
8
- def configure_options(options = {})
9
- options.each do |key, value|
10
- send("#{key}=", value) if dsl_option?(key)
11
- end
12
- end
13
- end
14
- end
15
- end
16
- end