Class: Jimmy::Schema

Inherits:
Json::Hash show all
Includes:
Declaration
Defined in:
lib/jimmy/schema.rb,
lib/jimmy/schema/json.rb,
lib/jimmy/schema/array.rb,
lib/jimmy/schema/number.rb,
lib/jimmy/schema/object.rb,
lib/jimmy/schema/string.rb,
lib/jimmy/schema/casting.rb,
lib/jimmy/schema/operators.rb,
lib/jimmy/schema/conditions.rb

Overview

Represents a schema as defined by json-schema.org/draft-07/schema

Constant Summary collapse

PROPERTIES =
%w[
  title description default readOnly writeOnly examples
  multipleOf maximum exclusiveMaximum minimum exclusiveMinimum
  maxLength minLength pattern
  additionalItems items maxItems minItems uniqueItems contains
  maxProperties minProperties required additionalProperties
  definitions properties patternProperties dependencies propertyNames
  const enum type format
  contentMediaType contentEncoding
  if then else
  allOf anyOf oneOf
  not
].freeze
PROPERTY_SEQUENCE =
PROPERTIES.each.with_index.to_h.freeze
SCHEMA =

The JSON Schema draft 7 schema URI

'http://json-schema.org/draft-07/schema#'

Constants included from Declaration

Declaration::BOOLEANS, Declaration::FORMATS, Declaration::SIMPLE_TYPES

Instance Method Summary collapse

Methods included from Declaration

#all_of, #any_of, #array, #boolean, #const, #date, #date_time, #default, #define, #definitions, #description, #email, #enum, #examples, #hostname, #idn_email, #idn_hostname, #if, #integer, #ipv4, #ipv6, #iri, #iri_reference, #json_pointer, #multiple_of, #not, #null, #number, #object, #one_of, #pattern, #range, #read_only, #regex, #relative_json_pointer, #string, #struct, #time, #title, #type, #uri, #uri_reference, #uri_template, #write_only

Methods inherited from Json::Hash

#each, #fetch, #get_fragment, #key?, #keys, #merge!

Methods included from Json::Collection

#[], #clear, #dig, #empty?, #freeze, #to_json

Constructor Details

#initialize(schema = {}) {|schema| ... } ⇒ Schema

Returns a new instance of Schema.

Yield Parameters:

  • schema (self)

    The new schema



28
29
30
31
32
33
34
35
36
37
38
# File 'lib/jimmy/schema.rb', line 28

def initialize(schema = {})
  @nothing = false
  case schema
  when *CASTABLE_CLASSES
    super({})
    apply_cast self, schema
  when Hash then super
  else raise Error::WrongType, "Unexpected #{schema.class}"
  end
  yield self if block_given?
end

Instance Method Details

#==(other) ⇒ true, false

Compare the schema to another schema.



8
9
10
11
12
# File 'lib/jimmy/schema/operators.rb', line 8

def ==(other)
  return false unless other.is_a? Schema

  other.as_json == as_json
end

#[]=(key, value) ⇒ Object

Set a property of the schema.



55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/jimmy/schema.rb', line 55

def []=(key, value)
  @nothing = false

  case key
  when '$id' then @id = value # TODO: something, with this
  when '$ref' then ref value
  when '$schema'
    URI(value) == URI(SCHEMA) or
      raise Error::BadArgument, 'Unsupported JSON schema draft'
  when '$comment' then @comment = value # TODO: something, with this
  else super
  end
end

#additional_properties(schema) ⇒ self

Set the schema for additional properties not matching those given to #require, #property, and #properties. Pass false to disallow additional properties.



83
84
85
# File 'lib/jimmy/schema/object.rb', line 83

def additional_properties(schema)
  set additionalProperties: cast_schema(schema)
end

#and(other) ⇒ Jimmy::Schema Also known as: &

Combine this schema with another schema using an allOf schema. If this schema’s only property is allOf, its items will be flattened into the new schema.

Since #& is an alias of #and, the following two statements are equivalent:

schema.and(other)
schema & other


48
49
50
# File 'lib/jimmy/schema/operators.rb', line 48

def and(other)
  make_new_composite 'allOf', other
end

#anything?true, false

Returns true when the schema will validate against anything.



48
49
50
# File 'lib/jimmy/schema.rb', line 48

def anything?
  !@nothing && empty?
end

#as_json(id: '', index: nil) ⇒ Hash, ...

Get the schema as a plain Hash. Given an id, the $id and $schema keys will also be set.



12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/jimmy/schema/json.rb', line 12

def as_json(id: '', index: nil)
  id = Json::URI.new(id)

  if index.nil? && id.absolute?
    return top_level_json(id) { super index: {}, id: id }
  end

  return true if anything?
  return false if nothing?

  super index: index || {}, id: id
end

#count(range) ⇒ self

Set the minimum and maximum items for an array value, using a range.



39
40
41
42
43
44
45
# File 'lib/jimmy/schema/array.rb', line 39

def count(range)
  range = range..range if range.is_a?(Integer)
  assert_range range
  min_items range.min
  max_items range.max unless range.end.nil?
  self
end

#deep_dupJimmy::Json::Schema

Duplicate the schema.



127
128
129
# File 'lib/jimmy/schema.rb', line 127

def deep_dup(*)
  nothing? ? self.class.new.nothing : super
end

#dupJimmy::Json::Schema

Duplicate the schema with a shallow copy. Collections like properties, enum, etc, will be shared with the duplicate.



121
122
123
# File 'lib/jimmy/schema.rb', line 121

def dup
  nothing? ? self.class.new.nothing : super
end

#else(schema) ⇒ self

Define the schema that must be valid if the if schema is not valid.



15
16
17
# File 'lib/jimmy/schema/conditions.rb', line 15

def else(schema)
  set else: cast_schema(schema)
end

#exclusive_maximum(number) ⇒ self

Set the exclusive maximum value.



33
34
35
# File 'lib/jimmy/schema/number.rb', line 33

def exclusive_maximum(number)
  maximum number, exclusive: true
end

#exclusive_minimum(number) ⇒ self

Set the exclusive minimum value.



17
18
19
# File 'lib/jimmy/schema/number.rb', line 17

def exclusive_minimum(number)
  minimum number, exclusive: true
end

#format(format_name) ⇒ self

Set the format for a string value.



38
39
40
41
42
# File 'lib/jimmy/schema/string.rb', line 38

def format(format_name)
  valid_for 'string'
  assert_string format_name
  set format: format_name
end

#inspectObject

See Also:

  • Object#inspect


70
71
72
# File 'lib/jimmy/schema.rb', line 70

def inspect
  "#<#{self.class} #{super}>"
end

#item(*single_item_schemas) ⇒ self

Add a single-item schema, or several, to the items array. Only valid if a match-all schema has not been set.



71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/jimmy/schema/array.rb', line 71

def item(*single_item_schemas)
  valid_for 'array'
  assert_array(single_item_schemas, minimum: 1)
  existing = getset('items') { [] }
  assert !existing.is_a?(Schema) do
    'Cannot add individual item schemas after adding a match-all schema'
  end
  single_item_schemas.each do |schema|
    existing << cast_schema(schema)
  end
  self
end

#items(schema_or_schemas, rest_schema = nil) ⇒ self

Set the schema or schemas for validating items in an array value.



56
57
58
59
60
61
62
63
64
# File 'lib/jimmy/schema/array.rb', line 56

def items(schema_or_schemas, rest_schema = nil)
  if schema_or_schemas.is_a? Array
    item *schema_or_schemas
    set additionalItems: cast_schema(rest_schema) if rest_schema
  else
    match_all_items schema_or_schemas, rest_schema
  end
  self
end

#length(range) ⇒ self

Set the minimum and maximum length for a string value, using a range.



27
28
29
30
31
32
33
# File 'lib/jimmy/schema/string.rb', line 27

def length(range)
  range = range..range if range.is_a?(Integer)
  assert_range range
  min_length range.min
  max_length range.max unless range.end.nil?
  self
end

#max_items(count) ⇒ self

Set the maximum items for an array value.



20
21
22
23
24
# File 'lib/jimmy/schema/array.rb', line 20

def max_items(count)
  valid_for 'array'
  assert_numeric count, minimum: 0
  set maxItems: count
end

#max_length(length) ⇒ self

Set the maximum length for a string value.



8
9
10
11
12
# File 'lib/jimmy/schema/string.rb', line 8

def max_length(length)
  valid_for 'string'
  assert_numeric length, minimum: 0
  set maxLength: length
end

#maximum(number, exclusive: false) ⇒ self

Set the maximum value.



26
27
28
# File 'lib/jimmy/schema/number.rb', line 26

def maximum(number, exclusive: false)
  set_numeric_boundary 'maximum', number, exclusive
end

#min_items(count) ⇒ self

Set the minimum items for an array value.



29
30
31
32
33
# File 'lib/jimmy/schema/array.rb', line 29

def min_items(count)
  valid_for 'array'
  assert_numeric count, minimum: 0
  set minItems: count
end

#min_length(length) ⇒ self

Set the minimum length for a string value.



17
18
19
20
21
# File 'lib/jimmy/schema/string.rb', line 17

def min_length(length)
  valid_for 'string'
  assert_numeric length, minimum: 0
  set minLength: length
end

#minimum(number, exclusive: false) ⇒ self

Set the minimum value.



10
11
12
# File 'lib/jimmy/schema/number.rb', line 10

def minimum(number, exclusive: false)
  set_numeric_boundary 'minimum', number, exclusive
end

#negatedJimmy::Schema Also known as: !

Get the opposite of this schema, by wrapping it in a new schema’s not property.

If this schema’s only property is not, its value will instead be returned. Therefore:

schema.negated.negated == schema

Since #! is an alias for #negated, this also works:

!!schema == schema

Schemas matching absolutes ANYTHING or NOTHING will return the opposite absolute.



29
30
31
32
33
34
35
# File 'lib/jimmy/schema/operators.rb', line 29

def negated
  return Schema.new if nothing?
  return Schema.new.nothing if anything?
  return get('not') if keys == ['not']

  Schema.new.not self
end

#nothingself

Make the schema validate nothing (i.e. everything is invalid).



90
91
92
93
94
# File 'lib/jimmy/schema.rb', line 90

def nothing
  clear
  @nothing = true
  self
end

#nothing?true, false

Returns true when the schema will never validate against anything.



42
43
44
# File 'lib/jimmy/schema.rb', line 42

def nothing?
  @nothing
end

#or(other) ⇒ Jimmy::Schema Also known as: |

Combine this schema with another schema using an anyOf schema. If this schema’s only property is anyOf, its items will be flattened into the new schema.

Since #| is an alias of #or, the following two statements are equivalent:

schema.or(other)
schema | other


63
64
65
# File 'lib/jimmy/schema/operators.rb', line 63

def or(other)
  make_new_composite 'anyOf', other
end

#properties(properties, required: false) {|name, schema| ... } ⇒ self Also known as: allow

Define properties for an object value.

Yield Parameters:

  • name (String)

    The name of a property that was given with a nil schema.

  • schema (Jimmy::Schema)

    A new schema created for a property that was given without one.



33
34
35
36
37
38
39
40
41
42
# File 'lib/jimmy/schema/object.rb', line 33

def properties(properties, required: false, &block)
  valid_for 'object'
  assert_hash properties
  groups = properties.group_by { |k, _| collection_for_property_key k }
  groups.each do |collection, pairs|
    batch_assign_to_schema_hash collection, pairs.to_h, &block
  end
  require *properties.keys if required
  self
end

#property(name, schema = Schema.new, required: false) {|schema| ... } ⇒ self

Define a property for an object value.

Yield Parameters:



14
15
16
17
18
19
20
21
22
# File 'lib/jimmy/schema/object.rb', line 14

def property(name, schema = Schema.new, required: false, &block)
  return properties(name, required: required, &block) if name.is_a? Hash

  valid_for 'object'
  collection = collection_for_property_key(name)
  assign_to_schema_hash collection, name, schema, &block
  require name if required
  self
end

#ref(uri) ⇒ self

Turns the schema into a reference to another schema. Freezes the schema so that no further changes can be made.



79
80
81
82
83
84
85
86
# File 'lib/jimmy/schema.rb', line 79

def ref(uri)
  assert empty? do
    'Reference schemas cannot have other properties: ' +
      keys.join(', ')
  end
  @members['$ref'] = Json::URI.new(uri)
  freeze
end

#ref?true, false

Returns true if the schema refers to another schema.



105
106
107
# File 'lib/jimmy/schema.rb', line 105

def ref?
  key? '$ref'
end

#require(*properties) {|name, schema| ... } ⇒ self Also known as: requires

Designate the given properties as required for object values.

Yield Parameters:

  • name (String)

    The name of a property that was given with a nil schema.

  • schema (Jimmy::Schema)

    A new schema created for a property that was given without one.



56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/jimmy/schema/object.rb', line 56

def require(*properties, &block)
  properties.each do |name|
    if name.is_a? Hash
      self.properties name, required: true, &block
    else
      arr  = getset('required') { [] }
      name = validate_property_name(name)
      arr << name unless arr.include? name
    end
  end
  self
end

#require_allself

Require all properties that have been explicitly defined for object

values.


74
75
76
# File 'lib/jimmy/schema/object.rb', line 74

def require_all
  require *get('properties') { {} }.keys
end

#sort_keys_by(key, _value) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



114
115
116
# File 'lib/jimmy/schema.rb', line 114

def sort_keys_by(key, _value) # :nodoc:
  PROPERTY_SEQUENCE.fetch(key) { raise KeyError, 'Not a valid schema key' }
end

#targetJson::URI?

Get the URI of the schema to which this schema refers, or nil if the schema is not a reference.



99
100
101
# File 'lib/jimmy/schema.rb', line 99

def target
  self['$ref']
end

#then(schema) ⇒ self

Define the schema that must be valid if the if schema is valid.



8
9
10
# File 'lib/jimmy/schema/conditions.rb', line 8

def then(schema)
  set then: cast_schema(schema)
end

#unique_items(unique = true) ⇒ self Also known as: unique

Set whether the array value is required to have unique items.



9
10
11
12
13
# File 'lib/jimmy/schema/array.rb', line 9

def unique_items(unique = true)
  valid_for 'array'
  assert_boolean unique
  set uniqueItems: unique
end

#xor(other) ⇒ Jimmy::Schema Also known as: ^

Combine this schema with another schema using a oneOf schema. If this schema’s only property is oneOf, its items will be flattened into the new schema.

Since #^ is an alias of #xor, the following two statements are equivalent:

schema.xor(other)
schema ^ other


78
79
80
# File 'lib/jimmy/schema/operators.rb', line 78

def xor(other)
  make_new_composite 'oneOf', other
end