avro-builder 0.4.0 → 0.5.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: a2caec0734e6bf6ba5d83e453fe9d067f9f47fa6
4
- data.tar.gz: 79fca4e7bf6c5bc9ad9bbcb2425afb902e1b9026
3
+ metadata.gz: 6a6a7c469ac63da10199ce29df3e1b6d82e6904a
4
+ data.tar.gz: aa7a5d4506499cc9a75ce963e54254d6ded16b49
5
5
  SHA512:
6
- metadata.gz: 73e3846ea11202adb26505a47bc3ec7a7772f10f1ff7560593788926922a9c9b45ece9c49bc2fecb0df48627923b44dcc23603cad25020d7c45314f64fcd191f
7
- data.tar.gz: ae4ffe269bbd87c8fdc6f548898eb3fbf3749781604744f8f79deb38133670a5cdc4a5f2dec0347ff94fb568c0e2e2ae5a1411b4d69efecd32db0f4ceff8bd57
6
+ metadata.gz: 2803b8ebb4bedb1256041eaf4533c2c1309e416fb7152ef3f55e76191e32b1af17608895d42e729b026a1ca0edada181aa779993b4ddfd485dcd594d824c057f
7
+ data.tar.gz: bba50fb4e40081ab522201c764f90c583e351b3be14b2ec237c850565bc84fad1e12ab42b8f8fc32289286e380f1cd41c2c600a10e5afe3bb316a43726304f67
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # avro-builder changelog
2
2
 
3
+ ## v0.5.0
4
+ - Support references to named types that are defined inline.
5
+ - Raise an error for duplicate definitions with the same fullname.
6
+
3
7
  ## v0.4.0
4
8
  - Add validation for required DSL attributes that are not specified.
5
9
  - Allow name to be configured via a block for top-level record, enum, and fixed
@@ -0,0 +1,60 @@
1
+ module Avro
2
+ module Builder
3
+
4
+ # This class is used to cache previously defined schema objects
5
+ # preventing direct access via the DSL.
6
+ class DefinitionCache
7
+ def initialize(builder)
8
+ @builder = builder
9
+ @schema_objects = {}
10
+ @schema_names = Set.new
11
+ end
12
+
13
+ # Cache and schema object by name (for convenience) and fullname.
14
+ # The schema object is only available by name if the unqualified name is unique.
15
+ def add_schema_object(object)
16
+ store_by_name(object) if object.namespace
17
+ store_by_fullname(object)
18
+ end
19
+
20
+ # Lookup an Avro schema object by name, possibly fully qualified by namespace.
21
+ def lookup_named_type(key, namespace = nil)
22
+ key_str = Avro::Name.make_fullname(key.to_s, namespace && namespace.to_s)
23
+ object = schema_objects[key_str]
24
+
25
+ if object.nil? && !schema_names.include?(key.to_s)
26
+ object = builder.import(key)
27
+ end
28
+
29
+ raise DefinitionNotFoundError.new(key) if object.nil? && namespace.nil?
30
+
31
+ # Return object or retry without namespace
32
+ object || lookup_named_type(key, nil)
33
+ end
34
+
35
+ private
36
+
37
+ # Schemas are stored by name, provided that the name is unique.
38
+ # If the unqualified name is ambiguous then it is removed from the cache.
39
+ # A set of unqualified names is kept to avoid reloading files for
40
+ # ambiguous references.
41
+ def store_by_name(object)
42
+ key = object.name.to_s
43
+ if schema_objects.key?(key)
44
+ schema_objects.delete(key)
45
+ elsif !schema_names.include?(key)
46
+ schema_objects.store(key, object)
47
+ end
48
+ schema_names.add(key)
49
+ end
50
+
51
+ def store_by_fullname(object)
52
+ key = object.fullname
53
+ raise DuplicateDefinitionError.new(key, object, schema_objects[key]) if schema_objects.key?(key)
54
+ schema_objects.store(key, object)
55
+ end
56
+
57
+ attr_reader :schema_objects, :schema_names, :builder
58
+ end
59
+ end
60
+ end
@@ -2,6 +2,7 @@ require 'avro'
2
2
  require 'avro/builder/errors'
3
3
  require 'avro/builder/dsl_attributes'
4
4
  require 'avro/builder/namespaceable'
5
+ require 'avro/builder/definition_cache'
5
6
  require 'avro/builder/type_factory'
6
7
  require 'avro/builder/types'
7
8
  require 'avro/builder/field'
@@ -33,8 +34,9 @@ module Avro
33
34
  # Imports from the file with specified name fragment.
34
35
  def import(name)
35
36
  previous_namespace = namespace
36
- eval_file(name)
37
+ result = eval_file(name)
37
38
  namespace(previous_namespace)
39
+ result
38
40
  end
39
41
 
40
42
  ## DSL methods for Types
@@ -48,20 +50,6 @@ module Avro
48
50
  create_named_type(name, :fixed, size_option.merge(options), &block)
49
51
  end
50
52
 
51
- # Lookup an Avro schema object by name, possibly fully qualified by namespace.
52
- def lookup_named_type(key)
53
- key_str = key.to_s
54
- object = schema_objects[key_str]
55
-
56
- unless object
57
- import(key)
58
- object = schema_objects[key_str]
59
- end
60
-
61
- raise "Schema object #{key} not found" unless object
62
- object
63
- end
64
-
65
53
  # Return the last schema object processed as a Hash representing
66
54
  # the Avro schema.
67
55
  def to_h
@@ -85,28 +73,18 @@ module Avro
85
73
 
86
74
  private
87
75
 
88
- def builder
89
- self
90
- end
91
-
92
- def schema_objects
93
- @schema_objects ||= {}
94
- end
95
-
96
- def add_schema_object(object)
97
- @last_object = object
98
- schema_objects[object.name.to_s] = object
99
- schema_objects[object.fullname] = object if object.namespace
76
+ def cache
77
+ @cache ||= Avro::Builder::DefinitionCache.new(self)
100
78
  end
101
79
 
102
80
  def create_named_type(name, type_name, options = {}, &block)
103
81
  create_and_configure_builtin_type(type_name,
104
- builder: self,
82
+ cache: cache,
105
83
  internal: { name: name, namespace: namespace },
106
84
  options: options,
107
85
  &block).tap do |type|
108
86
  type.validate!
109
- add_schema_object(type)
87
+ @last_object = cache.add_schema_object(type)
110
88
  end
111
89
  end
112
90
 
@@ -11,5 +11,33 @@ module Avro
11
11
  super("attribute :#{attribute} missing for #{location}type :#{type}")
12
12
  end
13
13
  end
14
+
15
+ class DuplicateDefinitionError < StandardError
16
+ def initialize(key, object, existing_object)
17
+ super("definition for #{key.inspect} already exists\n"\
18
+ "existing definition:\n#{to_json(existing_object)}\n"\
19
+ "new definition:\n#{to_json(object)})")
20
+ end
21
+
22
+ private
23
+
24
+ def to_json(object)
25
+ object.to_h(SchemaSerializerReferenceState.new).to_json
26
+ end
27
+ end
28
+
29
+ class DefinitionNotFoundError < StandardError
30
+ def initialize(name)
31
+ super("definition not found for '#{name.to_s}'.#{suggest_namespace(name)}")
32
+ end
33
+
34
+ private
35
+
36
+ def suggest_namespace(name)
37
+ unless name.to_s.index('.')
38
+ ' Try specifying the full namespace.'
39
+ end
40
+ end
41
+ end
14
42
  end
15
43
  end
@@ -7,18 +7,15 @@ module Avro
7
7
  # A field must be initialized with a type.
8
8
  class Field
9
9
  include Avro::Builder::DslAttributes
10
- include Avro::Builder::Namespaceable
11
10
  include Avro::Builder::TypeFactory
12
11
 
13
12
  INTERNAL_ATTRIBUTES = Set.new(%i(optional_field)).freeze
14
13
 
15
- attr_accessor :type, :optional_field, :builder, :record
16
-
17
14
  # These attributes may be set as options or via a block in the DSL
18
15
  dsl_attributes :doc, :aliases, :default, :order
19
16
 
20
- def initialize(name:, type_name:, record:, builder:, internal: {}, options: {}, &block)
21
- @builder = builder
17
+ def initialize(name:, type_name:, record:, cache:, internal: {}, options: {}, &block)
18
+ @cache = cache
22
19
  @record = record
23
20
  @name = name.to_s
24
21
 
@@ -33,15 +30,18 @@ module Avro
33
30
  @type = if builtin_type?(type_name)
34
31
  create_and_configure_builtin_type(type_name,
35
32
  field: self,
33
+ cache: cache,
36
34
  internal: internal,
37
35
  options: options)
38
36
  else
39
- builder.lookup_named_type(type_name)
37
+ named_type = true
38
+ cache.lookup_named_type(type_name, namespace)
40
39
  end
41
40
 
42
41
  # DSL calls must be evaluated after the type has been constructed
43
42
  instance_eval(&block) if block_given?
44
43
  @type.validate!
44
+ @type.cache! unless named_type
45
45
  end
46
46
 
47
47
  ## Delegate additional DSL calls to the type
@@ -58,6 +58,16 @@ module Avro
58
58
  record.name_fragment
59
59
  end
60
60
 
61
+ # Delegate setting namespace explicitly via DSL to type
62
+ # and return the namespace value from the enclosing record.
63
+ def namespace(value = nil)
64
+ if value
65
+ type.namespace(value)
66
+ else
67
+ record.namespace
68
+ end
69
+ end
70
+
61
71
  # Delegate setting name explicitly via DSL to type
62
72
  def name(value = nil)
63
73
  if value
@@ -82,6 +92,8 @@ module Avro
82
92
 
83
93
  private
84
94
 
95
+ attr_accessor :type, :optional_field, :cache, :record
96
+
85
97
  # Optional fields must be serialized as a union -- an array of types.
86
98
  def serialized_type(reference_state)
87
99
  result = type.serialize(reference_state)
@@ -10,13 +10,13 @@ module Avro
10
10
  private
11
11
 
12
12
  # Return a new Type instance
13
- def create_builtin_type(type_name)
13
+ def create_builtin_type(type_name, field:, cache:)
14
14
  name = type_name.to_s.downcase
15
15
  case
16
16
  when Avro::Schema::PRIMITIVE_TYPES.include?(name)
17
- Avro::Builder::Types::Type.new(name)
17
+ Avro::Builder::Types::Type.new(name, field: field, cache: cache)
18
18
  when COMPLEX_TYPES.include?(name)
19
- Avro::Builder::Types.const_get("#{name.capitalize}Type").new
19
+ Avro::Builder::Types.const_get("#{name.capitalize}Type").new(field: field, cache: cache)
20
20
  else
21
21
  raise "Invalid builtin type: #{type_name}"
22
22
  end
@@ -26,13 +26,11 @@ module Avro
26
26
  # and setting attributes via the DSL
27
27
  def create_and_configure_builtin_type(type_name,
28
28
  field: nil,
29
- builder: nil,
29
+ cache: nil,
30
30
  internal: {},
31
31
  options: {},
32
32
  &block)
33
- create_builtin_type(type_name).tap do |type|
34
- type.field = field
35
- type.builder = builder
33
+ create_builtin_type(type_name, field: field, cache: cache).tap do |type|
36
34
  type.configure_options(internal.merge(options))
37
35
  type.instance_eval(&block) if block_given?
38
36
  end
@@ -11,11 +11,12 @@ module Avro
11
11
  end
12
12
 
13
13
  # Override initialize so that type name is not required
14
- def initialize
14
+ def initialize(cache:, field: nil)
15
+ super(self.class.type_name, cache: cache, field: field)
15
16
  end
16
17
 
17
- def type_name
18
- self.class.type_name
18
+ def namespace
19
+ field.namespace
19
20
  end
20
21
 
21
22
  module ClassMethods
@@ -26,6 +26,10 @@ module Avro
26
26
  required_attribute_error!(:name) if field.nil? && @name.nil?
27
27
  end
28
28
 
29
+ def cache!
30
+ cache.add_schema_object(self)
31
+ end
32
+
29
33
  # Named types that do not have an explicit name are assigned
30
34
  # a named based on the field and its nesting.
31
35
  def name_fragment
@@ -7,11 +7,14 @@ module Avro
7
7
 
8
8
  dsl_attributes :doc
9
9
 
10
- def initialize(name = nil, options = {})
10
+ def initialize(name = nil, options: {}, cache:, field: nil, &block)
11
+ @type_name = :record
11
12
  @name = name
12
- options.each do |key, value|
13
- send(key, value)
14
- end
13
+ @cache = cache
14
+ @field = field
15
+
16
+ configure_options(options)
17
+ instance_eval(&block) if block_given?
15
18
  end
16
19
 
17
20
  # Add a required field to the record
@@ -19,7 +22,7 @@ module Avro
19
22
  new_field = Avro::Builder::Field.new(name: name,
20
23
  type_name: type_name,
21
24
  record: self,
22
- builder: builder,
25
+ cache: cache,
23
26
  internal: { namespace: namespace },
24
27
  options: options,
25
28
  &block)
@@ -32,7 +35,7 @@ module Avro
32
35
  new_field = Avro::Builder::Field.new(name: name,
33
36
  type_name: type_name,
34
37
  record: self,
35
- builder: builder,
38
+ cache: cache,
36
39
  internal: { namespace: namespace,
37
40
  optional_field: true },
38
41
  options: options,
@@ -43,7 +46,7 @@ module Avro
43
46
  # Adds fields from the record with the specified name to the current
44
47
  # record.
45
48
  def extends(name)
46
- fields.merge!(builder.lookup_named_type(name).duplicated_fields)
49
+ fields.merge!(cache.lookup_named_type(name, namespace).duplicated_fields)
47
50
  end
48
51
 
49
52
  def to_h(reference_state = SchemaSerializerReferenceState.new)
@@ -79,12 +82,6 @@ module Avro
79
82
  def fields
80
83
  @fields ||= {}
81
84
  end
82
-
83
- # A record may be defined as a top-level schema or as the
84
- # type for a field.
85
- def builder
86
- super || field.builder
87
- end
88
85
  end
89
86
  end
90
87
  end
@@ -8,10 +8,11 @@ module Avro
8
8
  include Avro::Builder::DslAttributes
9
9
 
10
10
  attr_reader :type_name
11
- attr_accessor :field, :builder
12
11
 
13
- def initialize(type_name)
12
+ def initialize(type_name, cache:, field: nil)
14
13
  @type_name = type_name
14
+ @cache = cache
15
+ @field = field
15
16
  end
16
17
 
17
18
  def serialize(_reference_state)
@@ -36,6 +37,11 @@ module Avro
36
37
  def validate!
37
38
  end
38
39
 
40
+ # Subclasses should override this method if the type definition should
41
+ # be cached for reuse.
42
+ def cache!
43
+ end
44
+
39
45
  private
40
46
 
41
47
  def required_attribute_error!(attribute_name)
@@ -51,6 +57,10 @@ module Avro
51
57
  required_attribute_error!(attribute_name)
52
58
  end
53
59
  end
60
+
61
+ private
62
+
63
+ attr_accessor :field, :cache
54
64
  end
55
65
  end
56
66
  end
@@ -8,15 +8,11 @@ module Avro
8
8
  module TypeReferencer
9
9
  include Avro::Builder::TypeFactory
10
10
 
11
- def builder
12
- (!field.nil? && field.builder) || super
13
- end
14
-
15
11
  def create_builtin_or_lookup_named_type(type_name)
16
12
  if builtin_type?(type_name)
17
- create_builtin_type(type_name)
13
+ create_builtin_type(type_name, field: field, cache: cache)
18
14
  else
19
- builder.lookup_named_type(type_name)
15
+ cache.lookup_named_type(type_name)
20
16
  end
21
17
  end
22
18
  end
@@ -1,5 +1,5 @@
1
1
  module Avro
2
2
  module Builder
3
- VERSION = "0.4.0"
3
+ VERSION = "0.5.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: avro-builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Salsify Engineering
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-05-13 00:00:00.000000000 Z
11
+ date: 2016-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: avro
@@ -113,6 +113,7 @@ files:
113
113
  - bin/console
114
114
  - bin/setup
115
115
  - lib/avro/builder.rb
116
+ - lib/avro/builder/definition_cache.rb
116
117
  - lib/avro/builder/dsl.rb
117
118
  - lib/avro/builder/dsl_attributes.rb
118
119
  - lib/avro/builder/errors.rb