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.

Parameters:

Returns:

  • (true, false)


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.

Parameters:

  • key (String, Symbol)

    Symbols are converted to camel-case strings.

  • value (Object)


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.

Parameters:

Returns:

  • (self)

    self, for chaining



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

Parameters:

Returns:



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.

Returns:

  • (true, false)


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.

Parameters:

  • id (Json::URI, URI, String) (defaults to: '')

Returns:

  • (Hash, true, false)


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.

Parameters:

  • range (Range, Integer)

    The minimum and maximum items for an array value. If an integer is given, it is taken to be both.

Returns:

  • (self)

    self, for chaining



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.

Returns:

  • (Jimmy::Json::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.

Returns:

  • (Jimmy::Json::Schema)


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.

Parameters:

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • number (Numeric)

    The exclusive maximum numeric value.

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • number (Numeric)

    The exclusive minimum numeric value.

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • format_name (String)

    The named format for a string value.

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • single_item_schemas (Array<Jimmy::Schema>)

    One or more schemas to add to the existing items array.

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • schema_or_schemas (Jimmy::Schema, Array<Jimmy::Schema>)

    A schema or array of schemas for validating items in an array value. If an array of schemas is given, the first schema will apply to the first item, and so on.

  • rest_schema (Jimmy::Schema, nil) (defaults to: nil)

    The schema to apply to items with indexes greater than the length of the first argument. Only applicable when an array is given for the first argument.

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • range (Range, Integer)

    The minimum and maximum length for a string value. If an integer is given, it is taken to be both.

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • count (Numeric)

    The maximum items for an array value.

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • length (Numeric)

    The maximum length for a string value.

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • number (Numeric)

    The maximum numeric value.

  • exclusive (true, false) (defaults to: false)

    Whether the value is included in the maximum.

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • count (Numeric)

    The minimum items for an array value.

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • length (Numeric)

    The minimum length for a string value.

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • number (Numeric)

    The minimum numeric value.

  • exclusive (true, false) (defaults to: false)

    Whether the value is included in the minimum.

Returns:

  • (self)

    self, for chaining



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.

Returns:



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).

Returns:

  • (self)

    self



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.

Returns:

  • (true, false)


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

Parameters:

Returns:



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.

Parameters:

  • properties (Hash{String, Symbol => Jimmy::Schema, nil})
  • required (true, false) (defaults to: false)

    If true, literal (non-pattern) properties will be added to the required property.

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.

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • name (String, Symbol)

    The name of the property.

  • schema (Jimmy::Schema) (defaults to: Schema.new)

    The schema for the property. If omitted, a new Schema will be created, and will be yielded if a block is given.

  • required (true, false) (defaults to: false)

    If true, name will be added to the required property.

Yield Parameters:

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • uri (Json::URI, URI, String)

    The URI of the JSON schema to reference.

Returns:

  • (self)


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.

Returns:

  • (true, false)


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.

Parameters:

  • properties (Array<String, Symbol, Hash{String, Symbol => Jimmy::Schema, nil}>)

    Names of properties that are required, or hashes that can be passed to #properties, and whose keys will also be added to the required property.

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.

Returns:

  • (self)

    self, for chaining



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.

Returns:

  • (self)

    self, for chaining



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.

Returns:



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.

Parameters:

Returns:

  • (self)

    self, for chaining



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.

Parameters:

  • unique (true, false) (defaults to: true)

    Whether the array value should have unique items.

Returns:

  • (self)

    self, for chaining



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

Parameters:

Returns:



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

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