json_schemer-2.4.0/0000755000004100000410000000000014751003725014230 5ustar www-datawww-datajson_schemer-2.4.0/Gemfile.lock0000644000004100000410000000157314751003725016460 0ustar www-datawww-dataPATH remote: . specs: json_schemer (2.4.0) bigdecimal hana (~> 1.3) regexp_parser (~> 2.0) simpleidn (~> 0.2) GEM remote: https://rubygems.org/ specs: base64 (0.2.0) bigdecimal (3.1.9) bigdecimal (3.1.9-java) concurrent-ruby (1.3.5) csv (3.3.2) docile (1.4.1) hana (1.3.7) i18n (1.14.6) concurrent-ruby (~> 1.0) i18n-debug (1.2.0) i18n (< 2) minitest (5.25.4) rake (13.2.1) regexp_parser (2.10.0) simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-html (0.13.1) simplecov_json_formatter (0.1.4) simpleidn (0.2.3) PLATFORMS java ruby DEPENDENCIES base64 bundler (~> 2.4.0) csv i18n i18n-debug json_schemer! minitest (~> 5.0) rake (~> 13.0) simplecov (~> 0.22) BUNDLED WITH 2.4.22 json_schemer-2.4.0/bin/0000755000004100000410000000000014751003725015000 5ustar www-datawww-datajson_schemer-2.4.0/bin/setup0000755000004100000410000000020314751003725016061 0ustar www-datawww-data#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install # Do any other automated setup that you need to do here json_schemer-2.4.0/bin/rake0000755000004100000410000000142314751003725015650 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true # # This file was generated by Bundler. # # The application 'rake' is installed as part of a gem, and # this file is here to facilitate running it. # require "pathname" ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", Pathname.new(__FILE__).realpath) bundle_binstub = File.expand_path("../bundle", __FILE__) if File.file?(bundle_binstub) if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ load(bundle_binstub) else abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") end end require "rubygems" require "bundler/setup" load Gem.bin_path("rake", "rake") json_schemer-2.4.0/bin/hostname_character_classes0000755000004100000410000000276414751003725022306 0ustar www-datawww-data#!/usr/bin/env ruby require 'open-uri' require 'csv' # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.1 # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.2 csv_options = { :col_sep => ';', :skip_blanks => true, :skip_lines => /\A#/ } unicode_data = URI('https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt') derived_joining_type = URI('https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedJoiningType.txt') # https://www.unicode.org/reports/tr44/#Canonical_Combining_Class_Values virama_canonical_combining_class = '9' virama_codes = CSV.new(unicode_data.read, **csv_options).select do |code, _name, _category, canonical_combining_class| canonical_combining_class == virama_canonical_combining_class end.map(&:first) # https://www.unicode.org/reports/tr44/#Default_Values # https://www.unicode.org/reports/tr44/#Derived_Extracted codes_by_joining_type = CSV.new(derived_joining_type.read, **csv_options).group_by do |_code, joining_type| joining_type.gsub(/#.+/, '').strip end.transform_values do |rows| rows.map do |code, _joining_type| code.strip end end def codes_to_character_class(codes) characters = codes.map do |code| code.gsub(/(\h+)/, '\u{\1}').gsub('..', '-') end "[#{characters.join}]" end puts "VIRAMA_CHARACTER_CLASS = '#{codes_to_character_class(virama_codes)}'" codes_by_joining_type.slice('L', 'D', 'T', 'R').each do |joining_type, codes| puts "JOINING_TYPE_#{joining_type}_CHARACTER_CLASS = '#{codes_to_character_class(codes)}'" end json_schemer-2.4.0/bin/console0000755000004100000410000000053314751003725016371 0ustar www-datawww-data#!/usr/bin/env ruby require "bundler/setup" require "json_schemer" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require "irb" IRB.start(__FILE__) json_schemer-2.4.0/.gitignore0000644000004100000410000000013014751003725016212 0ustar www-datawww-data/.bundle/ /.yardoc /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ /.ruby-version json_schemer-2.4.0/exe/0000755000004100000410000000000014751003725015011 5ustar www-datawww-datajson_schemer-2.4.0/exe/json_schemer0000755000004100000410000000310314751003725017413 0ustar www-datawww-data#!/usr/bin/env ruby require 'json' require 'optparse' require 'pathname' require 'json_schemer' parser = OptionParser.new('Usage:', 32, ' ') parser.separator(" #{parser.program_name} [options] ...") parser.separator(" #{parser.program_name} [options] -") parser.separator(" #{parser.program_name} [options] - ...") parser.separator(" #{parser.program_name} -h | --help") parser.separator(" #{parser.program_name} --version") parser.separator('') parser.separator('Options:') parser.on('-e', '--errors MAX', Integer, 'Maximum number of errors to output', 'Use "0" to validate with no output') parser.on_tail('-h', '--help', 'Show help') parser.on_tail('-v', '--version', 'Show version') options = {} parser.parse!(:into => options) if options[:help] $stdout.puts(parser) exit end if options[:version] $stdout.puts("#{parser.program_name} #{JSONSchemer::VERSION}") exit end if ARGV.size == 0 $stderr.puts("#{parser.program_name}: no schema or data") exit(false) end if ARGV.size == 1 $stderr.puts("#{parser.program_name}: no data") exit(false) end if ARGV.count('-') > 1 $stderr.puts("#{parser.program_name}: multiple stdin") exit(false) end errors = 0 schema = ARGF.file.is_a?(File) ? Pathname.new(ARGF.file.path) : ARGF.file.read schemer = JSONSchemer.schema(schema) while ARGV.any? data = JSON.parse(ARGF.skip.file.read) schemer.validate(data).each do |error| exit(false) if options[:errors] == 0 errors += 1 $stdout.puts(JSON.generate(error)) exit(false) if options[:errors] == errors end end exit(errors.zero?) json_schemer-2.4.0/.github/0000755000004100000410000000000014751003725015570 5ustar www-datawww-datajson_schemer-2.4.0/.github/workflows/0000755000004100000410000000000014751003725017625 5ustar www-datawww-datajson_schemer-2.4.0/.github/workflows/ci.yml0000644000004100000410000000131514751003725020743 0ustar www-datawww-dataname: ci on: [push, pull_request] jobs: test: strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] ruby: [2.7, 3.0, 3.1, 3.2, 3.3, 3.4, head, jruby, jruby-head, truffleruby, truffleruby-head] exclude: - os: ubuntu-latest ruby: head - os: macos-latest ruby: head - os: windows-latest ruby: truffleruby - os: windows-latest ruby: truffleruby-head runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - run: bin/rake test json_schemer-2.4.0/lib/0000755000004100000410000000000014751003725014776 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer.rb0000644000004100000410000002443414751003725020011 0ustar www-datawww-data# frozen_string_literal: true require 'bigdecimal' require 'forwardable' require 'ipaddr' require 'json' require 'net/http' require 'pathname' require 'set' require 'time' require 'uri' require 'hana' require 'regexp_parser' require 'simpleidn' require 'json_schemer/version' require 'json_schemer/format/duration' require 'json_schemer/format/hostname' require 'json_schemer/format/json_pointer' require 'json_schemer/format/uri_template' require 'json_schemer/format/email' require 'json_schemer/format' require 'json_schemer/content' require 'json_schemer/errors' require 'json_schemer/cached_resolver' require 'json_schemer/ecma_regexp' require 'json_schemer/location' require 'json_schemer/result' require 'json_schemer/output' require 'json_schemer/keyword' require 'json_schemer/draft202012/meta' require 'json_schemer/draft202012/vocab/core' require 'json_schemer/draft202012/vocab/applicator' require 'json_schemer/draft202012/vocab/unevaluated' require 'json_schemer/draft202012/vocab/validation' require 'json_schemer/draft202012/vocab/format_annotation' require 'json_schemer/draft202012/vocab/format_assertion' require 'json_schemer/draft202012/vocab/content' require 'json_schemer/draft202012/vocab/meta_data' require 'json_schemer/draft202012/vocab' require 'json_schemer/draft201909/meta' require 'json_schemer/draft201909/vocab/core' require 'json_schemer/draft201909/vocab/applicator' require 'json_schemer/draft201909/vocab' require 'json_schemer/draft7/meta' require 'json_schemer/draft7/vocab/validation' require 'json_schemer/draft7/vocab' require 'json_schemer/draft6/meta' require 'json_schemer/draft6/vocab' require 'json_schemer/draft4/meta' require 'json_schemer/draft4/vocab/validation' require 'json_schemer/draft4/vocab' require 'json_schemer/openapi31/meta' require 'json_schemer/openapi31/vocab/base' require 'json_schemer/openapi31/vocab' require 'json_schemer/openapi31/document' require 'json_schemer/openapi30/document' require 'json_schemer/openapi30/meta' require 'json_schemer/openapi30/vocab/base' require 'json_schemer/openapi30/vocab' require 'json_schemer/openapi' require 'json_schemer/configuration' require 'json_schemer/resources' require 'json_schemer/schema' module JSONSchemer class UnsupportedOpenAPIVersion < StandardError; end class UnknownRef < StandardError; end class UnknownFormat < StandardError; end class UnknownVocabulary < StandardError; end class UnknownContentEncoding < StandardError; end class UnknownContentMediaType < StandardError; end class UnknownOutputFormat < StandardError; end class InvalidRefResolution < StandardError; end class InvalidRefPointer < StandardError; end class InvalidRegexpResolution < StandardError; end class InvalidFileURI < StandardError; end class InvalidEcmaRegexp < StandardError; end VOCABULARIES = { 'https://json-schema.org/draft/2020-12/vocab/core' => Draft202012::Vocab::CORE, 'https://json-schema.org/draft/2020-12/vocab/applicator' => Draft202012::Vocab::APPLICATOR, 'https://json-schema.org/draft/2020-12/vocab/unevaluated' => Draft202012::Vocab::UNEVALUATED, 'https://json-schema.org/draft/2020-12/vocab/validation' => Draft202012::Vocab::VALIDATION, 'https://json-schema.org/draft/2020-12/vocab/format-annotation' => Draft202012::Vocab::FORMAT_ANNOTATION, 'https://json-schema.org/draft/2020-12/vocab/format-assertion' => Draft202012::Vocab::FORMAT_ASSERTION, 'https://json-schema.org/draft/2020-12/vocab/content' => Draft202012::Vocab::CONTENT, 'https://json-schema.org/draft/2020-12/vocab/meta-data' => Draft202012::Vocab::META_DATA, 'https://json-schema.org/draft/2019-09/vocab/core' => Draft201909::Vocab::CORE, 'https://json-schema.org/draft/2019-09/vocab/applicator' => Draft201909::Vocab::APPLICATOR, 'https://json-schema.org/draft/2019-09/vocab/validation' => Draft201909::Vocab::VALIDATION, 'https://json-schema.org/draft/2019-09/vocab/format' => Draft201909::Vocab::FORMAT, 'https://json-schema.org/draft/2019-09/vocab/content' => Draft201909::Vocab::CONTENT, 'https://json-schema.org/draft/2019-09/vocab/meta-data' => Draft201909::Vocab::META_DATA, 'json-schemer://draft7' => Draft7::Vocab::ALL, 'json-schemer://draft6' => Draft6::Vocab::ALL, 'json-schemer://draft4' => Draft4::Vocab::ALL, 'https://spec.openapis.org/oas/3.1/vocab/base' => OpenAPI31::Vocab::BASE, 'json-schemer://openapi30' => OpenAPI30::Vocab::BASE } VOCABULARY_ORDER = VOCABULARIES.transform_values.with_index { |_vocabulary, index| index } WINDOWS_URI_PATH_REGEX = /\A\/[a-z]:/i # :nocov: URI_PARSER = URI.const_defined?(:RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::DEFAULT_PARSER # :nocov: FILE_URI_REF_RESOLVER = proc do |uri| raise InvalidFileURI, 'must use `file` scheme' unless uri.scheme == 'file' raise InvalidFileURI, 'cannot have a host (use `file:///`)' if uri.host && !uri.host.empty? path = uri.path path = path[1..-1] if path.match?(WINDOWS_URI_PATH_REGEX) JSON.parse(File.read(URI_PARSER.unescape(path))) end class << self def schema(schema, **options) schema = resolve(schema, options) Schema.new(schema, **options) end def valid_schema?(schema, **options) schema = resolve(schema, options) meta_schema(schema, options).valid?(schema, **options.slice(:output_format, :resolve_enumerators, :access_mode)) end def validate_schema(schema, **options) schema = resolve(schema, options) meta_schema(schema, options).validate(schema, **options.slice(:output_format, :resolve_enumerators, :access_mode)) end def draft202012 @draft202012 ||= Schema.new( Draft202012::SCHEMA, :base_uri => Draft202012::BASE_URI, :formats => Draft202012::FORMATS, :content_encodings => Draft202012::CONTENT_ENCODINGS, :content_media_types => Draft202012::CONTENT_MEDIA_TYPES, :ref_resolver => Draft202012::Meta::SCHEMAS.to_proc, :regexp_resolver => 'ecma' ) end def draft201909 @draft201909 ||= Schema.new( Draft201909::SCHEMA, :base_uri => Draft201909::BASE_URI, :formats => Draft201909::FORMATS, :content_encodings => Draft201909::CONTENT_ENCODINGS, :content_media_types => Draft201909::CONTENT_MEDIA_TYPES, :ref_resolver => Draft201909::Meta::SCHEMAS.to_proc, :regexp_resolver => 'ecma' ) end def draft7 @draft7 ||= Schema.new( Draft7::SCHEMA, :vocabulary => { 'json-schemer://draft7' => true }, :base_uri => Draft7::BASE_URI, :formats => Draft7::FORMATS, :content_encodings => Draft7::CONTENT_ENCODINGS, :content_media_types => Draft7::CONTENT_MEDIA_TYPES, :regexp_resolver => 'ecma' ) end def draft6 @draft6 ||= Schema.new( Draft6::SCHEMA, :vocabulary => { 'json-schemer://draft6' => true }, :base_uri => Draft6::BASE_URI, :formats => Draft6::FORMATS, :content_encodings => Draft6::CONTENT_ENCODINGS, :content_media_types => Draft6::CONTENT_MEDIA_TYPES, :regexp_resolver => 'ecma' ) end def draft4 @draft4 ||= Schema.new( Draft4::SCHEMA, :vocabulary => { 'json-schemer://draft4' => true }, :base_uri => Draft4::BASE_URI, :formats => Draft4::FORMATS, :content_encodings => Draft4::CONTENT_ENCODINGS, :content_media_types => Draft4::CONTENT_MEDIA_TYPES, :regexp_resolver => 'ecma' ) end def openapi31 @openapi31 ||= Schema.new( OpenAPI31::SCHEMA, :base_uri => OpenAPI31::BASE_URI, :formats => OpenAPI31::FORMATS, :ref_resolver => OpenAPI31::Meta::SCHEMAS.to_proc, :regexp_resolver => 'ecma' ) end def openapi30 @openapi30 ||= Schema.new( OpenAPI30::SCHEMA, :vocabulary => { 'json-schemer://draft4' => true, 'json-schemer://openapi30' => true }, :base_uri => OpenAPI30::BASE_URI, :formats => OpenAPI30::FORMATS, :ref_resolver => OpenAPI30::Meta::SCHEMAS.to_proc, :regexp_resolver => 'ecma' ) end def openapi31_document @openapi31_document ||= Schema.new( OpenAPI31::Document::SCHEMA_BASE, :ref_resolver => OpenAPI31::Document::SCHEMAS.to_proc, :regexp_resolver => 'ecma' ) end def openapi30_document @openapi30_document ||= Schema.new( OpenAPI30::Document::SCHEMA, :ref_resolver => OpenAPI30::Document::SCHEMAS.to_proc, :regexp_resolver => 'ecma' ) end def openapi(document, **options) OpenAPI.new(document, **options) end def configuration @configuration ||= Configuration.new end def configure yield configuration end private def resolve(schema, options) case schema when String JSON.parse(schema) when Pathname base_uri = URI.parse(File.join('file:', URI_PARSER.escape(schema.realpath.to_s))) options[:base_uri] = base_uri if options.key?(:ref_resolver) FILE_URI_REF_RESOLVER.call(base_uri) else ref_resolver = CachedResolver.new(&FILE_URI_REF_RESOLVER) options[:ref_resolver] = ref_resolver ref_resolver.call(base_uri) end else schema end end def meta_schema(schema, options) parseable_schema = {} if schema.is_a?(Hash) meta_schema = schema['$schema'] || schema[:'$schema'] parseable_schema['$schema'] = meta_schema if meta_schema.is_a?(String) end schema(parseable_schema, **options).meta_schema end end META_SCHEMA_CALLABLES_BY_BASE_URI_STR = { Draft202012::BASE_URI.to_s => method(:draft202012), Draft201909::BASE_URI.to_s => method(:draft201909), Draft7::BASE_URI.to_s => method(:draft7), Draft6::BASE_URI.to_s => method(:draft6), Draft4::BASE_URI.to_s => method(:draft4), # version-less $schema deprecated after Draft 4 'http://json-schema.org/schema#' => method(:draft4), OpenAPI31::BASE_URI.to_s => method(:openapi31), OpenAPI30::BASE_URI.to_s => method(:openapi30) }.freeze META_SCHEMAS_BY_BASE_URI_STR = Hash.new do |hash, base_uri_str| next unless META_SCHEMA_CALLABLES_BY_BASE_URI_STR.key?(base_uri_str) hash[base_uri_str] = META_SCHEMA_CALLABLES_BY_BASE_URI_STR.fetch(base_uri_str).call end end json_schemer-2.4.0/lib/json_schemer/0000755000004100000410000000000014751003725017455 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/output.rb0000644000004100000410000000264314751003725021347 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Output FRAGMENT_ENCODE_REGEX = /[^\w?\/:@\-.~!$&'()*+,;=]/ attr_reader :keyword, :schema def x_error return @x_error if defined?(@x_error) @x_error = schema.parsed['x-error']&.message(error_key) end private def result(instance, instance_location, keyword_location, valid, nested = nil, type: nil, annotation: nil, details: nil, ignore_nested: false) Result.new(self, instance, instance_location, keyword_location, valid, nested, type, annotation, details, ignore_nested, valid ? 'annotations' : 'errors') end def escaped_keyword @escaped_keyword ||= Location.escape_json_pointer_token(keyword) end def join_location(location, keyword) Location.join(location, keyword) end def fragment_encode(location) Format.percent_encode(location, FRAGMENT_ENCODE_REGEX) end # :nocov: if Symbol.method_defined?(:name) def stringify(key) key.is_a?(Symbol) ? key.name : key.to_s end else def stringify(key) key.to_s end end # :nocov: def deep_stringify_keys(obj) case obj when Hash obj.each_with_object({}) do |(key, value), out| out[stringify(key)] = deep_stringify_keys(value) end when Array obj.map { |item| deep_stringify_keys(item) } else obj end end end end json_schemer-2.4.0/lib/json_schemer/ecma_regexp.rb0000644000004100000410000000426114751003725022264 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer class EcmaRegexp class Syntax < Regexp::Syntax::Base # regexp_parser >= 2.3.0 uses syntax classes directly instead of instances # :nocov: SYNTAX = respond_to?(:implements) ? self : new # :nocov: SYNTAX.implements :anchor, Anchor::Extended SYNTAX.implements :assertion, Assertion::All # literal %i[number] to support regexp_parser < 2.2.0 (Backreference::Plain) SYNTAX.implements :backref, %i[number] + Backreference::Name # :meta_sequence, :bell, and :escape are not supported in ecma SYNTAX.implements :escape, Escape::Basic + (Escape::Control - %i[meta_sequence]) + (Escape::ASCII - %i[bell escape]) + Escape::Unicode + Escape::Meta + Escape::Hex + Escape::Octal SYNTAX.implements :property, UnicodeProperty::All SYNTAX.implements :nonproperty, UnicodeProperty::All # :comment is not supported in ecma SYNTAX.implements :free_space, (FreeSpace::All - %i[comment]) SYNTAX.implements :group, Group::Basic + Group::Named + Group::Passive SYNTAX.implements :literal, Literal::All SYNTAX.implements :meta, Meta::Extended SYNTAX.implements :quantifier, Quantifier::Greedy + Quantifier::Reluctant + Quantifier::Interval + Quantifier::IntervalReluctant SYNTAX.implements :set, CharacterSet::Basic SYNTAX.implements :type, CharacterType::Extended end RUBY_EQUIVALENTS = { :anchor => { :bol => '\A', :eol => '\z' }, :type => { :space => '[\t\r\n\f\v\uFEFF\u2029\p{Zs}]', :nonspace => '[^\t\r\n\f\v\uFEFF\u2029\p{Zs}]' } }.freeze class << self def ruby_equivalent(pattern) Regexp::Scanner.scan(pattern).map do |type, token, text| Syntax::SYNTAX.check!(*Syntax::SYNTAX.normalize(type, token)) RUBY_EQUIVALENTS.dig(type, token) || text rescue Regexp::Syntax::NotImplementedError raise InvalidEcmaRegexp, "invalid token #{text.inspect} (#{type}:#{token}) in #{pattern.inspect}" end.join rescue Regexp::Scanner::ScannerError raise InvalidEcmaRegexp, "invalid pattern #{pattern.inspect}" end end end end json_schemer-2.4.0/lib/json_schemer/configuration.rb0000644000004100000410000000203614751003725022652 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer Configuration = Struct.new( :base_uri, :meta_schema, :vocabulary, :format, :formats, :content_encodings, :content_media_types, :keywords, :before_property_validation, :after_property_validation, :insert_property_defaults, :property_default_resolver, :ref_resolver, :regexp_resolver, :output_format, :resolve_enumerators, :access_mode, keyword_init: true ) do def initialize( base_uri: URI('json-schemer://schema'), meta_schema: Draft202012::BASE_URI.to_s, vocabulary: nil, format: true, formats: {}, content_encodings: {}, content_media_types: {}, keywords: {}, before_property_validation: [], after_property_validation: [], insert_property_defaults: false, property_default_resolver: nil, ref_resolver: proc { |uri| raise UnknownRef, uri.to_s }, regexp_resolver: 'ruby', output_format: 'classic', resolve_enumerators: false, access_mode: nil ) super end end end json_schemer-2.4.0/lib/json_schemer/draft6/0000755000004100000410000000000014751003725020643 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/draft6/meta.rb0000644000004100000410000001200214751003725022111 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft6 BASE_URI = URI('http://json-schema.org/draft-06/schema#') FORMATS = Draft7::FORMATS.dup FORMATS.delete('date') FORMATS.delete('time') FORMATS.delete('idn-email') FORMATS.delete('idn-hostname') FORMATS.delete('iri') FORMATS.delete('iri-reference') FORMATS.delete('relative-json-pointer') FORMATS.delete('regex') CONTENT_ENCODINGS = Draft7::CONTENT_ENCODINGS CONTENT_MEDIA_TYPES = Draft7::CONTENT_MEDIA_TYPES SCHEMA = { '$schema' => 'http://json-schema.org/draft-06/schema#', '$id' => 'http://json-schema.org/draft-06/schema#', 'title' => 'Core schema meta-schema', 'definitions' => { 'schemaArray' => { 'type' => 'array', 'minItems' => 1, 'items' => { '$ref' => '#' } }, 'nonNegativeInteger' => { 'type' => 'integer', 'minimum' => 0 }, 'nonNegativeIntegerDefault0' => { 'allOf' => [ { '$ref' => '#/definitions/nonNegativeInteger' }, { 'default' => 0 } ] }, 'simpleTypes' => { 'enum' => [ 'array', 'boolean', 'integer', 'null', 'number', 'object', 'string' ] }, 'stringArray' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'uniqueItems' => true, 'default' => [] } }, 'type' => ['object', 'boolean'], 'properties' => { '$id' => { 'type' => 'string', 'format' => 'uri-reference' }, '$schema' => { 'type' => 'string', 'format' => 'uri' }, '$ref' => { 'type' => 'string', 'format' => 'uri-reference' }, 'title' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'default' => {}, 'examples' => { 'type' => 'array', 'items' => {} }, 'multipleOf' => { 'type' => 'number', 'exclusiveMinimum' => 0 }, 'maximum' => { 'type' => 'number' }, 'exclusiveMaximum' => { 'type' => 'number' }, 'minimum' => { 'type' => 'number' }, 'exclusiveMinimum' => { 'type' => 'number' }, 'maxLength' => { '$ref' => '#/definitions/nonNegativeInteger' }, 'minLength' => { '$ref' => '#/definitions/nonNegativeIntegerDefault0' }, 'pattern' => { 'type' => 'string', 'format' => 'regex' }, 'additionalItems' => { '$ref' => '#' }, 'items' => { 'anyOf' => [ { '$ref' => '#' }, { '$ref' => '#/definitions/schemaArray' } ], 'default' => {} }, 'maxItems' => { '$ref' => '#/definitions/nonNegativeInteger' }, 'minItems' => { '$ref' => '#/definitions/nonNegativeIntegerDefault0' }, 'uniqueItems' => { 'type' => 'boolean', 'default' => false }, 'contains' => { '$ref' => '#' }, 'maxProperties' => { '$ref' => '#/definitions/nonNegativeInteger' }, 'minProperties' => { '$ref' => '#/definitions/nonNegativeIntegerDefault0' }, 'required' => { '$ref' => '#/definitions/stringArray' }, 'additionalProperties' => { '$ref' => '#' }, 'definitions' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#' }, 'default' => {} }, 'properties' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#' }, 'default' => {} }, 'patternProperties' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#' }, 'propertyNames' => { 'format' => 'regex' }, 'default' => {} }, 'dependencies' => { 'type' => 'object', 'additionalProperties' => { 'anyOf' => [ { '$ref' => '#' }, { '$ref' => '#/definitions/stringArray' } ] } }, 'propertyNames' => { '$ref' => '#' }, 'const' => {}, 'enum' => { 'type' => 'array', 'minItems' => 1, 'uniqueItems' => true }, 'type' => { 'anyOf' => [ { '$ref' => '#/definitions/simpleTypes' }, { 'type' => 'array', 'items' => { '$ref' => '#/definitions/simpleTypes' }, 'minItems' => 1, 'uniqueItems' => true } ] }, 'format' => { 'type' => 'string' }, 'allOf' => { '$ref' => '#/definitions/schemaArray' }, 'anyOf' => { '$ref' => '#/definitions/schemaArray' }, 'oneOf' => { '$ref' => '#/definitions/schemaArray' }, 'not' => { '$ref' => '#' } }, 'default' => {} } end end json_schemer-2.4.0/lib/json_schemer/draft6/vocab.rb0000644000004100000410000000056114751003725022264 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft6 module Vocab ALL = Draft7::Vocab::ALL.dup ALL.delete('$comment') ALL.delete('if') ALL.delete('then') ALL.delete('else') ALL.delete('readOnly') ALL.delete('writeOnly') ALL.delete('contentMediaType') ALL.delete('contentEncoding') end end end json_schemer-2.4.0/lib/json_schemer/schema.rb0000644000004100000410000003737314751003725021257 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer class Schema Context = Struct.new(:instance, :dynamic_scope, :adjacent_results, :short_circuit, :access_mode) do def original_instance(instance_location) Hana::Pointer.parse(Location.resolve(instance_location)).reduce(instance) do |obj, token| if obj.is_a?(Array) obj.fetch(token.to_i) elsif !obj.key?(token) && obj.key?(token.to_sym) obj.fetch(token.to_sym) else obj.fetch(token) end end end end extend Forwardable include Output SCHEMA_KEYWORD_CLASS = Draft202012::Vocab::Core::Schema VOCABULARY_KEYWORD_CLASS = Draft202012::Vocab::Core::Vocabulary ID_KEYWORD_CLASS = Draft202012::Vocab::Core::Id UNKNOWN_KEYWORD_CLASS = Draft202012::Vocab::Core::UnknownKeyword NOT_KEYWORD_CLASS = Draft202012::Vocab::Applicator::Not PROPERTIES_KEYWORD_CLASS = Draft202012::Vocab::Applicator::Properties NET_HTTP_REF_RESOLVER = proc { |uri| JSON.parse(Net::HTTP.get(uri)) } RUBY_REGEXP_RESOLVER = proc { |pattern| Regexp.new(pattern) } ECMA_REGEXP_RESOLVER = proc { |pattern| Regexp.new(EcmaRegexp.ruby_equivalent(pattern)) } DEFAULT_PROPERTY_DEFAULT_RESOLVER = proc do |instance, property, results_with_tree_validity| results_with_tree_validity = results_with_tree_validity.select(&:last) unless results_with_tree_validity.size == 1 annotations = results_with_tree_validity.to_set { |result, _tree_valid| result.annotation } if annotations.size == 1 instance[property] = annotations.first.clone true else false end end SYMBOL_PROPERTY_DEFAULT_RESOLVER = proc do |instance, property, results_with_tree_validity| DEFAULT_PROPERTY_DEFAULT_RESOLVER.call(instance, property.to_sym, results_with_tree_validity) end attr_accessor :base_uri, :meta_schema, :keywords, :keyword_order attr_reader :value, :parent, :root, :configuration, :parsed def_delegators :@configuration, :vocabulary, :format, :formats, :content_encodings, :content_media_types, :before_property_validation, :after_property_validation, :insert_property_defaults def_delegator :@configuration, :keywords, :custom_keywords def initialize( value, parent = nil, root = self, keyword = nil, configuration: JSONSchemer.configuration, base_uri: configuration.base_uri, meta_schema: configuration.meta_schema, vocabulary: configuration.vocabulary, format: configuration.format, formats: configuration.formats, content_encodings: configuration.content_encodings, content_media_types: configuration.content_media_types, keywords: configuration.keywords, before_property_validation: configuration.before_property_validation, after_property_validation: configuration.after_property_validation, insert_property_defaults: configuration.insert_property_defaults, property_default_resolver: configuration.property_default_resolver, ref_resolver: configuration.ref_resolver, regexp_resolver: configuration.regexp_resolver, output_format: configuration.output_format, resolve_enumerators: configuration.resolve_enumerators, access_mode: configuration.access_mode ) @value = deep_stringify_keys(value) @parent = parent @root = root @keyword = keyword @schema = self @base_uri = base_uri @meta_schema = meta_schema @configuration = Configuration.new( :base_uri => base_uri, :meta_schema => meta_schema, :vocabulary => vocabulary, :format => format, :formats => formats, :content_encodings => content_encodings, :content_media_types => content_media_types, :keywords => keywords, :before_property_validation => Array(before_property_validation), :after_property_validation => Array(after_property_validation), :insert_property_defaults => insert_property_defaults, :property_default_resolver => property_default_resolver, :ref_resolver => ref_resolver, :regexp_resolver => regexp_resolver, :output_format => output_format, :resolve_enumerators => resolve_enumerators, :access_mode => access_mode ) @parsed = parse end def valid?(instance, **options) validate(instance, :output_format => 'flag', **options).fetch('valid') end def validate(instance, output_format: @configuration.output_format, resolve_enumerators: @configuration.resolve_enumerators, access_mode: @configuration.access_mode) instance_location = Location.root context = Context.new(instance, [], nil, (!insert_property_defaults && output_format == 'flag'), access_mode) result = validate_instance(deep_stringify_keys(instance), instance_location, root_keyword_location, context) if insert_property_defaults && result.insert_property_defaults(context, &property_default_resolver) result = validate_instance(deep_stringify_keys(instance), instance_location, root_keyword_location, context) end output = result.output(output_format) resolve_enumerators!(output) if resolve_enumerators output end def valid_schema?(**options) meta_schema.valid?(value, **options) end def validate_schema(**options) meta_schema.validate(value, **options) end def ref(value) root.resolve_ref(URI.join(base_uri, value)) end def validate_instance(instance, instance_location, keyword_location, context) context.dynamic_scope.push(self) original_adjacent_results = context.adjacent_results adjacent_results = context.adjacent_results = {} short_circuit = context.short_circuit begin return result(instance, instance_location, keyword_location, false) if value == false return result(instance, instance_location, keyword_location, true) if value == true || value.empty? valid = true nested = [] parsed.each do |keyword, keyword_instance| next unless keyword_result = keyword_instance.validate(instance, instance_location, join_location(keyword_location, keyword), context) valid &&= keyword_result.valid return result(instance, instance_location, keyword_location, false) if short_circuit && !valid nested << keyword_result adjacent_results[keyword_instance.class] = keyword_result end if root.custom_keywords.any? resolved_instance_location = Location.resolve(instance_location) root.custom_keywords.each do |custom_keyword, callable| if value.key?(custom_keyword) [*callable.call(instance, value, resolved_instance_location)].each do |custom_keyword_result| custom_keyword_valid = custom_keyword_result == true valid &&= custom_keyword_valid type = custom_keyword_result.is_a?(String) ? custom_keyword_result : custom_keyword details = { 'keyword' => custom_keyword, 'result' => custom_keyword_result } nested << result(instance, instance_location, keyword_location, custom_keyword_valid, :type => type, :details => details) end end end end result(instance, instance_location, keyword_location, valid, nested) ensure context.dynamic_scope.pop context.adjacent_results = original_adjacent_results end end def resolve_ref(uri) pointer = '' if Format.valid_json_pointer?(uri.fragment) pointer = URI.decode_www_form_component(uri.fragment) uri.fragment = nil end lexical_resources = resources.fetch(:lexical) schema = lexical_resources[uri] if !schema && uri.fragment.nil? empty_fragment_uri = uri.dup empty_fragment_uri.fragment = '' schema = lexical_resources[empty_fragment_uri] end unless schema location_independent_identifier = uri.fragment uri.fragment = nil remote_schema = JSONSchemer.schema( ref_resolver.call(uri) || raise(InvalidRefResolution, uri.to_s), :configuration => configuration, :base_uri => uri, :meta_schema => meta_schema, :ref_resolver => ref_resolver, :regexp_resolver => regexp_resolver ) remote_uri = remote_schema.base_uri.dup remote_uri.fragment = location_independent_identifier if location_independent_identifier schema = remote_schema.resources.fetch(:lexical).fetch(remote_uri) end schema = Hana::Pointer.parse(pointer).reduce(schema) do |obj, token| obj.fetch(token) rescue IndexError raise InvalidRefPointer, pointer end schema = schema.parsed_schema if schema.is_a?(Keyword) raise InvalidRefPointer, pointer unless schema.is_a?(Schema) schema end def resolve_regexp(pattern) regexp_resolver.call(pattern) || raise(InvalidRegexpResolution, pattern) end def bundle return value unless value.is_a?(Hash) id_keyword = meta_schema.id_keyword defs_keyword = meta_schema.defs_keyword compound_document = value.dup compound_document[id_keyword] = base_uri.to_s compound_document['$schema'] = meta_schema.base_uri.to_s embedded_resources = compound_document[defs_keyword] = (compound_document[defs_keyword]&.dup || {}) if compound_document.key?('$ref') && meta_schema.keywords.fetch('$ref').exclusive? compound_document['allOf'] = (compound_document['allOf']&.dup || []) compound_document['allOf'] << { '$ref' => compound_document.delete('$ref') } end values = [self] while value = values.shift case value when Schema values << value.parsed when Keyword if value.respond_to?(:ref_uri) && value.respond_to?(:ref_schema) ref_uri = value.ref_uri.dup ref_uri.fragment = nil ref_id = ref_uri.to_s ref_schema = value.ref_schema.root next if ref_schema == root || embedded_resources.key?(ref_id) embedded_resource = ref_schema.value.dup embedded_resource[id_keyword] = ref_id embedded_resource['$schema'] = ref_schema.meta_schema.base_uri.to_s embedded_resources[ref_id] = embedded_resource values << ref_schema else values << value.parsed end when Hash values.concat(value.values) when Array values.concat(value) end end compound_document end def absolute_keyword_location # using `equal?` because `URI::Generic#==` is slow @absolute_keyword_location ||= if !parent || (!parent.schema.base_uri.equal?(base_uri) && (base_uri.fragment.nil? || base_uri.fragment.empty?)) absolute_keyword_location_uri = base_uri.dup absolute_keyword_location_uri.fragment = '' absolute_keyword_location_uri.to_s elsif keyword "#{parent.absolute_keyword_location}/#{fragment_encode(escaped_keyword)}" else parent.absolute_keyword_location end end def schema_pointer @schema_pointer ||= if !parent '' elsif keyword "#{parent.schema_pointer}/#{escaped_keyword}" else parent.schema_pointer end end def error_key '^' end def fetch(key) parsed.fetch(key) end def fetch_format(format, *args, &block) if meta_schema == self formats.fetch(format, *args, &block) else formats.fetch(format) { meta_schema.fetch_format(format, *args, &block) } end end def fetch_content_encoding(content_encoding, *args, &block) if meta_schema == self content_encodings.fetch(content_encoding, *args, &block) else content_encodings.fetch(content_encoding) { meta_schema.fetch_content_encoding(content_encoding, *args, &block) } end end def fetch_content_media_type(content_media_type, *args, &block) if meta_schema == self content_media_types.fetch(content_media_type, *args, &block) else content_media_types.fetch(content_media_type) { meta_schema.fetch_content_media_type(content_media_type, *args, &block) } end end def id_keyword @id_keyword ||= (keywords.key?('$id') ? '$id' : 'id') end def defs_keyword @defs_keyword ||= (keywords.key?('$defs') ? '$defs' : 'definitions') end def resources @resources ||= { :lexical => Resources.new, :dynamic => Resources.new } end def error(formatted_instance_location:, **options) if value == false && parent&.respond_to?(:false_schema_error) parent.false_schema_error(:formatted_instance_location => formatted_instance_location, **options) else "value at #{formatted_instance_location} does not match schema" end end def ref_resolver @ref_resolver ||= @configuration.ref_resolver == 'net/http' ? CachedResolver.new(&NET_HTTP_REF_RESOLVER) : @configuration.ref_resolver end def regexp_resolver @regexp_resolver ||= case @configuration.regexp_resolver when 'ecma' CachedResolver.new(&ECMA_REGEXP_RESOLVER) when 'ruby' CachedResolver.new(&RUBY_REGEXP_RESOLVER) else @configuration.regexp_resolver end end def inspect "#<#{self.class.name} @value=#{@value.inspect} @parent=#{@parent.inspect} @keyword=#{@keyword.inspect}>" end private def parse @parsed = {} if value.is_a?(Hash) && value.key?('$schema') @parsed['$schema'] = SCHEMA_KEYWORD_CLASS.new(value.fetch('$schema'), self, '$schema') elsif meta_schema.is_a?(String) SCHEMA_KEYWORD_CLASS.new(meta_schema, self, '$schema') end if value.is_a?(Hash) && value.key?('$vocabulary') @parsed['$vocabulary'] = VOCABULARY_KEYWORD_CLASS.new(value.fetch('$vocabulary'), self, '$vocabulary') elsif vocabulary VOCABULARY_KEYWORD_CLASS.new(vocabulary, self, '$vocabulary') end keywords = meta_schema.keywords exclusive_ref = value.is_a?(Hash) && value.key?('$ref') && keywords.fetch('$ref').exclusive? if root == self && (!value.is_a?(Hash) || !value.key?(meta_schema.id_keyword) || exclusive_ref) ID_KEYWORD_CLASS.new(base_uri, self, meta_schema.id_keyword) end if exclusive_ref @parsed['$ref'] = keywords.fetch('$ref').new(value.fetch('$ref'), self, '$ref') defs_keyword = meta_schema.defs_keyword if value.key?(defs_keyword) && keywords.key?(defs_keyword) @parsed[defs_keyword] = keywords.fetch(defs_keyword).new(value.fetch(defs_keyword), self, defs_keyword) end elsif value.is_a?(Hash) keyword_order = meta_schema.keyword_order last = keywords.size value.sort do |(keyword_a, _value_a), (keyword_b, _value_b)| keyword_order.fetch(keyword_a, last) <=> keyword_order.fetch(keyword_b, last) end.each do |keyword, value| @parsed[keyword] ||= keywords.fetch(keyword, UNKNOWN_KEYWORD_CLASS).new(value, self, keyword) end end @parsed end def root_keyword_location @root_keyword_location ||= Location.root end def property_default_resolver @property_default_resolver ||= if @configuration.property_default_resolver @configuration.property_default_resolver else insert_property_defaults == :symbol ? SYMBOL_PROPERTY_DEFAULT_RESOLVER : DEFAULT_PROPERTY_DEFAULT_RESOLVER end end def resolve_enumerators!(output) case output when Hash output.transform_values! { |value| resolve_enumerators!(value) } when Enumerator output.map { |value| resolve_enumerators!(value) } else output end end end end json_schemer-2.4.0/lib/json_schemer/format/0000755000004100000410000000000014751003725020745 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/format/duration.rb0000644000004100000410000000270014751003725023116 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Format module Duration # https://datatracker.ietf.org/doc/html/rfc3339#appendix-A DUR_SECOND = '\d+S' # dur-second = 1*DIGIT "S" DUR_MINUTE = "\\d+M(#{DUR_SECOND})?" # dur-minute = 1*DIGIT "M" [dur-second] DUR_HOUR = "\\d+H(#{DUR_MINUTE})?" # dur-hour = 1*DIGIT "H" [dur-minute] DUR_TIME = "T(#{DUR_HOUR}|#{DUR_MINUTE}|#{DUR_SECOND})" # dur-time = "T" (dur-hour / dur-minute / dur-second) DUR_DAY = '\d+D' # dur-day = 1*DIGIT "D" DUR_WEEK = '\d+W' # dur-week = 1*DIGIT "W" DUR_MONTH = "\\d+M(#{DUR_DAY})?" # dur-month = 1*DIGIT "M" [dur-day] DUR_YEAR = "\\d+Y(#{DUR_MONTH})?" # dur-year = 1*DIGIT "Y" [dur-month] DUR_DATE = "(#{DUR_DAY}|#{DUR_MONTH}|#{DUR_YEAR})(#{DUR_TIME})?" # dur-date = (dur-day / dur-month / dur-year) [dur-time] DURATION = "P(#{DUR_DATE}|#{DUR_TIME}|#{DUR_WEEK})" # duration = "P" (dur-date / dur-time / dur-week) DURATION_REGEX = /\A#{DURATION}\z/ def valid_duration?(data) DURATION_REGEX.match?(data) end end end end json_schemer-2.4.0/lib/json_schemer/format/uri_template.rb0000644000004100000410000000537014751003725023771 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Format module URITemplate # https://datatracker.ietf.org/doc/html/rfc6570 PCT_ENCODED = '%\h{2}' # pct-encoded = "%" HEXDIG HEXDIG EXPLODE = '\*' # explode = "*" MAX_LENGTH = '[1-9]\d{0,3}' # max-length = %x31-39 0*3DIGIT ; positive integer < 10000 PREFIX = ":#{MAX_LENGTH}" # prefix = ":" max-length MODIFIER_LEVEL4 = "#{PREFIX}|#{EXPLODE}" # modifier-level4 = prefix / explode VARCHAR = "(\\w|#{PCT_ENCODED})" # varchar = ALPHA / DIGIT / "_" / pct-encoded VARNAME = "#{VARCHAR}(\\.?#{VARCHAR})*" # varname = varchar *( ["."] varchar ) VARSPEC = "#{VARNAME}(#{MODIFIER_LEVEL4})?" # varspec = varname [ modifier-level4 ] VARIABLE_LIST = "#{VARSPEC}(,#{VARSPEC})*" # variable-list = varspec *( "," varspec ) OPERATOR = '[+#./;?&=,!@|]' # operator = op-level2 / op-level3 / op-reserve # op-level2 = "+" / "#" # op-level3 = "." / "/" / ";" / "?" / "&" # op-reserve = "=" / "," / "!" / "@" / "|" EXPRESSION = "{#{OPERATOR}?#{VARIABLE_LIST}}" # expression = "{" [ operator ] variable-list "}" LITERALS = "[^\\x00-\\x20\\x7F\"%'<>\\\\^`{|}]|#{PCT_ENCODED}" # literals = %x21 / %x23-24 / %x26 / %x28-3B / %x3D / %x3F-5B # / %x5D / %x5F / %x61-7A / %x7E / ucschar / iprivate # / pct-encoded # ; any Unicode character except: CTL, SP, # ; DQUOTE, "'", "%" (aside from pct-encoded), # ; "<", ">", "\", "^", "`", "{", "|", "}" URI_TEMPLATE = "(#{LITERALS}|#{EXPRESSION})*" # URI-Template = *( literals / expression ) URI_TEMPLATE_REGEX = /\A#{URI_TEMPLATE}\z/ def valid_uri_template?(data) URI_TEMPLATE_REGEX.match?(data) end end end end json_schemer-2.4.0/lib/json_schemer/format/email.rb0000644000004100000410000001132414751003725022362 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Format module Email # https://datatracker.ietf.org/doc/html/rfc6531#section-3.3 # I think this is the same as "UTF8-non-ascii"? (https://datatracker.ietf.org/doc/html/rfc6532#section-3.1) UTF8_NON_ASCII = '[^[:ascii:]]' # https://datatracker.ietf.org/doc/html/rfc5321#section-4.1.2 A_TEXT = "([\\w!#$%&'*+\\-/=?\\^`{|}~]|#{UTF8_NON_ASCII})" # atext = ALPHA / DIGIT / ; Printable US-ASCII # "!" / "#" / ; characters not including # "$" / "%" / ; specials. Used for atoms. # "&" / "'" / # "*" / "+" / # "-" / "/" / # "=" / "?" / # "^" / "_" / # "`" / "{" / # "|" / "}" / # "~" Q_TEXT_SMTP = "([\\x20-\\x21\\x23-\\x5B\\x5D-\\x7E]|#{UTF8_NON_ASCII})" # qtextSMTP = %d32-33 / %d35-91 / %d93-126 # ; i.e., within a quoted string, any # ; ASCII graphic or space is permitted # ; without blackslash-quoting except # ; double-quote and the backslash itself. QUOTED_PAIR_SMTP = '\x5C[\x20-\x7E]' # quoted-pairSMTP = %d92 %d32-126 # ; i.e., backslash followed by any ASCII # ; graphic (including itself) or SPace Q_CONTENT_SMTP = "#{Q_TEXT_SMTP}|#{QUOTED_PAIR_SMTP}" # QcontentSMTP = qtextSMTP / quoted-pairSMTP QUOTED_STRING = "\"(#{Q_CONTENT_SMTP})*\"" # Quoted-string = DQUOTE *QcontentSMTP DQUOTE ATOM = "#{A_TEXT}+" # Atom = 1*atext DOT_STRING = "#{ATOM}(\\.#{ATOM})*" # Dot-string = Atom *("." Atom) LOCAL_PART = "#{DOT_STRING}|#{QUOTED_STRING}" # Local-part = Dot-string / Quoted-string # ; MAY be case-sensitive # IPv4-address-literal = Snum 3("." Snum) # using `valid_id?` to check ip addresses because it's complicated. # IPv6-address-literal = "IPv6:" IPv6-addr ADDRESS_LITERAL = '\[(IPv6:(?[\h:]+)|(?[\d.]+))\]' # address-literal = "[" ( IPv4-address-literal / # IPv6-address-literal / # General-address-literal ) "]" # ; See Section 4.1.3 # using `valid_hostname?` to check domain because it's complicated MAILBOX = "(#{LOCAL_PART})@(#{ADDRESS_LITERAL}|(?.+))" # Mailbox = Local-part "@" ( Domain / address-literal ) EMAIL_REGEX = /\A#{MAILBOX}\z/ def valid_email?(data) return false unless match = EMAIL_REGEX.match(data) if ipv4 = match.named_captures.fetch('ipv4') valid_ip?(ipv4, Socket::AF_INET) elsif ipv6 = match.named_captures.fetch('ipv6') valid_ip?(ipv6, Socket::AF_INET6) else valid_hostname?(match.named_captures.fetch('domain')) end end end end end json_schemer-2.4.0/lib/json_schemer/format/hostname.rb0000644000004100000410000002603314751003725023114 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Format module Hostname # https://datatracker.ietf.org/doc/html/rfc5892#section-2.1 MARKS = '\p{Mn}\p{Mc}' LETTER_DIGITS = "\\p{Ll}\\p{Lu}\\p{Lo}\\p{Nd}\\p{Lm}#{MARKS}" # https://datatracker.ietf.org/doc/html/rfc5892#section-2.6 EXCEPTIONS_PVALID = '\u{06FD}\u{06FE}\u{0F0B}\u{3007}' # \u{00DF}\u{03C2} covered by \p{Ll} EXCEPTIONS_DISALLOWED = '\u{0640}\u{07FA}\u{302E}\u{302F}\u{3031}\u{3032}\u{3033}\u{3034}\u{3035}\u{303B}' LABEL_CHARACTER_CLASS = "[#{LETTER_DIGITS}#{EXCEPTIONS_PVALID}&&[^#{EXCEPTIONS_DISALLOWED}]]" # https://datatracker.ietf.org/doc/html/rfc5891#section-4.2.3.2 LEADING_CHARACTER_CLASS = "[#{LABEL_CHARACTER_CLASS}&&[^#{MARKS}]]" LABEL_REGEX_STRING = "#{LEADING_CHARACTER_CLASS}([#{LABEL_CHARACTER_CLASS}\-]*#{LABEL_CHARACTER_CLASS})?" HOSTNAME_REGEX = /\A(#{LABEL_REGEX_STRING}\.)*#{LABEL_REGEX_STRING}\z/i.freeze # bin/hostname_character_classes VIRAMA_CHARACTER_CLASS = '[\u{094D}\u{09CD}\u{0A4D}\u{0ACD}\u{0B4D}\u{0BCD}\u{0C4D}\u{0CCD}\u{0D3B}\u{0D3C}\u{0D4D}\u{0DCA}\u{0E3A}\u{0EBA}\u{0F84}\u{1039}\u{103A}\u{1714}\u{1715}\u{1734}\u{17D2}\u{1A60}\u{1B44}\u{1BAA}\u{1BAB}\u{1BF2}\u{1BF3}\u{2D7F}\u{A806}\u{A82C}\u{A8C4}\u{A953}\u{A9C0}\u{AAF6}\u{ABED}\u{10A3F}\u{11046}\u{11070}\u{1107F}\u{110B9}\u{11133}\u{11134}\u{111C0}\u{11235}\u{112EA}\u{1134D}\u{11442}\u{114C2}\u{115BF}\u{1163F}\u{116B6}\u{1172B}\u{11839}\u{1193D}\u{1193E}\u{119E0}\u{11A34}\u{11A47}\u{11A99}\u{11C3F}\u{11D44}\u{11D45}\u{11D97}\u{11F41}\u{11F42}]' JOINING_TYPE_L_CHARACTER_CLASS = '[\u{A872}\u{10ACD}\u{10AD7}\u{10D00}\u{10FCB}]' JOINING_TYPE_D_CHARACTER_CLASS = '[\u{0620}\u{0626}\u{0628}\u{062A}-\u{062E}\u{0633}-\u{063F}\u{0641}-\u{0647}\u{0649}-\u{064A}\u{066E}-\u{066F}\u{0678}-\u{0687}\u{069A}-\u{06BF}\u{06C1}-\u{06C2}\u{06CC}\u{06CE}\u{06D0}-\u{06D1}\u{06FA}-\u{06FC}\u{06FF}\u{0712}-\u{0714}\u{071A}-\u{071D}\u{071F}-\u{0727}\u{0729}\u{072B}\u{072D}-\u{072E}\u{074E}-\u{0758}\u{075C}-\u{076A}\u{076D}-\u{0770}\u{0772}\u{0775}-\u{0777}\u{077A}-\u{077F}\u{07CA}-\u{07EA}\u{0841}-\u{0845}\u{0848}\u{084A}-\u{0853}\u{0855}\u{0860}\u{0862}-\u{0865}\u{0868}\u{0886}\u{0889}-\u{088D}\u{08A0}-\u{08A9}\u{08AF}-\u{08B0}\u{08B3}-\u{08B8}\u{08BA}-\u{08C8}\u{1807}\u{1820}-\u{1842}\u{1843}\u{1844}-\u{1878}\u{1887}-\u{18A8}\u{18AA}\u{A840}-\u{A871}\u{10AC0}-\u{10AC4}\u{10AD3}-\u{10AD6}\u{10AD8}-\u{10ADC}\u{10ADE}-\u{10AE0}\u{10AEB}-\u{10AEE}\u{10B80}\u{10B82}\u{10B86}-\u{10B88}\u{10B8A}-\u{10B8B}\u{10B8D}\u{10B90}\u{10BAD}-\u{10BAE}\u{10D01}-\u{10D21}\u{10D23}\u{10F30}-\u{10F32}\u{10F34}-\u{10F44}\u{10F51}-\u{10F53}\u{10F70}-\u{10F73}\u{10F76}-\u{10F81}\u{10FB0}\u{10FB2}-\u{10FB3}\u{10FB8}\u{10FBB}-\u{10FBC}\u{10FBE}-\u{10FBF}\u{10FC1}\u{10FC4}\u{10FCA}\u{1E900}-\u{1E943}]' JOINING_TYPE_T_CHARACTER_CLASS = '[\u{00AD}\u{0300}-\u{036F}\u{0483}-\u{0487}\u{0488}-\u{0489}\u{0591}-\u{05BD}\u{05BF}\u{05C1}-\u{05C2}\u{05C4}-\u{05C5}\u{05C7}\u{0610}-\u{061A}\u{061C}\u{064B}-\u{065F}\u{0670}\u{06D6}-\u{06DC}\u{06DF}-\u{06E4}\u{06E7}-\u{06E8}\u{06EA}-\u{06ED}\u{070F}\u{0711}\u{0730}-\u{074A}\u{07A6}-\u{07B0}\u{07EB}-\u{07F3}\u{07FD}\u{0816}-\u{0819}\u{081B}-\u{0823}\u{0825}-\u{0827}\u{0829}-\u{082D}\u{0859}-\u{085B}\u{0898}-\u{089F}\u{08CA}-\u{08E1}\u{08E3}-\u{0902}\u{093A}\u{093C}\u{0941}-\u{0948}\u{094D}\u{0951}-\u{0957}\u{0962}-\u{0963}\u{0981}\u{09BC}\u{09C1}-\u{09C4}\u{09CD}\u{09E2}-\u{09E3}\u{09FE}\u{0A01}-\u{0A02}\u{0A3C}\u{0A41}-\u{0A42}\u{0A47}-\u{0A48}\u{0A4B}-\u{0A4D}\u{0A51}\u{0A70}-\u{0A71}\u{0A75}\u{0A81}-\u{0A82}\u{0ABC}\u{0AC1}-\u{0AC5}\u{0AC7}-\u{0AC8}\u{0ACD}\u{0AE2}-\u{0AE3}\u{0AFA}-\u{0AFF}\u{0B01}\u{0B3C}\u{0B3F}\u{0B41}-\u{0B44}\u{0B4D}\u{0B55}-\u{0B56}\u{0B62}-\u{0B63}\u{0B82}\u{0BC0}\u{0BCD}\u{0C00}\u{0C04}\u{0C3C}\u{0C3E}-\u{0C40}\u{0C46}-\u{0C48}\u{0C4A}-\u{0C4D}\u{0C55}-\u{0C56}\u{0C62}-\u{0C63}\u{0C81}\u{0CBC}\u{0CBF}\u{0CC6}\u{0CCC}-\u{0CCD}\u{0CE2}-\u{0CE3}\u{0D00}-\u{0D01}\u{0D3B}-\u{0D3C}\u{0D41}-\u{0D44}\u{0D4D}\u{0D62}-\u{0D63}\u{0D81}\u{0DCA}\u{0DD2}-\u{0DD4}\u{0DD6}\u{0E31}\u{0E34}-\u{0E3A}\u{0E47}-\u{0E4E}\u{0EB1}\u{0EB4}-\u{0EBC}\u{0EC8}-\u{0ECE}\u{0F18}-\u{0F19}\u{0F35}\u{0F37}\u{0F39}\u{0F71}-\u{0F7E}\u{0F80}-\u{0F84}\u{0F86}-\u{0F87}\u{0F8D}-\u{0F97}\u{0F99}-\u{0FBC}\u{0FC6}\u{102D}-\u{1030}\u{1032}-\u{1037}\u{1039}-\u{103A}\u{103D}-\u{103E}\u{1058}-\u{1059}\u{105E}-\u{1060}\u{1071}-\u{1074}\u{1082}\u{1085}-\u{1086}\u{108D}\u{109D}\u{135D}-\u{135F}\u{1712}-\u{1714}\u{1732}-\u{1733}\u{1752}-\u{1753}\u{1772}-\u{1773}\u{17B4}-\u{17B5}\u{17B7}-\u{17BD}\u{17C6}\u{17C9}-\u{17D3}\u{17DD}\u{180B}-\u{180D}\u{180F}\u{1885}-\u{1886}\u{18A9}\u{1920}-\u{1922}\u{1927}-\u{1928}\u{1932}\u{1939}-\u{193B}\u{1A17}-\u{1A18}\u{1A1B}\u{1A56}\u{1A58}-\u{1A5E}\u{1A60}\u{1A62}\u{1A65}-\u{1A6C}\u{1A73}-\u{1A7C}\u{1A7F}\u{1AB0}-\u{1ABD}\u{1ABE}\u{1ABF}-\u{1ACE}\u{1B00}-\u{1B03}\u{1B34}\u{1B36}-\u{1B3A}\u{1B3C}\u{1B42}\u{1B6B}-\u{1B73}\u{1B80}-\u{1B81}\u{1BA2}-\u{1BA5}\u{1BA8}-\u{1BA9}\u{1BAB}-\u{1BAD}\u{1BE6}\u{1BE8}-\u{1BE9}\u{1BED}\u{1BEF}-\u{1BF1}\u{1C2C}-\u{1C33}\u{1C36}-\u{1C37}\u{1CD0}-\u{1CD2}\u{1CD4}-\u{1CE0}\u{1CE2}-\u{1CE8}\u{1CED}\u{1CF4}\u{1CF8}-\u{1CF9}\u{1DC0}-\u{1DFF}\u{200B}\u{200E}-\u{200F}\u{202A}-\u{202E}\u{2060}-\u{2064}\u{206A}-\u{206F}\u{20D0}-\u{20DC}\u{20DD}-\u{20E0}\u{20E1}\u{20E2}-\u{20E4}\u{20E5}-\u{20F0}\u{2CEF}-\u{2CF1}\u{2D7F}\u{2DE0}-\u{2DFF}\u{302A}-\u{302D}\u{3099}-\u{309A}\u{A66F}\u{A670}-\u{A672}\u{A674}-\u{A67D}\u{A69E}-\u{A69F}\u{A6F0}-\u{A6F1}\u{A802}\u{A806}\u{A80B}\u{A825}-\u{A826}\u{A82C}\u{A8C4}-\u{A8C5}\u{A8E0}-\u{A8F1}\u{A8FF}\u{A926}-\u{A92D}\u{A947}-\u{A951}\u{A980}-\u{A982}\u{A9B3}\u{A9B6}-\u{A9B9}\u{A9BC}-\u{A9BD}\u{A9E5}\u{AA29}-\u{AA2E}\u{AA31}-\u{AA32}\u{AA35}-\u{AA36}\u{AA43}\u{AA4C}\u{AA7C}\u{AAB0}\u{AAB2}-\u{AAB4}\u{AAB7}-\u{AAB8}\u{AABE}-\u{AABF}\u{AAC1}\u{AAEC}-\u{AAED}\u{AAF6}\u{ABE5}\u{ABE8}\u{ABED}\u{FB1E}\u{FE00}-\u{FE0F}\u{FE20}-\u{FE2F}\u{FEFF}\u{FFF9}-\u{FFFB}\u{101FD}\u{102E0}\u{10376}-\u{1037A}\u{10A01}-\u{10A03}\u{10A05}-\u{10A06}\u{10A0C}-\u{10A0F}\u{10A38}-\u{10A3A}\u{10A3F}\u{10AE5}-\u{10AE6}\u{10D24}-\u{10D27}\u{10EAB}-\u{10EAC}\u{10EFD}-\u{10EFF}\u{10F46}-\u{10F50}\u{10F82}-\u{10F85}\u{11001}\u{11038}-\u{11046}\u{11070}\u{11073}-\u{11074}\u{1107F}-\u{11081}\u{110B3}-\u{110B6}\u{110B9}-\u{110BA}\u{110C2}\u{11100}-\u{11102}\u{11127}-\u{1112B}\u{1112D}-\u{11134}\u{11173}\u{11180}-\u{11181}\u{111B6}-\u{111BE}\u{111C9}-\u{111CC}\u{111CF}\u{1122F}-\u{11231}\u{11234}\u{11236}-\u{11237}\u{1123E}\u{11241}\u{112DF}\u{112E3}-\u{112EA}\u{11300}-\u{11301}\u{1133B}-\u{1133C}\u{11340}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11438}-\u{1143F}\u{11442}-\u{11444}\u{11446}\u{1145E}\u{114B3}-\u{114B8}\u{114BA}\u{114BF}-\u{114C0}\u{114C2}-\u{114C3}\u{115B2}-\u{115B5}\u{115BC}-\u{115BD}\u{115BF}-\u{115C0}\u{115DC}-\u{115DD}\u{11633}-\u{1163A}\u{1163D}\u{1163F}-\u{11640}\u{116AB}\u{116AD}\u{116B0}-\u{116B5}\u{116B7}\u{1171D}-\u{1171F}\u{11722}-\u{11725}\u{11727}-\u{1172B}\u{1182F}-\u{11837}\u{11839}-\u{1183A}\u{1193B}-\u{1193C}\u{1193E}\u{11943}\u{119D4}-\u{119D7}\u{119DA}-\u{119DB}\u{119E0}\u{11A01}-\u{11A0A}\u{11A33}-\u{11A38}\u{11A3B}-\u{11A3E}\u{11A47}\u{11A51}-\u{11A56}\u{11A59}-\u{11A5B}\u{11A8A}-\u{11A96}\u{11A98}-\u{11A99}\u{11C30}-\u{11C36}\u{11C38}-\u{11C3D}\u{11C3F}\u{11C92}-\u{11CA7}\u{11CAA}-\u{11CB0}\u{11CB2}-\u{11CB3}\u{11CB5}-\u{11CB6}\u{11D31}-\u{11D36}\u{11D3A}\u{11D3C}-\u{11D3D}\u{11D3F}-\u{11D45}\u{11D47}\u{11D90}-\u{11D91}\u{11D95}\u{11D97}\u{11EF3}-\u{11EF4}\u{11F00}-\u{11F01}\u{11F36}-\u{11F3A}\u{11F40}\u{11F42}\u{13430}-\u{1343F}\u{13440}\u{13447}-\u{13455}\u{16AF0}-\u{16AF4}\u{16B30}-\u{16B36}\u{16F4F}\u{16F8F}-\u{16F92}\u{16FE4}\u{1BC9D}-\u{1BC9E}\u{1BCA0}-\u{1BCA3}\u{1CF00}-\u{1CF2D}\u{1CF30}-\u{1CF46}\u{1D167}-\u{1D169}\u{1D173}-\u{1D17A}\u{1D17B}-\u{1D182}\u{1D185}-\u{1D18B}\u{1D1AA}-\u{1D1AD}\u{1D242}-\u{1D244}\u{1DA00}-\u{1DA36}\u{1DA3B}-\u{1DA6C}\u{1DA75}\u{1DA84}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}-\u{1E024}\u{1E026}-\u{1E02A}\u{1E08F}\u{1E130}-\u{1E136}\u{1E2AE}\u{1E2EC}-\u{1E2EF}\u{1E4EC}-\u{1E4EF}\u{1E8D0}-\u{1E8D6}\u{1E944}-\u{1E94A}\u{1E94B}\u{E0001}\u{E0020}-\u{E007F}\u{E0100}-\u{E01EF}]' JOINING_TYPE_R_CHARACTER_CLASS = '[\u{0622}-\u{0625}\u{0627}\u{0629}\u{062F}-\u{0632}\u{0648}\u{0671}-\u{0673}\u{0675}-\u{0677}\u{0688}-\u{0699}\u{06C0}\u{06C3}-\u{06CB}\u{06CD}\u{06CF}\u{06D2}-\u{06D3}\u{06D5}\u{06EE}-\u{06EF}\u{0710}\u{0715}-\u{0719}\u{071E}\u{0728}\u{072A}\u{072C}\u{072F}\u{074D}\u{0759}-\u{075B}\u{076B}-\u{076C}\u{0771}\u{0773}-\u{0774}\u{0778}-\u{0779}\u{0840}\u{0846}-\u{0847}\u{0849}\u{0854}\u{0856}-\u{0858}\u{0867}\u{0869}-\u{086A}\u{0870}-\u{0882}\u{088E}\u{08AA}-\u{08AC}\u{08AE}\u{08B1}-\u{08B2}\u{08B9}\u{10AC5}\u{10AC7}\u{10AC9}-\u{10ACA}\u{10ACE}-\u{10AD2}\u{10ADD}\u{10AE1}\u{10AE4}\u{10AEF}\u{10B81}\u{10B83}-\u{10B85}\u{10B89}\u{10B8C}\u{10B8E}-\u{10B8F}\u{10B91}\u{10BA9}-\u{10BAC}\u{10D22}\u{10F33}\u{10F54}\u{10F74}-\u{10F75}\u{10FB4}-\u{10FB6}\u{10FB9}-\u{10FBA}\u{10FBD}\u{10FC2}-\u{10FC3}\u{10FC9}]' # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.1 # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.2 ZERO_WIDTH_VIRAMA = "#{VIRAMA_CHARACTER_CLASS}[\\u{200C}\\u{200D}]" ZERO_WIDTH_NON_JOINER_JOINING_TYPE = "[#{JOINING_TYPE_L_CHARACTER_CLASS}#{JOINING_TYPE_D_CHARACTER_CLASS}]#{JOINING_TYPE_T_CHARACTER_CLASS}*\\u{200C}#{JOINING_TYPE_T_CHARACTER_CLASS}*[#{JOINING_TYPE_R_CHARACTER_CLASS}#{JOINING_TYPE_D_CHARACTER_CLASS}]" # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.3 MIDDLE_DOT = '\u{006C}\u{00B7}\u{006C}' # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.4 GREEK_LOWER_NUMERAL_SIGN = '\u{0375}\p{Greek}' # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.5 # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.6 HEBREW_PUNCTUATION = '\p{Hebrew}[\u{05F3}\u{05F4}]' CONTEXT_REGEX = /(#{ZERO_WIDTH_VIRAMA}|#{ZERO_WIDTH_NON_JOINER_JOINING_TYPE}|#{MIDDLE_DOT}|#{GREEK_LOWER_NUMERAL_SIGN}|#{HEBREW_PUNCTUATION})/.freeze # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.7 KATAKANA_MIDDLE_DOT_REGEX = /\u{30FB}/.freeze KATAKANA_MIDDLE_DOT_CONTEXT_REGEX = /[\p{Hiragana}\p{Katakana}\p{Han}]/.freeze # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.8 # https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.9 ARABIC_INDIC_DIGITS_REGEX = /[\u{0660}-\u{0669}]/.freeze ARABIC_EXTENDED_DIGITS_REGEX = /[\u{06F0}-\u{06F9}]/.freeze def valid_hostname?(data) data.split('.').map do |a_label| return false if a_label.size > 63 u_label = SimpleIDN.to_unicode(a_label) # https://datatracker.ietf.org/doc/html/rfc5891#section-4.2.3.1 return false if u_label.slice(2, 2) == '--' return false if ARABIC_INDIC_DIGITS_REGEX.match?(u_label) && ARABIC_EXTENDED_DIGITS_REGEX.match?(u_label) u_label.gsub!(CONTEXT_REGEX, 'ok') u_label.gsub!(KATAKANA_MIDDLE_DOT_REGEX, 'ok') if KATAKANA_MIDDLE_DOT_CONTEXT_REGEX.match?(u_label) u_label end.join('.').match?(HOSTNAME_REGEX) rescue SimpleIDN::ConversionError false end end end end json_schemer-2.4.0/lib/json_schemer/format/json_pointer.rb0000644000004100000410000000100714751003725024001 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Format module JSONPointer JSON_POINTER_REGEX_STRING = '(\/([^~\/]|~[01])*)*' JSON_POINTER_REGEX = /\A#{JSON_POINTER_REGEX_STRING}\z/.freeze RELATIVE_JSON_POINTER_REGEX = /\A(0|[1-9]\d*)(#|#{JSON_POINTER_REGEX_STRING})?\z/.freeze def valid_json_pointer?(data) JSON_POINTER_REGEX.match?(data) end def valid_relative_json_pointer?(data) RELATIVE_JSON_POINTER_REGEX.match?(data) end end end end json_schemer-2.4.0/lib/json_schemer/draft202012/0000755000004100000410000000000014751003725021224 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/draft202012/meta.rb0000644000004100000410000003215714751003725022507 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft202012 BASE_URI = URI('https://json-schema.org/draft/2020-12/schema') FORMATS = { 'date-time' => Format::DATE_TIME, 'date' => Format::DATE, 'time' => Format::TIME, 'duration' => Format::DURATION, 'email' => Format::EMAIL, 'idn-email' => Format::IDN_EMAIL, 'hostname' => Format::HOSTNAME, 'idn-hostname' => Format::IDN_HOSTNAME, 'ipv4' => Format::IPV4, 'ipv6' => Format::IPV6, 'uri' => Format::URI, 'uri-reference' => Format::URI_REFERENCE, 'iri' => Format::IRI, 'iri-reference' => Format::IRI_REFERENCE, 'uuid' => Format::UUID, 'uri-template' => Format::URI_TEMPLATE, 'json-pointer' => Format::JSON_POINTER, 'relative-json-pointer' => Format::RELATIVE_JSON_POINTER, 'regex' => Format::REGEX } CONTENT_ENCODINGS = { 'base64' => ContentEncoding::BASE64 } CONTENT_MEDIA_TYPES = { 'application/json' => ContentMediaType::JSON } SCHEMA = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', '$id' => 'https://json-schema.org/draft/2020-12/schema', '$vocabulary' => { 'https://json-schema.org/draft/2020-12/vocab/core' => true, 'https://json-schema.org/draft/2020-12/vocab/applicator' => true, 'https://json-schema.org/draft/2020-12/vocab/unevaluated' => true, 'https://json-schema.org/draft/2020-12/vocab/validation' => true, 'https://json-schema.org/draft/2020-12/vocab/meta-data' => true, 'https://json-schema.org/draft/2020-12/vocab/format-annotation' => true, 'https://json-schema.org/draft/2020-12/vocab/content' => true }, '$dynamicAnchor' => 'meta', 'title' => 'Core and Validation specifications meta-schema', 'allOf' => [ {'$ref' => 'meta/core'}, {'$ref' => 'meta/applicator'}, {'$ref' => 'meta/unevaluated'}, {'$ref' => 'meta/validation'}, {'$ref' => 'meta/meta-data'}, {'$ref' => 'meta/format-annotation'}, {'$ref' => 'meta/content'} ], 'type' => ['object', 'boolean'], '$comment' => 'This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.', 'properties' => { 'definitions' => { '$comment' => '"definitions" has been replaced by "$defs".', 'type' => 'object', 'additionalProperties' => { '$dynamicRef' => '#meta' }, 'deprecated' => true, 'default' => {} }, 'dependencies' => { '$comment' => '"dependencies" has been split and replaced by "dependentSchemas" and "dependentRequired" in order to serve their differing semantics.', 'type' => 'object', 'additionalProperties' => { 'anyOf' => [ { '$dynamicRef' => '#meta' }, { '$ref' => 'meta/validation#/$defs/stringArray' } ] }, 'deprecated' => true, 'default' => {} }, '$recursiveAnchor' => { '$comment' => '"$recursiveAnchor" has been replaced by "$dynamicAnchor".', '$ref' => 'meta/core#/$defs/anchorString', 'deprecated' => true }, '$recursiveRef' => { '$comment' => '"$recursiveRef" has been replaced by "$dynamicRef".', '$ref' => 'meta/core#/$defs/uriReferenceString', 'deprecated' => true } } } module Meta CORE = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', '$id' => 'https://json-schema.org/draft/2020-12/meta/core', '$dynamicAnchor' => 'meta', 'title' => 'Core vocabulary meta-schema', 'type' => ['object', 'boolean'], 'properties' => { '$id' => { '$ref' => '#/$defs/uriReferenceString', '$comment' => 'Non-empty fragments not allowed.', 'pattern' => '^[^#]*#?$' }, '$schema' => { '$ref' => '#/$defs/uriString' }, '$ref' => { '$ref' => '#/$defs/uriReferenceString' }, '$anchor' => { '$ref' => '#/$defs/anchorString' }, '$dynamicRef' => { '$ref' => '#/$defs/uriReferenceString' }, '$dynamicAnchor' => { '$ref' => '#/$defs/anchorString' }, '$vocabulary' => { 'type' => 'object', 'propertyNames' => { '$ref' => '#/$defs/uriString' }, 'additionalProperties' => { 'type' => 'boolean' } }, '$comment' => { 'type' => 'string' }, '$defs' => { 'type' => 'object', 'additionalProperties' => { '$dynamicRef' => '#meta' } } }, '$defs' => { 'anchorString' => { 'type' => 'string', 'pattern' => '^[A-Za-z_][-A-Za-z0-9._]*$' }, 'uriString' => { 'type' => 'string', 'format' => 'uri' }, 'uriReferenceString' => { 'type' => 'string', 'format' => 'uri-reference' } } } APPLICATOR = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', '$id' => 'https://json-schema.org/draft/2020-12/meta/applicator', '$dynamicAnchor' => 'meta', 'title' => 'Applicator vocabulary meta-schema', 'type' => ['object', 'boolean'], 'properties' => { 'prefixItems' => { '$ref' => '#/$defs/schemaArray' }, 'items' => { '$dynamicRef' => '#meta' }, 'contains' => { '$dynamicRef' => '#meta' }, 'additionalProperties' => { '$dynamicRef' => '#meta' }, 'properties' => { 'type' => 'object', 'additionalProperties' => { '$dynamicRef' => '#meta' }, 'default' => {} }, 'patternProperties' => { 'type' => 'object', 'additionalProperties' => { '$dynamicRef' => '#meta' }, 'propertyNames' => { 'format' => 'regex' }, 'default' => {} }, 'dependentSchemas' => { 'type' => 'object', 'additionalProperties' => { '$dynamicRef' => '#meta' }, 'default' => {} }, 'propertyNames' => { '$dynamicRef' => '#meta' }, 'if' => { '$dynamicRef' => '#meta' }, 'then' => { '$dynamicRef' => '#meta' }, 'else' => { '$dynamicRef' => '#meta' }, 'allOf' => { '$ref' => '#/$defs/schemaArray' }, 'anyOf' => { '$ref' => '#/$defs/schemaArray' }, 'oneOf' => { '$ref' => '#/$defs/schemaArray' }, 'not' => { '$dynamicRef' => '#meta' } }, '$defs' => { 'schemaArray' => { 'type' => 'array', 'minItems' => 1, 'items' => { '$dynamicRef' => '#meta' } } } } UNEVALUATED = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', '$id' => 'https://json-schema.org/draft/2020-12/meta/unevaluated', '$dynamicAnchor' => 'meta', 'title' => 'Unevaluated applicator vocabulary meta-schema', 'type' => ['object', 'boolean'], 'properties' => { 'unevaluatedItems' => { '$dynamicRef' => '#meta' }, 'unevaluatedProperties' => { '$dynamicRef' => '#meta' } } } VALIDATION = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', '$id' => 'https://json-schema.org/draft/2020-12/meta/validation', '$dynamicAnchor' => 'meta', 'title' => 'Validation vocabulary meta-schema', 'type' => ['object', 'boolean'], 'properties' => { 'type' => { 'anyOf' => [ { '$ref' => '#/$defs/simpleTypes' }, { 'type' => 'array', 'items' => { '$ref' => '#/$defs/simpleTypes' }, 'minItems' => 1, 'uniqueItems' => true } ] }, 'const' => true, 'enum' => { 'type' => 'array', 'items' => true }, 'multipleOf' => { 'type' => 'number', 'exclusiveMinimum' => 0 }, 'maximum' => { 'type' => 'number' }, 'exclusiveMaximum' => { 'type' => 'number' }, 'minimum' => { 'type' => 'number' }, 'exclusiveMinimum' => { 'type' => 'number' }, 'maxLength' => { '$ref' => '#/$defs/nonNegativeInteger' }, 'minLength' => { '$ref' => '#/$defs/nonNegativeIntegerDefault0' }, 'pattern' => { 'type' => 'string', 'format' => 'regex' }, 'maxItems' => { '$ref' => '#/$defs/nonNegativeInteger' }, 'minItems' => { '$ref' => '#/$defs/nonNegativeIntegerDefault0' }, 'uniqueItems' => { 'type' => 'boolean', 'default' => false }, 'maxContains' => { '$ref' => '#/$defs/nonNegativeInteger' }, 'minContains' => { '$ref' => '#/$defs/nonNegativeInteger', 'default' => 1 }, 'maxProperties' => { '$ref' => '#/$defs/nonNegativeInteger' }, 'minProperties' => { '$ref' => '#/$defs/nonNegativeIntegerDefault0' }, 'required' => { '$ref' => '#/$defs/stringArray' }, 'dependentRequired' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/stringArray' } } }, '$defs' => { 'nonNegativeInteger' => { 'type' => 'integer', 'minimum' => 0 }, 'nonNegativeIntegerDefault0' => { '$ref' => '#/$defs/nonNegativeInteger', 'default' => 0 }, 'simpleTypes' => { 'enum' => [ 'array', 'boolean', 'integer', 'null', 'number', 'object', 'string' ] }, 'stringArray' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'uniqueItems' => true, 'default' => [] } } } META_DATA = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', '$id' => 'https://json-schema.org/draft/2020-12/meta/meta-data', '$dynamicAnchor' => 'meta', 'title' => 'Meta-data vocabulary meta-schema', 'type' => ['object', 'boolean'], 'properties' => { 'title' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'default' => true, 'deprecated' => { 'type' => 'boolean', 'default' => false }, 'readOnly' => { 'type' => 'boolean', 'default' => false }, 'writeOnly' => { 'type' => 'boolean', 'default' => false }, 'examples' => { 'type' => 'array', 'items' => true } } } FORMAT_ANNOTATION = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', '$id' => 'https://json-schema.org/draft/2020-12/meta/format-annotation', '$dynamicAnchor' => 'meta', 'title' => 'Format vocabulary meta-schema for annotation results', 'type' => ['object', 'boolean'], 'properties' => { 'format' => { 'type' => 'string' } } } FORMAT_ASSERTION = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', '$id' => 'https://json-schema.org/draft/2020-12/meta/format-assertion', '$dynamicAnchor' => 'meta', 'title' => 'Format vocabulary meta-schema for assertion results', 'type' => ['object', 'boolean'], 'properties' => { 'format' => { 'type' => 'string' } } } CONTENT = { '$schema' => 'https://json-schema.org/draft/2020-12/schema', '$id' => 'https://json-schema.org/draft/2020-12/meta/content', '$dynamicAnchor' => 'meta', 'title' => 'Content vocabulary meta-schema', 'type' => ['object', 'boolean'], 'properties' => { 'contentEncoding' => { 'type' => 'string' }, 'contentMediaType' => { 'type' => 'string' }, 'contentSchema' => { '$dynamicRef' => '#meta' } } } SCHEMAS = { URI('https://json-schema.org/draft/2020-12/meta/core') => CORE, URI('https://json-schema.org/draft/2020-12/meta/applicator') => APPLICATOR, URI('https://json-schema.org/draft/2020-12/meta/unevaluated') => UNEVALUATED, URI('https://json-schema.org/draft/2020-12/meta/validation') => VALIDATION, URI('https://json-schema.org/draft/2020-12/meta/meta-data') => META_DATA, URI('https://json-schema.org/draft/2020-12/meta/format-annotation') => FORMAT_ANNOTATION, URI('https://json-schema.org/draft/2020-12/meta/format-assertion') => FORMAT_ASSERTION, URI('https://json-schema.org/draft/2020-12/meta/content') => CONTENT } end end end json_schemer-2.4.0/lib/json_schemer/draft202012/vocab.rb0000644000004100000410000001166614751003725022655 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft202012 module Vocab # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-8 CORE = { # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-8.1 '$schema' => Core::Schema, '$vocabulary' => Core::Vocabulary, # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-8.2 '$id' => Core::Id, '$anchor' => Core::Anchor, '$ref' => Core::Ref, '$dynamicAnchor' => Core::DynamicAnchor, '$dynamicRef' => Core::DynamicRef, '$defs' => Core::Defs, 'definitions' => Core::Defs, # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-8.3 '$comment' => Core::Comment, # https://github.com/orgs/json-schema-org/discussions/329 'x-error' => Core::XError } # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10 APPLICATOR = { # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10.2 'allOf' => Applicator::AllOf, 'anyOf' => Applicator::AnyOf, 'oneOf' => Applicator::OneOf, 'not' => Applicator::Not, # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10.2.2 'if' => Applicator::If, 'then' => Applicator::Then, 'else' => Applicator::Else, 'dependentSchemas' => Applicator::DependentSchemas, # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10.3 'prefixItems' => Applicator::PrefixItems, 'items' => Applicator::Items, 'contains' => Applicator::Contains, # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10.3.2 'properties' => Applicator::Properties, 'patternProperties' => Applicator::PatternProperties, 'additionalProperties' => Applicator::AdditionalProperties, 'propertyNames' => Applicator::PropertyNames, 'dependencies' => Applicator::Dependencies } # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-11 UNEVALUATED = { 'unevaluatedItems' => Unevaluated::UnevaluatedItems, 'unevaluatedProperties' => Unevaluated::UnevaluatedProperties } # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6 VALIDATION = { # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.1 'type' => Validation::Type, 'enum' => Validation::Enum, 'const' => Validation::Const, # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.2 'multipleOf' => Validation::MultipleOf, 'maximum' => Validation::Maximum, 'exclusiveMaximum' => Validation::ExclusiveMaximum, 'minimum' => Validation::Minimum, 'exclusiveMinimum' => Validation::ExclusiveMinimum, # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.3 'maxLength' => Validation::MaxLength, 'minLength' => Validation::MinLength, 'pattern' => Validation::Pattern, # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.4 'maxItems' => Validation::MaxItems, 'minItems' => Validation::MinItems, 'uniqueItems' => Validation::UniqueItems, 'maxContains' => Validation::MaxContains, 'minContains' => Validation::MinContains, # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-6.5 'maxProperties' => Validation::MaxProperties, 'minProperties' => Validation::MinProperties, 'required' => Validation::Required, 'dependentRequired' => Validation::DependentRequired } # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.2.1 FORMAT_ANNOTATION = { 'format' => FormatAnnotation::Format } # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.2.2 FORMAT_ASSERTION = { 'format' => FormatAssertion::Format } # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-8 CONTENT = { 'contentEncoding' => Content::ContentEncoding, 'contentMediaType' => Content::ContentMediaType, 'contentSchema' => Content::ContentSchema } # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-9 META_DATA = { # 'title' => MetaData::Title, # 'description' => MetaData::Description, # 'default' => MetaData::Default, # 'deprecated' => MetaData::Deprecated, 'readOnly' => MetaData::ReadOnly, 'writeOnly' => MetaData::WriteOnly, # 'examples' => MetaData::Examples } end end end json_schemer-2.4.0/lib/json_schemer/draft202012/vocab/0000755000004100000410000000000014751003725022316 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/draft202012/vocab/unevaluated.rb0000644000004100000410000000752114751003725025165 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft202012 module Vocab module Unevaluated class UnevaluatedItems < Keyword def error(formatted_instance_location:, **) "array items at #{formatted_instance_location} do not match `unevaluatedItems` schema" end def false_schema_error(formatted_instance_location:, **) "array item at #{formatted_instance_location} is a disallowed unevaluated item" end def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array) unevaluated_items = instance.size.times.to_set context.adjacent_results.each_value do |adjacent_result| collect_unevaluated_items(adjacent_result, unevaluated_items) end nested = unevaluated_items.map do |index| parsed.validate_instance(instance.fetch(index), join_location(instance_location, index.to_s), keyword_location, context) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => nested.any?) end private def collect_unevaluated_items(result, unevaluated_items) case result.source when Applicator::PrefixItems unevaluated_items.subtract(0..result.annotation) when Applicator::Items, UnevaluatedItems unevaluated_items.clear if result.annotation when Applicator::Contains unevaluated_items.subtract(result.annotation) end result.nested&.each do |subresult| if subresult.valid && subresult.instance_location == result.instance_location collect_unevaluated_items(subresult, unevaluated_items) end end end end class UnevaluatedProperties < Keyword def error(formatted_instance_location:, **) "object properties at #{formatted_instance_location} do not match `unevaluatedProperties` schema" end def false_schema_error(formatted_instance_location:, **) "object property at #{formatted_instance_location} is a disallowed unevaluated property" end def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash) evaluated_keys = Set[] context.adjacent_results.each_value do |adjacent_result| collect_evaluated_keys(adjacent_result, evaluated_keys) end evaluated = instance.reject do |key, _value| evaluated_keys.include?(key) end nested = evaluated.map do |key, value| parsed.validate_instance(value, join_location(instance_location, key), keyword_location, context) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => evaluated.keys) end private def collect_evaluated_keys(result, evaluated_keys) case result.source when Applicator::Properties, Applicator::PatternProperties, Applicator::AdditionalProperties, UnevaluatedProperties evaluated_keys.merge(result.annotation) end result.nested&.each do |subresult| if subresult.valid && subresult.instance_location == result.instance_location collect_evaluated_keys(subresult, evaluated_keys) end end end end end end end end json_schemer-2.4.0/lib/json_schemer/draft202012/vocab/applicator.rb0000644000004100000410000003632514751003725025012 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft202012 module Vocab module Applicator class AllOf < Keyword def error(formatted_instance_location:, **) "value at #{formatted_instance_location} does not match all `allOf` schemas" end def parse value.map.with_index do |subschema, index| subschema(subschema, index.to_s) end end def validate(instance, instance_location, keyword_location, context) nested = parsed.map.with_index do |subschema, index| subschema.validate_instance(instance, instance_location, join_location(keyword_location, index.to_s), context) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested) end end class AnyOf < Keyword def error(formatted_instance_location:, **) "value at #{formatted_instance_location} does not match any `anyOf` schemas" end def parse value.map.with_index do |subschema, index| subschema(subschema, index.to_s) end end def validate(instance, instance_location, keyword_location, context) nested = parsed.map.with_index do |subschema, index| subschema.validate_instance(instance, instance_location, join_location(keyword_location, index.to_s), context) end result(instance, instance_location, keyword_location, nested.any?(&:valid), nested) end end class OneOf < Keyword def error(formatted_instance_location:, **) "value at #{formatted_instance_location} does not match exactly one `oneOf` schema" end def parse value.map.with_index do |subschema, index| subschema(subschema, index.to_s) end end def validate(instance, instance_location, keyword_location, context) nested = parsed.map.with_index do |subschema, index| subschema.validate_instance(instance, instance_location, join_location(keyword_location, index.to_s), context) end valid_count = nested.count(&:valid) result(instance, instance_location, keyword_location, valid_count == 1, nested, :ignore_nested => valid_count > 1) end end class Not < Keyword def error(formatted_instance_location:, **) "value at #{formatted_instance_location} matches `not` schema" end def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) subschema_result = parsed.validate_instance(instance, instance_location, keyword_location, context) result(instance, instance_location, keyword_location, !subschema_result.valid, subschema_result.nested) end end class If < Keyword def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) subschema_result = parsed.validate_instance(instance, instance_location, keyword_location, context) result(instance, instance_location, keyword_location, true, subschema_result.nested, :annotation => subschema_result.valid) end end class Then < Keyword def error(formatted_instance_location:, **) "value at #{formatted_instance_location} does not match conditional `then` schema" end def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) return unless context.adjacent_results.key?(If) && context.adjacent_results.fetch(If).annotation subschema_result = parsed.validate_instance(instance, instance_location, keyword_location, context) result(instance, instance_location, keyword_location, subschema_result.valid, subschema_result.nested) end end class Else < Keyword def error(formatted_instance_location:, **) "value at #{formatted_instance_location} does not match conditional `else` schema" end def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) return unless context.adjacent_results.key?(If) && !context.adjacent_results.fetch(If).annotation subschema_result = parsed.validate_instance(instance, instance_location, keyword_location, context) result(instance, instance_location, keyword_location, subschema_result.valid, subschema_result.nested) end end class DependentSchemas < Keyword def error(formatted_instance_location:, **) "value at #{formatted_instance_location} does not match applicable `dependentSchemas` schemas" end def parse value.each_with_object({}) do |(key, subschema), out| out[key] = subschema(subschema, key) end end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash) nested = parsed.select do |key, _subschema| instance.key?(key) end.map do |key, subschema| subschema.validate_instance(instance, instance_location, join_location(keyword_location, key), context) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested) end end class PrefixItems < Keyword def error(formatted_instance_location:, **) "array items at #{formatted_instance_location} do not match corresponding `prefixItems` schemas" end def parse value.map.with_index do |subschema, index| subschema(subschema, index.to_s) end end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array) nested = instance.take(parsed.size).map.with_index do |item, index| parsed.fetch(index).validate_instance(item, join_location(instance_location, index.to_s), join_location(keyword_location, index.to_s), context) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => (nested.size - 1)) end end class Items < Keyword def error(formatted_instance_location:, **) "array items at #{formatted_instance_location} do not match `items` schema" end def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array) evaluated_index = context.adjacent_results[PrefixItems]&.annotation offset = evaluated_index ? (evaluated_index + 1) : 0 nested = instance.slice(offset..-1).map.with_index do |item, index| parsed.validate_instance(item, join_location(instance_location, (offset + index).to_s), keyword_location, context) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => nested.any?) end end class Contains < Keyword def error(formatted_instance_location:, **) "array at #{formatted_instance_location} does not contain enough items that match `contains` schema" end def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array) nested = instance.map.with_index do |item, index| parsed.validate_instance(item, join_location(instance_location, index.to_s), keyword_location, context) end annotation = [] nested.each_with_index do |nested_result, index| annotation << index if nested_result.valid end min_contains = schema.parsed['minContains']&.parsed || 1 result(instance, instance_location, keyword_location, annotation.size >= min_contains, nested, :annotation => annotation, :ignore_nested => true) end end class Properties < Keyword def error(formatted_instance_location:, **) "object properties at #{formatted_instance_location} do not match corresponding `properties` schemas" end def parse value.each_with_object({}) do |(property, subschema), out| out[property] = subschema(subschema, property) end end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash) if root.before_property_validation.any? original_instance = context.original_instance(instance_location) root.before_property_validation.each do |hook| parsed.each do |property, subschema| hook.call(original_instance, property, subschema.value, schema.value) end end instance.replace(deep_stringify_keys(original_instance)) end evaluated_keys = [] nested = [] parsed.each do |property, subschema| if instance.key?(property) evaluated_keys << property nested << subschema.validate_instance(instance.fetch(property), join_location(instance_location, property), join_location(keyword_location, property), context) end end if root.after_property_validation.any? original_instance = context.original_instance(instance_location) root.after_property_validation.each do |hook| parsed.each do |property, subschema| hook.call(original_instance, property, subschema.value, schema.value) end end instance.replace(deep_stringify_keys(original_instance)) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => evaluated_keys) end end class PatternProperties < Keyword def error(formatted_instance_location:, **) "object properties at #{formatted_instance_location} do not match corresponding `patternProperties` schemas" end def parse value.each_with_object({}) do |(pattern, subschema), out| out[pattern] = subschema(subschema, pattern) end end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash) evaluated = Set[] nested = [] parsed.each do |pattern, subschema| regexp = root.resolve_regexp(pattern) instance.each do |key, value| if regexp.match?(key) evaluated << key nested << subschema.validate_instance(value, join_location(instance_location, key), join_location(keyword_location, pattern), context) end end end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => evaluated.to_a) end end class AdditionalProperties < Keyword def error(formatted_instance_location:, **) "object properties at #{formatted_instance_location} do not match `additionalProperties` schema" end def false_schema_error(formatted_instance_location:, **) "object property at #{formatted_instance_location} is a disallowed additional property" end def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash) evaluated_keys = context.adjacent_results[Properties]&.annotation || [] evaluated_keys += context.adjacent_results[PatternProperties]&.annotation || [] evaluated_keys = evaluated_keys.to_set evaluated = instance.reject do |key, _value| evaluated_keys.include?(key) end nested = evaluated.map do |key, value| parsed.validate_instance(value, join_location(instance_location, key), keyword_location, context) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => evaluated.keys) end end class PropertyNames < Keyword def error(formatted_instance_location:, **) "object property names at #{formatted_instance_location} do not match `propertyNames` schema" end def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash) nested = instance.map do |key, _value| parsed.validate_instance(key, instance_location, keyword_location, context) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested) end end class Dependencies < Keyword def error(formatted_instance_location:, **) "object at #{formatted_instance_location} either does not match applicable `dependencies` schemas or is missing required `dependencies` properties" end def parse value.each_with_object({}) do |(key, value), out| out[key] = value.is_a?(Array) ? value : subschema(value, key) end end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash) existing_keys = instance.keys nested = parsed.select do |key, _value| instance.key?(key) end.map do |key, value| if value.is_a?(Array) missing_keys = value - existing_keys result(instance, instance_location, join_location(keyword_location, key), missing_keys.none?, :details => { 'missing_keys' => missing_keys }) else value.validate_instance(instance, instance_location, join_location(keyword_location, key), context) end end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested) end end end end end end json_schemer-2.4.0/lib/json_schemer/draft202012/vocab/validation.rb0000644000004100000410000002740314751003725025003 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft202012 module Vocab module Validation class Type < Keyword def self.valid_integer?(instance) instance.is_a?(Numeric) && (instance.is_a?(Integer) || instance.floor == instance) end def error(formatted_instance_location:, **) case value when 'null' "value at #{formatted_instance_location} is not null" when 'boolean' "value at #{formatted_instance_location} is not a boolean" when 'number' "value at #{formatted_instance_location} is not a number" when 'integer' "value at #{formatted_instance_location} is not an integer" when 'string' "value at #{formatted_instance_location} is not a string" when 'array' "value at #{formatted_instance_location} is not an array" when 'object' "value at #{formatted_instance_location} is not an object" else "value at #{formatted_instance_location} is not one of the types: #{value}" end end def validate(instance, instance_location, keyword_location, _context) case parsed when String result(instance, instance_location, keyword_location, valid_type(parsed, instance), :type => parsed) when Array result(instance, instance_location, keyword_location, parsed.any? { |type| valid_type(type, instance) }) end end private def valid_type(type, instance) case type when 'null' instance.nil? when 'boolean' instance == true || instance == false when 'number' instance.is_a?(Numeric) when 'integer' self.class.valid_integer?(instance) when 'string' instance.is_a?(String) when 'array' instance.is_a?(Array) when 'object' instance.is_a?(Hash) else true end end end class Enum < Keyword def error(formatted_instance_location:, **) "value at #{formatted_instance_location} is not one of: #{value}" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !value || value.include?(instance)) end end class Const < Keyword def error(formatted_instance_location:, **) "value at #{formatted_instance_location} is not: #{value}" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, value == instance) end end class MultipleOf < Keyword def error(formatted_instance_location:, **) "number at #{formatted_instance_location} is not a multiple of: #{value}" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !instance.is_a?(Numeric) || BigDecimal(instance.to_s).modulo(value).zero?) end end class Maximum < Keyword def error(formatted_instance_location:, **) "number at #{formatted_instance_location} is greater than: #{value}" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !instance.is_a?(Numeric) || instance <= value) end end class ExclusiveMaximum < Keyword def error(formatted_instance_location:, **) "number at #{formatted_instance_location} is greater than or equal to: #{value}" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !instance.is_a?(Numeric) || instance < value) end end class Minimum < Keyword def error(formatted_instance_location:, **) "number at #{formatted_instance_location} is less than: #{value}" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !instance.is_a?(Numeric) || instance >= value) end end class ExclusiveMinimum < Keyword def error(formatted_instance_location:, **) "number at #{formatted_instance_location} is less than or equal to: #{value}" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !instance.is_a?(Numeric) || instance > value) end end class MaxLength < Keyword def error(formatted_instance_location:, **) "string length at #{formatted_instance_location} is greater than: #{value}" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !instance.is_a?(String) || instance.size <= value) end end class MinLength < Keyword def error(formatted_instance_location:, **) "string length at #{formatted_instance_location} is less than: #{value}" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !instance.is_a?(String) || instance.size >= value) end end class Pattern < Keyword def error(formatted_instance_location:, **) "string at #{formatted_instance_location} does not match pattern: #{value}" end def parse root.resolve_regexp(value) end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !instance.is_a?(String) || parsed.match?(instance)) end end class MaxItems < Keyword def error(formatted_instance_location:, **) "array size at #{formatted_instance_location} is greater than: #{value}" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !instance.is_a?(Array) || instance.size <= value) end end class MinItems < Keyword def error(formatted_instance_location:, **) "array size at #{formatted_instance_location} is less than: #{value}" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !instance.is_a?(Array) || instance.size >= value) end end class UniqueItems < Keyword def error(formatted_instance_location:, **) "array items at #{formatted_instance_location} are not unique" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !instance.is_a?(Array) || value == false || instance.size == instance.uniq.size) end end class MaxContains < Keyword def error(formatted_instance_location:, **) "number of array items at #{formatted_instance_location} matching `contains` schema is greater than: #{value}" end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array) && context.adjacent_results.key?(Applicator::Contains) evaluated_items = context.adjacent_results.fetch(Applicator::Contains).annotation result(instance, instance_location, keyword_location, evaluated_items.size <= value) end end class MinContains < Keyword def error(formatted_instance_location:, **) "number of array items at #{formatted_instance_location} matching `contains` schema is less than: #{value}" end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array) && context.adjacent_results.key?(Applicator::Contains) evaluated_items = context.adjacent_results.fetch(Applicator::Contains).annotation result(instance, instance_location, keyword_location, evaluated_items.size >= value) end end class MaxProperties < Keyword def error(formatted_instance_location:, **) "object size at #{formatted_instance_location} is greater than: #{value}" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !instance.is_a?(Hash) || instance.size <= value) end end class MinProperties < Keyword def error(formatted_instance_location:, **) "object size at #{formatted_instance_location} is less than: #{value}" end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, !instance.is_a?(Hash) || instance.size >= value) end end class Required < Keyword def error(formatted_instance_location:, details:, **) "object at #{formatted_instance_location} is missing required properties: #{details.fetch('missing_keys').join(', ')}" end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash) required_keys = value if context.access_mode && schema.parsed.key?('properties') inapplicable_access_mode_keys = [] schema.parsed.fetch('properties').parsed.each do |property, subschema| read_only, write_only = subschema.parsed.values_at('readOnly', 'writeOnly') inapplicable_access_mode_keys << property if context.access_mode == 'write' && read_only&.parsed == true inapplicable_access_mode_keys << property if context.access_mode == 'read' && write_only&.parsed == true end required_keys -= inapplicable_access_mode_keys end missing_keys = required_keys - instance.keys result(instance, instance_location, keyword_location, missing_keys.none?, :details => { 'missing_keys' => missing_keys }) end end class DependentRequired < Keyword def error(formatted_instance_location:, **) "object at #{formatted_instance_location} is missing required `dependentRequired` properties" end def validate(instance, instance_location, keyword_location, _context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Hash) existing_keys = instance.keys nested = value.select do |key, _required_keys| instance.key?(key) end.map do |key, required_keys| result(instance, join_location(instance_location, key), join_location(keyword_location, key), (required_keys - existing_keys).none?) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested) end end end end end end json_schemer-2.4.0/lib/json_schemer/draft202012/vocab/format_annotation.rb0000644000004100000410000000126614751003725026372 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft202012 module Vocab module FormatAnnotation class Format < Keyword def error(formatted_instance_location:, **) "value at #{formatted_instance_location} does not match format: #{value}" end def parse root.format && root.fetch_format(value, false) end def validate(instance, instance_location, keyword_location, _context) valid = parsed == false || parsed.call(instance, value) result(instance, instance_location, keyword_location, valid, :annotation => value) end end end end end end json_schemer-2.4.0/lib/json_schemer/draft202012/vocab/core.rb0000644000004100000410000001155314751003725023600 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft202012 module Vocab module Core class Schema < Keyword def parse schema.meta_schema = if value == schema.base_uri.to_s schema else META_SCHEMAS_BY_BASE_URI_STR[value] || root.resolve_ref(URI(value)) end value end end class Vocabulary < Keyword def parse value.each_with_object({}) do |(vocabulary, required), out| if VOCABULARIES.key?(vocabulary) out[vocabulary] = VOCABULARIES.fetch(vocabulary) elsif required raise UnknownVocabulary, vocabulary end end.tap do |vocabularies| schema.keywords = vocabularies.sort_by do |vocabulary, _keywords| VOCABULARY_ORDER.fetch(vocabulary, Float::INFINITY) end.each_with_object({}) do |(_vocabulary, keywords), out| out.merge!(keywords) end schema.keyword_order = schema.keywords.transform_values.with_index { |_keyword_class, index| index } end end end class Id < Keyword def parse URI.join(schema.base_uri, value).tap do |uri| schema.base_uri = uri root.resources[:lexical][uri] = schema end end end class Anchor < Keyword def parse URI.join(schema.base_uri, "##{value}").tap do |uri| root.resources[:lexical][uri] = schema end end end class Ref < Keyword def self.exclusive? false end def ref_uri @ref_uri ||= URI.join(schema.base_uri, value) end def ref_schema @ref_schema ||= root.resolve_ref(ref_uri) end def validate(instance, instance_location, keyword_location, context) ref_schema.validate_instance(instance, instance_location, keyword_location, context) end end class DynamicAnchor < Keyword def parse URI.join(schema.base_uri, "##{value}").tap do |uri| root.resources[:lexical][uri] = schema root.resources[:dynamic][uri] = schema end end end class DynamicRef < Keyword def ref_uri @ref_uri ||= URI.join(schema.base_uri, value) end def ref_schema @ref_schema ||= root.resolve_ref(ref_uri) end def dynamic_anchor return @dynamic_anchor if defined?(@dynamic_anchor) fragment = ref_schema.parsed['$dynamicAnchor']&.parsed&.fragment @dynamic_anchor = (fragment == ref_uri.fragment ? fragment : nil) end def validate(instance, instance_location, keyword_location, context) schema = ref_schema if dynamic_anchor context.dynamic_scope.each do |ancestor| dynamic_uri = URI.join(ancestor.base_uri, "##{dynamic_anchor}") if ancestor.root.resources.fetch(:dynamic).key?(dynamic_uri) schema = ancestor.root.resources.fetch(:dynamic).fetch(dynamic_uri) break end end end schema.validate_instance(instance, instance_location, keyword_location, context) end end class Defs < Keyword def parse value.each_with_object({}) do |(key, subschema), out| out[key] = subschema(subschema, key) end end end class Comment < Keyword; end class XError < Keyword def message(error_key) value.is_a?(Hash) ? (value[error_key] || value[CATCHALL]) : value end end class UnknownKeyword < Keyword def parse if value.is_a?(Hash) {} elsif value.is_a?(Array) [] else value end end def fetch(token) if value.is_a?(Hash) parsed[token] ||= JSONSchemer::Schema::UNKNOWN_KEYWORD_CLASS.new(value.fetch(token), self, token, schema) elsif value.is_a?(Array) parsed[token.to_i] ||= JSONSchemer::Schema::UNKNOWN_KEYWORD_CLASS.new(value.fetch(token.to_i), self, token, schema) else raise KeyError.new(:receiver => parsed, :key => token) end end def parsed_schema @parsed_schema ||= subschema(value) end def validate(instance, instance_location, keyword_location, _context) result(instance, instance_location, keyword_location, true, :annotation => value) end end end end end end json_schemer-2.4.0/lib/json_schemer/draft202012/vocab/meta_data.rb0000644000004100000410000000205114751003725024560 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft202012 module Vocab module MetaData class ReadOnly < Keyword def error(formatted_instance_location:, **) "value at #{formatted_instance_location} is `readOnly`" end def validate(instance, instance_location, keyword_location, context) valid = parsed != true || !context.access_mode || context.access_mode == 'read' result(instance, instance_location, keyword_location, valid, :annotation => value) end end class WriteOnly < Keyword def error(formatted_instance_location:, **) "value at #{formatted_instance_location} is `writeOnly`" end def validate(instance, instance_location, keyword_location, context) valid = parsed != true || !context.access_mode || context.access_mode == 'write' result(instance, instance_location, keyword_location, valid, :annotation => value) end end end end end end json_schemer-2.4.0/lib/json_schemer/draft202012/vocab/format_assertion.rb0000644000004100000410000000131514751003725026222 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft202012 module Vocab module FormatAssertion class Format < Keyword def error(formatted_instance_location:, **) "value at #{formatted_instance_location} does not match format: #{value}" end def parse root.format && root.fetch_format(value) { raise UnknownFormat, value } end def validate(instance, instance_location, keyword_location, _context) valid = parsed == false || parsed.call(instance, value) result(instance, instance_location, keyword_location, valid, :annotation => value) end end end end end end json_schemer-2.4.0/lib/json_schemer/draft202012/vocab/content.rb0000644000004100000410000000366414751003725024326 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft202012 module Vocab module Content class ContentEncoding < Keyword def parse root.fetch_content_encoding(value) { raise UnknownContentEncoding, value } end def validate(instance, instance_location, keyword_location, _context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String) _valid, annotation = parsed.call(instance) result(instance, instance_location, keyword_location, true, :annotation => annotation) end end class ContentMediaType < Keyword def parse root.fetch_content_media_type(value) { raise UnknownContentMediaType, value } end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String) decoded_instance = context.adjacent_results[ContentEncoding]&.annotation || instance _valid, annotation = parsed.call(decoded_instance) result(instance, instance_location, keyword_location, true, :annotation => annotation) end end class ContentSchema < Keyword def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless context.adjacent_results.key?(ContentMediaType) parsed_instance = context.adjacent_results.fetch(ContentMediaType).annotation annotation = parsed.validate_instance(parsed_instance, instance_location, keyword_location, context) result(instance, instance_location, keyword_location, true, :annotation => annotation.to_output_unit) end end end end end end json_schemer-2.4.0/lib/json_schemer/cached_resolver.rb0000644000004100000410000000051214751003725023130 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer class CachedResolver def initialize(&resolver) @resolver = resolver @cache = {} end def call(*args) @cache[args] = @resolver.call(*args) unless @cache.key?(args) @cache[args] end end class CachedRefResolver < CachedResolver; end end json_schemer-2.4.0/lib/json_schemer/resources.rb0000644000004100000410000000056414751003725022021 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer class Resources def initialize @resources = {} end def [](uri) @resources[uri.to_s] end def []=(uri, resource) @resources[uri.to_s] = resource end def fetch(uri) @resources.fetch(uri.to_s) end def key?(uri) @resources.key?(uri.to_s) end end end json_schemer-2.4.0/lib/json_schemer/openapi31/0000755000004100000410000000000014751003725021254 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/openapi31/document.rb0000644000004100000410000013541314751003725023426 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module OpenAPI31 module Document # http://json-schema.org/blog/posts/validating-openapi-and-json-schema DIALECTS = [ OpenAPI31::BASE_URI.to_s, Draft202012::BASE_URI.to_s, Draft201909::BASE_URI.to_s, Draft7::BASE_URI.to_s, Draft6::BASE_URI.to_s, Draft4::BASE_URI.to_s ] DEFAULT_DIALECT, *OTHER_DIALECTS = DIALECTS def self.dialect_schema(dialect) { '$id' => dialect.object_id.to_s, '$ref' => 'https://spec.openapis.org/oas/3.1/schema/2022-10-07', '$defs' => { 'schema' => { '$dynamicAnchor' => 'meta', 'properties' => { '$schema' => { '$ref' => 'json-schemer://openapi31/schema-base#/$defs/dialect' } }, 'allOf' => [ { 'if' => { 'properties' => { '$schema' => { 'const' => dialect } } }, 'then' => { '$ref' => dialect } }, *(DIALECTS - [dialect]).map do |other_dialect| { 'if' => { 'type' => 'object', 'required' => ['$schema'], 'properties' => { '$schema' => { 'const' => other_dialect } } }, 'then' => { '$ref' => other_dialect } } end ] } } } end SCHEMA_BASE = { '$id' => 'json-schemer://openapi31/schema-base', '$schema' => 'https://json-schema.org/draft/2020-12/schema', '$defs' => { 'dialect' => { 'enum' => DIALECTS } }, 'properties' => { 'jsonSchemaDialect' => { '$ref' => '#/$defs/dialect' } }, 'allOf' => [ { 'if' => { 'properties' => { 'jsonSchemaDialect' => { 'const' => DEFAULT_DIALECT } } }, 'then' => dialect_schema(DEFAULT_DIALECT) }, *OTHER_DIALECTS.map do |other_dialect| { 'if' => { 'type' => 'object', 'required' => ['jsonSchemaDialect'], 'properties' => { 'jsonSchemaDialect' => { 'const' => other_dialect } } }, 'then' => dialect_schema(other_dialect) } end ] } SCHEMA = { '$id' => 'https://spec.openapis.org/oas/3.1/schema/2022-10-07', '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'description' => 'The description of OpenAPI v3.1.x documents without schema validation, as defined by https://spec.openapis.org/oas/v3.1.0', 'type' => 'object', 'properties' => { 'openapi' => { 'type' => 'string', 'pattern' => '^3\.1\.\d+(-.+)?$' }, 'info' => { '$ref' => '#/$defs/info' }, 'jsonSchemaDialect' => { 'type' => 'string', 'format' => 'uri', 'default' => 'https://spec.openapis.org/oas/3.1/dialect/base' }, 'servers' => { 'type' => 'array', 'items' => { '$ref' => '#/$defs/server' }, 'default' => [ { 'url' => '/' } ] }, 'paths' => { '$ref' => '#/$defs/paths' }, 'webhooks' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/path-item-or-reference' } }, 'components' => { '$ref' => '#/$defs/components' }, 'security' => { 'type' => 'array', 'items' => { '$ref' => '#/$defs/security-requirement' } }, 'tags' => { 'type' => 'array', 'items' => { '$ref' => '#/$defs/tag' } }, 'externalDocs' => { '$ref' => '#/$defs/external-documentation' } }, 'required' => [ 'openapi', 'info' ], 'anyOf' => [ { 'required' => [ 'paths' ] }, { 'required' => [ 'components' ] }, { 'required' => [ 'webhooks' ] } ], '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false, '$defs' => { 'info' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#info-object', 'type' => 'object', 'properties' => { 'title' => { 'type' => 'string' }, 'summary' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'termsOfService' => { 'type' => 'string', 'format' => 'uri' }, 'contact' => { '$ref' => '#/$defs/contact' }, 'license' => { '$ref' => '#/$defs/license' }, 'version' => { 'type' => 'string' } }, 'required' => [ 'title', 'version' ], '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'contact' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#contact-object', 'type' => 'object', 'properties' => { 'name' => { 'type' => 'string' }, 'url' => { 'type' => 'string', 'format' => 'uri' }, 'email' => { 'type' => 'string', 'format' => 'email' } }, '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'license' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#license-object', 'type' => 'object', 'properties' => { 'name' => { 'type' => 'string' }, 'identifier' => { 'type' => 'string' }, 'url' => { 'type' => 'string', 'format' => 'uri' } }, 'required' => [ 'name' ], 'dependentSchemas' => { 'identifier' => { 'not' => { 'required' => [ 'url' ] } } }, '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'server' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#server-object', 'type' => 'object', 'properties' => { 'url' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'variables' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/server-variable' } } }, 'required' => [ 'url' ], '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'server-variable' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#server-variable-object', 'type' => 'object', 'properties' => { 'enum' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'minItems' => 1 }, 'default' => { 'type' => 'string' }, 'description' => { 'type' => 'string' } }, 'required' => [ 'default' ], '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'components' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#components-object', 'type' => 'object', 'properties' => { 'schemas' => { 'type' => 'object', 'additionalProperties' => { '$dynamicRef' => '#meta' } }, 'responses' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/response-or-reference' } }, 'parameters' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/parameter-or-reference' } }, 'examples' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/example-or-reference' } }, 'requestBodies' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/request-body-or-reference' } }, 'headers' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/header-or-reference' } }, 'securitySchemes' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/security-scheme-or-reference' } }, 'links' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/link-or-reference' } }, 'callbacks' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/callbacks-or-reference' } }, 'pathItems' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/path-item-or-reference' } } }, 'patternProperties' => { '^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$' => { '$comment' => 'Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected', 'propertyNames' => { 'pattern' => '^[a-zA-Z0-9._-]+$' } } }, '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'paths' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#paths-object', 'type' => 'object', 'patternProperties' => { '^/' => { '$ref' => '#/$defs/path-item' } }, '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'path-item' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#path-item-object', 'type' => 'object', 'properties' => { 'summary' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'servers' => { 'type' => 'array', 'items' => { '$ref' => '#/$defs/server' } }, 'parameters' => { 'type' => 'array', 'items' => { '$ref' => '#/$defs/parameter-or-reference' } }, 'get' => { '$ref' => '#/$defs/operation' }, 'put' => { '$ref' => '#/$defs/operation' }, 'post' => { '$ref' => '#/$defs/operation' }, 'delete' => { '$ref' => '#/$defs/operation' }, 'options' => { '$ref' => '#/$defs/operation' }, 'head' => { '$ref' => '#/$defs/operation' }, 'patch' => { '$ref' => '#/$defs/operation' }, 'trace' => { '$ref' => '#/$defs/operation' } }, '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'path-item-or-reference' => { 'if' => { 'type' => 'object', 'required' => [ '$ref' ] }, 'then' => { '$ref' => '#/$defs/reference' }, 'else' => { '$ref' => '#/$defs/path-item' } }, 'operation' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#operation-object', 'type' => 'object', 'properties' => { 'tags' => { 'type' => 'array', 'items' => { 'type' => 'string' } }, 'summary' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'externalDocs' => { '$ref' => '#/$defs/external-documentation' }, 'operationId' => { 'type' => 'string' }, 'parameters' => { 'type' => 'array', 'items' => { '$ref' => '#/$defs/parameter-or-reference' } }, 'requestBody' => { '$ref' => '#/$defs/request-body-or-reference' }, 'responses' => { '$ref' => '#/$defs/responses' }, 'callbacks' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/callbacks-or-reference' } }, 'deprecated' => { 'default' => false, 'type' => 'boolean' }, 'security' => { 'type' => 'array', 'items' => { '$ref' => '#/$defs/security-requirement' } }, 'servers' => { 'type' => 'array', 'items' => { '$ref' => '#/$defs/server' } } }, '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'external-documentation' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#external-documentation-object', 'type' => 'object', 'properties' => { 'description' => { 'type' => 'string' }, 'url' => { 'type' => 'string', 'format' => 'uri' } }, 'required' => [ 'url' ], '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'parameter' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#parameter-object', 'type' => 'object', 'properties' => { 'name' => { 'type' => 'string' }, 'in' => { 'enum' => [ 'query', 'header', 'path', 'cookie' ] }, 'description' => { 'type' => 'string' }, 'required' => { 'default' => false, 'type' => 'boolean' }, 'deprecated' => { 'default' => false, 'type' => 'boolean' }, 'schema' => { '$dynamicRef' => '#meta' }, 'content' => { '$ref' => '#/$defs/content', 'minProperties' => 1, 'maxProperties' => 1 } }, 'required' => [ 'name', 'in' ], 'oneOf' => [ { 'required' => [ 'schema' ] }, { 'required' => [ 'content' ] } ], 'if' => { 'properties' => { 'in' => { 'const' => 'query' } }, 'required' => [ 'in' ] }, 'then' => { 'properties' => { 'allowEmptyValue' => { 'default' => false, 'type' => 'boolean' } } }, 'dependentSchemas' => { 'schema' => { 'properties' => { 'style' => { 'type' => 'string' }, 'explode' => { 'type' => 'boolean' } }, 'allOf' => [ { '$ref' => '#/$defs/examples' }, { '$ref' => '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path' }, { '$ref' => '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header' }, { '$ref' => '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query' }, { '$ref' => '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie' }, { '$ref' => '#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-form' } ], '$defs' => { 'styles-for-path' => { 'if' => { 'properties' => { 'in' => { 'const' => 'path' } }, 'required' => [ 'in' ] }, 'then' => { 'properties' => { 'name' => { 'pattern' => '[^/#?]+$' }, 'style' => { 'default' => 'simple', 'enum' => [ 'matrix', 'label', 'simple' ] }, 'required' => { 'const' => true } }, 'required' => [ 'required' ] } }, 'styles-for-header' => { 'if' => { 'properties' => { 'in' => { 'const' => 'header' } }, 'required' => [ 'in' ] }, 'then' => { 'properties' => { 'style' => { 'default' => 'simple', 'const' => 'simple' } } } }, 'styles-for-query' => { 'if' => { 'properties' => { 'in' => { 'const' => 'query' } }, 'required' => [ 'in' ] }, 'then' => { 'properties' => { 'style' => { 'default' => 'form', 'enum' => [ 'form', 'spaceDelimited', 'pipeDelimited', 'deepObject' ] }, 'allowReserved' => { 'default' => false, 'type' => 'boolean' } } } }, 'styles-for-cookie' => { 'if' => { 'properties' => { 'in' => { 'const' => 'cookie' } }, 'required' => [ 'in' ] }, 'then' => { 'properties' => { 'style' => { 'default' => 'form', 'const' => 'form' } } } }, 'styles-for-form' => { 'if' => { 'properties' => { 'style' => { 'const' => 'form' } }, 'required' => [ 'style' ] }, 'then' => { 'properties' => { 'explode' => { 'default' => true } } }, 'else' => { 'properties' => { 'explode' => { 'default' => false } } } } } } }, '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'parameter-or-reference' => { 'if' => { 'type' => 'object', 'required' => [ '$ref' ] }, 'then' => { '$ref' => '#/$defs/reference' }, 'else' => { '$ref' => '#/$defs/parameter' } }, 'request-body' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#request-body-object', 'type' => 'object', 'properties' => { 'description' => { 'type' => 'string' }, 'content' => { '$ref' => '#/$defs/content' }, 'required' => { 'default' => false, 'type' => 'boolean' } }, 'required' => [ 'content' ], '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'request-body-or-reference' => { 'if' => { 'type' => 'object', 'required' => [ '$ref' ] }, 'then' => { '$ref' => '#/$defs/reference' }, 'else' => { '$ref' => '#/$defs/request-body' } }, 'content' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#fixed-fields-10', 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/media-type' }, 'propertyNames' => { 'format' => 'media-range' } }, 'media-type' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#media-type-object', 'type' => 'object', 'properties' => { 'schema' => { '$dynamicRef' => '#meta' }, 'encoding' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/encoding' } } }, 'allOf' => [ { '$ref' => '#/$defs/specification-extensions' }, { '$ref' => '#/$defs/examples' } ], 'unevaluatedProperties' => false }, 'encoding' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#encoding-object', 'type' => 'object', 'properties' => { 'contentType' => { 'type' => 'string', 'format' => 'media-range' }, 'headers' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/header-or-reference' } }, 'style' => { 'default' => 'form', 'enum' => [ 'form', 'spaceDelimited', 'pipeDelimited', 'deepObject' ] }, 'explode' => { 'type' => 'boolean' }, 'allowReserved' => { 'default' => false, 'type' => 'boolean' } }, 'allOf' => [ { '$ref' => '#/$defs/specification-extensions' }, { '$ref' => '#/$defs/encoding/$defs/explode-default' } ], 'unevaluatedProperties' => false, '$defs' => { 'explode-default' => { 'if' => { 'properties' => { 'style' => { 'const' => 'form' } }, 'required' => [ 'style' ] }, 'then' => { 'properties' => { 'explode' => { 'default' => true } } }, 'else' => { 'properties' => { 'explode' => { 'default' => false } } } } } }, 'responses' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#responses-object', 'type' => 'object', 'properties' => { 'default' => { '$ref' => '#/$defs/response-or-reference' } }, 'patternProperties' => { '^[1-5](?:[0-9]{2}|XX)$' => { '$ref' => '#/$defs/response-or-reference' } }, 'minProperties' => 1, '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'response' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#response-object', 'type' => 'object', 'properties' => { 'description' => { 'type' => 'string' }, 'headers' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/header-or-reference' } }, 'content' => { '$ref' => '#/$defs/content' }, 'links' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/link-or-reference' } } }, 'required' => [ 'description' ], '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'response-or-reference' => { 'if' => { 'type' => 'object', 'required' => [ '$ref' ] }, 'then' => { '$ref' => '#/$defs/reference' }, 'else' => { '$ref' => '#/$defs/response' } }, 'callbacks' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#callback-object', 'type' => 'object', '$ref' => '#/$defs/specification-extensions', 'additionalProperties' => { '$ref' => '#/$defs/path-item-or-reference' } }, 'callbacks-or-reference' => { 'if' => { 'type' => 'object', 'required' => [ '$ref' ] }, 'then' => { '$ref' => '#/$defs/reference' }, 'else' => { '$ref' => '#/$defs/callbacks' } }, 'example' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#example-object', 'type' => 'object', 'properties' => { 'summary' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'value' => true, 'externalValue' => { 'type' => 'string', 'format' => 'uri' } }, 'not' => { 'required' => [ 'value', 'externalValue' ] }, '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'example-or-reference' => { 'if' => { 'type' => 'object', 'required' => [ '$ref' ] }, 'then' => { '$ref' => '#/$defs/reference' }, 'else' => { '$ref' => '#/$defs/example' } }, 'link' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#link-object', 'type' => 'object', 'properties' => { 'operationRef' => { 'type' => 'string' }, 'operationId' => { 'type' => 'string' }, 'parameters' => { '$ref' => '#/$defs/map-of-strings' }, 'requestBody' => true, 'description' => { 'type' => 'string' }, 'body' => { '$ref' => '#/$defs/server' } }, 'oneOf' => [ { 'required' => [ 'operationRef' ] }, { 'required' => [ 'operationId' ] } ], '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'link-or-reference' => { 'if' => { 'type' => 'object', 'required' => [ '$ref' ] }, 'then' => { '$ref' => '#/$defs/reference' }, 'else' => { '$ref' => '#/$defs/link' } }, 'header' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#header-object', 'type' => 'object', 'properties' => { 'description' => { 'type' => 'string' }, 'required' => { 'default' => false, 'type' => 'boolean' }, 'deprecated' => { 'default' => false, 'type' => 'boolean' }, 'schema' => { '$dynamicRef' => '#meta' }, 'content' => { '$ref' => '#/$defs/content', 'minProperties' => 1, 'maxProperties' => 1 } }, 'oneOf' => [ { 'required' => [ 'schema' ] }, { 'required' => [ 'content' ] } ], 'dependentSchemas' => { 'schema' => { 'properties' => { 'style' => { 'default' => 'simple', 'const' => 'simple' }, 'explode' => { 'default' => false, 'type' => 'boolean' } }, '$ref' => '#/$defs/examples' } }, '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'header-or-reference' => { 'if' => { 'type' => 'object', 'required' => [ '$ref' ] }, 'then' => { '$ref' => '#/$defs/reference' }, 'else' => { '$ref' => '#/$defs/header' } }, 'tag' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#tag-object', 'type' => 'object', 'properties' => { 'name' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'externalDocs' => { '$ref' => '#/$defs/external-documentation' } }, 'required' => [ 'name' ], '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'reference' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#reference-object', 'type' => 'object', 'properties' => { '$ref' => { 'type' => 'string', 'format' => 'uri-reference' }, 'summary' => { 'type' => 'string' }, 'description' => { 'type' => 'string' } }, 'unevaluatedProperties' => false }, 'schema' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#schema-object', '$dynamicAnchor' => 'meta', 'type' => [ 'object', 'boolean' ] }, 'security-scheme' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#security-scheme-object', 'type' => 'object', 'properties' => { 'type' => { 'enum' => [ 'apiKey', 'http', 'mutualTLS', 'oauth2', 'openIdConnect' ] }, 'description' => { 'type' => 'string' } }, 'required' => [ 'type' ], 'allOf' => [ { '$ref' => '#/$defs/specification-extensions' }, { '$ref' => '#/$defs/security-scheme/$defs/type-apikey' }, { '$ref' => '#/$defs/security-scheme/$defs/type-http' }, { '$ref' => '#/$defs/security-scheme/$defs/type-http-bearer' }, { '$ref' => '#/$defs/security-scheme/$defs/type-oauth2' }, { '$ref' => '#/$defs/security-scheme/$defs/type-oidc' } ], 'unevaluatedProperties' => false, '$defs' => { 'type-apikey' => { 'if' => { 'properties' => { 'type' => { 'const' => 'apiKey' } }, 'required' => [ 'type' ] }, 'then' => { 'properties' => { 'name' => { 'type' => 'string' }, 'in' => { 'enum' => [ 'query', 'header', 'cookie' ] } }, 'required' => [ 'name', 'in' ] } }, 'type-http' => { 'if' => { 'properties' => { 'type' => { 'const' => 'http' } }, 'required' => [ 'type' ] }, 'then' => { 'properties' => { 'scheme' => { 'type' => 'string' } }, 'required' => [ 'scheme' ] } }, 'type-http-bearer' => { 'if' => { 'properties' => { 'type' => { 'const' => 'http' }, 'scheme' => { 'type' => 'string', 'pattern' => '^[Bb][Ee][Aa][Rr][Ee][Rr]$' } }, 'required' => [ 'type', 'scheme' ] }, 'then' => { 'properties' => { 'bearerFormat' => { 'type' => 'string' } } } }, 'type-oauth2' => { 'if' => { 'properties' => { 'type' => { 'const' => 'oauth2' } }, 'required' => [ 'type' ] }, 'then' => { 'properties' => { 'flows' => { '$ref' => '#/$defs/oauth-flows' } }, 'required' => [ 'flows' ] } }, 'type-oidc' => { 'if' => { 'properties' => { 'type' => { 'const' => 'openIdConnect' } }, 'required' => [ 'type' ] }, 'then' => { 'properties' => { 'openIdConnectUrl' => { 'type' => 'string', 'format' => 'uri' } }, 'required' => [ 'openIdConnectUrl' ] } } } }, 'security-scheme-or-reference' => { 'if' => { 'type' => 'object', 'required' => [ '$ref' ] }, 'then' => { '$ref' => '#/$defs/reference' }, 'else' => { '$ref' => '#/$defs/security-scheme' } }, 'oauth-flows' => { 'type' => 'object', 'properties' => { 'implicit' => { '$ref' => '#/$defs/oauth-flows/$defs/implicit' }, 'password' => { '$ref' => '#/$defs/oauth-flows/$defs/password' }, 'clientCredentials' => { '$ref' => '#/$defs/oauth-flows/$defs/client-credentials' }, 'authorizationCode' => { '$ref' => '#/$defs/oauth-flows/$defs/authorization-code' } }, '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false, '$defs' => { 'implicit' => { 'type' => 'object', 'properties' => { 'authorizationUrl' => { 'type' => 'string', 'format' => 'uri' }, 'refreshUrl' => { 'type' => 'string', 'format' => 'uri' }, 'scopes' => { '$ref' => '#/$defs/map-of-strings' } }, 'required' => [ 'authorizationUrl', 'scopes' ], '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'password' => { 'type' => 'object', 'properties' => { 'tokenUrl' => { 'type' => 'string', 'format' => 'uri' }, 'refreshUrl' => { 'type' => 'string', 'format' => 'uri' }, 'scopes' => { '$ref' => '#/$defs/map-of-strings' } }, 'required' => [ 'tokenUrl', 'scopes' ], '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'client-credentials' => { 'type' => 'object', 'properties' => { 'tokenUrl' => { 'type' => 'string', 'format' => 'uri' }, 'refreshUrl' => { 'type' => 'string', 'format' => 'uri' }, 'scopes' => { '$ref' => '#/$defs/map-of-strings' } }, 'required' => [ 'tokenUrl', 'scopes' ], '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false }, 'authorization-code' => { 'type' => 'object', 'properties' => { 'authorizationUrl' => { 'type' => 'string', 'format' => 'uri' }, 'tokenUrl' => { 'type' => 'string', 'format' => 'uri' }, 'refreshUrl' => { 'type' => 'string', 'format' => 'uri' }, 'scopes' => { '$ref' => '#/$defs/map-of-strings' } }, 'required' => [ 'authorizationUrl', 'tokenUrl', 'scopes' ], '$ref' => '#/$defs/specification-extensions', 'unevaluatedProperties' => false } } }, 'security-requirement' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#security-requirement-object', 'type' => 'object', 'additionalProperties' => { 'type' => 'array', 'items' => { 'type' => 'string' } } }, 'specification-extensions' => { '$comment' => 'https://spec.openapis.org/oas/v3.1.0#specification-extensions', 'patternProperties' => { '^x-' => true } }, 'examples' => { 'properties' => { 'example' => true, 'examples' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/example-or-reference' } } } }, 'map-of-strings' => { 'type' => 'object', 'additionalProperties' => { 'type' => 'string' } } } } SCHEMAS = OpenAPI31::Meta::SCHEMAS .merge(Draft202012::Meta::SCHEMAS) .merge(Draft201909::Meta::SCHEMAS) .merge( URI('https://spec.openapis.org/oas/3.1/schema/2022-10-07') => SCHEMA, OpenAPI31::BASE_URI => OpenAPI31::SCHEMA, Draft202012::BASE_URI => Draft202012::SCHEMA, Draft201909::BASE_URI => Draft201909::SCHEMA, Draft7::BASE_URI.dup.tap { |uri| uri.fragment = nil } => Draft7::SCHEMA, Draft6::BASE_URI.dup.tap { |uri| uri.fragment = nil } => Draft6::SCHEMA, Draft4::BASE_URI.dup.tap { |uri| uri.fragment = nil } => Draft4::SCHEMA ) end end end json_schemer-2.4.0/lib/json_schemer/openapi31/meta.rb0000644000004100000410000001073214751003725022532 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module OpenAPI31 BASE_URI = URI('https://spec.openapis.org/oas/3.1/dialect/base') # https://spec.openapis.org/oas/v3.1.0#data-types FORMATS = { 'int32' => proc { |instance, _format| !Draft202012::Vocab::Validation::Type.valid_integer?(instance) || instance.floor.bit_length < 32 }, 'int64' => proc { |instance, _format| !Draft202012::Vocab::Validation::Type.valid_integer?(instance) || instance.floor.bit_length < 64 }, 'float' => proc { |instance, _format| !instance.is_a?(Numeric) || instance.is_a?(Float) }, 'double' => proc { |instance, _format| !instance.is_a?(Numeric) || instance.is_a?(Float) }, 'password' => proc { |_instance, _format| true } } SCHEMA = { '$id' => 'https://spec.openapis.org/oas/3.1/dialect/base', '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'title' => 'OpenAPI 3.1 Schema Object Dialect', 'description' => 'A JSON Schema dialect describing schemas found in OpenAPI documents', '$vocabulary' => { 'https://json-schema.org/draft/2020-12/vocab/core' => true, 'https://json-schema.org/draft/2020-12/vocab/applicator' => true, 'https://json-schema.org/draft/2020-12/vocab/unevaluated' => true, 'https://json-schema.org/draft/2020-12/vocab/validation' => true, 'https://json-schema.org/draft/2020-12/vocab/meta-data' => true, 'https://json-schema.org/draft/2020-12/vocab/format-annotation' => true, 'https://json-schema.org/draft/2020-12/vocab/content' => true, 'https://spec.openapis.org/oas/3.1/vocab/base' => false }, '$dynamicAnchor' => 'meta', 'allOf' => [ { '$ref' => 'https://json-schema.org/draft/2020-12/schema' }, { '$ref' => 'https://spec.openapis.org/oas/3.1/meta/base' } ] } module Meta BASE = { '$id' => 'https://spec.openapis.org/oas/3.1/meta/base', '$schema' => 'https://json-schema.org/draft/2020-12/schema', 'title' => 'OAS Base vocabulary', 'description' => 'A JSON Schema Vocabulary used in the OpenAPI Schema Dialect', '$vocabulary' => { 'https://spec.openapis.org/oas/3.1/vocab/base' => true }, '$dynamicAnchor' => 'meta', 'type' => ['object', 'boolean'], 'properties' => { 'example' => true, 'discriminator' => { '$ref' => '#/$defs/discriminator' }, 'externalDocs' => { '$ref' => '#/$defs/external-docs' }, 'xml' => { '$ref' => '#/$defs/xml' } }, '$defs' => { 'extensible' => { 'patternProperties' => { '^x-' => true } }, 'discriminator' => { '$ref' => '#/$defs/extensible', 'type' => 'object', 'properties' => { 'propertyName' => { 'type' => 'string' }, 'mapping' => { 'type' => 'object', 'additionalProperties' => { 'type' => 'string' } } }, 'required' => ['propertyName'], 'unevaluatedProperties' => false }, 'external-docs' => { '$ref' => '#/$defs/extensible', 'type' => 'object', 'properties' => { 'url' => { 'type' => 'string', 'format' => 'uri-reference' }, 'description' => { 'type' => 'string' } }, 'required' => ['url'], 'unevaluatedProperties' => false }, 'xml' => { '$ref' => '#/$defs/extensible', 'type' => 'object', 'properties' => { 'name' => { 'type' => 'string' }, 'namespace' => { 'type' => 'string', 'format' => 'uri' }, 'prefix' => { 'type' => 'string' }, 'attribute' => { 'type' => 'boolean' }, 'wrapped' => { 'type' => 'boolean' } }, 'unevaluatedProperties' => false } } } SCHEMAS = Draft202012::Meta::SCHEMAS.merge( Draft202012::BASE_URI => Draft202012::SCHEMA, URI('https://spec.openapis.org/oas/3.1/meta/base') => BASE ) end end end json_schemer-2.4.0/lib/json_schemer/openapi31/vocab.rb0000644000004100000410000000101114751003725022664 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module OpenAPI31 module Vocab # https://spec.openapis.org/oas/latest.html#schema-object BASE = { # https://spec.openapis.org/oas/latest.html#discriminator-object 'discriminator' => Base::Discriminator, 'allOf' => Base::AllOf, 'anyOf' => Base::AnyOf, 'oneOf' => Base::OneOf # 'xml' => Base::Xml, # 'externalDocs' => Base::ExternalDocs, # 'example' => Base::Example } end end end json_schemer-2.4.0/lib/json_schemer/openapi31/vocab/0000755000004100000410000000000014751003725022346 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/openapi31/vocab/base.rb0000644000004100000410000001141614751003725023610 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module OpenAPI31 module Vocab module Base class AllOf < Draft202012::Vocab::Applicator::AllOf attr_accessor :skip_ref_once def validate(instance, instance_location, keyword_location, context) nested = [] parsed.each_with_index do |subschema, index| if ref_schema = subschema.parsed['$ref']&.ref_schema next if skip_ref_once == ref_schema.absolute_keyword_location ref_schema.parsed['discriminator']&.skip_ref_once = schema.absolute_keyword_location end nested << subschema.validate_instance(instance, instance_location, join_location(keyword_location, index.to_s), context) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested) ensure self.skip_ref_once = nil end end class AnyOf < Draft202012::Vocab::Applicator::AnyOf def validate(*) schema.parsed.key?('discriminator') ? nil : super end end class OneOf < Draft202012::Vocab::Applicator::OneOf def validate(*) schema.parsed.key?('discriminator') ? nil : super end end class Discriminator < Keyword # https://spec.openapis.org/oas/v3.1.0#components-object FIXED_FIELD_REGEX = /\A[a-zA-Z0-9\.\-_]+$\z/ attr_accessor :skip_ref_once def error(formatted_instance_location:, **) "value at #{formatted_instance_location} does not match `discriminator` schema" end def mapping @mapping ||= value['mapping'] || {} end def subschemas_by_property_value @subschemas_by_property_value ||= if schema.parsed.key?('anyOf') || schema.parsed.key?('oneOf') subschemas = schema.parsed['anyOf']&.parsed || [] subschemas += schema.parsed['oneOf']&.parsed || [] subschemas_by_ref = {} subschemas_by_schema_name = {} subschemas.each do |subschema| subschema_ref = subschema.parsed.fetch('$ref').parsed subschemas_by_ref[subschema_ref] = subschema if subschema_ref.start_with?('#/components/schemas/') schema_name = subschema_ref.delete_prefix('#/components/schemas/') subschemas_by_schema_name[schema_name] = subschema if FIXED_FIELD_REGEX.match?(schema_name) end end explicit_mapping = mapping.transform_values do |schema_name_or_ref| subschemas_by_schema_name.fetch(schema_name_or_ref) { subschemas_by_ref.fetch(schema_name_or_ref) } end implicit_mapping = subschemas_by_schema_name.reject do |_schema_name, subschema| explicit_mapping.value?(subschema) end implicit_mapping.merge(explicit_mapping) else Hash.new do |hash, property_value| schema_name_or_ref = mapping.fetch(property_value, property_value) subschema = nil if FIXED_FIELD_REGEX.match?(schema_name_or_ref) subschema = begin schema.ref("#/components/schemas/#{schema_name_or_ref}") rescue InvalidRefPointer nil end end subschema ||= begin schema.ref(schema_name_or_ref) rescue InvalidRefResolution, UnknownRef nil end hash[property_value] = subschema end end end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, false) unless instance.is_a?(Hash) property_name = value.fetch('propertyName') return result(instance, instance_location, keyword_location, false) unless instance.key?(property_name) property_value = instance.fetch(property_name) subschema = subschemas_by_property_value[property_value] return result(instance, instance_location, keyword_location, false) unless subschema return if skip_ref_once == subschema.absolute_keyword_location subschema.parsed['allOf']&.skip_ref_once = schema.absolute_keyword_location subschema_result = subschema.validate_instance(instance, instance_location, keyword_location, context) result(instance, instance_location, keyword_location, subschema_result.valid, subschema_result.nested) ensure self.skip_ref_once = nil end end end end end end json_schemer-2.4.0/lib/json_schemer/openapi30/0000755000004100000410000000000014751003725021253 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/openapi30/document.rb0000644000004100000410000013512714751003725023427 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module OpenAPI30 module Document SCHEMA = { 'id' => 'https://spec.openapis.org/oas/3.0/schema/2021-09-28', '$schema' => 'http://json-schema.org/draft-04/schema#', 'description' => 'The description of OpenAPI v3.0.x documents, as defined by https://spec.openapis.org/oas/v3.0.3', 'type' => 'object', 'required' => [ 'openapi', 'info', 'paths' ], 'properties' => { 'openapi' => { 'type' => 'string', 'pattern' => '^3\.0\.\d(-.+)?$' }, 'info' => { '$ref' => '#/definitions/Info' }, 'externalDocs' => { '$ref' => '#/definitions/ExternalDocumentation' }, 'servers' => { 'type' => 'array', 'items' => { '$ref' => '#/definitions/Server' } }, 'security' => { 'type' => 'array', 'items' => { '$ref' => '#/definitions/SecurityRequirement' } }, 'tags' => { 'type' => 'array', 'items' => { '$ref' => '#/definitions/Tag' }, 'uniqueItems' => true }, 'paths' => { '$ref' => '#/definitions/Paths' }, 'components' => { '$ref' => '#/definitions/Components' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false, 'definitions' => { 'Reference' => { 'type' => 'object', 'required' => [ '$ref' ], 'patternProperties' => { '^\$ref$' => { 'type' => 'string', 'format' => 'uri-reference' } } }, 'Info' => { 'type' => 'object', 'required' => [ 'title', 'version' ], 'properties' => { 'title' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'termsOfService' => { 'type' => 'string', 'format' => 'uri-reference' }, 'contact' => { '$ref' => '#/definitions/Contact' }, 'license' => { '$ref' => '#/definitions/License' }, 'version' => { 'type' => 'string' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'Contact' => { 'type' => 'object', 'properties' => { 'name' => { 'type' => 'string' }, 'url' => { 'type' => 'string', 'format' => 'uri-reference' }, 'email' => { 'type' => 'string', 'format' => 'email' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'License' => { 'type' => 'object', 'required' => [ 'name' ], 'properties' => { 'name' => { 'type' => 'string' }, 'url' => { 'type' => 'string', 'format' => 'uri-reference' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'Server' => { 'type' => 'object', 'required' => [ 'url' ], 'properties' => { 'url' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'variables' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/definitions/ServerVariable' } } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'ServerVariable' => { 'type' => 'object', 'required' => [ 'default' ], 'properties' => { 'enum' => { 'type' => 'array', 'items' => { 'type' => 'string' } }, 'default' => { 'type' => 'string' }, 'description' => { 'type' => 'string' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'Components' => { 'type' => 'object', 'properties' => { 'schemas' => { 'type' => 'object', 'patternProperties' => { '^[a-zA-Z0-9\.\-_]+$' => { 'oneOf' => [ { '$ref' => '#/definitions/Schema' }, { '$ref' => '#/definitions/Reference' } ] } } }, 'responses' => { 'type' => 'object', 'patternProperties' => { '^[a-zA-Z0-9\.\-_]+$' => { 'oneOf' => [ { '$ref' => '#/definitions/Reference' }, { '$ref' => '#/definitions/Response' } ] } } }, 'parameters' => { 'type' => 'object', 'patternProperties' => { '^[a-zA-Z0-9\.\-_]+$' => { 'oneOf' => [ { '$ref' => '#/definitions/Reference' }, { '$ref' => '#/definitions/Parameter' } ] } } }, 'examples' => { 'type' => 'object', 'patternProperties' => { '^[a-zA-Z0-9\.\-_]+$' => { 'oneOf' => [ { '$ref' => '#/definitions/Reference' }, { '$ref' => '#/definitions/Example' } ] } } }, 'requestBodies' => { 'type' => 'object', 'patternProperties' => { '^[a-zA-Z0-9\.\-_]+$' => { 'oneOf' => [ { '$ref' => '#/definitions/Reference' }, { '$ref' => '#/definitions/RequestBody' } ] } } }, 'headers' => { 'type' => 'object', 'patternProperties' => { '^[a-zA-Z0-9\.\-_]+$' => { 'oneOf' => [ { '$ref' => '#/definitions/Reference' }, { '$ref' => '#/definitions/Header' } ] } } }, 'securitySchemes' => { 'type' => 'object', 'patternProperties' => { '^[a-zA-Z0-9\.\-_]+$' => { 'oneOf' => [ { '$ref' => '#/definitions/Reference' }, { '$ref' => '#/definitions/SecurityScheme' } ] } } }, 'links' => { 'type' => 'object', 'patternProperties' => { '^[a-zA-Z0-9\.\-_]+$' => { 'oneOf' => [ { '$ref' => '#/definitions/Reference' }, { '$ref' => '#/definitions/Link' } ] } } }, 'callbacks' => { 'type' => 'object', 'patternProperties' => { '^[a-zA-Z0-9\.\-_]+$' => { 'oneOf' => [ { '$ref' => '#/definitions/Reference' }, { '$ref' => '#/definitions/Callback' } ] } } } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'Schema' => { 'type' => 'object', 'properties' => { 'title' => { 'type' => 'string' }, 'multipleOf' => { 'type' => 'number', 'minimum' => 0, 'exclusiveMinimum' => true }, 'maximum' => { 'type' => 'number' }, 'exclusiveMaximum' => { 'type' => 'boolean', 'default' => false }, 'minimum' => { 'type' => 'number' }, 'exclusiveMinimum' => { 'type' => 'boolean', 'default' => false }, 'maxLength' => { 'type' => 'integer', 'minimum' => 0 }, 'minLength' => { 'type' => 'integer', 'minimum' => 0, 'default' => 0 }, 'pattern' => { 'type' => 'string', 'format' => 'regex' }, 'maxItems' => { 'type' => 'integer', 'minimum' => 0 }, 'minItems' => { 'type' => 'integer', 'minimum' => 0, 'default' => 0 }, 'uniqueItems' => { 'type' => 'boolean', 'default' => false }, 'maxProperties' => { 'type' => 'integer', 'minimum' => 0 }, 'minProperties' => { 'type' => 'integer', 'minimum' => 0, 'default' => 0 }, 'required' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'minItems' => 1, 'uniqueItems' => true }, 'enum' => { 'type' => 'array', 'items' => { }, 'minItems' => 1, 'uniqueItems' => false }, 'type' => { 'type' => 'string', 'enum' => [ 'array', 'boolean', 'integer', 'number', 'object', 'string' ] }, 'not' => { 'oneOf' => [ { '$ref' => '#/definitions/Schema' }, { '$ref' => '#/definitions/Reference' } ] }, 'allOf' => { 'type' => 'array', 'items' => { 'oneOf' => [ { '$ref' => '#/definitions/Schema' }, { '$ref' => '#/definitions/Reference' } ] } }, 'oneOf' => { 'type' => 'array', 'items' => { 'oneOf' => [ { '$ref' => '#/definitions/Schema' }, { '$ref' => '#/definitions/Reference' } ] } }, 'anyOf' => { 'type' => 'array', 'items' => { 'oneOf' => [ { '$ref' => '#/definitions/Schema' }, { '$ref' => '#/definitions/Reference' } ] } }, 'items' => { 'oneOf' => [ { '$ref' => '#/definitions/Schema' }, { '$ref' => '#/definitions/Reference' } ] }, 'properties' => { 'type' => 'object', 'additionalProperties' => { 'oneOf' => [ { '$ref' => '#/definitions/Schema' }, { '$ref' => '#/definitions/Reference' } ] } }, 'additionalProperties' => { 'oneOf' => [ { '$ref' => '#/definitions/Schema' }, { '$ref' => '#/definitions/Reference' }, { 'type' => 'boolean' } ], 'default' => true }, 'description' => { 'type' => 'string' }, 'format' => { 'type' => 'string' }, 'default' => { }, 'nullable' => { 'type' => 'boolean', 'default' => false }, 'discriminator' => { '$ref' => '#/definitions/Discriminator' }, 'readOnly' => { 'type' => 'boolean', 'default' => false }, 'writeOnly' => { 'type' => 'boolean', 'default' => false }, 'example' => { }, 'externalDocs' => { '$ref' => '#/definitions/ExternalDocumentation' }, 'deprecated' => { 'type' => 'boolean', 'default' => false }, 'xml' => { '$ref' => '#/definitions/XML' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'Discriminator' => { 'type' => 'object', 'required' => [ 'propertyName' ], 'properties' => { 'propertyName' => { 'type' => 'string' }, 'mapping' => { 'type' => 'object', 'additionalProperties' => { 'type' => 'string' } } } }, 'XML' => { 'type' => 'object', 'properties' => { 'name' => { 'type' => 'string' }, 'namespace' => { 'type' => 'string', 'format' => 'uri' }, 'prefix' => { 'type' => 'string' }, 'attribute' => { 'type' => 'boolean', 'default' => false }, 'wrapped' => { 'type' => 'boolean', 'default' => false } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'Response' => { 'type' => 'object', 'required' => [ 'description' ], 'properties' => { 'description' => { 'type' => 'string' }, 'headers' => { 'type' => 'object', 'additionalProperties' => { 'oneOf' => [ { '$ref' => '#/definitions/Header' }, { '$ref' => '#/definitions/Reference' } ] } }, 'content' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/definitions/MediaType' } }, 'links' => { 'type' => 'object', 'additionalProperties' => { 'oneOf' => [ { '$ref' => '#/definitions/Link' }, { '$ref' => '#/definitions/Reference' } ] } } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'MediaType' => { 'type' => 'object', 'properties' => { 'schema' => { 'oneOf' => [ { '$ref' => '#/definitions/Schema' }, { '$ref' => '#/definitions/Reference' } ] }, 'example' => { }, 'examples' => { 'type' => 'object', 'additionalProperties' => { 'oneOf' => [ { '$ref' => '#/definitions/Example' }, { '$ref' => '#/definitions/Reference' } ] } }, 'encoding' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/definitions/Encoding' } } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false, 'allOf' => [ { '$ref' => '#/definitions/ExampleXORExamples' } ] }, 'Example' => { 'type' => 'object', 'properties' => { 'summary' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'value' => { }, 'externalValue' => { 'type' => 'string', 'format' => 'uri-reference' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'Header' => { 'type' => 'object', 'properties' => { 'description' => { 'type' => 'string' }, 'required' => { 'type' => 'boolean', 'default' => false }, 'deprecated' => { 'type' => 'boolean', 'default' => false }, 'allowEmptyValue' => { 'type' => 'boolean', 'default' => false }, 'style' => { 'type' => 'string', 'enum' => [ 'simple' ], 'default' => 'simple' }, 'explode' => { 'type' => 'boolean' }, 'allowReserved' => { 'type' => 'boolean', 'default' => false }, 'schema' => { 'oneOf' => [ { '$ref' => '#/definitions/Schema' }, { '$ref' => '#/definitions/Reference' } ] }, 'content' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/definitions/MediaType' }, 'minProperties' => 1, 'maxProperties' => 1 }, 'example' => { }, 'examples' => { 'type' => 'object', 'additionalProperties' => { 'oneOf' => [ { '$ref' => '#/definitions/Example' }, { '$ref' => '#/definitions/Reference' } ] } } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false, 'allOf' => [ { '$ref' => '#/definitions/ExampleXORExamples' }, { '$ref' => '#/definitions/SchemaXORContent' } ] }, 'Paths' => { 'type' => 'object', 'patternProperties' => { '^\/' => { '$ref' => '#/definitions/PathItem' }, '^x-' => { } }, 'additionalProperties' => false }, 'PathItem' => { 'type' => 'object', 'properties' => { '$ref' => { 'type' => 'string' }, 'summary' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'servers' => { 'type' => 'array', 'items' => { '$ref' => '#/definitions/Server' } }, 'parameters' => { 'type' => 'array', 'items' => { 'oneOf' => [ { '$ref' => '#/definitions/Parameter' }, { '$ref' => '#/definitions/Reference' } ] }, 'uniqueItems' => true } }, 'patternProperties' => { '^(get|put|post|delete|options|head|patch|trace)$' => { '$ref' => '#/definitions/Operation' }, '^x-' => { } }, 'additionalProperties' => false }, 'Operation' => { 'type' => 'object', 'required' => [ 'responses' ], 'properties' => { 'tags' => { 'type' => 'array', 'items' => { 'type' => 'string' } }, 'summary' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'externalDocs' => { '$ref' => '#/definitions/ExternalDocumentation' }, 'operationId' => { 'type' => 'string' }, 'parameters' => { 'type' => 'array', 'items' => { 'oneOf' => [ { '$ref' => '#/definitions/Parameter' }, { '$ref' => '#/definitions/Reference' } ] }, 'uniqueItems' => true }, 'requestBody' => { 'oneOf' => [ { '$ref' => '#/definitions/RequestBody' }, { '$ref' => '#/definitions/Reference' } ] }, 'responses' => { '$ref' => '#/definitions/Responses' }, 'callbacks' => { 'type' => 'object', 'additionalProperties' => { 'oneOf' => [ { '$ref' => '#/definitions/Callback' }, { '$ref' => '#/definitions/Reference' } ] } }, 'deprecated' => { 'type' => 'boolean', 'default' => false }, 'security' => { 'type' => 'array', 'items' => { '$ref' => '#/definitions/SecurityRequirement' } }, 'servers' => { 'type' => 'array', 'items' => { '$ref' => '#/definitions/Server' } } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'Responses' => { 'type' => 'object', 'properties' => { 'default' => { 'oneOf' => [ { '$ref' => '#/definitions/Response' }, { '$ref' => '#/definitions/Reference' } ] } }, 'patternProperties' => { '^[1-5](?:\d{2}|XX)$' => { 'oneOf' => [ { '$ref' => '#/definitions/Response' }, { '$ref' => '#/definitions/Reference' } ] }, '^x-' => { } }, 'minProperties' => 1, 'additionalProperties' => false }, 'SecurityRequirement' => { 'type' => 'object', 'additionalProperties' => { 'type' => 'array', 'items' => { 'type' => 'string' } } }, 'Tag' => { 'type' => 'object', 'required' => [ 'name' ], 'properties' => { 'name' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'externalDocs' => { '$ref' => '#/definitions/ExternalDocumentation' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'ExternalDocumentation' => { 'type' => 'object', 'required' => [ 'url' ], 'properties' => { 'description' => { 'type' => 'string' }, 'url' => { 'type' => 'string', 'format' => 'uri-reference' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'ExampleXORExamples' => { 'description' => 'Example and examples are mutually exclusive', 'not' => { 'required' => [ 'example', 'examples' ] } }, 'SchemaXORContent' => { 'description' => 'Schema and content are mutually exclusive, at least one is required', 'not' => { 'required' => [ 'schema', 'content' ] }, 'oneOf' => [ { 'required' => [ 'schema' ] }, { 'required' => [ 'content' ], 'description' => 'Some properties are not allowed if content is present', 'allOf' => [ { 'not' => { 'required' => [ 'style' ] } }, { 'not' => { 'required' => [ 'explode' ] } }, { 'not' => { 'required' => [ 'allowReserved' ] } }, { 'not' => { 'required' => [ 'example' ] } }, { 'not' => { 'required' => [ 'examples' ] } } ] } ] }, 'Parameter' => { 'type' => 'object', 'properties' => { 'name' => { 'type' => 'string' }, 'in' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'required' => { 'type' => 'boolean', 'default' => false }, 'deprecated' => { 'type' => 'boolean', 'default' => false }, 'allowEmptyValue' => { 'type' => 'boolean', 'default' => false }, 'style' => { 'type' => 'string' }, 'explode' => { 'type' => 'boolean' }, 'allowReserved' => { 'type' => 'boolean', 'default' => false }, 'schema' => { 'oneOf' => [ { '$ref' => '#/definitions/Schema' }, { '$ref' => '#/definitions/Reference' } ] }, 'content' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/definitions/MediaType' }, 'minProperties' => 1, 'maxProperties' => 1 }, 'example' => { }, 'examples' => { 'type' => 'object', 'additionalProperties' => { 'oneOf' => [ { '$ref' => '#/definitions/Example' }, { '$ref' => '#/definitions/Reference' } ] } } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false, 'required' => [ 'name', 'in' ], 'allOf' => [ { '$ref' => '#/definitions/ExampleXORExamples' }, { '$ref' => '#/definitions/SchemaXORContent' }, { '$ref' => '#/definitions/ParameterLocation' } ] }, 'ParameterLocation' => { 'description' => 'Parameter location', 'oneOf' => [ { 'description' => 'Parameter in path', 'required' => [ 'required' ], 'properties' => { 'in' => { 'enum' => [ 'path' ] }, 'style' => { 'enum' => [ 'matrix', 'label', 'simple' ], 'default' => 'simple' }, 'required' => { 'enum' => [ true ] } } }, { 'description' => 'Parameter in query', 'properties' => { 'in' => { 'enum' => [ 'query' ] }, 'style' => { 'enum' => [ 'form', 'spaceDelimited', 'pipeDelimited', 'deepObject' ], 'default' => 'form' } } }, { 'description' => 'Parameter in header', 'properties' => { 'in' => { 'enum' => [ 'header' ] }, 'style' => { 'enum' => [ 'simple' ], 'default' => 'simple' } } }, { 'description' => 'Parameter in cookie', 'properties' => { 'in' => { 'enum' => [ 'cookie' ] }, 'style' => { 'enum' => [ 'form' ], 'default' => 'form' } } } ] }, 'RequestBody' => { 'type' => 'object', 'required' => [ 'content' ], 'properties' => { 'description' => { 'type' => 'string' }, 'content' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/definitions/MediaType' } }, 'required' => { 'type' => 'boolean', 'default' => false } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'SecurityScheme' => { 'oneOf' => [ { '$ref' => '#/definitions/APIKeySecurityScheme' }, { '$ref' => '#/definitions/HTTPSecurityScheme' }, { '$ref' => '#/definitions/OAuth2SecurityScheme' }, { '$ref' => '#/definitions/OpenIdConnectSecurityScheme' } ] }, 'APIKeySecurityScheme' => { 'type' => 'object', 'required' => [ 'type', 'name', 'in' ], 'properties' => { 'type' => { 'type' => 'string', 'enum' => [ 'apiKey' ] }, 'name' => { 'type' => 'string' }, 'in' => { 'type' => 'string', 'enum' => [ 'header', 'query', 'cookie' ] }, 'description' => { 'type' => 'string' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'HTTPSecurityScheme' => { 'type' => 'object', 'required' => [ 'scheme', 'type' ], 'properties' => { 'scheme' => { 'type' => 'string' }, 'bearerFormat' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'type' => { 'type' => 'string', 'enum' => [ 'http' ] } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false, 'oneOf' => [ { 'description' => 'Bearer', 'properties' => { 'scheme' => { 'type' => 'string', 'pattern' => '^[Bb][Ee][Aa][Rr][Ee][Rr]$' } } }, { 'description' => 'Non Bearer', 'not' => { 'required' => [ 'bearerFormat' ] }, 'properties' => { 'scheme' => { 'not' => { 'type' => 'string', 'pattern' => '^[Bb][Ee][Aa][Rr][Ee][Rr]$' } } } } ] }, 'OAuth2SecurityScheme' => { 'type' => 'object', 'required' => [ 'type', 'flows' ], 'properties' => { 'type' => { 'type' => 'string', 'enum' => [ 'oauth2' ] }, 'flows' => { '$ref' => '#/definitions/OAuthFlows' }, 'description' => { 'type' => 'string' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'OpenIdConnectSecurityScheme' => { 'type' => 'object', 'required' => [ 'type', 'openIdConnectUrl' ], 'properties' => { 'type' => { 'type' => 'string', 'enum' => [ 'openIdConnect' ] }, 'openIdConnectUrl' => { 'type' => 'string', 'format' => 'uri-reference' }, 'description' => { 'type' => 'string' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'OAuthFlows' => { 'type' => 'object', 'properties' => { 'implicit' => { '$ref' => '#/definitions/ImplicitOAuthFlow' }, 'password' => { '$ref' => '#/definitions/PasswordOAuthFlow' }, 'clientCredentials' => { '$ref' => '#/definitions/ClientCredentialsFlow' }, 'authorizationCode' => { '$ref' => '#/definitions/AuthorizationCodeOAuthFlow' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'ImplicitOAuthFlow' => { 'type' => 'object', 'required' => [ 'authorizationUrl', 'scopes' ], 'properties' => { 'authorizationUrl' => { 'type' => 'string', 'format' => 'uri-reference' }, 'refreshUrl' => { 'type' => 'string', 'format' => 'uri-reference' }, 'scopes' => { 'type' => 'object', 'additionalProperties' => { 'type' => 'string' } } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'PasswordOAuthFlow' => { 'type' => 'object', 'required' => [ 'tokenUrl', 'scopes' ], 'properties' => { 'tokenUrl' => { 'type' => 'string', 'format' => 'uri-reference' }, 'refreshUrl' => { 'type' => 'string', 'format' => 'uri-reference' }, 'scopes' => { 'type' => 'object', 'additionalProperties' => { 'type' => 'string' } } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'ClientCredentialsFlow' => { 'type' => 'object', 'required' => [ 'tokenUrl', 'scopes' ], 'properties' => { 'tokenUrl' => { 'type' => 'string', 'format' => 'uri-reference' }, 'refreshUrl' => { 'type' => 'string', 'format' => 'uri-reference' }, 'scopes' => { 'type' => 'object', 'additionalProperties' => { 'type' => 'string' } } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'AuthorizationCodeOAuthFlow' => { 'type' => 'object', 'required' => [ 'authorizationUrl', 'tokenUrl', 'scopes' ], 'properties' => { 'authorizationUrl' => { 'type' => 'string', 'format' => 'uri-reference' }, 'tokenUrl' => { 'type' => 'string', 'format' => 'uri-reference' }, 'refreshUrl' => { 'type' => 'string', 'format' => 'uri-reference' }, 'scopes' => { 'type' => 'object', 'additionalProperties' => { 'type' => 'string' } } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false }, 'Link' => { 'type' => 'object', 'properties' => { 'operationId' => { 'type' => 'string' }, 'operationRef' => { 'type' => 'string' }, 'parameters' => { 'type' => 'object', 'additionalProperties' => { } }, 'requestBody' => { }, 'description' => { 'type' => 'string' }, 'server' => { '$ref' => '#/definitions/Server' } }, 'patternProperties' => { '^x-' => { } }, 'additionalProperties' => false, 'not' => { 'description' => 'Operation Id and Operation Ref are mutually exclusive', 'required' => [ 'operationId', 'operationRef' ] } }, 'Callback' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/definitions/PathItem' }, 'patternProperties' => { '^x-' => { } } }, 'Encoding' => { 'type' => 'object', 'properties' => { 'contentType' => { 'type' => 'string' }, 'headers' => { 'type' => 'object', 'additionalProperties' => { 'oneOf' => [ { '$ref' => '#/definitions/Header' }, { '$ref' => '#/definitions/Reference' } ] } }, 'style' => { 'type' => 'string', 'enum' => [ 'form', 'spaceDelimited', 'pipeDelimited', 'deepObject' ] }, 'explode' => { 'type' => 'boolean' }, 'allowReserved' => { 'type' => 'boolean', 'default' => false } }, 'additionalProperties' => false } } } SCHEMAS = { Draft4::BASE_URI.dup.tap { |uri| uri.fragment = nil } => Draft4::SCHEMA } end end end json_schemer-2.4.0/lib/json_schemer/openapi30/meta.rb0000644000004100000410000000267614751003725022541 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module OpenAPI30 BASE_URI = URI('json-schemer://openapi30/schema') # https://spec.openapis.org/oas/v3.0.3#data-types FORMATS = OpenAPI31::FORMATS.merge( 'int32' => proc { |instance, _format| !Draft4::Vocab::Validation::Type.valid_integer?(instance) || instance.floor.bit_length < 32 }, 'int64' => proc { |instance, _format| !Draft4::Vocab::Validation::Type.valid_integer?(instance) || instance.floor.bit_length < 64 }, 'byte' => proc { |instance, _value| !instance.is_a?(String) || ContentEncoding::BASE64.call(instance).first }, 'binary' => proc { |instance, _value| !instance.is_a?(String) || instance.encoding == Encoding::BINARY }, 'date' => Format::DATE ) SCHEMA = { 'id' => 'json-schemer://openapi30/schema', '$schema' => 'http://json-schema.org/draft-04/schema#', 'allOf' => [ { '$ref' => 'http://json-schema.org/draft-04/schema#' }, { 'oneOf' => [ { '$ref' => 'https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Schema' }, { '$ref' => 'https://spec.openapis.org/oas/3.0/schema/2021-09-28#/definitions/Reference' } ] } ] } module Meta SCHEMAS = { Draft4::BASE_URI.dup.tap { |uri| uri.fragment = nil } => Draft4::SCHEMA, URI('https://spec.openapis.org/oas/3.0/schema/2021-09-28') => Document::SCHEMA } end end end json_schemer-2.4.0/lib/json_schemer/openapi30/vocab.rb0000644000004100000410000000046114751003725022673 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module OpenAPI30 module Vocab # https://spec.openapis.org/oas/v3.0.3#schema-object BASE = OpenAPI31::Vocab::BASE.merge( # https://spec.openapis.org/oas/v3.0.3#fixed-fields-19 'type' => Base::Type ) end end end json_schemer-2.4.0/lib/json_schemer/openapi30/vocab/0000755000004100000410000000000014751003725022345 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/openapi30/vocab/base.rb0000644000004100000410000000057014751003725023606 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module OpenAPI30 module Vocab module Base class Type < Draft4::Vocab::Validation::Type def parse if schema.value['nullable'] == true (Array(value) + ['null']).uniq else super end end end end end end end json_schemer-2.4.0/lib/json_schemer/location.rb0000644000004100000410000000131414751003725021611 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Location JSON_POINTER_TOKEN_ESCAPE_CHARS = { '~' => '~0', '/' => '~1' } JSON_POINTER_TOKEN_ESCAPE_REGEX = Regexp.union(JSON_POINTER_TOKEN_ESCAPE_CHARS.keys) class << self def root {} end def join(location, name) location[name] ||= { :name => name, :parent => location } end def resolve(location) location[:resolve] ||= location[:parent] ? "#{resolve(location[:parent])}/#{escape_json_pointer_token(location[:name])}" : '' end def escape_json_pointer_token(token) token.gsub(JSON_POINTER_TOKEN_ESCAPE_REGEX, JSON_POINTER_TOKEN_ESCAPE_CHARS) end end end end json_schemer-2.4.0/lib/json_schemer/keyword.rb0000644000004100000410000000246214751003725021472 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer class Keyword include Output attr_reader :value, :parent, :root, :parsed def initialize(value, parent, keyword, schema = parent) @value = value @parent = parent @root = parent.root @keyword = keyword @schema = schema @parsed = parse end def validate(_instance, _instance_location, _keyword_location, _context) nil end def absolute_keyword_location @absolute_keyword_location ||= "#{parent.absolute_keyword_location}/#{fragment_encode(escaped_keyword)}" end def schema_pointer @schema_pointer ||= "#{parent.schema_pointer}/#{escaped_keyword}" end def error_key keyword end def fetch(key) parsed.fetch(parsed.is_a?(Array) ? key.to_i : key) end def parsed_schema parsed.is_a?(Schema) ? parsed : nil end private def parse value end def subschema(value, keyword = nil, **options) options[:configuration] ||= schema.configuration options[:base_uri] ||= schema.base_uri options[:meta_schema] ||= schema.meta_schema options[:ref_resolver] ||= schema.ref_resolver options[:regexp_resolver] ||= schema.regexp_resolver Schema.new(value, self, root, keyword, **options) end end end json_schemer-2.4.0/lib/json_schemer/draft4/0000755000004100000410000000000014751003725020641 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/draft4/meta.rb0000644000004100000410000001136314751003725022120 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft4 BASE_URI = URI('http://json-schema.org/draft-04/schema#') FORMATS = Draft6::FORMATS.dup FORMATS.delete('uri-reference') FORMATS.delete('uri-template') FORMATS.delete('json-pointer') CONTENT_ENCODINGS = Draft6::CONTENT_ENCODINGS CONTENT_MEDIA_TYPES = Draft6::CONTENT_MEDIA_TYPES SCHEMA = { 'id' => 'http://json-schema.org/draft-04/schema#', '$schema' => 'http://json-schema.org/draft-04/schema#', 'description' => 'Core schema meta-schema', 'definitions' => { 'schemaArray' => { 'type' => 'array', 'minItems' => 1, 'items' => { '$ref' => '#' } }, 'positiveInteger' => { 'type' => 'integer', 'minimum' => 0 }, 'positiveIntegerDefault0' => { 'allOf' => [ { '$ref' => '#/definitions/positiveInteger' }, { 'default' => 0 } ] }, 'simpleTypes' => { 'enum' => [ 'array', 'boolean', 'integer', 'null', 'number', 'object', 'string' ] }, 'stringArray' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'minItems' => 1, 'uniqueItems' => true } }, 'type' => 'object', 'properties' => { 'id' => { 'type' => 'string' }, '$schema' => { 'type' => 'string' }, 'title' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'default' => {}, 'multipleOf' => { 'type' => 'number', 'minimum' => 0, 'exclusiveMinimum' => true }, 'maximum' => { 'type' => 'number' }, 'exclusiveMaximum' => { 'type' => 'boolean', 'default' => false }, 'minimum' => { 'type' => 'number' }, 'exclusiveMinimum' => { 'type' => 'boolean', 'default' => false }, 'maxLength' => { '$ref' => '#/definitions/positiveInteger' }, 'minLength' => { '$ref' => '#/definitions/positiveIntegerDefault0' }, 'pattern' => { 'type' => 'string', 'format' => 'regex' }, 'additionalItems' => { 'anyOf' => [ { 'type' => 'boolean' }, { '$ref' => '#' } ], 'default' => {} }, 'items' => { 'anyOf' => [ { '$ref' => '#' }, { '$ref' => '#/definitions/schemaArray' } ], 'default' => {} }, 'maxItems' => { '$ref' => '#/definitions/positiveInteger' }, 'minItems' => { '$ref' => '#/definitions/positiveIntegerDefault0' }, 'uniqueItems' => { 'type' => 'boolean', 'default' => false }, 'maxProperties' => { '$ref' => '#/definitions/positiveInteger' }, 'minProperties' => { '$ref' => '#/definitions/positiveIntegerDefault0' }, 'required' => { '$ref' => '#/definitions/stringArray' }, 'additionalProperties' => { 'anyOf' => [ { 'type' => 'boolean' }, { '$ref' => '#' } ], 'default' => {} }, 'definitions' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#' }, 'default' => {} }, 'properties' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#' }, 'default' => {} }, 'patternProperties' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#' }, 'default' => {} }, 'dependencies' => { 'type' => 'object', 'additionalProperties' => { 'anyOf' => [ { '$ref' => '#' }, { '$ref' => '#/definitions/stringArray' } ] } }, 'enum' => { 'type' => 'array', 'minItems' => 1, 'uniqueItems' => true }, 'type' => { 'anyOf' => [ { '$ref' => '#/definitions/simpleTypes' }, { 'type' => 'array', 'items' => { '$ref' => '#/definitions/simpleTypes' }, 'minItems' => 1, 'uniqueItems' => true } ] }, 'format' => { 'type' => 'string' }, 'allOf' => { '$ref' => '#/definitions/schemaArray' }, 'anyOf' => { '$ref' => '#/definitions/schemaArray' }, 'oneOf' => { '$ref' => '#/definitions/schemaArray' }, 'not' => { '$ref' => '#' } }, 'dependencies' => { 'exclusiveMaximum' => [ 'maximum' ], 'exclusiveMinimum' => [ 'minimum' ] }, 'default' => {} } end end json_schemer-2.4.0/lib/json_schemer/draft4/vocab.rb0000644000004100000410000000076014751003725022263 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft4 module Vocab ALL = Draft6::Vocab::ALL.dup ALL.transform_keys! { |key| key == '$id' ? 'id' : key } ALL.delete('contains') ALL.delete('propertyNames') ALL.delete('const') ALL.delete('examples') ALL.merge!( 'type' => Validation::Type, 'exclusiveMaximum' => Validation::ExclusiveMaximum, 'exclusiveMinimum' => Validation::ExclusiveMinimum ) end end end json_schemer-2.4.0/lib/json_schemer/draft4/vocab/0000755000004100000410000000000014751003725021733 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/draft4/vocab/validation.rb0000644000004100000410000000251714751003725024417 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft4 module Vocab module Validation class Type < Draft202012::Vocab::Validation::Type def self.valid_integer?(instance) instance.is_a?(Integer) end end class ExclusiveMaximum < Keyword def error(formatted_instance_location:, **) "number at #{formatted_instance_location} is greater than or equal to `maximum`" end def validate(instance, instance_location, keyword_location, _context) maximum = schema.parsed.fetch('maximum').parsed valid = !instance.is_a?(Numeric) || !value || !maximum || instance < maximum result(instance, instance_location, keyword_location, valid) end end class ExclusiveMinimum < Keyword def error(formatted_instance_location:, **) "number at #{formatted_instance_location} is less than or equal to `minimum`" end def validate(instance, instance_location, keyword_location, _context) minimum = schema.parsed.fetch('minimum').parsed valid = !instance.is_a?(Numeric) || !value || !minimum || instance > minimum result(instance, instance_location, keyword_location, valid) end end end end end end json_schemer-2.4.0/lib/json_schemer/version.rb0000644000004100000410000000011114751003725021460 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer VERSION = '2.4.0' end json_schemer-2.4.0/lib/json_schemer/content.rb0000644000004100000410000000051114751003725021451 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module ContentEncoding BASE64 = proc do |instance| [true, instance.unpack1("m0")] rescue [false, nil] end end module ContentMediaType JSON = proc do |instance| [true, ::JSON.parse(instance)] rescue [false, nil] end end end json_schemer-2.4.0/lib/json_schemer/openapi.rb0000644000004100000410000000166414751003725021444 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer class OpenAPI def initialize(document, **options) @document = document version = document['openapi'] case version when /\A3\.1\.\d+\z/ @document_schema = JSONSchemer.openapi31_document meta_schema = document.fetch('jsonSchemaDialect') { OpenAPI31::BASE_URI.to_s } when /\A3\.0\.\d+\z/ @document_schema = JSONSchemer.openapi30_document meta_schema = OpenAPI30::BASE_URI.to_s else raise UnsupportedOpenAPIVersion, version end @schema = JSONSchemer.schema(@document, :meta_schema => meta_schema, **options) end def valid? @document_schema.valid?(@document) end def validate(**options) @document_schema.validate(@document, **options) end def ref(value) @schema.ref(value) end def schema(name) ref("#/components/schemas/#{name}") end end end json_schemer-2.4.0/lib/json_schemer/errors.rb0000644000004100000410000000220414751003725021314 0ustar www-datawww-data# frozen_string_literal: true # Based on code from @robacarp found in issue 48: # https://github.com/davishmcclurg/json_schemer/issues/48 # module JSONSchemer module Errors class << self def pretty(error) data_pointer, type, schema = error.values_at('data_pointer', 'type', 'schema') location = data_pointer.empty? ? 'root' : "property '#{data_pointer}'" case type when 'required' keys = error.fetch('details').fetch('missing_keys').join(', ') "#{location} is missing required keys: #{keys}" when 'null', 'string', 'boolean', 'integer', 'number', 'array', 'object' "#{location} is not of type: #{type}" when 'pattern' "#{location} does not match pattern: #{schema.fetch('pattern')}" when 'format' "#{location} does not match format: #{schema.fetch('format')}" when 'const' "#{location} is not: #{schema.fetch('const').inspect}" when 'enum' "#{location} is not one of: #{schema.fetch('enum')}" else "#{location} is invalid: error_type=#{type}" end end end end end json_schemer-2.4.0/lib/json_schemer/result.rb0000644000004100000410000001777314751003725021337 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer CATCHALL = '*' I18N_SEPARATOR = "\x1F" # unit separator I18N_SCOPE = 'json_schemer' I18N_ERRORS_SCOPE = "#{I18N_SCOPE}#{I18N_SEPARATOR}errors" X_ERROR_REGEX = /%\{(instance|instanceLocation|keywordLocation|absoluteKeywordLocation)\}/ CLASSIC_ERROR_TYPES = Hash.new do |hash, klass| hash[klass] = klass.name.rpartition('::').last.sub(/\A[[:alpha:]]/, &:downcase) end Result = Struct.new(:source, :instance, :instance_location, :keyword_location, :valid, :nested, :type, :annotation, :details, :ignore_nested, :nested_key) do def output(output_format) case output_format when 'classic' classic when 'flag' flag when 'basic' basic when 'detailed' detailed when 'verbose' verbose else raise UnknownOutputFormat, output_format end end def error return @error if defined?(@error) if source.x_error # not using sprintf because it warns: "too many arguments for format string" @error = source.x_error.gsub( X_ERROR_REGEX, '%{instance}' => instance, '%{instanceLocation}' => Location.resolve(instance_location), '%{keywordLocation}' => Location.resolve(keyword_location), '%{absoluteKeywordLocation}' => source.absolute_keyword_location ) @x_error = true else resolved_instance_location = Location.resolve(instance_location) formatted_instance_location = resolved_instance_location.empty? ? 'root' : "`#{resolved_instance_location}`" @error = source.error(:formatted_instance_location => formatted_instance_location, :details => details) if i18n? begin @error = i18n! @i18n = true rescue I18n::MissingTranslationData end end end @error end def i18n? return @@i18n if defined?(@@i18n) @@i18n = defined?(I18n) && I18n.exists?(I18N_SCOPE) end def i18n! base_uri_str = source.schema.base_uri.to_s meta_schema_base_uri_str = source.schema.meta_schema.base_uri.to_s resolved_keyword_location = Location.resolve(keyword_location) error_key = source.error_key I18n.translate!( source.absolute_keyword_location, :default => [ "#{base_uri_str}#{I18N_SEPARATOR}##{resolved_keyword_location}", "##{resolved_keyword_location}", "#{base_uri_str}#{I18N_SEPARATOR}#{error_key}", "#{base_uri_str}#{I18N_SEPARATOR}#{CATCHALL}", "#{meta_schema_base_uri_str}#{I18N_SEPARATOR}#{error_key}", "#{meta_schema_base_uri_str}#{I18N_SEPARATOR}#{CATCHALL}", error_key, CATCHALL ].map!(&:to_sym), :separator => I18N_SEPARATOR, :scope => I18N_ERRORS_SCOPE, :instance => instance, :instanceLocation => Location.resolve(instance_location), :keywordLocation => resolved_keyword_location, :absoluteKeywordLocation => source.absolute_keyword_location ) end def to_output_unit out = { 'valid' => valid, 'keywordLocation' => Location.resolve(keyword_location), 'absoluteKeywordLocation' => source.absolute_keyword_location, 'instanceLocation' => Location.resolve(instance_location) } if valid out['annotation'] = annotation if annotation else out['error'] = error out['x-error'] = true if @x_error out['i18n'] = true if @i18n end out end def to_classic schema = source.schema out = { 'data' => instance, 'data_pointer' => Location.resolve(instance_location), 'schema' => schema.value, 'schema_pointer' => schema.schema_pointer, 'root_schema' => schema.root.value, 'type' => type || CLASSIC_ERROR_TYPES[source.class] } out['error'] = error out['x-error'] = true if @x_error out['i18n'] = true if @i18n out['details'] = details if details out end def flag { 'valid' => valid } end def basic out = to_output_unit if nested&.any? out[nested_key] = Enumerator.new do |yielder| results = [self] while result = results.pop if result.ignore_nested || !result.nested&.any? yielder << result.to_output_unit else previous_results_size = results.size result.nested.reverse_each do |nested_result| results << nested_result if nested_result.valid == valid end yielder << result.to_output_unit unless (results.size - previous_results_size) == 1 end end end end out end def detailed return to_output_unit if ignore_nested || !nested&.any? matching_results = nested.select { |nested_result| nested_result.valid == valid } if matching_results.size == 1 matching_results.first.detailed else out = to_output_unit if matching_results.any? out[nested_key] = Enumerator.new do |yielder| matching_results.each { |nested_result| yielder << nested_result.detailed } end end out end end def verbose out = to_output_unit if nested&.any? out[nested_key] = Enumerator.new do |yielder| nested.each { |nested_result| yielder << nested_result.verbose } end end out end def classic Enumerator.new do |yielder| unless valid results = [self] while result = results.pop if result.ignore_nested || !result.nested&.any? yielder << result.to_classic else previous_results_size = results.size result.nested.reverse_each do |nested_result| results << nested_result if nested_result.valid == valid end yielder << result.to_classic if (results.size - previous_results_size) == 0 end end end end end def insert_property_defaults(context) instance_locations = {} instance_locations.compare_by_identity results = [[self, true]] while (result, valid = results.pop) next if result.source.is_a?(Schema::NOT_KEYWORD_CLASS) valid &&= result.valid result.nested&.each { |nested_result| results << [nested_result, valid] } if result.source.is_a?(Schema::PROPERTIES_KEYWORD_CLASS) && result.instance.is_a?(Hash) result.source.parsed.each do |property, schema| next if result.instance.key?(property) next unless default = default_keyword_instance(schema) instance_location = Location.join(result.instance_location, property) keyword_location = Location.join(Location.join(result.keyword_location, property), default.keyword) default_result = default.validate(nil, instance_location, keyword_location, nil) instance_locations[result.instance_location] ||= {} instance_locations[result.instance_location][property] ||= [] instance_locations[result.instance_location][property] << [default_result, valid] end end end inserted = false instance_locations.each do |instance_location, properties| original_instance = context.original_instance(instance_location) properties.each do |property, results_with_tree_validity| property_inserted = yield(original_instance, property, results_with_tree_validity) inserted ||= (property_inserted != false) end end inserted end private def default_keyword_instance(schema) schema.parsed.fetch('default') do schema.parsed.find do |_keyword, keyword_instance| next unless keyword_instance.respond_to?(:ref_schema) next unless default = default_keyword_instance(keyword_instance.ref_schema) break default end end end end end json_schemer-2.4.0/lib/json_schemer/draft7/0000755000004100000410000000000014751003725020644 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/draft7/meta.rb0000644000004100000410000001251014751003725022116 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft7 BASE_URI = URI('http://json-schema.org/draft-07/schema#') FORMATS = Draft201909::FORMATS.dup FORMATS.delete('duration') FORMATS.delete('uuid') CONTENT_ENCODINGS = Draft201909::CONTENT_ENCODINGS CONTENT_MEDIA_TYPES = Draft201909::CONTENT_MEDIA_TYPES SCHEMA = { '$schema' => 'http://json-schema.org/draft-07/schema#', '$id' => 'http://json-schema.org/draft-07/schema#', 'title' => 'Core schema meta-schema', 'definitions' => { 'schemaArray' => { 'type' => 'array', 'minItems' => 1, 'items' => { '$ref' => '#' } }, 'nonNegativeInteger' => { 'type' => 'integer', 'minimum' => 0 }, 'nonNegativeIntegerDefault0' => { 'allOf' => [ { '$ref' => '#/definitions/nonNegativeInteger' }, { 'default' => 0 } ] }, 'simpleTypes' => { 'enum' => [ 'array', 'boolean', 'integer', 'null', 'number', 'object', 'string' ] }, 'stringArray' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'uniqueItems' => true, 'default' => [] } }, 'type' => ['object', 'boolean'], 'properties' => { '$id' => { 'type' => 'string', 'format' => 'uri-reference' }, '$schema' => { 'type' => 'string', 'format' => 'uri' }, '$ref' => { 'type' => 'string', 'format' => 'uri-reference' }, '$comment' => { 'type' => 'string' }, 'title' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'default' => true, 'readOnly' => { 'type' => 'boolean', 'default' => false }, 'writeOnly' => { 'type' => 'boolean', 'default' => false }, 'examples' => { 'type' => 'array', 'items' => true }, 'multipleOf' => { 'type' => 'number', 'exclusiveMinimum' => 0 }, 'maximum' => { 'type' => 'number' }, 'exclusiveMaximum' => { 'type' => 'number' }, 'minimum' => { 'type' => 'number' }, 'exclusiveMinimum' => { 'type' => 'number' }, 'maxLength' => { '$ref' => '#/definitions/nonNegativeInteger' }, 'minLength' => { '$ref' => '#/definitions/nonNegativeIntegerDefault0' }, 'pattern' => { 'type' => 'string', 'format' => 'regex' }, 'additionalItems' => { '$ref' => '#' }, 'items' => { 'anyOf' => [ { '$ref' => '#' }, { '$ref' => '#/definitions/schemaArray' } ], 'default' => true }, 'maxItems' => { '$ref' => '#/definitions/nonNegativeInteger' }, 'minItems' => { '$ref' => '#/definitions/nonNegativeIntegerDefault0' }, 'uniqueItems' => { 'type' => 'boolean', 'default' => false }, 'contains' => { '$ref' => '#' }, 'maxProperties' => { '$ref' => '#/definitions/nonNegativeInteger' }, 'minProperties' => { '$ref' => '#/definitions/nonNegativeIntegerDefault0' }, 'required' => { '$ref' => '#/definitions/stringArray' }, 'additionalProperties' => { '$ref' => '#' }, 'definitions' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#' }, 'default' => {} }, 'properties' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#' }, 'default' => {} }, 'patternProperties' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#' }, 'propertyNames' => { 'format' => 'regex' }, 'default' => {} }, 'dependencies' => { 'type' => 'object', 'additionalProperties' => { 'anyOf' => [ { '$ref' => '#' }, { '$ref' => '#/definitions/stringArray' } ] } }, 'propertyNames' => { '$ref' => '#' }, 'const' => true, 'enum' => { 'type' => 'array', 'items' => true, 'minItems' => 1, 'uniqueItems' => true }, 'type' => { 'anyOf' => [ { '$ref' => '#/definitions/simpleTypes' }, { 'type' => 'array', 'items' => { '$ref' => '#/definitions/simpleTypes' }, 'minItems' => 1, 'uniqueItems' => true } ] }, 'format' => { 'type' => 'string' }, 'contentMediaType' => { 'type' => 'string' }, 'contentEncoding' => { 'type' => 'string' }, 'if' => { '$ref' => '#' }, 'then' => { '$ref' => '#' }, 'else' => { '$ref' => '#' }, 'allOf' => { '$ref' => '#/definitions/schemaArray' }, 'anyOf' => { '$ref' => '#/definitions/schemaArray' }, 'oneOf' => { '$ref' => '#/definitions/schemaArray' }, 'not' => { '$ref' => '#' } }, 'default' => true } end end json_schemer-2.4.0/lib/json_schemer/draft7/vocab.rb0000644000004100000410000000173114751003725022265 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft7 module Vocab ALL = Draft201909::Vocab::CORE.dup ALL.delete('$recursiveAnchor') ALL.delete('$recursiveRef') ALL.delete('$vocabulary') ALL.delete('$anchor') ALL.delete('$defs') ALL.merge!(Draft201909::Vocab::APPLICATOR) ALL.delete('dependentSchemas') ALL.delete('unevaluatedItems') ALL.delete('unevaluatedProperties') ALL.merge!(Draft201909::Vocab::VALIDATION) ALL.delete('dependentRequired') ALL.delete('maxContains') ALL.delete('minContains') ALL.merge!(Draft202012::Vocab::FORMAT_ANNOTATION) ALL.merge!(Draft201909::Vocab::META_DATA) ALL.delete('deprecated') ALL.merge!( '$ref' => Validation::Ref, 'additionalItems' => Validation::AdditionalItems, 'contentEncoding' => Validation::ContentEncoding, 'contentMediaType' => Validation::ContentMediaType ) end end end json_schemer-2.4.0/lib/json_schemer/draft7/vocab/0000755000004100000410000000000014751003725021736 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/draft7/vocab/validation.rb0000644000004100000410000000505414751003725024421 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft7 module Vocab module Validation class Ref < Draft202012::Vocab::Core::Ref def self.exclusive? true end end class AdditionalItems < Keyword def error(formatted_instance_location:, **) "array items at #{formatted_instance_location} do not match `additionalItems` schema" end def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) items = schema.parsed['items']&.parsed if !instance.is_a?(Array) || !items.is_a?(Array) || items.size >= instance.size return result(instance, instance_location, keyword_location, true) end offset = items.size nested = instance.slice(offset..-1).map.with_index do |item, index| parsed.validate_instance(item, join_location(instance_location, (offset + index).to_s), keyword_location, context) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => nested.any?) end end class ContentEncoding < Draft202012::Vocab::Content::ContentEncoding def error(formatted_instance_location:, **) "string at #{formatted_instance_location} could not be decoded using encoding: #{value}" end def validate(instance, instance_location, keyword_location, _context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String) valid, annotation = parsed.call(instance) result(instance, instance_location, keyword_location, valid, :annotation => annotation) end end class ContentMediaType < Draft202012::Vocab::Content::ContentMediaType def error(formatted_instance_location:, **) "string at #{formatted_instance_location} could not be parsed using media type: #{value}" end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String) decoded_instance = context.adjacent_results[ContentEncoding]&.annotation || instance valid, annotation = parsed.call(decoded_instance) result(instance, instance_location, keyword_location, valid, :annotation => annotation) end end end end end end json_schemer-2.4.0/lib/json_schemer/draft201909/0000755000004100000410000000000014751003725021242 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/draft201909/meta.rb0000644000004100000410000002541014751003725022517 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft201909 BASE_URI = URI('https://json-schema.org/draft/2019-09/schema') FORMATS = Draft202012::FORMATS CONTENT_ENCODINGS = Draft202012::CONTENT_ENCODINGS CONTENT_MEDIA_TYPES = Draft202012::CONTENT_MEDIA_TYPES SCHEMA = { '$schema' => 'https://json-schema.org/draft/2019-09/schema', '$id' => 'https://json-schema.org/draft/2019-09/schema', '$vocabulary' => { 'https://json-schema.org/draft/2019-09/vocab/core' => true, 'https://json-schema.org/draft/2019-09/vocab/applicator' => true, 'https://json-schema.org/draft/2019-09/vocab/validation' => true, 'https://json-schema.org/draft/2019-09/vocab/meta-data' => true, 'https://json-schema.org/draft/2019-09/vocab/format' => false, 'https://json-schema.org/draft/2019-09/vocab/content' => true }, '$recursiveAnchor' => true, 'title' => 'Core and Validation specifications meta-schema', 'allOf' => [ {'$ref' => 'meta/core'}, {'$ref' => 'meta/applicator'}, {'$ref' => 'meta/validation'}, {'$ref' => 'meta/meta-data'}, {'$ref' => 'meta/format'}, {'$ref' => 'meta/content'} ], 'type' => ['object', 'boolean'], 'properties' => { 'definitions' => { '$comment' => 'While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.', 'type' => 'object', 'additionalProperties' => { '$recursiveRef' => '#' }, 'default' => {} }, 'dependencies' => { '$comment' => '"dependencies" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to "dependentSchemas" and "dependentRequired"', 'type' => 'object', 'additionalProperties' => { 'anyOf' => [ { '$recursiveRef' => '#' }, { '$ref' => 'meta/validation#/$defs/stringArray' } ] } } } } module Meta CORE = { '$schema' => 'https://json-schema.org/draft/2019-09/schema', '$id' => 'https://json-schema.org/draft/2019-09/meta/core', '$recursiveAnchor' => true, 'title' => 'Core vocabulary meta-schema', 'type' => ['object', 'boolean'], 'properties' => { '$id' => { 'type' => 'string', 'format' => 'uri-reference', '$comment' => 'Non-empty fragments not allowed.', 'pattern' => '^[^#]*#?$' }, '$schema' => { 'type' => 'string', 'format' => 'uri' }, '$anchor' => { 'type' => 'string', 'pattern' => '^[A-Za-z][-A-Za-z0-9.:_]*$' }, '$ref' => { 'type' => 'string', 'format' => 'uri-reference' }, '$recursiveRef' => { 'type' => 'string', 'format' => 'uri-reference' }, '$recursiveAnchor' => { 'type' => 'boolean', 'default' => false }, '$vocabulary' => { 'type' => 'object', 'propertyNames' => { 'type' => 'string', 'format' => 'uri' }, 'additionalProperties' => { 'type' => 'boolean' } }, '$comment' => { 'type' => 'string' }, '$defs' => { 'type' => 'object', 'additionalProperties' => { '$recursiveRef' => '#' }, 'default' => {} } } } APPLICATOR = { '$schema' => 'https://json-schema.org/draft/2019-09/schema', '$id' => 'https://json-schema.org/draft/2019-09/meta/applicator', '$recursiveAnchor' => true, 'title' => 'Applicator vocabulary meta-schema', 'type' => ['object', 'boolean'], 'properties' => { 'additionalItems' => { '$recursiveRef' => '#' }, 'unevaluatedItems' => { '$recursiveRef' => '#' }, 'items' => { 'anyOf' => [ { '$recursiveRef' => '#' }, { '$ref' => '#/$defs/schemaArray' } ] }, 'contains' => { '$recursiveRef' => '#' }, 'additionalProperties' => { '$recursiveRef' => '#' }, 'unevaluatedProperties' => { '$recursiveRef' => '#' }, 'properties' => { 'type' => 'object', 'additionalProperties' => { '$recursiveRef' => '#' }, 'default' => {} }, 'patternProperties' => { 'type' => 'object', 'additionalProperties' => { '$recursiveRef' => '#' }, 'propertyNames' => { 'format' => 'regex' }, 'default' => {} }, 'dependentSchemas' => { 'type' => 'object', 'additionalProperties' => { '$recursiveRef' => '#' } }, 'propertyNames' => { '$recursiveRef' => '#' }, 'if' => { '$recursiveRef' => '#' }, 'then' => { '$recursiveRef' => '#' }, 'else' => { '$recursiveRef' => '#' }, 'allOf' => { '$ref' => '#/$defs/schemaArray' }, 'anyOf' => { '$ref' => '#/$defs/schemaArray' }, 'oneOf' => { '$ref' => '#/$defs/schemaArray' }, 'not' => { '$recursiveRef' => '#' } }, '$defs' => { 'schemaArray' => { 'type' => 'array', 'minItems' => 1, 'items' => { '$recursiveRef' => '#' } } } } VALIDATION = { '$schema' => 'https://json-schema.org/draft/2019-09/schema', '$id' => 'https://json-schema.org/draft/2019-09/meta/validation', '$recursiveAnchor' => true, 'title' => 'Validation vocabulary meta-schema', 'type' => ['object', 'boolean'], 'properties' => { 'multipleOf' => { 'type' => 'number', 'exclusiveMinimum' => 0 }, 'maximum' => { 'type' => 'number' }, 'exclusiveMaximum' => { 'type' => 'number' }, 'minimum' => { 'type' => 'number' }, 'exclusiveMinimum' => { 'type' => 'number' }, 'maxLength' => { '$ref' => '#/$defs/nonNegativeInteger' }, 'minLength' => { '$ref' => '#/$defs/nonNegativeIntegerDefault0' }, 'pattern' => { 'type' => 'string', 'format' => 'regex' }, 'maxItems' => { '$ref' => '#/$defs/nonNegativeInteger' }, 'minItems' => { '$ref' => '#/$defs/nonNegativeIntegerDefault0' }, 'uniqueItems' => { 'type' => 'boolean', 'default' => false }, 'maxContains' => { '$ref' => '#/$defs/nonNegativeInteger' }, 'minContains' => { '$ref' => '#/$defs/nonNegativeInteger', 'default' => 1 }, 'maxProperties' => { '$ref' => '#/$defs/nonNegativeInteger' }, 'minProperties' => { '$ref' => '#/$defs/nonNegativeIntegerDefault0' }, 'required' => { '$ref' => '#/$defs/stringArray' }, 'dependentRequired' => { 'type' => 'object', 'additionalProperties' => { '$ref' => '#/$defs/stringArray' } }, 'const' => true, 'enum' => { 'type' => 'array', 'items' => true }, 'type' => { 'anyOf' => [ { '$ref' => '#/$defs/simpleTypes' }, { 'type' => 'array', 'items' => { '$ref' => '#/$defs/simpleTypes' }, 'minItems' => 1, 'uniqueItems' => true } ] } }, '$defs' => { 'nonNegativeInteger' => { 'type' => 'integer', 'minimum' => 0 }, 'nonNegativeIntegerDefault0' => { '$ref' => '#/$defs/nonNegativeInteger', 'default' => 0 }, 'simpleTypes' => { 'enum' => [ 'array', 'boolean', 'integer', 'null', 'number', 'object', 'string' ] }, 'stringArray' => { 'type' => 'array', 'items' => { 'type' => 'string' }, 'uniqueItems' => true, 'default' => [] } } } META_DATA = { '$schema' => 'https://json-schema.org/draft/2019-09/schema', '$id' => 'https://json-schema.org/draft/2019-09/meta/meta-data', '$recursiveAnchor' => true, 'title' => 'Meta-data vocabulary meta-schema', 'type' => ['object', 'boolean'], 'properties' => { 'title' => { 'type' => 'string' }, 'description' => { 'type' => 'string' }, 'default' => true, 'deprecated' => { 'type' => 'boolean', 'default' => false }, 'readOnly' => { 'type' => 'boolean', 'default' => false }, 'writeOnly' => { 'type' => 'boolean', 'default' => false }, 'examples' => { 'type' => 'array', 'items' => true } } } FORMAT = { '$schema' => 'https://json-schema.org/draft/2019-09/schema', '$id' => 'https://json-schema.org/draft/2019-09/meta/format', '$recursiveAnchor' => true, 'title' => 'Format vocabulary meta-schema', 'type' => ['object', 'boolean'], 'properties' => { 'format' => { 'type' => 'string' } } } CONTENT = { '$schema' => 'https://json-schema.org/draft/2019-09/schema', '$id' => 'https://json-schema.org/draft/2019-09/meta/content', '$recursiveAnchor' => true, 'title' => 'Content vocabulary meta-schema', 'type' => ['object', 'boolean'], 'properties' => { 'contentMediaType' => { 'type' => 'string' }, 'contentEncoding' => { 'type' => 'string' }, 'contentSchema' => { '$recursiveRef' => '#' } } } SCHEMAS = { URI('https://json-schema.org/draft/2019-09/meta/core') => CORE, URI('https://json-schema.org/draft/2019-09/meta/applicator') => APPLICATOR, URI('https://json-schema.org/draft/2019-09/meta/validation') => VALIDATION, URI('https://json-schema.org/draft/2019-09/meta/meta-data') => META_DATA, URI('https://json-schema.org/draft/2019-09/meta/format') => FORMAT, URI('https://json-schema.org/draft/2019-09/meta/content') => CONTENT } end end end json_schemer-2.4.0/lib/json_schemer/draft201909/vocab.rb0000644000004100000410000000224714751003725022666 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft201909 module Vocab CORE = Draft202012::Vocab::CORE.dup CORE.delete('$dynamicAnchor') CORE.delete('$dynamicRef') CORE.merge!( # https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-02#section-8.2.4.2 '$recursiveAnchor' => Core::RecursiveAnchor, '$recursiveRef' => Core::RecursiveRef ) APPLICATOR = Draft202012::Vocab::APPLICATOR.dup APPLICATOR.delete('prefixItems') APPLICATOR.merge!( # https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-02#section-9.3.1 'items' => Applicator::Items, 'additionalItems' => Applicator::AdditionalItems, 'unevaluatedItems' => Applicator::UnevaluatedItems, # https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-02#section-9.3.2.4 'unevaluatedProperties' => Draft202012::Vocab::Unevaluated::UnevaluatedProperties ) VALIDATION = Draft202012::Vocab::VALIDATION FORMAT = Draft202012::Vocab::FORMAT_ANNOTATION CONTENT = Draft202012::Vocab::CONTENT META_DATA = Draft202012::Vocab::META_DATA end end end json_schemer-2.4.0/lib/json_schemer/draft201909/vocab/0000755000004100000410000000000014751003725022334 5ustar www-datawww-datajson_schemer-2.4.0/lib/json_schemer/draft201909/vocab/applicator.rb0000644000004100000410000001001414751003725025013 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft201909 module Vocab module Applicator class Items < Keyword def error(formatted_instance_location:, **) "array items at #{formatted_instance_location} do not match `items` schema(s)" end def parse if value.is_a?(Array) value.map.with_index do |subschema, index| subschema(subschema, index.to_s) end else subschema(value) end end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array) nested = if parsed.is_a?(Array) instance.take(parsed.size).map.with_index do |item, index| parsed.fetch(index).validate_instance(item, join_location(instance_location, index.to_s), join_location(keyword_location, index.to_s), context) end else instance.map.with_index do |item, index| parsed.validate_instance(item, join_location(instance_location, index.to_s), keyword_location, context) end end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => (nested.size - 1)) end end class AdditionalItems < Keyword def error(formatted_instance_location:, **) "array items at #{formatted_instance_location} do not match `additionalItems` schema" end def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array) evaluated_index = context.adjacent_results[Items]&.annotation offset = evaluated_index ? (evaluated_index + 1) : instance.size nested = instance.slice(offset..-1).map.with_index do |item, index| parsed.validate_instance(item, join_location(instance_location, (offset + index).to_s), keyword_location, context) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => nested.any?) end end class UnevaluatedItems < Keyword def error(formatted_instance_location:, **) "array items at #{formatted_instance_location} do not match `unevaluatedItems` schema" end def parse subschema(value) end def validate(instance, instance_location, keyword_location, context) return result(instance, instance_location, keyword_location, true) unless instance.is_a?(Array) unevaluated_items = instance.size.times.to_set context.adjacent_results.each_value do |adjacent_result| collect_unevaluated_items(adjacent_result, instance_location, unevaluated_items) end nested = unevaluated_items.map do |index| parsed.validate_instance(instance.fetch(index), join_location(instance_location, index.to_s), keyword_location, context) end result(instance, instance_location, keyword_location, nested.all?(&:valid), nested, :annotation => nested.any?) end private def collect_unevaluated_items(result, instance_location, unevaluated_items) return unless result.valid && result.instance_location == instance_location case result.source when Items unevaluated_items.subtract(0..result.annotation) when AdditionalItems, UnevaluatedItems unevaluated_items.clear if result.annotation end result.nested&.each do |nested_result| collect_unevaluated_items(nested_result, instance_location, unevaluated_items) end end end end end end end json_schemer-2.4.0/lib/json_schemer/draft201909/vocab/core.rb0000644000004100000410000000244614751003725023617 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Draft201909 module Vocab module Core class RecursiveAnchor < Keyword def parse root.resources[:dynamic][schema.base_uri] = schema if value == true value end end class RecursiveRef < Keyword def ref_uri @ref_uri ||= URI.join(schema.base_uri, value) end def ref_schema @ref_schema ||= root.resolve_ref(ref_uri) end def recursive_anchor return @recursive_anchor if defined?(@recursive_anchor) @recursive_anchor = (ref_schema.parsed['$recursiveAnchor']&.parsed == true) end def validate(instance, instance_location, keyword_location, context) schema = ref_schema if recursive_anchor context.dynamic_scope.each do |ancestor| if ancestor.root.resources.fetch(:dynamic).key?(ancestor.base_uri) schema = ancestor.root.resources.fetch(:dynamic).fetch(ancestor.base_uri) break end end end schema.validate_instance(instance, instance_location, keyword_location, context) end end end end end end json_schemer-2.4.0/lib/json_schemer/format.rb0000644000004100000410000001262114751003725021274 0ustar www-datawww-data# frozen_string_literal: true module JSONSchemer module Format # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3 DATE_TIME = proc do |instance, _format| !instance.is_a?(String) || valid_date_time?(instance) end DATE = proc do |instance, _format| !instance.is_a?(String) || valid_date_time?("#{instance}T04:05:06.123456789+07:00") end TIME = proc do |instance, _format| !instance.is_a?(String) || valid_date_time?("2001-02-03T#{instance}") end DURATION = proc do |instance, _format| !instance.is_a?(String) || valid_duration?(instance) end # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.2 EMAIL = proc do |instance, _format| !instance.is_a?(String) || instance.ascii_only? && valid_email?(instance) end IDN_EMAIL = proc do |instance, _format| !instance.is_a?(String) || valid_email?(instance) end # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.3 HOSTNAME = proc do |instance, _format| !instance.is_a?(String) || instance.ascii_only? && valid_hostname?(instance) end IDN_HOSTNAME = proc do |instance, _format| !instance.is_a?(String) || valid_hostname?(instance) end # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.4 IPV4 = proc do |instance, _format| !instance.is_a?(String) || valid_ip?(instance, Socket::AF_INET) end IPV6 = proc do |instance, _format| !instance.is_a?(String) || valid_ip?(instance, Socket::AF_INET6) end # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.5 URI = proc do |instance, _format| !instance.is_a?(String) || valid_uri?(instance) end URI_REFERENCE = proc do |instance, _format| !instance.is_a?(String) || valid_uri_reference?(instance) end IRI = proc do |instance, _format| !instance.is_a?(String) || valid_uri?(iri_escape(instance)) end IRI_REFERENCE = proc do |instance, _format| !instance.is_a?(String) || valid_uri_reference?(iri_escape(instance)) end UUID = proc do |instance, _format| !instance.is_a?(String) || valid_uuid?(instance) end # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.6 URI_TEMPLATE = proc do |instance, _format| !instance.is_a?(String) || valid_uri_template?(instance) end # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.7 JSON_POINTER = proc do |instance, _format| !instance.is_a?(String) || valid_json_pointer?(instance) end RELATIVE_JSON_POINTER = proc do |instance, _format| !instance.is_a?(String) || valid_relative_json_pointer?(instance) end # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-01#section-7.3.8 REGEX = proc do |instance, _format| !instance.is_a?(String) || valid_regex?(instance) end DATE_TIME_OFFSET_REGEX = /(Z|[\+\-]([01][0-9]|2[0-3]):[0-5][0-9])\z/i.freeze DATE_TIME_SEPARATOR_CHARACTER_CLASS = '[Tt\s]' HOUR_24_REGEX = /#{DATE_TIME_SEPARATOR_CHARACTER_CLASS}24:/.freeze LEAP_SECOND_REGEX = /#{DATE_TIME_SEPARATOR_CHARACTER_CLASS}\d{2}:\d{2}:6/.freeze IP_REGEX = /\A[\h:.]+\z/.freeze INVALID_QUERY_REGEX = /\s/.freeze IRI_ESCAPE_REGEX = /[^[:ascii:]]/ UUID_REGEX = /\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/i NIL_UUID = '00000000-0000-0000-0000-000000000000' BINARY_TO_PERCENT_ENCODED = 256.times.each_with_object({}) do |byte, out| out[-byte.chr(Encoding::BINARY)] = -sprintf('%%%02X', byte) end.freeze class << self include Duration include Email include Hostname include JSONPointer include URITemplate def percent_encode(data, regexp) binary = data.b binary.gsub!(regexp, BINARY_TO_PERCENT_ENCODED) binary.force_encoding(data.encoding) end def valid_date_time?(data) return false if HOUR_24_REGEX.match?(data) datetime = DateTime.rfc3339(data) return false if LEAP_SECOND_REGEX.match?(data) && datetime.new_offset.strftime('%H:%M') != '23:59' DATE_TIME_OFFSET_REGEX.match?(data) rescue ArgumentError false end def valid_ip?(data, family) IPAddr.new(data, family) IP_REGEX.match?(data) rescue IPAddr::Error false end def parse_uri_scheme(data) scheme, _userinfo, _host, _port, _registry, _path, opaque, query, _fragment = ::URI::RFC3986_PARSER.split(data) # ::URI::RFC3986_PARSER.parse allows spaces in these and I don't think it should raise ::URI::InvalidURIError if INVALID_QUERY_REGEX.match?(query) || INVALID_QUERY_REGEX.match?(opaque) scheme end def valid_uri?(data) !!parse_uri_scheme(data) rescue ::URI::InvalidURIError false end def valid_uri_reference?(data) parse_uri_scheme(data) true rescue ::URI::InvalidURIError false end def iri_escape(data) Format.percent_encode(data, IRI_ESCAPE_REGEX) end def valid_regex?(data) !!EcmaRegexp.ruby_equivalent(data) rescue InvalidEcmaRegexp false end def valid_uuid?(data) UUID_REGEX.match?(data) || NIL_UUID == data end end end end json_schemer-2.4.0/LICENSE.txt0000644000004100000410000000206714751003725016060 0ustar www-datawww-dataThe MIT License (MIT) Copyright (c) 2018 David Harsha Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. json_schemer-2.4.0/Rakefile0000644000004100000410000000030614751003725015674 0ustar www-datawww-datarequire "bundler/gem_tasks" require "rake/testtask" Rake::TestTask.new(:test) do |t| t.libs << "test" t.libs << "lib" t.test_files = FileList["test/**/*_test.rb"] end task :default => :test json_schemer-2.4.0/json_schemer.gemspec0000644000004100000410000000267514751003725020266 0ustar www-datawww-data lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "json_schemer/version" Gem::Specification.new do |spec| spec.name = "json_schemer" spec.version = JSONSchemer::VERSION spec.authors = ["David Harsha"] spec.email = ["davishmcclurg@gmail.com"] spec.summary = "JSON Schema validator. Supports drafts 4, 6, 7, 2019-09, 2020-12, OpenAPI 3.0, and OpenAPI 3.1." spec.homepage = "https://github.com/davishmcclurg/json_schemer" spec.license = "MIT" spec.files = `git ls-files -z`.split("\x0").reject do |f| f.match(%r{^(test|spec|features|JSON-Schema-Test-Suite)/}) end spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] spec.required_ruby_version = '>= 2.7' spec.add_development_dependency "base64" spec.add_development_dependency "bundler", "~> 2.4.0" spec.add_development_dependency "rake", "~> 13.0" spec.add_development_dependency "minitest", "~> 5.0" spec.add_development_dependency "simplecov", "~> 0.22" spec.add_development_dependency "csv" spec.add_development_dependency "i18n" spec.add_development_dependency "i18n-debug" spec.add_runtime_dependency "bigdecimal" spec.add_runtime_dependency "hana", "~> 1.3" spec.add_runtime_dependency "regexp_parser", "~> 2.0" spec.add_runtime_dependency "simpleidn", "~> 0.2" end json_schemer-2.4.0/Gemfile0000644000004100000410000000024714751003725015526 0ustar www-datawww-datasource "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } # Specify your gem's dependencies in json_schemer.gemspec gemspec json_schemer-2.4.0/README.md0000644000004100000410000004067714751003725015525 0ustar www-datawww-data# JSONSchemer JSON Schema validator. Supports drafts 4, 6, 7, 2019-09, 2020-12, OpenAPI 3.0, and OpenAPI 3.1. ## Installation Add this line to your application's Gemfile: ```ruby gem 'json_schemer' ``` And then execute: $ bundle Or install it yourself as: $ gem install json_schemer ## Usage ```ruby require 'json_schemer' schema = { 'type' => 'object', 'properties' => { 'abc' => { 'type' => 'integer', 'minimum' => 11 } } } schemer = JSONSchemer.schema(schema) # true/false validation schemer.valid?({ 'abc' => 11 }) # => true schemer.valid?({ 'abc' => 10 }) # => false # error validation (`validate` returns an enumerator) schemer.validate({ 'abc' => 10 }).to_a # => [{"data"=>10, # "data_pointer"=>"/abc", # "schema"=>{"type"=>"integer", "minimum"=>11}, # "schema_pointer"=>"/properties/abc", # "root_schema"=>{"type"=>"object", "properties"=>{"abc"=>{"type"=>"integer", "minimum"=>11}}}, # "type"=>"minimum", # "error"=>"number at `/abc` is less than: 11"}] # default property values data = {} JSONSchemer.schema( { 'properties' => { 'foo' => { 'default' => 'bar' } } }, insert_property_defaults: true ).valid?(data) data # => {"foo"=>"bar"} # schema files require 'pathname' schema = Pathname.new('/path/to/schema.json') schemer = JSONSchemer.schema(schema) # schema json string schema = '{ "type": "integer" }' schemer = JSONSchemer.schema(schema) # schema validation JSONSchemer.valid_schema?({ '$id' => 'valid' }) # => true JSONSchemer.validate_schema({ '$id' => '#invalid' }).to_a # => [{"data"=>"#invalid", # "data_pointer"=>"/$id", # "schema"=>{"$ref"=>"#/$defs/uriReferenceString", "$comment"=>"Non-empty fragments not allowed.", "pattern"=>"^[^#]*#?$"}, # "schema_pointer"=>"/properties/$id", # "root_schema"=>{...meta schema}, # "type"=>"pattern", # "error"=>"string at `/$id` does not match pattern: ^[^#]*#?$"}] # subschemas schema = { 'type' => 'integer', '$defs' => { 'foo' => { 'type' => 'string' } } } schemer = JSONSchemer.schema(schema) schemer.ref('#/$defs/foo').validate(1).to_a # => [{"data"=>1, # "data_pointer"=>"", # "schema"=>{"type"=>"string"}, # "schema_pointer"=>"/$defs/foo", # "root_schema"=>{"type"=>"integer", "$defs"=>{"foo"=>{"type"=>"string"}}}, # "type"=>"string", # "error"=>"value at root is not a string"}] # schema bundling (https://json-schema.org/draft/2020-12/json-schema-core.html#section-9.3) schema = { '$id' => 'http://example.com/schema', 'allOf' => [ { '$ref' => 'schema/one' }, { '$ref' => 'schema/two' } ] } refs = { URI('http://example.com/schema/one') => { 'type' => 'integer' }, URI('http://example.com/schema/two') => { 'minimum' => 11 } } schemer = JSONSchemer.schema(schema, :ref_resolver => refs.to_proc) schemer.bundle # => {"$id"=>"http://example.com/schema", # "allOf"=>[{"$ref"=>"schema/one"}, {"$ref"=>"schema/two"}], # "$schema"=>"https://json-schema.org/draft/2020-12/schema", # "$defs"=> # {"http://example.com/schema/one"=>{"type"=>"integer", "$id"=>"http://example.com/schema/one", "$schema"=>"https://json-schema.org/draft/2020-12/schema"}, # "http://example.com/schema/two"=>{"minimum"=>11, "$id"=>"http://example.com/schema/two", "$schema"=>"https://json-schema.org/draft/2020-12/schema"}}} ``` ## Options ```ruby JSONSchemer.schema( schema, # meta schema to use for vocabularies (keyword behavior) and schema validation # String/JSONSchemer::Schema # 'https://json-schema.org/draft/2020-12/schema': JSONSchemer.draft202012 # 'https://json-schema.org/draft/2019-09/schema': JSONSchemer.draft201909 # 'http://json-schema.org/draft-07/schema#': JSONSchemer.draft7 # 'http://json-schema.org/draft-06/schema#': JSONSchemer.draft6 # 'http://json-schema.org/draft-04/schema#': JSONSchemer.draft4 # 'http://json-schema.org/schema#': JSONSchemer.draft4 # 'https://spec.openapis.org/oas/3.1/dialect/base': JSONSchemer.openapi31 # 'json-schemer://openapi30/schema': JSONSchemer.openapi30 # default: JSONSchemer.draft202012 meta_schema: 'https://json-schema.org/draft/2020-12/schema', # validate `format` (https://json-schema.org/draft/2020-12/json-schema-validation.html#section-7) # true/false # default: true format: true, # custom formats formats: { 'int32' => proc do |instance, _format| instance.is_a?(Integer) && instance.bit_length <= 32 end, # disable specific format 'email' => false }, # custom content encodings # only `base64` is available by default content_encodings: { # return [success, annotation] tuple 'urlsafe_base64' => proc do |instance| [true, Base64.urlsafe_decode64(instance)] rescue [false, nil] end }, # custom content media types # only `application/json` is available by default content_media_types: { # return [success, annotation] tuple 'text/csv' => proc do |instance| [true, CSV.parse(instance)] rescue [false, nil] end }, # insert default property values during validation # string keys by default (use `:symbol` to insert symbol keys) # true/false/:symbol # default: false insert_property_defaults: true, # modify properties during validation. You can pass one Proc or a list of Procs to modify data. # Proc/[Proc] # default: nil before_property_validation: proc do |data, property, property_schema, _parent| data[property] ||= 42 end, # modify properties after validation. You can pass one Proc or a list of Procs to modify data. # Proc/[Proc] # default: nil after_property_validation: proc do |data, property, property_schema, _parent| data[property] = Date.iso8601(data[property]) if property_schema.is_a?(Hash) && property_schema['format'] == 'date' end, # resolve external references # 'net/http'/proc/lambda/respond_to?(:call) # 'net/http': proc { |uri| JSON.parse(Net::HTTP.get(uri)) } # default: proc { |uri| raise UnknownRef, uri.to_s } ref_resolver: 'net/http', # use different method to match regexes # 'ruby'/'ecma'/proc/lambda/respond_to?(:call) # 'ruby': proc { |pattern| Regexp.new(pattern) } # default: 'ruby' regexp_resolver: proc do |pattern| RE2::Regexp.new(pattern) end, # output formatting (https://json-schema.org/draft/2020-12/json-schema-core.html#section-12) # 'classic'/'flag'/'basic'/'detailed'/'verbose' # default: 'classic' output_format: 'basic', # validate `readOnly`/`writeOnly` keywords (https://spec.openapis.org/oas/v3.0.3#fixed-fields-19) # 'read'/'write'/nil # default: nil access_mode: 'read' ) ``` ## Global Configuration Configuration options can be set globally by modifying `JSONSchemer.configuration`. Global options are applied to any new schemas at creation time (global configuration changes are not reflected in existing schemas). They can be overridden with the regular keyword arguments described [above](#options). ```ruby # configuration block JSONSchemer.configure do |config| config.regexp_resolver = 'ecma' end # configuration accessors JSONSchemer.configuration.insert_property_defaults = true ``` ## Custom Error Messages Error messages can be customized using the `x-error` keyword and/or [I18n](https://github.com/ruby-i18n/i18n) translations. `x-error` takes precedence if both are defined. ### `x-error` Keyword ```ruby # override all errors for a schema schemer = JSONSchemer.schema({ 'type' => 'string', 'x-error' => 'custom error for schema and all keywords' }) schemer.validate(1).first # => {"data"=>1, # "data_pointer"=>"", # "schema"=>{"type"=>"string", "x-error"=>"custom error for schema and all keywords"}, # "schema_pointer"=>"", # "root_schema"=>{"type"=>"string", "x-error"=>"custom error for schema and all keywords"}, # "type"=>"string", # "error"=>"custom error for schema and all keywords", # "x-error"=>true} schemer.validate(1, :output_format => 'basic') # => {"valid"=>false, # "keywordLocation"=>"", # "absoluteKeywordLocation"=>"json-schemer://schema#", # "instanceLocation"=>"", # "error"=>"custom error for schema and all keywords", # "x-error"=>true, # "errors"=>#} # keyword-specific errors schemer = JSONSchemer.schema({ 'type' => 'string', 'minLength' => 10, 'x-error' => { 'type' => 'custom error for `type` keyword', # special `^` keyword for schema-level error '^' => 'custom error for schema', # same behavior as when `x-error` is a string '*' => 'fallback error for schema and all keywords' } }) schemer.validate(1).map { _1.fetch('error') } # => ["custom error for `type` keyword"] schemer.validate('1').map { _1.fetch('error') } # => ["custom error for schema and all keywords"] schemer.validate(1, :output_format => 'basic').fetch('error') # => "custom error for schema" # variable interpolation (instance/instanceLocation/keywordLocation/absoluteKeywordLocation) schemer = JSONSchemer.schema({ '$id' => 'https://example.com/schema', 'properties' => { 'abc' => { 'type' => 'string', 'x-error' => <<~ERROR instance: %{instance} instance location: %{instanceLocation} keyword location: %{keywordLocation} absolute keyword location: %{absoluteKeywordLocation} ERROR } } }) puts schemer.validate({ 'abc' => 1 }).first.fetch('error') # instance: 1 # instance location: /abc # keyword location: /properties/abc/type # absolute keyword location: https://example.com/schema#/properties/abc/type ``` ### I18n When the [I18n gem](https://github.com/ruby-i18n/i18n) is loaded, custom error messages are looked up under the `json_schemer` key. It may be necessary to restart your application after adding the root key because the existence check is cached for performance reasons. Translation keys are looked up in this order: 1. `$LOCALE.json_schemer.errors.$ABSOLUTE_KEYWORD_LOCATION` 2. `$LOCALE.json_schemer.errors.$SCHEMA_ID.$KEYWORD_LOCATION` 3. `$LOCALE.json_schemer.errors.$KEYWORD_LOCATION` 4. `$LOCALE.json_schemer.errors.$SCHEMA_ID.$KEYWORD` 5. `$LOCALE.json_schemer.errors.$SCHEMA_ID.*` 6. `$LOCALE.json_schemer.errors.$META_SCHEMA_ID.$KEYWORD` 7. `$LOCALE.json_schemer.errors.$META_SCHEMA_ID.*` 8. `$LOCALE.json_schemer.errors.$KEYWORD` 9. `$LOCALE.json_schemer.errors.*` Example translations file: ```yaml en: json_schemer: errors: 'https://example.com/schema#/properties/abc/type': custom error for absolute keyword location 'https://example.com/schema': '#/properties/abc/type': custom error for keyword location, nested under schema $id 'type': custom error for `type` keyword, nested under schema $id '^': custom error for schema, nested under schema $id '*': fallback error for schema and all keywords, nested under schema $id '#/properties/abc/type': custom error for keyword location 'http://json-schema.org/draft-07/schema#': 'type': custom error for `type` keyword, nested under meta-schema $id ($schema) '^': custom error for schema, nested under meta-schema $id '*': fallback error for schema and all keywords, nested under meta-schema $id ($schema) 'type': custom error for `type` keyword '^': custom error for schema # variable interpolation (instance/instanceLocation/keywordLocation/absoluteKeywordLocation) '*': | fallback error for schema and all keywords instance: %{instance} instance location: %{instanceLocation} keyword location: %{keywordLocation} absolute keyword location: %{absoluteKeywordLocation} ``` And output: ```ruby require 'i18n' I18n.locale = :en # $LOCALE=en schemer = JSONSchemer.schema({ '$id' => 'https://example.com/schema', # $SCHEMA_ID=https://example.com/schema '$schema' => 'http://json-schema.org/draft-07/schema#', # $META_SCHEMA_ID=http://json-schema.org/draft-07/schema# 'properties' => { 'abc' => { 'type' => 'integer' # $KEYWORD=type } # $KEYWORD_LOCATION=#/properties/abc/type } # $ABSOLUTE_KEYWORD_LOCATION=https://example.com/schema#/properties/abc/type }) schemer.validate({ 'abc' => 'not-an-integer' }).first # => {"data"=>"not-an-integer", # "data_pointer"=>"/abc", # "schema"=>{"type"=>"integer"}, # "schema_pointer"=>"/properties/abc", # "root_schema"=>{"$id"=>"https://example.com/schema", "$schema"=>"http://json-schema.org/draft-07/schema#", "properties"=>{"abc"=>{"type"=>"integer"}}}, # "type"=>"integer", # "error"=>"custom error for absolute keyword location", # "i18n"=>true ``` In the example above, custom error messsages are looked up using the following keys (in order until one is found): 1. `en.json_schemer.errors.'https://example.com/schema#/properties/abc/type'` 2. `en.json_schemer.errors.'https://example.com/schema'.'#/properties/abc/type'` 3. `en.json_schemer.errors.'#/properties/abc/type'` 4. `en.json_schemer.errors.'https://example.com/schema'.type` 5. `en.json_schemer.errors.'https://example.com/schema'.*` 6. `en.json_schemer.errors.'http://json-schema.org/draft-07/schema#'.type` 7. `en.json_schemer.errors.'http://json-schema.org/draft-07/schema#'.*` 8. `en.json_schemer.errors.type` 9. `en.json_schemer.errors.*` ## OpenAPI ```ruby document = JSONSchemer.openapi({ 'openapi' => '3.1.0', 'info' => { 'title' => 'example' }, 'components' => { 'schemas' => { 'example' => { 'type' => 'integer' } } } }) # document validation using meta schema document.valid? # => false document.validate.to_a # => [{"data"=>{"title"=>"example"}, # "data_pointer"=>"/info", # "schema"=>{...info schema}, # "schema_pointer"=>"/$defs/info", # "root_schema"=>{...meta schema}, # "type"=>"required", # "details"=>{"missing_keys"=>["version"]}}, # ...] # data validation using schema by name (in `components/schemas`) document.schema('example').valid?(1) # => true document.schema('example').valid?('one') # => false # data validation using schema by ref document.ref('#/components/schemas/example').valid?(1) # => true document.ref('#/components/schemas/example').valid?('one') # => false ``` ## CLI The `json_schemer` executable takes a JSON schema file as the first argument followed by one or more JSON data files to validate. If there are any validation errors, it outputs them and returns an error code. Validation errors are output as single-line JSON objects. The `--errors` option can be used to limit the number of errors returned or prevent output entirely (and fail fast). The schema or data can also be read from stdin using `-`. ``` % json_schemer --help Usage: json_schemer [options] ... json_schemer [options] - json_schemer [options] - ... json_schemer -h | --help json_schemer --version Options: -e, --errors MAX Maximum number of errors to output Use "0" to validate with no output -h, --help Show help -v, --version Show version ``` ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. ## Build Status ![CI](https://github.com/davishmcclurg/json_schemer/actions/workflows/ci.yml/badge.svg) ![JSON Schema Versions](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fruby-json_schemer%2Fsupported_versions.json)
![Draft 2020-12](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fruby-json_schemer%2Fcompliance%2Fdraft2020-12.json) ![Draft 2019-09](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fruby-json_schemer%2Fcompliance%2Fdraft2019-09.json) ![Draft 7](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fruby-json_schemer%2Fcompliance%2Fdraft7.json) ![Draft 6](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fruby-json_schemer%2Fcompliance%2Fdraft6.json) ![Draft 4](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fruby-json_schemer%2Fcompliance%2Fdraft4.json) ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/davishmcclurg/json_schemer. ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). json_schemer-2.4.0/CHANGELOG.md0000644000004100000410000001716414751003725016052 0ustar www-datawww-data# Changelog ## [2.4.0] - 2025-02-01 ### Bug Fixes - Store schema resource file URIs as strings to prevent conflicts: https://github.com/davishmcclurg/json_schemer/pull/189 - Require OpenAPI `discriminator` instances to be objects: https://github.com/davishmcclurg/json_schemer/pull/206 - Pass configuration options to subschemas: https://github.com/davishmcclurg/json_schemer/pull/208 - Check applicable instance types in OpenAPI `format` extensions: https://github.com/davishmcclurg/json_schemer/pull/209 - Use correct max values for OpenAPI `int32`/`int64` formats: https://github.com/davishmcclurg/json_schemer/commit/386c2a6fe089350c61775716643ef0600898060e [2.4.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.4.0 ## [2.3.0] - 2024-05-30 ### Ruby Versions - Ruby 2.5 and 2.6 are no longer supported. ### Bug Fixes - Remove `base64` runtime dependency: https://github.com/davishmcclurg/json_schemer/pull/182 - Relax `uuid` format validation: https://github.com/davishmcclurg/json_schemer/pull/183 [2.3.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.3.0 ## [2.2.0] - 2024-03-02 ### Bug Fixes - Support symbol keys when accessing original instance: https://github.com/davishmcclurg/json_schemer/commit/d52c130e9967919c6cf1c9dbc3f0babfb8b01cf8 - Support custom keywords in nested schemas: https://github.com/davishmcclurg/json_schemer/commit/93c85a5006981347c7e9a4c11b73c6bdb65d8ba2 - Stringify instance location for custom keywords: https://github.com/davishmcclurg/json_schemer/commit/513c99130b9e7986b09881e7efd3fb7143744754 - Reduce unhelpful error output in `unevaluated` keywords: https://github.com/davishmcclurg/json_schemer/pull/164 - Handle parse errors during schema validation: https://github.com/davishmcclurg/json_schemer/pull/171 - Follow refs when finding default property values: https://github.com/davishmcclurg/json_schemer/pull/175 ### Features - Global configuration with `Configuration` object: https://github.com/davishmcclurg/json_schemer/pull/170 - Symbol key property defaults with `insert_property_defaults: :symbol`: https://github.com/davishmcclurg/json_schemer/commit/a72473dc84199107ddedc8998950e5b82273232a - Consistent schema type support for schema validation methods: https://github.com/davishmcclurg/json_schemer/commit/bbcd0cea20cbaa61cf2bdae5f53840861cae54b8 - Validation option support for schema validation methods: https://github.com/davishmcclurg/json_schemer/commit/2eeef77de522f127619b7d0faa51e0d7e40977ad [2.2.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.2.0 ## [2.1.1] - 2023-11-28 ### Bug Fixes - Fix refs to/through keyword objects: https://github.com/davishmcclurg/json_schemer/pull/160 - Temporary fix for incorrect `uri-reference` format in OpenAPI 3.x: https://github.com/davishmcclurg/json_schemer/pull/161 [2.1.1]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.1.1 ## [2.1.0] - 2023-11-17 ### Bug Fixes - Limit anyOf/oneOf discriminator to listed refs: https://github.com/davishmcclurg/json_schemer/pull/145 - Require discriminator `propertyName` property: https://github.com/davishmcclurg/json_schemer/pull/145 - Support `Schema#ref` in subschemas: https://github.com/davishmcclurg/json_schemer/pull/145 - Resolve JSON pointer refs using correct base URI: https://github.com/davishmcclurg/json_schemer/pull/147 - `date` format in OpenAPI 3.0: https://github.com/davishmcclurg/json_schemer/commit/69fe7a815ecf0cfb1c40ac402bf46a789c05e972 ### Features - Custom error messages with `x-error` keyword and I18n: https://github.com/davishmcclurg/json_schemer/pull/149 - Custom content encodings and media types: https://github.com/davishmcclurg/json_schemer/pull/148 [2.1.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.1.0 ## [2.0.0] - 2023-08-20 For 2.0.0, much of the codebase was rewritten to simplify support for the two new JSON Schema draft versions (2019-09 and 2020-12). The major change is moving each keyword into its own class and organizing them into vocabularies. [Output formats](https://json-schema.org/draft/2020-12/json-schema-core.html#section-12) and [annotations](https://json-schema.org/draft/2020-12/json-schema-core.html#section-7.7) from the new drafts are also supported. The known breaking changes are listed below, but there may be others that haven't been identified. ### Breaking Changes - The default meta schema is now Draft 2020-12. Other meta schemas can be specified using `meta_schema`. - Schemas use `json-schemer://schema` as the default base URI. Relative `$id` and `$ref` values are joined to the default base URI and are always absolute. For example, the schema `{ '$id' => 'foo', '$ref' => 'bar' }` uses `json-schemer://schema/foo` as the base URI and passes `json-schemer://schema/bar` to the ref resolver. For relative refs, `URI#path` can be used in the ref resolver to access the relative portion, ie: `URI('json-schemer://schema/bar').path => "/bar"`. - Property validation hooks (`before_property_validation` and `after_property_validation`) run immediately before and after `properties` validation. Previously, `before_property_validation` ran before all "object" validations (`dependencies`, `patternProperties`, `additionalProperties`, etc) and `after_property_validation` was called after them. - `insert_property_defaults` now inserts defaults in conditional subschemas when possible (if there's only one default or if there's only one unique default from a valid subtree). - Error output - Special characters in `schema_pointer` are no longer percent encoded (eg, `definitions/foo\"bar` instead of `/definitions/foo%22bar`) - Keyword validation order changed so errors may be returned in a different order (eg, `items` errors before `contains`). - Array `dependencies` return `"type": "dependencies"` errors instead of `"required"` and point to the schema that contains the `dependencies` keyword. - `not` errors point to the schema that contains the `not` keyword (instead of the schema defined by the `not` keyword). - Custom keyword errors are now always wrapped in regular error hashes. Returned strings are used to set `type`: ``` >> JSONSchemer.schema({ 'x' => 'y' }, :keywords => { 'x' => proc { false } }).validate({}).to_a => [{"data"=>{}, "data_pointer"=>"", "schema"=>{"x"=>"y"}, "schema_pointer"=>"", "root_schema"=>{"x"=>"y"}, "type"=>"x"}] >> JSONSchemer.schema({ 'x' => 'y' }, :keywords => { 'x' => proc { 'wrong!' } }).validate({}).to_a => [{"data"=>{}, "data_pointer"=>"", "schema"=>{"x"=>"y"}, "schema_pointer"=>"", "root_schema"=>{"x"=>"y"}, "type"=>"wrong!"}] ``` [2.0.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.0.0 ## [1.0.0] - 2023-05-26 ### Breaking Changes - Ruby 2.4 is no longer supported. - The default `regexp_resolver` is now `ruby`, which passes patterns directly to `Regexp`. The previous default, `ecma`, rewrites patterns to behave more like Javascript (ECMA-262) regular expressions: - Beginning of string: `^` -> `\A` - End of string: `$` -> `\z` - Space: `\s` -> `[\t\r\n\f\v\uFEFF\u2029\p{Zs}]` - Non-space: `\S` -> `[^\t\r\n\f\v\uFEFF\u2029\p{Zs}]` - Invalid ECMA-262 regular expressions raise `JSONSchemer::InvalidEcmaRegexp` when `regexp_resolver` is set to `ecma`. - Embedded subschemas (ie, subschemas referenced by `$id`) can only be found under "known" keywords (eg, `definitions`). Previously, the entire schema object was scanned for `$id`. - Empty fragments are now removed from `$ref` URIs before calling `ref_resolver`. - Refs that are fragment-only JSON pointers with special characters must use the proper encoding (eg, `"$ref": "#/definitions/some-%7Bid%7D"`). [1.0.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v1.0.0