class Jimmy::Schema

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

Constants

PROPERTIES
PROPERTY_SEQUENCE
SCHEMA

The JSON Schema draft 7 schema URI

Public Class Methods

new(schema = {}) { |self| ... } click to toggle source

@yieldparam schema [self] The new schema

Calls superclass method Jimmy::Json::Hash::new
# 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

Public Instance Methods

!()
Alias for: negated
&(other)
Alias for: and
==(other) click to toggle source

Compare the schema to another schema. @param [Schema] other @return [true, false]

# 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) click to toggle source

Set a property of the schema. @param [String, Symbol] key Symbols are converted to camel-case strings. @param [Object] value

Calls superclass method Jimmy::Json::Hash#[]=
# 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
^(other)
Alias for: xor
additional_properties(schema) click to toggle source

Set the schema for additional properties not matching those given to #require, #property, and #properties. Pass false to disallow additional properties. @param [Jimmy::Schema, true, false] schema @return [self] self, for chaining

# File lib/jimmy/schema/object.rb, line 83
def additional_properties(schema)
  set additionalProperties: cast_schema(schema)
end
allow(properties, required: false, &block)
Alias for: properties
and(other) click to toggle source

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

@param [Jimmy::Schema] other The other schema. @return [Jimmy::Schema] The new schema.

# File lib/jimmy/schema/operators.rb, line 48
def and(other)
  make_new_composite 'allOf', other
end
Also aliased as: &
anything?() click to toggle source

Returns true when the schema will validate against anything. @return [true, false]

# File lib/jimmy/schema.rb, line 48
def anything?
  !@nothing && empty?
end
as_json(id: '', index: nil) click to toggle source

Get the schema as a plain Hash. Given an id, the +$id+ and +$schema+ keys will also be set. @param [Json::URI, URI, String] id @return [Hash, true, false]

Calls superclass method Jimmy::Json::Collection#as_json
# 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) click to toggle source

Set the minimum and maximum items for an array value, using a range. @param [Range, Integer] range The minimum and maximum items for an array

value. If an integer is given, it is taken to be both.

@return [self] self, for chaining

# 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_dup(*) click to toggle source

Duplicate the schema. @return [Jimmy::Json::Schema]

Calls superclass method Jimmy::Json::Collection#deep_dup
# File lib/jimmy/schema.rb, line 127
def deep_dup(*)
  nothing? ? self.class.new.nothing : super
end
dup() click to toggle source

Duplicate the schema with a shallow copy. Collections like properties, enum, etc, will be shared with the duplicate. @return [Jimmy::Json::Schema]

Calls superclass method Jimmy::Json::Collection#dup
# File lib/jimmy/schema.rb, line 121
def dup
  nothing? ? self.class.new.nothing : super
end
else(schema) click to toggle source

Define the schema that must be valid if the if schema is not valid. @param schema [Jimmy::Schema] The else schema. @return [self] self, for chaining

# File lib/jimmy/schema/conditions.rb, line 15
def else(schema)
  set else: cast_schema(schema)
end
exclusive_maximum(number) click to toggle source

Set the exclusive maximum value. @param [Numeric] number The exclusive maximum numeric value. @return [self] self, for chaining

# File lib/jimmy/schema/number.rb, line 33
def exclusive_maximum(number)
  maximum number, exclusive: true
end
exclusive_minimum(number) click to toggle source

Set the exclusive minimum value. @param [Numeric] number The exclusive minimum numeric value. @return [self] self, for chaining

# File lib/jimmy/schema/number.rb, line 17
def exclusive_minimum(number)
  minimum number, exclusive: true
end
format(format_name) click to toggle source

Set the format for a string value. @param [String] format_name The named format for a string value. @return [self] self, for chaining

# File lib/jimmy/schema/string.rb, line 38
def format(format_name)
  valid_for 'string'
  assert_string format_name
  set format: format_name
end
inspect() click to toggle source

@see ::Object#inspect

# File lib/jimmy/schema.rb, line 70
def inspect
  "#<#{self.class} #{super}>"
end
item(*single_item_schemas) click to toggle source

Add a single-item schema, or several, to the items array. Only valid if a match-all schema has not been set. @param [Array<Jimmy::Schema>] single_item_schemas One or more schemas

to add to the existing +items+ array.

@return [self] self, for chaining

# 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) click to toggle source

Set the schema or schemas for validating items in an array value. @param [Jimmy::Schema, Array<Jimmy::Schema>] schema_or_schemas 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.

@param [Jimmy::Schema, nil] rest_schema 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.

@return [self] self, for chaining

# 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) click to toggle source

Set the minimum and maximum length for a string value, using a range. @param [Range, Integer] range The minimum and maximum length for a string

value. If an integer is given, it is taken to be both.

@return [self] self, for chaining

# 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) click to toggle source

Set the maximum items for an array value. @param [Numeric] count The maximum items for an array value. @return [self] self, for chaining

# 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) click to toggle source

Set the maximum length for a string value. @param [Numeric] length The maximum length for a string value. @return [self] self, for chaining

# 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) click to toggle source

Set the maximum value. @param [Numeric] number The maximum numeric value. @param [true, false] exclusive Whether the value is included in the

maximum.

@return [self] self, for chaining

# File lib/jimmy/schema/number.rb, line 26
def maximum(number, exclusive: false)
  set_numeric_boundary 'maximum', number, exclusive
end
min_items(count) click to toggle source

Set the minimum items for an array value. @param [Numeric] count The minimum items for an array value. @return [self] self, for chaining

# 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) click to toggle source

Set the minimum length for a string value. @param [Numeric] length The minimum length for a string value. @return [self] self, for chaining

# 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) click to toggle source

Set the minimum value. @param [Numeric] number The minimum numeric value. @param [true, false] exclusive Whether the value is included in the

minimum.

@return [self] self, for chaining

# File lib/jimmy/schema/number.rb, line 10
def minimum(number, exclusive: false)
  set_numeric_boundary 'minimum', number, exclusive
end
negated() click to toggle source

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. @return [Jimmy::Schema]

# 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
Also aliased as: !
nothing() click to toggle source

Make the schema validate nothing (i.e. everything is invalid). @return [self] self

# File lib/jimmy/schema.rb, line 90
def nothing
  clear
  @nothing = true
  self
end
nothing?() click to toggle source

Returns true when the schema will never validate against anything. @return [true, false]

# File lib/jimmy/schema.rb, line 42
def nothing?
  @nothing
end
or(other) click to toggle source

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

@param [Jimmy::Schema] other The other schema. @return [Jimmy::Schema] The new schema.

# File lib/jimmy/schema/operators.rb, line 63
def or(other)
  make_new_composite 'anyOf', other
end
Also aliased as: |
properties(properties, required: false, &block) click to toggle source

Define properties for an object value. @param [Hash{String, Symbol => Jimmy::Schema, nil}] properties @param [true, false] required If true, literal (non-pattern) properties

will be added to the +required+ property.

@yieldparam name [String] The name of a property that was given with a nil

schema.

@yieldparam schema [Jimmy::Schema] A new schema created for a property

that was given without one.

@return [self] self, for chaining

# 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
Also aliased as: allow
property(name, schema = Schema.new, required: false, &block) click to toggle source

Define a property for an object value. @param [String, Symbol] name The name of the property. @param [Jimmy::Schema] schema The schema for the property. If

omitted, a new Schema will be created, and will be yielded if a block
is given.

@param [true, false] required If true, name will be added to the

+required+ property.

@yieldparam schema [Jimmy::Schema] The schema being assigned. @return [self] self, for chaining

# 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) click to toggle source

Turns the schema into a reference to another schema. Freezes the schema so that no further changes can be made. @param [Json::URI, URI, String] uri The URI of the JSON schema to

reference.

@return [self]

# 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?() click to toggle source

Returns true if the schema refers to another schema. @return [true, false]

# File lib/jimmy/schema.rb, line 105
def ref?
  key? '$ref'
end
require(*properties, &block) click to toggle source

Designate the given properties as required for object values. @param [Array<String, Symbol, Hash{String, Symbol => Jimmy::Schema, nil}>]

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

@yieldparam name [String] The name of a property that was given with a nil

schema.

@yieldparam schema [Jimmy::Schema] A new schema created for a property

that was given without one.

@return [self] self, for chaining

# 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
Also aliased as: requires
require_all() click to toggle source

Require all properties that have been explicitly defined for object

values.

@return [self] self, for chaining

# File lib/jimmy/schema/object.rb, line 74
def require_all
  require *get('properties') { {} }.keys
end
requires(*properties, &block)
Alias for: require
target() click to toggle source

Get the URI of the schema to which this schema refers, or nil if the schema is not a reference. @return [Json::URI, nil]

# File lib/jimmy/schema.rb, line 99
def target
  self['$ref']
end
then(schema) click to toggle source

Define the schema that must be valid if the if schema is valid. @param schema [Jimmy::Schema] The then schema. @return [self] self, for chaining

# File lib/jimmy/schema/conditions.rb, line 8
def then(schema)
  set then: cast_schema(schema)
end
unique(unique = true)
Alias for: unique_items
unique_items(unique = true) click to toggle source

Set whether the array value is required to have unique items. @param [true, false] unique Whether the array value should have unique

items.

@return [self] self, for chaining

# File lib/jimmy/schema/array.rb, line 9
def unique_items(unique = true)
  valid_for 'array'
  assert_boolean unique
  set uniqueItems: unique
end
Also aliased as: unique
xor(other) click to toggle source

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

@param [Jimmy::Schema] other The other schema. @return [Jimmy::Schema] The new schema.

# File lib/jimmy/schema/operators.rb, line 78
def xor(other)
  make_new_composite 'oneOf', other
end
Also aliased as: ^
|(other)
Alias for: or

Protected Instance Methods

schema() { |self| ... } click to toggle source
# File lib/jimmy/schema.rb, line 133
def schema
  yield self if block_given?
  self
end

Private Instance Methods

cast_key(key) click to toggle source
Calls superclass method Jimmy::Json::Hash#cast_key
# File lib/jimmy/schema/casting.rb, line 7
def cast_key(key)
  case key
  when Regexp
    assert_regexp key
    super key.source
  else
    super
  end
end
collection_for_property_key(key) click to toggle source
# File lib/jimmy/schema/object.rb, line 89
def collection_for_property_key(key)
  if key.is_a? Regexp
    'patternProperties'
  else
    'properties'
  end
end
make_new_composite(name, other) click to toggle source
# File lib/jimmy/schema/operators.rb, line 89
def make_new_composite(name, other)
  return self if other == self

  this = keys == [name] ? get(name) : [self]
  Schema.new.instance_exec { set_composite name, [*this, other] }
end
match_all_items(schema, rest_schema) click to toggle source
# File lib/jimmy/schema/array.rb, line 86
def match_all_items(schema, rest_schema)
  valid_for 'array'
  assert(rest_schema.nil?) do
    'You cannot specify an additional items schema when using a '\
      'match-all schema'
  end
  set items: cast_schema(schema)
end
set_numeric_boundary(name, number, exclusive) click to toggle source
# File lib/jimmy/schema/number.rb, line 39
def set_numeric_boundary(name, number, exclusive)
  valid_for 'number', 'integer'
  assert_numeric number
  assert_boolean exclusive
  name = 'exclusive' + name[0].upcase + name[1..] if exclusive
  set name => number
end
top_level_json(id) { || ... } click to toggle source
# File lib/jimmy/schema/json.rb, line 27
def top_level_json(id)
  hash = {
    '$id'     => id.to_s,
    '$schema' => SCHEMA
  }
  if nothing?
    hash['not'] = true
  else
    hash.merge! yield
  end
  hash
end
validate_property_name(name) click to toggle source
# File lib/jimmy/schema/object.rb, line 97
def validate_property_name(name)
  name = cast_key(name)
  assert_string name
  return name unless get('additionalProperties', nil) == Schema.new.nothing

  names    = get('properties') { {} }.keys
  patterns = get('patternProperties') { {} }.keys
  assert names.include?(name) || patterns.any? { |p| p.match? name } do
    "Expected '#{name}' to be an existing property"
  end
  name
end