Class: JDDF::Schema

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

Overview

A JDDF schema.

This class is a Struct. Validate instances against it using Validator#validate.

This class's attributes are in SCHEMA_KEYWORDS.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.from_json(hash) ⇒ Schema

Construct a JDDF::Schema from parsed JSON.

This function performs type checks to ensure the data is well-typed, but does not perform all the checks necesary to ensure data is a correct JDDF schema. Using this function in combination with #verify ensures that a JDDF schema is guaranteed to be correct according to the spec.

hash should be the result of calling JSON#parse.

Parameters:

  • hash (Hash)

    a JSON object representing a JDDF schema

Returns:

  • (Schema)

    the parsed schema

Raises:

  • (ArgumentError, TypeError)

    if the inputted hash is not a valid schema


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
93
94
95
96
97
98
99
100
101
102
103
104
105
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/jddf/schema.rb', line 68

def self.from_json(hash)
  raise TypeError.new, 'hash must be a Hash' unless hash.is_a?(Hash)

  schema = Schema.new

  if hash.include?('definitions')
    unless hash['definitions'].is_a?(Hash)
      raise TypeError, 'definitions not Hash'
    end

    schema.definitions = hash['definitions'].map do |key, schema|
      [key, from_json(schema)]
    end.to_h
  end

  if hash.include?('ref')
    raise TypeError, 'ref not String' unless hash['ref'].is_a?(String)

    schema.ref = hash['ref']
  end

  if hash.include?('type')
    raise TypeError, 'type not String' unless hash['type'].is_a?(String)

    unless TYPES.map(&:to_s).include?(hash['type'])
      raise TypeError, "type not in #{TYPES}"
    end

    schema.type = hash['type'].to_sym
  end

  if hash.include?('enum')
    raise TypeError, 'enum not Array' unless hash['enum'].is_a?(Array)
    raise ArgumentError, 'enum is empty array' if hash['enum'].empty?

    hash['enum'].each do |value|
      raise TypeError, 'enum element not String' unless value.is_a?(String)
    end

    schema.enum = hash['enum'].to_set

    if schema.enum.size != hash['enum'].size
      raise ArgumentError, 'enum contains duplicates'
    end
  end

  if hash.include?('elements')
    raise TypeError, 'elements not Hash' unless hash['elements'].is_a?(Hash)

    schema.elements = from_json(hash['elements'])
  end

  if hash.include?('properties')
    unless hash['properties'].is_a?(Hash)
      raise TypeError, 'properties not Hash'
    end

    schema.properties = hash['properties'].map do |key, schema|
      [key, from_json(schema)]
    end.to_h
  end

  if hash.include?('optionalProperties')
    unless hash['optionalProperties'].is_a?(Hash)
      raise TypeError, 'optionalProperties not Hash'
    end

    optional_properties = hash['optionalProperties'].map do |key, schema|
      [key, from_json(schema)]
    end.to_h

    schema.optional_properties = optional_properties
  end

  if hash.include?('additionalProperties')
    unless [true, false].include?(hash['additionalProperties'])
      raise TypeError, 'additionalProperties not boolean'
    end

    schema.additional_properties = hash['additionalProperties']
  end

  if hash.include?('values')
    raise TypeError, 'values not Hash' unless hash['values'].is_a?(Hash)

    schema.values = from_json(hash['values'])
  end

  if hash.include?('discriminator')
    unless hash['discriminator'].is_a?(Hash)
      raise TypeError, 'discriminator not Hash'
    end

    schema.discriminator = Discriminator.from_json(hash['discriminator'])
  end

  schema
end

Instance Method Details

#formSymbol

Determine which of the eight forms this schema takes on.

This function is well-defined only if the schema is a correct schema – i.e., you have called #verify and no errors were raised.

Returns:

  • (Symbol)

    the form of the schema


173
174
175
176
177
178
179
180
181
182
183
# File 'lib/jddf/schema.rb', line 173

def form
  return :ref if ref
  return :type if type
  return :enum if enum
  return :elements if elements
  return :properties if properties || optional_properties
  return :values if values
  return :discriminator if discriminator

  :empty
end

#verify(root = self) ⇒ Schema

Check that the schema represents a correct JDDF schema.

To make it convenient to construct and verify a schema, this function returns self if the schema is correct.

Returns:

Raises:

  • (ArgumentError)

    if the schema is incorrect


193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/jddf/schema.rb', line 193

def verify(root = self)
  empty = true

  if ref
    empty = false

    unless root.definitions&.keys&.include?(ref)
      raise ArgumentError, 'reference to non-existent definition'
    end
  end

  if type
    raise ArgumentError, 'invalid form' unless empty

    empty = false
  end

  if enum
    raise ArgumentError, 'invalid form' unless empty

    empty = false
  end

  if elements
    raise ArgumentError, 'invalid form' unless empty

    empty = false

    elements.verify(root)
  end

  if properties || optional_properties
    raise ArgumentError, 'invalid form' unless empty

    empty = false

    properties&.values&.each { |schema| schema.verify(root) }
    optional_properties&.values&.each { |schema| schema.verify(root) }
  end

  if values
    raise ArgumentError, 'invalid form' unless empty

    empty = false

    values.verify(root)
  end

  if properties && optional_properties
    unless (properties.keys & optional_properties.keys).empty?
      raise ArgumentError, 'properties and optional_properties share key'
    end
  end

  if discriminator
    raise ArgumentError, 'invalid form' unless empty

    discriminator.mapping.values.each do |schema|
      schema.verify(root)

      unless schema.form == :properties
        raise ArgumentError, 'mapping value not of properties form'
      end

      if schema&.properties&.include?(discriminator.tag) ||
         schema&.optional_properties&.include?(discriminator.tag)
        raise ArgumentError, 'tag appears in mapping properties'
      end
    end
  end

  self
end