avro-builder 0.4.0 → 0.5.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: 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