avro-builder 0.10.0 → 0.11.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.
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