Class: Tros::Schema

Inherits:
Object
  • Object
show all
Defined in:
lib/tros/schema.rb

Defined Under Namespace

Classes: ArraySchema, EnumSchema, Field, FixedSchema, MapSchema, NamedSchema, PrimitiveSchema, RecordSchema, UnionSchema

Constant Summary collapse

PRIMITIVE_TYPES =

Sets of strings, for backwards compatibility. See below for sets of symbols, for better performance.

Set.new(%w[null boolean string bytes int long float double])
NAMED_TYPES =
Set.new(%w[fixed enum record error])
VALID_TYPES =
PRIMITIVE_TYPES + NAMED_TYPES + Set.new(%w[array map union request])
PRIMITIVE_TYPES_SYM =
Set.new(PRIMITIVE_TYPES.map(&:to_sym))
NAMED_TYPES_SYM =
Set.new(NAMED_TYPES.map(&:to_sym))
VALID_TYPES_SYM =
Set.new(VALID_TYPES.map(&:to_sym))
INT_MIN_VALUE =
-(1 << 31)
INT_MAX_VALUE =
(1 << 31) - 1
LONG_MIN_VALUE =
-(1 << 63)
LONG_MAX_VALUE =
(1 << 63) - 1

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(type) ⇒ Schema

Returns a new instance of Schema.



140
141
142
# File 'lib/tros/schema.rb', line 140

def initialize(type)
  @type_sym = type.is_a?(Symbol) ? type : type.to_sym
end

Instance Attribute Details

#type_symObject (readonly)

Returns the value of attribute type_sym.



144
145
146
# File 'lib/tros/schema.rb', line 144

def type_sym
  @type_sym
end

Class Method Details

.parse(json_string) ⇒ Object



35
36
37
# File 'lib/tros/schema.rb', line 35

def self.parse(json_string)
  real_parse(JSON.load("[#{json_string}]").first, {})
end

.real_parse(json_obj, names = nil, default_namespace = nil) ⇒ Object

Build Tros Schema from data parsed out of JSON string.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/tros/schema.rb', line 40

def self.real_parse(json_obj, names=nil, default_namespace=nil)
  if json_obj.is_a? Hash
    type = json_obj['type']
    raise SchemaParseError, %Q(No "type" property: #{json_obj}) if type.nil?

    # Check that the type is valid before calling #to_sym, since symbols are never garbage
    # collected (important to avoid DoS if we're accepting schemas from untrusted clients)
    unless VALID_TYPES.include?(type)
      raise SchemaParseError, "Unknown type: #{type}"
    end

    type_sym = type.to_sym
    if PRIMITIVE_TYPES_SYM.include?(type_sym)
      return PrimitiveSchema.new(type_sym)

    elsif NAMED_TYPES_SYM.include? type_sym
      name = json_obj['name']
      namespace = json_obj.include?('namespace') ? json_obj['namespace'] : default_namespace
      case type_sym
      when :fixed
        size = json_obj['size']
        return FixedSchema.new(name, namespace, size, names)
      when :enum
        symbols = json_obj['symbols']
        return EnumSchema.new(name, namespace, symbols, names)
      when :record, :error
        fields = json_obj['fields']
        return RecordSchema.new(name, namespace, fields, names, type_sym)
      else
        raise SchemaParseError.new("Unknown named type: #{type}")
      end

    else
      case type_sym
      when :array
        return ArraySchema.new(json_obj['items'], names, default_namespace)
      when :map
        return MapSchema.new(json_obj['values'], names, default_namespace)
      else
        raise SchemaParseError.new("Unknown Valid Type: #{type}")
      end
    end

  elsif json_obj.is_a? Array
    # JSON array (union)
    return UnionSchema.new(json_obj, names, default_namespace)
  elsif PRIMITIVE_TYPES.include? json_obj
    return PrimitiveSchema.new(json_obj)
  else
    msg = "#{json_obj.inspect} is not a schema we know about."
    raise SchemaParseError.new(msg)
  end
end

.validate(expected_schema, datum, validator_method = :validate) ⇒ Object

Determine if a ruby datum is an instance of a schema



95
96
97
98
99
100
101
102
103
# File 'lib/tros/schema.rb', line 95

def self.validate(expected_schema, datum, validator_method = :validate)
  return true if validate_strictly(expected_schema, datum, validator_method)
  case expected_schema.type_sym
  when :float, :double
    datum.is_a?(Numeric)
  else
    return false
  end
end

.validate_strictly(expected_schema, datum, validator_method = :validate_strictly) ⇒ Object

Determine if a ruby datum is an instance of a schema



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/tros/schema.rb', line 106

def self.validate_strictly(expected_schema, datum, validator_method = :validate_strictly)
  case expected_schema.type_sym
  when :null
    datum.nil?
  when :boolean
    datum == true || datum == false
  when :string, :bytes
    datum.is_a? String
  when :int
    datum.is_a?(Integer) && (INT_MIN_VALUE <= datum) && (datum <= INT_MAX_VALUE)
  when :long
    datum.is_a?(Integer) && (LONG_MIN_VALUE <= datum) && (datum <= LONG_MAX_VALUE)
  when :float, :double
    datum.is_a?(Float) || datum.is_a?(BigDecimal)
  when :fixed
    datum.is_a?(String) && datum.size == expected_schema.size
  when :enum
    expected_schema.symbols.include? datum
  when :array
    datum.is_a?(Array) &&
      datum.all?{|d| send(validator_method, expected_schema.items, d) }
  when :map
      datum.keys.all?{|k| k.is_a? String } &&
      datum.values.all?{|v| send(validator_method, expected_schema.values, v) }
  when :union
    expected_schema.schemas.any? { |s| send(validator_method, s, datum) }
  when :record, :error, :request
    datum.is_a?(Hash) &&
      expected_schema.fields.all? { |f| send(validator_method, f.type, datum[f.name]) }
  else
    raise TypeError, "#{expected_schema.inspect} is not recognized as type."
  end
end

Instance Method Details

#==(other, seen = nil) ⇒ Object



150
151
152
# File 'lib/tros/schema.rb', line 150

def ==(other, seen=nil)
  other.is_a?(Schema) && type_sym == other.type_sym
end

#hash(seen = nil) ⇒ Object



154
155
156
# File 'lib/tros/schema.rb', line 154

def hash(seen=nil)
  type_sym.hash
end

#subparse(json_obj, names = nil, namespace = nil) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/tros/schema.rb', line 158

def subparse(json_obj, names=nil, namespace=nil)
  if json_obj.is_a?(String) && names
    fullname = Name.make_fullname(json_obj, namespace)
    return names[fullname] if names.include?(fullname)
  end

  begin
    Schema.real_parse(json_obj, names, namespace)
  rescue => e
    raise e if e.is_a? SchemaParseError
    raise SchemaParseError, "Sub-schema for #{self.class.name} not a valid Tros schema. Bad schema: #{json_obj}"
  end
end

#to_avro(names = nil) ⇒ Object



172
173
174
# File 'lib/tros/schema.rb', line 172

def to_avro(names=nil)
  {'type' => type}
end

#to_sObject



176
177
178
# File 'lib/tros/schema.rb', line 176

def to_s
  to_avro.to_json
end

#typeObject

Returns the type as a string (rather than a symbol), for backwards compatibility. Deprecated in favor of #type_sym.



148
# File 'lib/tros/schema.rb', line 148

def type; @type_sym.to_s; end