├── spec ├── fixtures │ ├── key_value.yml │ └── key_value.json ├── spec_helper.rb ├── json_skooma │ ├── json_node_spec.rb │ ├── sources │ │ ├── local_spec.rb │ │ └── remote_spec.rb │ ├── json_schema_spec.rb │ └── result_spec.rb └── json_skooma_spec.rb ├── .rspec ├── lib ├── json_skooma │ ├── version.rb │ ├── keywords │ │ ├── meta_data │ │ │ ├── title.rb │ │ │ ├── default.rb │ │ │ ├── examples.rb │ │ │ ├── read_only.rb │ │ │ ├── deprecated.rb │ │ │ ├── write_only.rb │ │ │ └── description.rb │ │ ├── core │ │ │ ├── comment.rb │ │ │ ├── defs.rb │ │ │ ├── ref.rb │ │ │ ├── anchor.rb │ │ │ ├── dynamic_anchor.rb │ │ │ ├── id.rb │ │ │ ├── schema.rb │ │ │ ├── vocabulary.rb │ │ │ └── dynamic_ref.rb │ │ ├── base_annotation.rb │ │ ├── draft_2019_09 │ │ │ ├── recursive_anchor.rb │ │ │ ├── additional_items.rb │ │ │ ├── items.rb │ │ │ ├── recursive_ref.rb │ │ │ ├── unevaluated_properties.rb │ │ │ └── unevaluated_items.rb │ │ ├── content │ │ │ ├── content_encoding.rb │ │ │ ├── content_media_type.rb │ │ │ └── content_schema.rb │ │ ├── applicator │ │ │ ├── if.rb │ │ │ ├── not.rb │ │ │ ├── then.rb │ │ │ ├── else.rb │ │ │ ├── property_names.rb │ │ │ ├── any_of.rb │ │ │ ├── all_of.rb │ │ │ ├── contains.rb │ │ │ ├── prefix_items.rb │ │ │ ├── properties.rb │ │ │ ├── one_of.rb │ │ │ ├── dependent_schemas.rb │ │ │ ├── items.rb │ │ │ ├── additional_properties.rb │ │ │ └── pattern_properties.rb │ │ ├── validation │ │ │ ├── const.rb │ │ │ ├── maximum.rb │ │ │ ├── minimum.rb │ │ │ ├── max_items.rb │ │ │ ├── min_items.rb │ │ │ ├── exclusive_maximum.rb │ │ │ ├── max_length.rb │ │ │ ├── min_length.rb │ │ │ ├── exclusive_minimum.rb │ │ │ ├── min_properties.rb │ │ │ ├── max_properties.rb │ │ │ ├── required.rb │ │ │ ├── enum.rb │ │ │ ├── unique_items.rb │ │ │ ├── multiple_of.rb │ │ │ ├── pattern.rb │ │ │ ├── type.rb │ │ │ ├── max_contains.rb │ │ │ ├── dependent_required.rb │ │ │ └── min_contains.rb │ │ ├── unknown.rb │ │ ├── format_annotation │ │ │ └── format.rb │ │ ├── unevaluated │ │ │ ├── unevaluated_properties.rb │ │ │ └── unevaluated_items.rb │ │ ├── base.rb │ │ └── value_schemas.rb │ ├── vocabulary.rb │ ├── inflector.rb │ ├── validators │ │ ├── regex.rb │ │ ├── uuid.rb │ │ ├── iri_reference.rb │ │ ├── uri_reference.rb │ │ ├── ipv4.rb │ │ ├── hostname.rb │ │ ├── idn_hostname.rb │ │ ├── json_pointer.rb │ │ ├── date.rb │ │ ├── relative_json_pointer.rb │ │ ├── duration.rb │ │ ├── base.rb │ │ ├── ipv6.rb │ │ ├── uri_template.rb │ │ ├── date_time.rb │ │ ├── time.rb │ │ ├── idn_email.rb │ │ ├── email.rb │ │ ├── uri.rb │ │ └── iri.rb │ ├── validators.rb │ ├── metaschema.rb │ ├── sources.rb │ ├── json_pointer.rb │ ├── json_node.rb │ ├── formatters.rb │ ├── result.rb │ ├── registry.rb │ ├── json_schema.rb │ └── dialects │ │ ├── draft201909.rb │ │ └── draft202012.rb └── json_skooma.rb ├── .standard.yml ├── bin ├── setup └── console ├── .gitignore ├── Rakefile ├── Gemfile ├── .gitmodules ├── LICENSE.txt ├── .github └── workflows │ └── main.yml ├── json_skooma.gemspec ├── CHANGELOG.md ├── README.md └── assets └── logo.svg /spec/fixtures/key_value.yml: -------------------------------------------------------------------------------- 1 | key: value 2 | -------------------------------------------------------------------------------- /spec/fixtures/key_value.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "value" 3 | } 4 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /lib/json_skooma/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | VERSION = "0.2.3" 5 | end 6 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- 1 | # For available configuration options, see: 2 | # https://github.com/testdouble/standard 3 | ruby_version: 2.6 4 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | 10 | # rspec failure tracking 11 | .rspec_status 12 | 13 | Gemfile.lock 14 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rspec/core/rake_task" 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | require "standard/rake" 9 | 10 | task default: %i[spec standard] 11 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/meta_data/title.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module MetaData 6 | class Title < BaseAnnotation 7 | self.key = "title" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/meta_data/default.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module MetaData 6 | class Default < BaseAnnotation 7 | self.key = "default" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/meta_data/examples.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module MetaData 6 | class Examples < BaseAnnotation 7 | self.key = "examples" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/meta_data/read_only.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module MetaData 6 | class ReadOnly < BaseAnnotation 7 | self.key = "readOnly" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in json_skooma.gemspec 6 | gemspec 7 | 8 | gem "rake", "~> 13.0" 9 | 10 | gem "rspec", "~> 3.0" 11 | gem "webmock" 12 | 13 | gem "standard", "~> 1.35.0" 14 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/meta_data/deprecated.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module MetaData 6 | class Deprecated < BaseAnnotation 7 | self.key = "deprecated" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/meta_data/write_only.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module MetaData 6 | class WriteOnly < BaseAnnotation 7 | self.key = "writeOnly" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/core/comment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Core 6 | class Comment < Base 7 | self.key = "$comment" 8 | self.static = true 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/meta_data/description.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module MetaData 6 | class Description < BaseAnnotation 7 | self.key = "description" 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/base_annotation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | class BaseAnnotation < Base 6 | def evaluate(instance, result) 7 | result.annotate(json.value) 8 | result.skip_assertion 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/draft_2019_09/recursive_anchor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Draft201909 6 | class RecursiveAnchor < Base 7 | self.key = "$recursiveAnchor" 8 | self.static = true 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/json_skooma/vocabulary.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | class Vocabulary 5 | attr_reader :kw_classes, :uri 6 | 7 | def initialize(uri, *keywords) 8 | @kw_classes = keywords.map { |keyword| [keyword.key, keyword] }.to_h 9 | @uri = uri 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/core/defs.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Core 6 | class Defs < Base 7 | self.key = "$defs" 8 | self.static = true 9 | self.value_schema = :object_of_schemas 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/json_skooma/inflector.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | class Inflector < Zeitwerk::GemInflector 5 | def camelize(basename, _abspath) 6 | if basename.include?("json_") 7 | super.gsub("Json", "JSON") 8 | else 9 | super 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "json_skooma" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | require "irb" 11 | IRB.start(__FILE__) 12 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/content/content_encoding.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Content 6 | class ContentEncoding < BaseAnnotation 7 | self.key = "contentEncoding" 8 | self.instance_types = %w[string] 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/content/content_media_type.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Content 6 | class ContentMediaType < BaseAnnotation 7 | self.key = "contentMediaType" 8 | self.instance_types = %w[string] 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/regex.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "regexp_parser" 4 | 5 | module JSONSkooma 6 | module Validators 7 | class Regex < Base 8 | def call(data) 9 | Regexp::Parser.parse(data) 10 | rescue Regexp::Scanner::ScannerError => e 11 | raise FormatError, e.message 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/uuid.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class Uuid < Base 6 | REGEXP = /\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/ 7 | 8 | def call(data) 9 | return if REGEXP.match?(data) 10 | 11 | raise FormatError, "#{data} is not a valid UUID" 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/json_skooma/validators.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class FormatError < StandardError; end 6 | 7 | class << self 8 | attr_accessor :validators 9 | 10 | def register(name, validator) 11 | validators[name] = validator 12 | end 13 | end 14 | 15 | self.validators = {} 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/iri_reference.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class IriReference < Base 6 | REGEX = /\A#{Iri::IRI_REFERENCE}\z/.freeze 7 | 8 | def call(data) 9 | return if REGEX.match?(data.value) 10 | 11 | raise FormatError, "#{data} is not a valid IRI reference" 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/uri_reference.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class UriReference < Base 6 | REGEX = /\A#{Uri::URI_REFERENCE}\z/.freeze 7 | 8 | def call(data) 9 | return if REGEX.match?(data.value) 10 | 11 | raise FormatError, "#{data} is not a valid URI reference" 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/applicator/if.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Applicator 6 | class If < Base 7 | self.key = "if" 8 | self.value_schema = :schema 9 | 10 | def evaluate(instance, result) 11 | json.evaluate(instance, result) 12 | result.skip_assertion 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "spec/json_schema_test_suite"] 2 | path = spec/json_schema_test_suite 3 | url = https://github.com/json-schema-org/JSON-Schema-Test-Suite 4 | [submodule "data/draft-2020-12"] 5 | path = data/draft-2020-12 6 | url = https://github.com/json-schema-org/json-schema-spec 7 | branch = 2020-12 8 | [submodule "draft-2019-09"] 9 | path = data/draft-2019-09 10 | url = https://github.com/json-schema-org/json-schema-spec 11 | branch = 2019-09 12 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/const.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class Const < Base 7 | self.key = "const" 8 | 9 | def evaluate(instance, result) 10 | return if instance == json 11 | 12 | result.failure("The instance value #{instance.value} must be equal to the defined constant #{json.value}") 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/ipv4.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class Ipv4 < Base 6 | IPV4_ADDRESS = /((25[0-5]|(2[0-4]|1\d|[1-9])?\d)\.?\b){4}/ 7 | REGEXP = /\A#{IPV4_ADDRESS}\z/ 8 | 9 | class << self 10 | def call(data) 11 | match = REGEXP.match(data) 12 | raise FormatError, "must be a valid IPv4 address" if match.nil? 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "json_skooma" 4 | 5 | require "webmock/rspec" 6 | 7 | RSpec.configure do |config| 8 | # Enable flags like --only-failures and --next-failure 9 | config.example_status_persistence_file_path = ".rspec_status" 10 | 11 | # Disable RSpec exposing methods globally on `Module` and `main` 12 | config.disable_monkey_patching! 13 | 14 | config.expect_with :rspec do |c| 15 | c.syntax = :expect 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/maximum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class Maximum < Base 7 | self.key = "maximum" 8 | self.instance_types = "number" 9 | 10 | def evaluate(instance, result) 11 | if instance > json 12 | result.failure("The value may not be greater than #{json.value})") 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/hostname.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class Hostname < Base 6 | HOSTNAME = /(?=.{1,253}\.?\z)[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?(?:\.[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?)*\.?/ 7 | 8 | REGEXP = /\A#{HOSTNAME}\z/i 9 | 10 | def call(data) 11 | return if REGEXP.match?(data.value) 12 | 13 | raise FormatError, "#{data} is not a valid hostname" 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/minimum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class Minimum < Base 7 | self.key = "minimum" 8 | self.instance_types = "number" 9 | 10 | def evaluate(instance, result) 11 | if instance < json.value 12 | result.failure("The value may not be less than #{json.value})") 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/core/ref.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Core 6 | class Ref < Base 7 | self.key = "$ref" 8 | 9 | def resolve 10 | @ref_schema = parent_schema.resolve_ref(json) 11 | end 12 | 13 | def evaluate(instance, result) 14 | @ref_schema.evaluate(instance, result) 15 | result.ref_schema = @ref_schema 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/max_items.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class MaxItems < Base 7 | self.key = "maxItems" 8 | self.instance_types = "array" 9 | 10 | def evaluate(instance, result) 11 | if instance.length > json 12 | result.failure("The array has too many elements (maximum #{json.value})") 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/min_items.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class MinItems < Base 7 | self.key = "minItems" 8 | self.instance_types = "array" 9 | 10 | def evaluate(instance, result) 11 | if instance.length < json 12 | result.failure("The array has too few elements (minimum #{json.value})") 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/idn_hostname.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "uri/idna" 4 | 5 | module JSONSkooma 6 | module Validators 7 | class IdnHostname < Base 8 | def call(data) 9 | register_opts = data.value.ascii_only? ? {alabel: data.value} : {ulabel: data.value} 10 | URI::IDNA.register(**register_opts) 11 | rescue URI::IDNA::Error => e 12 | raise FormatError, "#{data} is not a valid IDN hostname: #{e.message}" 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/exclusive_maximum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class ExclusiveMaximum < Base 7 | self.key = "exclusiveMaximum" 8 | self.instance_types = "number" 9 | 10 | def evaluate(instance, result) 11 | if instance >= json 12 | result.failure("The value must be less than #{json.value})") 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/max_length.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class MaxLength < Base 7 | self.key = "maxLength" 8 | self.instance_types = "string" 9 | 10 | def evaluate(instance, result) 11 | if instance.length > json 12 | result.failure("The text is too long (maximum #{json.value} characters))") 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/min_length.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class MinLength < Base 7 | self.key = "minLength" 8 | self.instance_types = "string" 9 | 10 | def evaluate(instance, result) 11 | if instance.length < json 12 | result.failure("The text is too short (minimum #{json.value} characters))") 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/exclusive_minimum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class ExclusiveMinimum < Base 7 | self.key = "exclusiveMinimum" 8 | self.instance_types = "number" 9 | 10 | def evaluate(instance, result) 11 | if instance <= json 12 | result.failure("The value must be greater than #{json.value})") 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/min_properties.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class MinProperties < Base 7 | self.key = "minProperties" 8 | self.instance_types = "object" 9 | 10 | def evaluate(instance, result) 11 | if instance.length < json 12 | result.failure("The object has too few properties (minimum #{json.value})") 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/max_properties.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class MaxProperties < Base 7 | self.key = "maxProperties" 8 | self.instance_types = "object" 9 | 10 | def evaluate(instance, result) 11 | if instance.length > json 12 | result.failure("The object has too many properties (maximum #{json.value})") 13 | end 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/required.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class Required < Base 7 | self.key = "required" 8 | self.instance_types = "object" 9 | 10 | def evaluate(instance, result) 11 | return if json.value.all? { |val| instance.key?(val) } 12 | 13 | result.failure("The object is missing required properties #{json.value.join(", ")}") 14 | end 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/applicator/not.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Applicator 6 | class Not < Base 7 | self.key = "not" 8 | self.value_schema = :schema 9 | 10 | def evaluate(instance, result) 11 | json.evaluate(instance, result) 12 | return result.success unless result.passed? 13 | 14 | result.failure("The instance must not be valid against the subschema") 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/content/content_schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Content 6 | class ContentSchema < BaseAnnotation 7 | self.key = "contentSchema" 8 | self.instance_types = %w[string] 9 | self.depends_on = %w[contentMediaType] 10 | 11 | def evaluate(instance, result) 12 | return super if result.sibling(instance, "contentMediaType") 13 | 14 | result.discard 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/enum.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class Enum < Base 7 | self.key = "enum" 8 | 9 | def evaluate(instance, result) 10 | return if json.include?(instance) 11 | 12 | result.failure( 13 | "The instance value #{instance.value} must be equal to one of the elements in the defined enumeration: #{json.value.join(", ")}" 14 | ) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/json_pointer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class JSONPointer < Base 6 | ESC = /~[01]/ 7 | UNESC = /[\u0000-\u002E\u0030-\u007d\u007F-\u{10FFFF}]/ 8 | TOKEN = /(#{ESC}|#{UNESC})*/ 9 | JSON_POINTER = /(\/#{TOKEN})*/ 10 | REGEXP = /\A#{JSON_POINTER}\z/ 11 | 12 | def call(data) 13 | return if REGEXP.match?(data) 14 | 15 | raise FormatError, "#{data} is not a valid JSON pointer" 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/unknown.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Unknown 6 | class << self 7 | def [](key) 8 | @unknown_keywords ||= {} 9 | @unknown_keywords[key] ||= Class.new(Keywords::Base) do 10 | self.key = key 11 | 12 | def evaluate(instance, result) 13 | result.annotate(json.value) 14 | result.skip_assertion 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/date.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class Date < Base 6 | REGEXP = /\A#{DateTime::DATE_REGEXP}\z/ 7 | 8 | def call(data) 9 | match = REGEXP.match(data) 10 | raise FormatError, "must be a valid RFC 3339 date string" if match.nil? 11 | 12 | ::Date.new(match[:Y].to_i, match[:M].to_i, match[:D].to_i) 13 | rescue ::Date::Error 14 | raise FormatError, "must be a valid RFC 3339 date string" 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/unique_items.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class UniqueItems < Base 7 | self.key = "uniqueItems" 8 | self.instance_types = "array" 9 | 10 | def evaluate(instance, result) 11 | return unless json.value 12 | 13 | if instance.uniq.size != instance.size 14 | result.failure("The array's elements must all be unique") 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/relative_json_pointer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class RelativeJSONPointer < Base 6 | NN_INT = /0|[1-9][0-9]*/ 7 | INDEX = /[+-]#{NN_INT}/ 8 | RELATIVE_JSON_POINTER = /(#{NN_INT}(#{INDEX})?#{JSONPointer::JSON_POINTER})|(#{NN_INT}#)/ 9 | REGEXP = /\A#{RELATIVE_JSON_POINTER}\z/ 10 | 11 | def call(data) 12 | return if REGEXP.match?(data) 13 | 14 | raise FormatError, "#{data} is not a valid JSON pointer" 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/multiple_of.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bigdecimal" 4 | 5 | module JSONSkooma 6 | module Keywords 7 | module Validation 8 | class MultipleOf < Base 9 | self.key = "multipleOf" 10 | self.instance_types = "number" 11 | 12 | def evaluate(instance, result) 13 | if BigDecimal(instance.value.to_s).modulo(BigDecimal(json.value.to_s)).nonzero? 14 | result.failure("The value must be a multiple of #{json.value}") 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/applicator/then.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Applicator 6 | class Then < Base 7 | self.key = "then" 8 | self.value_schema = :schema 9 | self.depends_on = %w[if] 10 | 11 | def evaluate(instance, result) 12 | condition = result.sibling(instance, "if") 13 | if condition&.valid? 14 | json.evaluate(instance, result) 15 | else 16 | result.discard 17 | end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/applicator/else.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Applicator 6 | class Else < Base 7 | self.key = "else" 8 | self.value_schema = :schema 9 | self.depends_on = %w[if] 10 | 11 | def evaluate(instance, result) 12 | condition = result.sibling(instance, "if") 13 | if condition && !condition.valid? 14 | json.evaluate(instance, result) 15 | else 16 | result.discard 17 | end 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/pattern.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class Pattern < Base 7 | self.key = "pattern" 8 | self.instance_types = %w[string] 9 | 10 | def initialize(parent_schema, value) 11 | super 12 | @regexp = Regexp.new(value) 13 | end 14 | 15 | def evaluate(instance, result) 16 | return if @regexp.match(instance) 17 | 18 | result.failure("The text must match the regular expression #{json.value}") 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/core/anchor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Core 6 | class Anchor < Base 7 | self.key = "$anchor" 8 | self.static = true 9 | 10 | def initialize(parent_schema, value) 11 | super 12 | 13 | base_uri = parent_schema.base_uri 14 | raise Error, "No base URI for `$anchor` value `#{value}`" if base_uri.nil? 15 | 16 | uri = base_uri.dup.tap { |u| u.fragment = value } 17 | parent_schema.registry.add_schema(uri, parent_schema, cache_id: parent_schema.cache_id) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/core/dynamic_anchor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Core 6 | class DynamicAnchor < Base 7 | self.key = "$dynamicAnchor" 8 | self.static = true 9 | 10 | def initialize(parent_schema, value) 11 | super 12 | 13 | base_uri = parent_schema.base_uri 14 | raise Error, "No base URI for `$dynamicAnchor` value `#{value}`" if base_uri.nil? 15 | 16 | uri = base_uri.dup.tap { |u| u.fragment = value } 17 | parent_schema.registry.add_schema(uri, parent_schema, cache_id: parent_schema.cache_id) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/type.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class Type < Base 7 | self.key = "type" 8 | 9 | def evaluate(instance, result) 10 | return if json.value.include?(instance.type) 11 | return if integer?(instance) 12 | 13 | result.failure("The instance must be of type #{json.value}, but was #{instance.type}") 14 | end 15 | 16 | private 17 | 18 | def integer?(instance) 19 | instance.type == "number" && 20 | json.include?("integer") && 21 | instance == instance.to_i 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/duration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class Duration < Base 6 | SECOND = /\d+S/ 7 | MINUTE = /\d+M#{SECOND}?/ 8 | HOUR = /\d+H#{MINUTE}?/ 9 | DAY = /\d+D/ 10 | WEEK = /\d+W/ 11 | MONTH = /\d+M#{DAY}?/ 12 | YEAR = /\d+Y#{MONTH}?/ 13 | TIME = /T(#{HOUR}|#{MINUTE}|#{SECOND})/ 14 | DATE = /(#{DAY}|#{MONTH}|#{YEAR})#{TIME}?/ 15 | DURATION = /P(#{DATE}|#{TIME}|#{WEEK})/ 16 | REGEXP = /\A#{DURATION}\z/ 17 | 18 | def call(data) 19 | return if REGEXP.match?(data) 20 | 21 | raise FormatError, "#{data} is not a valid duration" 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/applicator/property_names.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Applicator 6 | class PropertyNames < Base 7 | self.key = "propertyNames" 8 | self.instance_types = "object" 9 | self.value_schema = :schema 10 | 11 | def evaluate(instance, result) 12 | error = [] 13 | instance.each_key do |name| 14 | next if json.evaluate(JSONNode.new(name, key: name, parent: instance), result).passed? 15 | 16 | error << name 17 | result.success 18 | end 19 | 20 | result.failure(error) if error.any? 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/core/id.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Core 6 | class Id < Base 7 | self.key = "$id" 8 | self.static = true 9 | 10 | def initialize(parent_schema, value) 11 | super 12 | 13 | uri = URI.parse(value) 14 | if uri.relative? 15 | base_uri = parent_schema.base_uri 16 | if base_uri 17 | uri = base_uri + uri 18 | else 19 | raise "No base URI against which to resolve the `$id` value `#{value}`" 20 | end 21 | end 22 | 23 | parent_schema.uri = uri 24 | end 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/max_contains.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class MaxContains < Base 7 | self.key = "maxContains" 8 | self.instance_types = %w[array] 9 | self.depends_on = %w[contains] 10 | 11 | def evaluate(instance, result) 12 | contains = result.sibling(instance, "contains") 13 | return if contains.nil? 14 | 15 | if contains.annotation && contains.annotation.length > json 16 | result.failure( 17 | "The array has too many elements matching the contains subschema (maximum #{json.value})" 18 | ) 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class Base 6 | class << self 7 | attr_reader :instance_types 8 | 9 | def instance_types=(value) 10 | @instance_types = Array(value) 11 | end 12 | 13 | def assert?(instance) 14 | instance_types.include?(instance.type) 15 | end 16 | 17 | def call(instance) 18 | new.call(instance) 19 | end 20 | 21 | def inherited(subclass) 22 | subclass.instance_types = "string" 23 | end 24 | end 25 | 26 | def call(_instance) 27 | raise NotImplementedError, "must be implemented by subclass" 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/json_skooma/json_node_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "spec_helper" 4 | 5 | RSpec.describe JSONSkooma::JSONNode do 6 | subject(:node) { described_class.new(value) } 7 | 8 | let(:value) { {a: {b: {c: [1, 2], d: "foo", e: nil}}} } 9 | 10 | describe "#path" do 11 | subject { node.path } 12 | 13 | it { is_expected.to eq JSONSkooma::JSONPointer.new "" } 14 | 15 | context "when node has parent" do 16 | let(:node) { super()["a"] } 17 | 18 | it { is_expected.to eq JSONSkooma::JSONPointer.new "/a" } 19 | end 20 | 21 | context "when node has array parent" do 22 | let(:node) { super()["a"]["b"]["c"]["1"] } 23 | 24 | it { is_expected.to eq JSONSkooma::JSONPointer.new "/a/b/c/1" } 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/validation/dependent_required.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Validation 6 | class DependentRequired < Base 7 | self.key = "dependentRequired" 8 | self.instance_types = "object" 9 | 10 | def evaluate(instance, result) 11 | missing = {} 12 | json.each do |name, dependents| 13 | next unless instance.key?(name) 14 | 15 | missing_deps = dependents.reject { |dep| instance.key?(dep) } 16 | missing[name] = missing_deps if missing_deps.any? 17 | end 18 | 19 | result.failure("The object is missing dependent properties #{missing}") if missing.any? 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/applicator/any_of.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Applicator 6 | class AnyOf < Base 7 | self.key = "anyOf" 8 | self.value_schema = :array_of_schemas 9 | 10 | def evaluate(instance, result) 11 | valid = false 12 | json.each.with_index do |subschema, index| 13 | result.call(instance, index.to_s) do |subresult| 14 | subschema.evaluate(instance, subresult) 15 | valid = true if subresult.passed? 16 | end 17 | end 18 | 19 | return if valid 20 | 21 | result.failure("The instance must be valid against at least one subschema") 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/ipv6.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class Ipv6 < Base 6 | H16 = /\h{1,4}/ 7 | LS32 = /(?:#{H16}:#{H16})|#{Ipv4::IPV4_ADDRESS}/ 8 | IPV6_ADDRESS = /(?:#{H16}:){6}#{LS32}|::(?:#{H16}:){5}#{LS32}|(?:#{H16})?::(?:#{H16}:){4}#{LS32}|(?:(?:#{H16}:){0,1}#{H16})?::(?:#{H16}:){3}#{LS32}|(?:(?:#{H16}:){0,2}#{H16})?::(?:#{H16}:){2}#{LS32}|(?:(?:#{H16}:){0,3}#{H16})?::(?:#{H16}:){1}#{LS32}|(?:(?:#{H16}:){0,4}#{H16})?::#{LS32}|(?:(?:#{H16}:){0,5}#{H16})?::#{H16}|(?:(?:#{H16}:){0,6}#{H16})?::/.freeze 9 | 10 | REGEXP = /\A#{IPV6_ADDRESS}\z/ 11 | 12 | def call(data) 13 | match = REGEXP.match(data) 14 | raise FormatError, "must be a valid IPv6 address" if match.nil? 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/applicator/all_of.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Applicator 6 | class AllOf < Base 7 | self.key = "allOf" 8 | self.value_schema = :array_of_schemas 9 | 10 | def evaluate(instance, result) 11 | err_indices = [] 12 | json.each.with_index do |subschema, index| 13 | result.call(instance, index.to_s) do |subresult| 14 | subschema.evaluate(instance, subresult) 15 | err_indices << index unless subresult.passed? 16 | end 17 | end 18 | return if err_indices.empty? 19 | 20 | result.failure("The instance is invalid against subschemas #{err_indices.join(", ")}") 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/core/schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module Core 6 | class Schema < Base 7 | self.key = "$schema" 8 | self.static = true 9 | 10 | def initialize(parent_schema, value) 11 | super 12 | 13 | # todo: validate URI.validate(require_scheme=True, require_normalized=True) 14 | uri = URI.parse(value) 15 | parent_schema.metaschema_uri = uri 16 | 17 | if parent_schema.is_a?(Metaschema) 18 | metaschema = parent_schema.registry.metaschema(uri) 19 | parent_schema.core_vocabulary ||= metaschema.core_vocabulary 20 | parent_schema.default_vocabularies ||= metaschema.default_vocabularies 21 | end 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/uri_template.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class UriTemplate < Base 6 | PCT = /%\h\h/ 7 | VAR_CHAR = /[A-Za-z\d_]|(#{PCT})/ 8 | VAR_NAME = /#{VAR_CHAR}(\.?#{VAR_CHAR})*/ 9 | MOD4 = /(:[1-9]\d{0,3}|(\*)?)/ 10 | VAR_SPEC = /#{VAR_NAME}(#{MOD4})?/ 11 | VAR_LIST = /#{VAR_SPEC}(,#{VAR_SPEC})*/ 12 | OPERATOR = /[+#.\/;?&=,!@|]/ 13 | EXPRESSION = /\{#{OPERATOR}?#{VAR_LIST}\}/ 14 | LITERALS = /[^\x00-\x20\x7F"'%<>\\^`{|}]/ 15 | URI_TEMPLATE = /((#{LITERALS})|(#{EXPRESSION}))*/ 16 | 17 | REGEXP = /\A#{URI_TEMPLATE}\z/ 18 | 19 | def call(data) 20 | return if REGEXP.match?(data) 21 | 22 | raise FormatError, "#{data} is not a valid URI template" 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/json_skooma/keywords/format_annotation/format.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Keywords 5 | module FormatAnnotation 6 | class Format < Base 7 | self.key = "format" 8 | 9 | def initialize(parent_schema, value) 10 | super 11 | if parent_schema.registry.format_enabled?(value) 12 | @validator = Validators.validators[value] 13 | end 14 | end 15 | 16 | def evaluate(instance, result) 17 | result.annotate(json) 18 | return result.skip_assertion unless @validator&.assert?(instance) 19 | 20 | @validator.call(instance) 21 | rescue Validators::FormatError => e 22 | result.failure("The instance is invalid against the #{json.value} format: #{e}") 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/json_skooma/validators/date_time.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JSONSkooma 4 | module Validators 5 | class DateTime < Base 6 | DATE_REGEXP = /(?\d{4})-(?\d{2})-(?\d{2})/ 7 | PARTIAL_TIME = /(?[01]\d|2[0-3]):(?[0-5]\d):(?[0-5]\d|60)(?\.\d+)?/ 8 | TIME_OFFSET = /[Zz]|(?[+-])(?[01]\d|2[0-3]):(?[0-5]\d)/ 9 | FULL_TIME = /#{PARTIAL_TIME}#{TIME_OFFSET}/ 10 | 11 | REGEXP = /\A(?#{DATE_REGEXP})[Tt](?