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
@yieldparam schema [self] The new schema
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
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
Set a property of the schema. @param [String, Symbol] key Symbols are converted to camel-case strings. @param [Object] value
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
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
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
Returns true when the schema will validate against anything. @return [true, false]
# File lib/jimmy/schema.rb, line 48 def anything? !@nothing && empty? end
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]
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
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
Duplicate the schema. @return [Jimmy::Json::Schema]
Jimmy::Json::Collection#deep_dup
# File lib/jimmy/schema.rb, line 127 def deep_dup(*) nothing? ? self.class.new.nothing : super end
Duplicate the schema with a shallow copy. Collections like properties, enum, etc, will be shared with the duplicate. @return [Jimmy::Json::Schema]
Jimmy::Json::Collection#dup
# File lib/jimmy/schema.rb, line 121 def dup nothing? ? self.class.new.nothing : super end
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
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
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
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
@see ::Object#inspect
# File lib/jimmy/schema.rb, line 70 def inspect "#<#{self.class} #{super}>" end
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
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
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
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
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
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
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
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
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
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
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
Returns true when the schema will never validate against anything. @return [true, false]
# File lib/jimmy/schema.rb, line 42 def nothing? @nothing end
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
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
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
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
Returns true if the schema refers to another schema. @return [true, false]
# File lib/jimmy/schema.rb, line 105 def ref? key? '$ref' end
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
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
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
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
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
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
Protected Instance Methods
# File lib/jimmy/schema.rb, line 133 def schema yield self if block_given? self end
Private Instance Methods
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
# File lib/jimmy/schema/object.rb, line 89 def collection_for_property_key(key) if key.is_a? Regexp 'patternProperties' else 'properties' end end
# 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
# 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
# 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
# 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
# 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