class Avro::SchemaValidator

Constants

BOOLEAN_VALUES
COMPLEX_TYPES
DEFAULT_VALIDATION_OPTIONS
INT_RANGE
LONG_RANGE
PATH_SEPARATOR
RECURSIVE_SIMPLE_VALIDATION_OPTIONS
ROOT_IDENTIFIER
RUBY_CLASS_TO_AVRO_TYPE
TypeMismatchError

Public Class Methods

validate!(expected_schema, logical_datum, options = DEFAULT_VALIDATION_OPTIONS) click to toggle source
   # File lib/avro/schema_validator.rb
74 def validate!(expected_schema, logical_datum, options = DEFAULT_VALIDATION_OPTIONS)
75   result = Result.new
76   if options.fetch(:recursive, true)
77     validate_recursive(expected_schema, logical_datum, ROOT_IDENTIFIER, result, options)
78   else
79     validate_simple(expected_schema, logical_datum, ROOT_IDENTIFIER, result, options)
80   end
81   fail ValidationError, result if result.failure?
82   result
83 end

Private Class Methods

actual_value_message(value) click to toggle source
    # File lib/avro/schema_validator.rb
226 def actual_value_message(value)
227   avro_type = if value.is_a?(Integer)
228                 ruby_integer_to_avro_type(value)
229               else
230                 ruby_to_avro_type(value.class)
231               end
232   if value.nil?
233     avro_type
234   else
235     "#{avro_type} with value #{value.inspect}"
236   end
237 end
deeper_path_for_hash(sub_key, path) click to toggle source
    # File lib/avro/schema_validator.rb
220 def deeper_path_for_hash(sub_key, path)
221   deeper_path = +"#{path}#{PATH_SEPARATOR}#{sub_key}"
222   deeper_path.squeeze!(PATH_SEPARATOR)
223   deeper_path.freeze
224 end
enum_message(symbols, datum) click to toggle source
    # File lib/avro/schema_validator.rb
168 def enum_message(symbols, datum)
169   "expected enum with values #{symbols}, got #{actual_value_message(datum)}"
170 end
first_compatible_type(datum, expected_schema, path, failures, options = {}) click to toggle source
    # File lib/avro/schema_validator.rb
208 def first_compatible_type(datum, expected_schema, path, failures, options = {})
209   expected_schema.schemas.find do |schema|
210     # Avoid expensive validation if we're just validating a nil
211     next datum.nil? if schema.type_sym == :null
212 
213     result = Result.new
214     validate_recursive(schema, datum, path, result, options)
215     failures << { type: schema.type_sym, result: result } if result.failure?
216     !result.failure?
217   end
218 end
fixed_string_message(size, datum) click to toggle source
    # File lib/avro/schema_validator.rb
164 def fixed_string_message(size, datum)
165   "expected fixed with size #{size}, got \"#{datum}\" with size #{datum.bytesize}"
166 end
resolve_datum(expected_schema, logical_datum, encoded) click to toggle source
    # File lib/avro/schema_validator.rb
150 def resolve_datum(expected_schema, logical_datum, encoded)
151   if encoded
152     logical_datum
153   else
154     expected_schema.type_adapter.encode(logical_datum) rescue nil
155   end
156 end
ruby_integer_to_avro_type(value) click to toggle source
    # File lib/avro/schema_validator.rb
243 def ruby_integer_to_avro_type(value)
244   INT_RANGE.cover?(value) ? 'int' : 'long'
245 end
ruby_to_avro_type(ruby_class) click to toggle source
    # File lib/avro/schema_validator.rb
239 def ruby_to_avro_type(ruby_class)
240   RUBY_CLASS_TO_AVRO_TYPE.fetch(ruby_class, ruby_class)
241 end
validate_array(expected_schema, datum, path, result, options) click to toggle source
    # File lib/avro/schema_validator.rb
172 def validate_array(expected_schema, datum, path, result, options)
173   fail TypeMismatchError unless datum.is_a?(Array)
174   datum.each_with_index do |d, i|
175     validate_recursive(expected_schema.items, d, "#{path}[#{i}]", result, options)
176   end
177 end
validate_map(expected_schema, datum, path, result, options) click to toggle source
    # File lib/avro/schema_validator.rb
179 def validate_map(expected_schema, datum, path, result, options)
180   fail TypeMismatchError unless datum.is_a?(Hash)
181   datum.keys.each do |k|
182     result.add_error(path, "unexpected key type '#{ruby_to_avro_type(k.class)}' in map") unless k.is_a?(String)
183   end
184   datum.each do |k, v|
185     deeper_path = deeper_path_for_hash(k, path)
186     validate_recursive(expected_schema.values, v, deeper_path, result, options)
187   end
188 end
validate_recursive(expected_schema, logical_datum, path, result, options) click to toggle source
    # File lib/avro/schema_validator.rb
 87 def validate_recursive(expected_schema, logical_datum, path, result, options)
 88   datum = resolve_datum(expected_schema, logical_datum, options[:encoded])
 89 
 90   validate_simple(expected_schema, datum, path, result, RECURSIVE_SIMPLE_VALIDATION_OPTIONS)
 91 
 92   case expected_schema.type_sym
 93   when :array
 94     validate_array(expected_schema, datum, path, result, options)
 95   when :map
 96     validate_map(expected_schema, datum, path, result, options)
 97   when :union
 98     validate_union(expected_schema, datum, path, result, options)
 99   when :record, :error, :request
100     fail TypeMismatchError unless datum.is_a?(Hash)
101     expected_schema.fields.each do |field|
102       deeper_path = deeper_path_for_hash(field.name, path)
103       nested_value = datum.key?(field.name) ? datum[field.name] : datum[field.name.to_sym]
104       validate_recursive(field.type, nested_value, deeper_path, result, options)
105     end
106     if options[:fail_on_extra_fields]
107       datum_fields = datum.keys.map(&:to_s)
108       schema_fields = expected_schema.fields.map(&:name)
109       (datum_fields - schema_fields).each do |extra_field|
110         result.add_error(path, "extra field '#{extra_field}' - not in schema")
111       end
112     end
113   end
114 rescue TypeMismatchError
115   result.add_error(path, "expected type #{expected_schema.type_sym}, got #{actual_value_message(datum)}")
116 end
validate_simple(expected_schema, logical_datum, path, result, options) click to toggle source
    # File lib/avro/schema_validator.rb
118 def validate_simple(expected_schema, logical_datum, path, result, options)
119   datum = resolve_datum(expected_schema, logical_datum, options[:encoded])
120   validate_type(expected_schema)
121 
122   case expected_schema.type_sym
123   when :null
124     fail TypeMismatchError unless datum.nil?
125   when :boolean
126     fail TypeMismatchError unless BOOLEAN_VALUES.include?(datum)
127   when :string, :bytes
128     fail TypeMismatchError unless datum.is_a?(String)
129   when :int
130     fail TypeMismatchError unless datum.is_a?(Integer)
131     result.add_error(path, "out of bound value #{datum}") unless INT_RANGE.cover?(datum)
132   when :long
133     fail TypeMismatchError unless datum.is_a?(Integer)
134     result.add_error(path, "out of bound value #{datum}") unless LONG_RANGE.cover?(datum)
135   when :float, :double
136     fail TypeMismatchError unless datum.is_a?(Float) || datum.is_a?(Integer) || datum.is_a?(BigDecimal)
137   when :fixed
138     if datum.is_a? String
139       result.add_error(path, fixed_string_message(expected_schema.size, datum)) unless datum.bytesize == expected_schema.size
140     else
141       result.add_error(path, "expected fixed with size #{expected_schema.size}, got #{actual_value_message(datum)}")
142     end
143   when :enum
144     result.add_error(path, enum_message(expected_schema.symbols, datum)) unless expected_schema.symbols.include?(datum)
145   end
146 rescue TypeMismatchError
147   result.add_error(path, "expected type #{expected_schema.type_sym}, got #{actual_value_message(datum)}")
148 end
validate_type(expected_schema) click to toggle source
    # File lib/avro/schema_validator.rb
158 def validate_type(expected_schema)
159   unless Avro::Schema::VALID_TYPES_SYM.include?(expected_schema.type_sym)
160     fail "Unexpected schema type #{expected_schema.type_sym} #{expected_schema.inspect}"
161   end
162 end
validate_union(expected_schema, datum, path, result, options) click to toggle source
    # File lib/avro/schema_validator.rb
190 def validate_union(expected_schema, datum, path, result, options)
191   if expected_schema.schemas.size == 1
192     validate_recursive(expected_schema.schemas.first, datum, path, result, options)
193     return
194   end
195   failures = []
196   compatible_type = first_compatible_type(datum, expected_schema, path, failures, options)
197   return unless compatible_type.nil?
198 
199   complex_type_failed = failures.detect { |r| COMPLEX_TYPES.include?(r[:type]) }
200   if complex_type_failed
201     complex_type_failed[:result].errors.each { |error| result << error }
202   else
203     types = expected_schema.schemas.map { |s| "'#{s.type_sym}'" }.join(', ')
204     result.add_error(path, "expected union of [#{types}], got #{actual_value_message(datum)}")
205   end
206 end