json_schemer 0.2.18 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/.github/workflows/ci.yml +3 -7
 - data/CHANGELOG.md +69 -0
 - data/Gemfile.lock +28 -10
 - data/README.md +379 -4
 - data/bin/hostname_character_classes +42 -0
 - data/bin/rake +29 -0
 - data/exe/json_schemer +62 -0
 - data/json_schemer.gemspec +6 -12
 - data/lib/json_schemer/cached_resolver.rb +16 -0
 - data/lib/json_schemer/content.rb +18 -0
 - data/lib/json_schemer/draft201909/meta.rb +320 -0
 - data/lib/json_schemer/draft201909/vocab/applicator.rb +104 -0
 - data/lib/json_schemer/draft201909/vocab/core.rb +45 -0
 - data/lib/json_schemer/draft201909/vocab.rb +31 -0
 - data/lib/json_schemer/draft202012/meta.rb +364 -0
 - data/lib/json_schemer/draft202012/vocab/applicator.rb +382 -0
 - data/lib/json_schemer/draft202012/vocab/content.rb +52 -0
 - data/lib/json_schemer/draft202012/vocab/core.rb +160 -0
 - data/lib/json_schemer/draft202012/vocab/format_annotation.rb +23 -0
 - data/lib/json_schemer/draft202012/vocab/format_assertion.rb +23 -0
 - data/lib/json_schemer/draft202012/vocab/meta_data.rb +30 -0
 - data/lib/json_schemer/draft202012/vocab/unevaluated.rb +94 -0
 - data/lib/json_schemer/draft202012/vocab/validation.rb +286 -0
 - data/lib/json_schemer/draft202012/vocab.rb +105 -0
 - data/lib/json_schemer/draft4/meta.rb +161 -0
 - data/lib/json_schemer/draft4/vocab/validation.rb +39 -0
 - data/lib/json_schemer/draft4/vocab.rb +18 -0
 - data/lib/json_schemer/draft6/meta.rb +172 -0
 - data/lib/json_schemer/draft6/vocab.rb +16 -0
 - data/lib/json_schemer/draft7/meta.rb +183 -0
 - data/lib/json_schemer/draft7/vocab/validation.rb +69 -0
 - data/lib/json_schemer/draft7/vocab.rb +30 -0
 - data/lib/json_schemer/ecma_regexp.rb +51 -0
 - data/lib/json_schemer/errors.rb +1 -0
 - data/lib/json_schemer/format/duration.rb +23 -0
 - data/lib/json_schemer/format/email.rb +56 -0
 - data/lib/json_schemer/format/hostname.rb +58 -0
 - data/lib/json_schemer/format/json_pointer.rb +18 -0
 - data/lib/json_schemer/format/uri_template.rb +34 -0
 - data/lib/json_schemer/format.rb +129 -109
 - data/lib/json_schemer/keyword.rb +53 -0
 - data/lib/json_schemer/location.rb +25 -0
 - data/lib/json_schemer/openapi.rb +40 -0
 - data/lib/json_schemer/openapi30/document.rb +1672 -0
 - data/lib/json_schemer/openapi30/meta.rb +32 -0
 - data/lib/json_schemer/openapi30/vocab/base.rb +18 -0
 - data/lib/json_schemer/openapi30/vocab.rb +12 -0
 - data/lib/json_schemer/openapi31/document.rb +1557 -0
 - data/lib/json_schemer/openapi31/meta.rb +136 -0
 - data/lib/json_schemer/openapi31/vocab/base.rb +127 -0
 - data/lib/json_schemer/openapi31/vocab.rb +18 -0
 - data/lib/json_schemer/output.rb +56 -0
 - data/lib/json_schemer/result.rb +229 -0
 - data/lib/json_schemer/schema.rb +423 -0
 - data/lib/json_schemer/version.rb +1 -1
 - data/lib/json_schemer.rb +218 -28
 - metadata +98 -25
 - data/lib/json_schemer/cached_ref_resolver.rb +0 -14
 - data/lib/json_schemer/schema/base.rb +0 -658
 - data/lib/json_schemer/schema/draft4.rb +0 -44
 - data/lib/json_schemer/schema/draft6.rb +0 -25
 - data/lib/json_schemer/schema/draft7.rb +0 -32
 
| 
         @@ -0,0 +1,423 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
            module JSONSchemer
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Schema
         
     | 
| 
      
 4 
     | 
    
         
            +
                Context = Struct.new(:instance, :dynamic_scope, :adjacent_results, :short_circuit, :access_mode) do
         
     | 
| 
      
 5 
     | 
    
         
            +
                  def original_instance(instance_location)
         
     | 
| 
      
 6 
     | 
    
         
            +
                    Hana::Pointer.parse(Location.resolve(instance_location)).reduce(instance) do |obj, token|
         
     | 
| 
      
 7 
     | 
    
         
            +
                      obj.fetch(obj.is_a?(Array) ? token.to_i : token)
         
     | 
| 
      
 8 
     | 
    
         
            +
                    end
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                include Output
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                DEFAULT_SCHEMA = Draft202012::BASE_URI.to_s.freeze
         
     | 
| 
      
 15 
     | 
    
         
            +
                SCHEMA_KEYWORD_CLASS = Draft202012::Vocab::Core::Schema
         
     | 
| 
      
 16 
     | 
    
         
            +
                VOCABULARY_KEYWORD_CLASS = Draft202012::Vocab::Core::Vocabulary
         
     | 
| 
      
 17 
     | 
    
         
            +
                ID_KEYWORD_CLASS = Draft202012::Vocab::Core::Id
         
     | 
| 
      
 18 
     | 
    
         
            +
                UNKNOWN_KEYWORD_CLASS = Draft202012::Vocab::Core::UnknownKeyword
         
     | 
| 
      
 19 
     | 
    
         
            +
                NOT_KEYWORD_CLASS = Draft202012::Vocab::Applicator::Not
         
     | 
| 
      
 20 
     | 
    
         
            +
                PROPERTIES_KEYWORD_CLASS = Draft202012::Vocab::Applicator::Properties
         
     | 
| 
      
 21 
     | 
    
         
            +
                DEFAULT_BASE_URI = URI('json-schemer://schema').freeze
         
     | 
| 
      
 22 
     | 
    
         
            +
                DEFAULT_FORMATS = {}.freeze
         
     | 
| 
      
 23 
     | 
    
         
            +
                DEFAULT_CONTENT_ENCODINGS = {}.freeze
         
     | 
| 
      
 24 
     | 
    
         
            +
                DEFAULT_CONTENT_MEDIA_TYPES = {}.freeze
         
     | 
| 
      
 25 
     | 
    
         
            +
                DEFAULT_KEYWORDS = {}.freeze
         
     | 
| 
      
 26 
     | 
    
         
            +
                DEFAULT_BEFORE_PROPERTY_VALIDATION = [].freeze
         
     | 
| 
      
 27 
     | 
    
         
            +
                DEFAULT_AFTER_PROPERTY_VALIDATION = [].freeze
         
     | 
| 
      
 28 
     | 
    
         
            +
                DEFAULT_REF_RESOLVER = proc { |uri| raise UnknownRef, uri.to_s }
         
     | 
| 
      
 29 
     | 
    
         
            +
                NET_HTTP_REF_RESOLVER = proc { |uri| JSON.parse(Net::HTTP.get(uri)) }
         
     | 
| 
      
 30 
     | 
    
         
            +
                RUBY_REGEXP_RESOLVER = proc { |pattern| Regexp.new(pattern) }
         
     | 
| 
      
 31 
     | 
    
         
            +
                ECMA_REGEXP_RESOLVER = proc { |pattern| Regexp.new(EcmaRegexp.ruby_equivalent(pattern)) }
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                DEFAULT_PROPERTY_DEFAULT_RESOLVER = proc do |instance, property, results_with_tree_validity|
         
     | 
| 
      
 34 
     | 
    
         
            +
                  results_with_tree_validity = results_with_tree_validity.select(&:last) unless results_with_tree_validity.size == 1
         
     | 
| 
      
 35 
     | 
    
         
            +
                  annotations = results_with_tree_validity.to_set { |result, _tree_valid| result.annotation }
         
     | 
| 
      
 36 
     | 
    
         
            +
                  if annotations.size == 1
         
     | 
| 
      
 37 
     | 
    
         
            +
                    instance[property] = annotations.first.clone
         
     | 
| 
      
 38 
     | 
    
         
            +
                    true
         
     | 
| 
      
 39 
     | 
    
         
            +
                  else
         
     | 
| 
      
 40 
     | 
    
         
            +
                    false
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                attr_accessor :base_uri, :meta_schema, :keywords, :keyword_order
         
     | 
| 
      
 45 
     | 
    
         
            +
                attr_reader :value, :parent, :root, :parsed
         
     | 
| 
      
 46 
     | 
    
         
            +
                attr_reader :vocabulary, :format, :formats, :content_encodings, :content_media_types, :custom_keywords, :before_property_validation, :after_property_validation, :insert_property_defaults, :property_default_resolver
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                def initialize(
         
     | 
| 
      
 49 
     | 
    
         
            +
                  value,
         
     | 
| 
      
 50 
     | 
    
         
            +
                  parent = nil,
         
     | 
| 
      
 51 
     | 
    
         
            +
                  root = self,
         
     | 
| 
      
 52 
     | 
    
         
            +
                  keyword = nil,
         
     | 
| 
      
 53 
     | 
    
         
            +
                  base_uri: DEFAULT_BASE_URI,
         
     | 
| 
      
 54 
     | 
    
         
            +
                  meta_schema: nil,
         
     | 
| 
      
 55 
     | 
    
         
            +
                  vocabulary: nil,
         
     | 
| 
      
 56 
     | 
    
         
            +
                  format: true,
         
     | 
| 
      
 57 
     | 
    
         
            +
                  formats: DEFAULT_FORMATS,
         
     | 
| 
      
 58 
     | 
    
         
            +
                  content_encodings: DEFAULT_CONTENT_ENCODINGS,
         
     | 
| 
      
 59 
     | 
    
         
            +
                  content_media_types: DEFAULT_CONTENT_MEDIA_TYPES,
         
     | 
| 
      
 60 
     | 
    
         
            +
                  keywords: DEFAULT_KEYWORDS,
         
     | 
| 
      
 61 
     | 
    
         
            +
                  before_property_validation: DEFAULT_BEFORE_PROPERTY_VALIDATION,
         
     | 
| 
      
 62 
     | 
    
         
            +
                  after_property_validation: DEFAULT_AFTER_PROPERTY_VALIDATION,
         
     | 
| 
      
 63 
     | 
    
         
            +
                  insert_property_defaults: false,
         
     | 
| 
      
 64 
     | 
    
         
            +
                  property_default_resolver: DEFAULT_PROPERTY_DEFAULT_RESOLVER,
         
     | 
| 
      
 65 
     | 
    
         
            +
                  ref_resolver: DEFAULT_REF_RESOLVER,
         
     | 
| 
      
 66 
     | 
    
         
            +
                  regexp_resolver: 'ruby',
         
     | 
| 
      
 67 
     | 
    
         
            +
                  output_format: 'classic',
         
     | 
| 
      
 68 
     | 
    
         
            +
                  resolve_enumerators: false,
         
     | 
| 
      
 69 
     | 
    
         
            +
                  access_mode: nil
         
     | 
| 
      
 70 
     | 
    
         
            +
                )
         
     | 
| 
      
 71 
     | 
    
         
            +
                  @value = deep_stringify_keys(value)
         
     | 
| 
      
 72 
     | 
    
         
            +
                  @parent = parent
         
     | 
| 
      
 73 
     | 
    
         
            +
                  @root = root
         
     | 
| 
      
 74 
     | 
    
         
            +
                  @keyword = keyword
         
     | 
| 
      
 75 
     | 
    
         
            +
                  @schema = self
         
     | 
| 
      
 76 
     | 
    
         
            +
                  @base_uri = base_uri
         
     | 
| 
      
 77 
     | 
    
         
            +
                  @meta_schema = meta_schema
         
     | 
| 
      
 78 
     | 
    
         
            +
                  @vocabulary = vocabulary
         
     | 
| 
      
 79 
     | 
    
         
            +
                  @format = format
         
     | 
| 
      
 80 
     | 
    
         
            +
                  @formats = formats
         
     | 
| 
      
 81 
     | 
    
         
            +
                  @content_encodings = content_encodings
         
     | 
| 
      
 82 
     | 
    
         
            +
                  @content_media_types = content_media_types
         
     | 
| 
      
 83 
     | 
    
         
            +
                  @custom_keywords = keywords
         
     | 
| 
      
 84 
     | 
    
         
            +
                  @before_property_validation = Array(before_property_validation)
         
     | 
| 
      
 85 
     | 
    
         
            +
                  @after_property_validation = Array(after_property_validation)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  @insert_property_defaults = insert_property_defaults
         
     | 
| 
      
 87 
     | 
    
         
            +
                  @property_default_resolver = property_default_resolver
         
     | 
| 
      
 88 
     | 
    
         
            +
                  @original_ref_resolver = ref_resolver
         
     | 
| 
      
 89 
     | 
    
         
            +
                  @original_regexp_resolver = regexp_resolver
         
     | 
| 
      
 90 
     | 
    
         
            +
                  @output_format = output_format
         
     | 
| 
      
 91 
     | 
    
         
            +
                  @resolve_enumerators = resolve_enumerators
         
     | 
| 
      
 92 
     | 
    
         
            +
                  @access_mode = access_mode
         
     | 
| 
      
 93 
     | 
    
         
            +
                  @parsed = parse
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                def valid?(instance, **options)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  validate(instance, :output_format => 'flag', **options).fetch('valid')
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                def validate(instance, output_format: @output_format, resolve_enumerators: @resolve_enumerators, access_mode: @access_mode)
         
     | 
| 
      
 101 
     | 
    
         
            +
                  instance_location = Location.root
         
     | 
| 
      
 102 
     | 
    
         
            +
                  context = Context.new(instance, [], nil, (!insert_property_defaults && output_format == 'flag'), access_mode)
         
     | 
| 
      
 103 
     | 
    
         
            +
                  result = validate_instance(deep_stringify_keys(instance), instance_location, root_keyword_location, context)
         
     | 
| 
      
 104 
     | 
    
         
            +
                  if insert_property_defaults && result.insert_property_defaults(context, &property_default_resolver)
         
     | 
| 
      
 105 
     | 
    
         
            +
                    result = validate_instance(deep_stringify_keys(instance), instance_location, root_keyword_location, context)
         
     | 
| 
      
 106 
     | 
    
         
            +
                  end
         
     | 
| 
      
 107 
     | 
    
         
            +
                  output = result.output(output_format)
         
     | 
| 
      
 108 
     | 
    
         
            +
                  resolve_enumerators!(output) if resolve_enumerators
         
     | 
| 
      
 109 
     | 
    
         
            +
                  output
         
     | 
| 
      
 110 
     | 
    
         
            +
                end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                def valid_schema?
         
     | 
| 
      
 113 
     | 
    
         
            +
                  meta_schema.valid?(value)
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                def validate_schema
         
     | 
| 
      
 117 
     | 
    
         
            +
                  meta_schema.validate(value)
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                def ref(value)
         
     | 
| 
      
 121 
     | 
    
         
            +
                  root.resolve_ref(URI.join(base_uri, value))
         
     | 
| 
      
 122 
     | 
    
         
            +
                end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                def validate_instance(instance, instance_location, keyword_location, context)
         
     | 
| 
      
 125 
     | 
    
         
            +
                  context.dynamic_scope.push(self)
         
     | 
| 
      
 126 
     | 
    
         
            +
                  original_adjacent_results = context.adjacent_results
         
     | 
| 
      
 127 
     | 
    
         
            +
                  adjacent_results = context.adjacent_results = {}
         
     | 
| 
      
 128 
     | 
    
         
            +
                  short_circuit = context.short_circuit
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 131 
     | 
    
         
            +
                    return result(instance, instance_location, keyword_location, false) if value == false
         
     | 
| 
      
 132 
     | 
    
         
            +
                    return result(instance, instance_location, keyword_location, true) if value == true || value.empty?
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                    valid = true
         
     | 
| 
      
 135 
     | 
    
         
            +
                    nested = []
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                    parsed.each do |keyword, keyword_instance|
         
     | 
| 
      
 138 
     | 
    
         
            +
                      next unless keyword_result = keyword_instance.validate(instance, instance_location, join_location(keyword_location, keyword), context)
         
     | 
| 
      
 139 
     | 
    
         
            +
                      valid &&= keyword_result.valid
         
     | 
| 
      
 140 
     | 
    
         
            +
                      return result(instance, instance_location, keyword_location, false) if short_circuit && !valid
         
     | 
| 
      
 141 
     | 
    
         
            +
                      nested << keyword_result
         
     | 
| 
      
 142 
     | 
    
         
            +
                      adjacent_results[keyword_instance.class] = keyword_result
         
     | 
| 
      
 143 
     | 
    
         
            +
                    end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                    if custom_keywords.any?
         
     | 
| 
      
 146 
     | 
    
         
            +
                      custom_keywords.each do |custom_keyword, callable|
         
     | 
| 
      
 147 
     | 
    
         
            +
                        if value.key?(custom_keyword)
         
     | 
| 
      
 148 
     | 
    
         
            +
                          [*callable.call(instance, value, instance_location)].each do |custom_keyword_result|
         
     | 
| 
      
 149 
     | 
    
         
            +
                            custom_keyword_valid = custom_keyword_result == true
         
     | 
| 
      
 150 
     | 
    
         
            +
                            valid &&= custom_keyword_valid
         
     | 
| 
      
 151 
     | 
    
         
            +
                            type = custom_keyword_result.is_a?(String) ? custom_keyword_result : custom_keyword
         
     | 
| 
      
 152 
     | 
    
         
            +
                            details = { 'keyword' => custom_keyword, 'result' => custom_keyword_result }
         
     | 
| 
      
 153 
     | 
    
         
            +
                            nested << result(instance, instance_location, keyword_location, custom_keyword_valid, :type => type, :details => details)
         
     | 
| 
      
 154 
     | 
    
         
            +
                          end
         
     | 
| 
      
 155 
     | 
    
         
            +
                        end
         
     | 
| 
      
 156 
     | 
    
         
            +
                      end
         
     | 
| 
      
 157 
     | 
    
         
            +
                    end
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
                    result(instance, instance_location, keyword_location, valid, nested)
         
     | 
| 
      
 160 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 161 
     | 
    
         
            +
                    context.dynamic_scope.pop
         
     | 
| 
      
 162 
     | 
    
         
            +
                    context.adjacent_results = original_adjacent_results
         
     | 
| 
      
 163 
     | 
    
         
            +
                  end
         
     | 
| 
      
 164 
     | 
    
         
            +
                end
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                def resolve_ref(uri)
         
     | 
| 
      
 167 
     | 
    
         
            +
                  pointer = ''
         
     | 
| 
      
 168 
     | 
    
         
            +
                  if Format.valid_json_pointer?(uri.fragment)
         
     | 
| 
      
 169 
     | 
    
         
            +
                    pointer = URI.decode_www_form_component(uri.fragment)
         
     | 
| 
      
 170 
     | 
    
         
            +
                    uri.fragment = nil
         
     | 
| 
      
 171 
     | 
    
         
            +
                  end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                  lexical_resources = resources.fetch(:lexical)
         
     | 
| 
      
 174 
     | 
    
         
            +
                  schema = lexical_resources[uri]
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                  if !schema && uri.fragment.nil?
         
     | 
| 
      
 177 
     | 
    
         
            +
                    empty_fragment_uri = uri.dup
         
     | 
| 
      
 178 
     | 
    
         
            +
                    empty_fragment_uri.fragment = ''
         
     | 
| 
      
 179 
     | 
    
         
            +
                    schema = lexical_resources[empty_fragment_uri]
         
     | 
| 
      
 180 
     | 
    
         
            +
                  end
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
                  unless schema
         
     | 
| 
      
 183 
     | 
    
         
            +
                    location_independent_identifier = uri.fragment
         
     | 
| 
      
 184 
     | 
    
         
            +
                    uri.fragment = nil
         
     | 
| 
      
 185 
     | 
    
         
            +
                    remote_schema = JSONSchemer.schema(
         
     | 
| 
      
 186 
     | 
    
         
            +
                      ref_resolver.call(uri) || raise(InvalidRefResolution, uri.to_s),
         
     | 
| 
      
 187 
     | 
    
         
            +
                      :base_uri => uri,
         
     | 
| 
      
 188 
     | 
    
         
            +
                      :meta_schema => meta_schema,
         
     | 
| 
      
 189 
     | 
    
         
            +
                      :format => format,
         
     | 
| 
      
 190 
     | 
    
         
            +
                      :formats => formats,
         
     | 
| 
      
 191 
     | 
    
         
            +
                      :content_encodings => content_encodings,
         
     | 
| 
      
 192 
     | 
    
         
            +
                      :content_media_types => content_media_types,
         
     | 
| 
      
 193 
     | 
    
         
            +
                      :keywords => custom_keywords,
         
     | 
| 
      
 194 
     | 
    
         
            +
                      :before_property_validation => before_property_validation,
         
     | 
| 
      
 195 
     | 
    
         
            +
                      :after_property_validation => after_property_validation,
         
     | 
| 
      
 196 
     | 
    
         
            +
                      :property_default_resolver => property_default_resolver,
         
     | 
| 
      
 197 
     | 
    
         
            +
                      :ref_resolver => ref_resolver,
         
     | 
| 
      
 198 
     | 
    
         
            +
                      :regexp_resolver => regexp_resolver
         
     | 
| 
      
 199 
     | 
    
         
            +
                    )
         
     | 
| 
      
 200 
     | 
    
         
            +
                    remote_uri = remote_schema.base_uri.dup
         
     | 
| 
      
 201 
     | 
    
         
            +
                    remote_uri.fragment = location_independent_identifier if location_independent_identifier
         
     | 
| 
      
 202 
     | 
    
         
            +
                    schema = remote_schema.resources.fetch(:lexical).fetch(remote_uri)
         
     | 
| 
      
 203 
     | 
    
         
            +
                  end
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
                  schema = Hana::Pointer.parse(pointer).reduce(schema) do |obj, token|
         
     | 
| 
      
 206 
     | 
    
         
            +
                    obj.fetch(token)
         
     | 
| 
      
 207 
     | 
    
         
            +
                  rescue IndexError
         
     | 
| 
      
 208 
     | 
    
         
            +
                    raise InvalidRefPointer, pointer
         
     | 
| 
      
 209 
     | 
    
         
            +
                  end
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                  schema = schema.parsed_schema if schema.is_a?(Keyword)
         
     | 
| 
      
 212 
     | 
    
         
            +
                  raise InvalidRefPointer, pointer unless schema.is_a?(Schema)
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
                  schema
         
     | 
| 
      
 215 
     | 
    
         
            +
                end
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
                def resolve_regexp(pattern)
         
     | 
| 
      
 218 
     | 
    
         
            +
                  regexp_resolver.call(pattern) || raise(InvalidRegexpResolution, pattern)
         
     | 
| 
      
 219 
     | 
    
         
            +
                end
         
     | 
| 
      
 220 
     | 
    
         
            +
             
     | 
| 
      
 221 
     | 
    
         
            +
                def bundle
         
     | 
| 
      
 222 
     | 
    
         
            +
                  return value unless value.is_a?(Hash)
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
                  id_keyword = meta_schema.id_keyword
         
     | 
| 
      
 225 
     | 
    
         
            +
                  defs_keyword = meta_schema.defs_keyword
         
     | 
| 
      
 226 
     | 
    
         
            +
             
     | 
| 
      
 227 
     | 
    
         
            +
                  compound_document = value.dup
         
     | 
| 
      
 228 
     | 
    
         
            +
                  compound_document[id_keyword] = base_uri.to_s
         
     | 
| 
      
 229 
     | 
    
         
            +
                  compound_document['$schema'] = meta_schema.base_uri.to_s
         
     | 
| 
      
 230 
     | 
    
         
            +
                  embedded_resources = compound_document[defs_keyword] = (compound_document[defs_keyword]&.dup || {})
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
                  if compound_document.key?('$ref') && meta_schema.keywords.fetch('$ref').exclusive?
         
     | 
| 
      
 233 
     | 
    
         
            +
                    compound_document['allOf'] = (compound_document['allOf']&.dup || [])
         
     | 
| 
      
 234 
     | 
    
         
            +
                    compound_document['allOf'] << { '$ref' => compound_document.delete('$ref') }
         
     | 
| 
      
 235 
     | 
    
         
            +
                  end
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
                  values = [self]
         
     | 
| 
      
 238 
     | 
    
         
            +
                  while value = values.shift
         
     | 
| 
      
 239 
     | 
    
         
            +
                    case value
         
     | 
| 
      
 240 
     | 
    
         
            +
                    when Schema
         
     | 
| 
      
 241 
     | 
    
         
            +
                      values << value.parsed
         
     | 
| 
      
 242 
     | 
    
         
            +
                    when Keyword
         
     | 
| 
      
 243 
     | 
    
         
            +
                      if value.respond_to?(:ref_uri) && value.respond_to?(:ref_schema)
         
     | 
| 
      
 244 
     | 
    
         
            +
                        ref_uri = value.ref_uri.dup
         
     | 
| 
      
 245 
     | 
    
         
            +
                        ref_uri.fragment = nil
         
     | 
| 
      
 246 
     | 
    
         
            +
                        ref_id = ref_uri.to_s
         
     | 
| 
      
 247 
     | 
    
         
            +
                        ref_schema = value.ref_schema.root
         
     | 
| 
      
 248 
     | 
    
         
            +
             
     | 
| 
      
 249 
     | 
    
         
            +
                        next if ref_schema == root || embedded_resources.key?(ref_id)
         
     | 
| 
      
 250 
     | 
    
         
            +
             
     | 
| 
      
 251 
     | 
    
         
            +
                        embedded_resource = ref_schema.value.dup
         
     | 
| 
      
 252 
     | 
    
         
            +
                        embedded_resource[id_keyword] = ref_id
         
     | 
| 
      
 253 
     | 
    
         
            +
                        embedded_resource['$schema'] = ref_schema.meta_schema.base_uri.to_s
         
     | 
| 
      
 254 
     | 
    
         
            +
                        embedded_resources[ref_id] = embedded_resource
         
     | 
| 
      
 255 
     | 
    
         
            +
             
     | 
| 
      
 256 
     | 
    
         
            +
                        values << ref_schema
         
     | 
| 
      
 257 
     | 
    
         
            +
                      else
         
     | 
| 
      
 258 
     | 
    
         
            +
                        values << value.parsed
         
     | 
| 
      
 259 
     | 
    
         
            +
                      end
         
     | 
| 
      
 260 
     | 
    
         
            +
                    when Hash
         
     | 
| 
      
 261 
     | 
    
         
            +
                      values.concat(value.values)
         
     | 
| 
      
 262 
     | 
    
         
            +
                    when Array
         
     | 
| 
      
 263 
     | 
    
         
            +
                      values.concat(value)
         
     | 
| 
      
 264 
     | 
    
         
            +
                    end
         
     | 
| 
      
 265 
     | 
    
         
            +
                  end
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
                  compound_document
         
     | 
| 
      
 268 
     | 
    
         
            +
                end
         
     | 
| 
      
 269 
     | 
    
         
            +
             
     | 
| 
      
 270 
     | 
    
         
            +
                def absolute_keyword_location
         
     | 
| 
      
 271 
     | 
    
         
            +
                  # using `equal?` because `URI::Generic#==` is slow
         
     | 
| 
      
 272 
     | 
    
         
            +
                  @absolute_keyword_location ||= if !parent || (!parent.schema.base_uri.equal?(base_uri) && (base_uri.fragment.nil? || base_uri.fragment.empty?))
         
     | 
| 
      
 273 
     | 
    
         
            +
                    absolute_keyword_location_uri = base_uri.dup
         
     | 
| 
      
 274 
     | 
    
         
            +
                    absolute_keyword_location_uri.fragment = ''
         
     | 
| 
      
 275 
     | 
    
         
            +
                    absolute_keyword_location_uri.to_s
         
     | 
| 
      
 276 
     | 
    
         
            +
                  elsif keyword
         
     | 
| 
      
 277 
     | 
    
         
            +
                    "#{parent.absolute_keyword_location}/#{fragment_encode(escaped_keyword)}"
         
     | 
| 
      
 278 
     | 
    
         
            +
                  else
         
     | 
| 
      
 279 
     | 
    
         
            +
                    parent.absolute_keyword_location
         
     | 
| 
      
 280 
     | 
    
         
            +
                  end
         
     | 
| 
      
 281 
     | 
    
         
            +
                end
         
     | 
| 
      
 282 
     | 
    
         
            +
             
     | 
| 
      
 283 
     | 
    
         
            +
                def schema_pointer
         
     | 
| 
      
 284 
     | 
    
         
            +
                  @schema_pointer ||= if !parent
         
     | 
| 
      
 285 
     | 
    
         
            +
                    ''
         
     | 
| 
      
 286 
     | 
    
         
            +
                  elsif keyword
         
     | 
| 
      
 287 
     | 
    
         
            +
                    "#{parent.schema_pointer}/#{escaped_keyword}"
         
     | 
| 
      
 288 
     | 
    
         
            +
                  else
         
     | 
| 
      
 289 
     | 
    
         
            +
                    parent.schema_pointer
         
     | 
| 
      
 290 
     | 
    
         
            +
                  end
         
     | 
| 
      
 291 
     | 
    
         
            +
                end
         
     | 
| 
      
 292 
     | 
    
         
            +
             
     | 
| 
      
 293 
     | 
    
         
            +
                def error_key
         
     | 
| 
      
 294 
     | 
    
         
            +
                  '^'
         
     | 
| 
      
 295 
     | 
    
         
            +
                end
         
     | 
| 
      
 296 
     | 
    
         
            +
             
     | 
| 
      
 297 
     | 
    
         
            +
                def fetch(key)
         
     | 
| 
      
 298 
     | 
    
         
            +
                  parsed.fetch(key)
         
     | 
| 
      
 299 
     | 
    
         
            +
                end
         
     | 
| 
      
 300 
     | 
    
         
            +
             
     | 
| 
      
 301 
     | 
    
         
            +
                def fetch_format(format, *args, &block)
         
     | 
| 
      
 302 
     | 
    
         
            +
                  if meta_schema == self
         
     | 
| 
      
 303 
     | 
    
         
            +
                    formats.fetch(format, *args, &block)
         
     | 
| 
      
 304 
     | 
    
         
            +
                  else
         
     | 
| 
      
 305 
     | 
    
         
            +
                    formats.fetch(format) { meta_schema.fetch_format(format, *args, &block) }
         
     | 
| 
      
 306 
     | 
    
         
            +
                  end
         
     | 
| 
      
 307 
     | 
    
         
            +
                end
         
     | 
| 
      
 308 
     | 
    
         
            +
             
     | 
| 
      
 309 
     | 
    
         
            +
                def fetch_content_encoding(content_encoding, *args, &block)
         
     | 
| 
      
 310 
     | 
    
         
            +
                  if meta_schema == self
         
     | 
| 
      
 311 
     | 
    
         
            +
                    content_encodings.fetch(content_encoding, *args, &block)
         
     | 
| 
      
 312 
     | 
    
         
            +
                  else
         
     | 
| 
      
 313 
     | 
    
         
            +
                    content_encodings.fetch(content_encoding) { meta_schema.fetch_content_encoding(content_encoding, *args, &block) }
         
     | 
| 
      
 314 
     | 
    
         
            +
                  end
         
     | 
| 
      
 315 
     | 
    
         
            +
                end
         
     | 
| 
      
 316 
     | 
    
         
            +
             
     | 
| 
      
 317 
     | 
    
         
            +
                def fetch_content_media_type(content_media_type, *args, &block)
         
     | 
| 
      
 318 
     | 
    
         
            +
                  if meta_schema == self
         
     | 
| 
      
 319 
     | 
    
         
            +
                    content_media_types.fetch(content_media_type, *args, &block)
         
     | 
| 
      
 320 
     | 
    
         
            +
                  else
         
     | 
| 
      
 321 
     | 
    
         
            +
                    content_media_types.fetch(content_media_type) { meta_schema.fetch_content_media_type(content_media_type, *args, &block) }
         
     | 
| 
      
 322 
     | 
    
         
            +
                  end
         
     | 
| 
      
 323 
     | 
    
         
            +
                end
         
     | 
| 
      
 324 
     | 
    
         
            +
             
     | 
| 
      
 325 
     | 
    
         
            +
                def id_keyword
         
     | 
| 
      
 326 
     | 
    
         
            +
                  @id_keyword ||= (keywords.key?('$id') ? '$id' : 'id')
         
     | 
| 
      
 327 
     | 
    
         
            +
                end
         
     | 
| 
      
 328 
     | 
    
         
            +
             
     | 
| 
      
 329 
     | 
    
         
            +
                def defs_keyword
         
     | 
| 
      
 330 
     | 
    
         
            +
                  @defs_keyword ||= (keywords.key?('$defs') ? '$defs' : 'definitions')
         
     | 
| 
      
 331 
     | 
    
         
            +
                end
         
     | 
| 
      
 332 
     | 
    
         
            +
             
     | 
| 
      
 333 
     | 
    
         
            +
                def resources
         
     | 
| 
      
 334 
     | 
    
         
            +
                  @resources ||= { :lexical => {}, :dynamic => {} }
         
     | 
| 
      
 335 
     | 
    
         
            +
                end
         
     | 
| 
      
 336 
     | 
    
         
            +
             
     | 
| 
      
 337 
     | 
    
         
            +
                def error(formatted_instance_location:, **options)
         
     | 
| 
      
 338 
     | 
    
         
            +
                  if value == false && parent&.respond_to?(:false_schema_error)
         
     | 
| 
      
 339 
     | 
    
         
            +
                    parent.false_schema_error(:formatted_instance_location => formatted_instance_location, **options)
         
     | 
| 
      
 340 
     | 
    
         
            +
                  else
         
     | 
| 
      
 341 
     | 
    
         
            +
                    "value at #{formatted_instance_location} does not match schema"
         
     | 
| 
      
 342 
     | 
    
         
            +
                  end
         
     | 
| 
      
 343 
     | 
    
         
            +
                end
         
     | 
| 
      
 344 
     | 
    
         
            +
             
     | 
| 
      
 345 
     | 
    
         
            +
                def inspect
         
     | 
| 
      
 346 
     | 
    
         
            +
                  "#<#{self.class.name} @value=#{@value.inspect} @parent=#{@parent.inspect} @keyword=#{@keyword.inspect}>"
         
     | 
| 
      
 347 
     | 
    
         
            +
                end
         
     | 
| 
      
 348 
     | 
    
         
            +
             
     | 
| 
      
 349 
     | 
    
         
            +
              private
         
     | 
| 
      
 350 
     | 
    
         
            +
             
     | 
| 
      
 351 
     | 
    
         
            +
                def parse
         
     | 
| 
      
 352 
     | 
    
         
            +
                  @parsed = {}
         
     | 
| 
      
 353 
     | 
    
         
            +
             
     | 
| 
      
 354 
     | 
    
         
            +
                  if value.is_a?(Hash) && value.key?('$schema')
         
     | 
| 
      
 355 
     | 
    
         
            +
                    @parsed['$schema'] = SCHEMA_KEYWORD_CLASS.new(value.fetch('$schema'), self, '$schema')
         
     | 
| 
      
 356 
     | 
    
         
            +
                  elsif root == self && !meta_schema
         
     | 
| 
      
 357 
     | 
    
         
            +
                    SCHEMA_KEYWORD_CLASS.new(DEFAULT_SCHEMA, self, '$schema')
         
     | 
| 
      
 358 
     | 
    
         
            +
                  end
         
     | 
| 
      
 359 
     | 
    
         
            +
             
     | 
| 
      
 360 
     | 
    
         
            +
                  if value.is_a?(Hash) && value.key?('$vocabulary')
         
     | 
| 
      
 361 
     | 
    
         
            +
                    @parsed['$vocabulary'] = VOCABULARY_KEYWORD_CLASS.new(value.fetch('$vocabulary'), self, '$vocabulary')
         
     | 
| 
      
 362 
     | 
    
         
            +
                  elsif vocabulary
         
     | 
| 
      
 363 
     | 
    
         
            +
                    VOCABULARY_KEYWORD_CLASS.new(vocabulary, self, '$vocabulary')
         
     | 
| 
      
 364 
     | 
    
         
            +
                  end
         
     | 
| 
      
 365 
     | 
    
         
            +
             
     | 
| 
      
 366 
     | 
    
         
            +
                  keywords = meta_schema.keywords
         
     | 
| 
      
 367 
     | 
    
         
            +
                  exclusive_ref = value.is_a?(Hash) && value.key?('$ref') && keywords.fetch('$ref').exclusive?
         
     | 
| 
      
 368 
     | 
    
         
            +
             
     | 
| 
      
 369 
     | 
    
         
            +
                  if root == self && (!value.is_a?(Hash) || !value.key?(meta_schema.id_keyword) || exclusive_ref)
         
     | 
| 
      
 370 
     | 
    
         
            +
                    ID_KEYWORD_CLASS.new(base_uri, self, meta_schema.id_keyword)
         
     | 
| 
      
 371 
     | 
    
         
            +
                  end
         
     | 
| 
      
 372 
     | 
    
         
            +
             
     | 
| 
      
 373 
     | 
    
         
            +
                  if exclusive_ref
         
     | 
| 
      
 374 
     | 
    
         
            +
                    @parsed['$ref'] = keywords.fetch('$ref').new(value.fetch('$ref'), self, '$ref')
         
     | 
| 
      
 375 
     | 
    
         
            +
                    defs_keyword = meta_schema.defs_keyword
         
     | 
| 
      
 376 
     | 
    
         
            +
                    if value.key?(defs_keyword) && keywords.key?(defs_keyword)
         
     | 
| 
      
 377 
     | 
    
         
            +
                      @parsed[defs_keyword] = keywords.fetch(defs_keyword).new(value.fetch(defs_keyword), self, defs_keyword)
         
     | 
| 
      
 378 
     | 
    
         
            +
                    end
         
     | 
| 
      
 379 
     | 
    
         
            +
                  elsif value.is_a?(Hash)
         
     | 
| 
      
 380 
     | 
    
         
            +
                    keyword_order = meta_schema.keyword_order
         
     | 
| 
      
 381 
     | 
    
         
            +
                    last = keywords.size
         
     | 
| 
      
 382 
     | 
    
         
            +
             
     | 
| 
      
 383 
     | 
    
         
            +
                    value.sort do |(keyword_a, _value_a), (keyword_b, _value_b)|
         
     | 
| 
      
 384 
     | 
    
         
            +
                      keyword_order.fetch(keyword_a, last) <=> keyword_order.fetch(keyword_b, last)
         
     | 
| 
      
 385 
     | 
    
         
            +
                    end.each do |keyword, value|
         
     | 
| 
      
 386 
     | 
    
         
            +
                      @parsed[keyword] ||= keywords.fetch(keyword, UNKNOWN_KEYWORD_CLASS).new(value, self, keyword)
         
     | 
| 
      
 387 
     | 
    
         
            +
                    end
         
     | 
| 
      
 388 
     | 
    
         
            +
                  end
         
     | 
| 
      
 389 
     | 
    
         
            +
             
     | 
| 
      
 390 
     | 
    
         
            +
                  @parsed
         
     | 
| 
      
 391 
     | 
    
         
            +
                end
         
     | 
| 
      
 392 
     | 
    
         
            +
             
     | 
| 
      
 393 
     | 
    
         
            +
                def root_keyword_location
         
     | 
| 
      
 394 
     | 
    
         
            +
                  @root_keyword_location ||= Location.root
         
     | 
| 
      
 395 
     | 
    
         
            +
                end
         
     | 
| 
      
 396 
     | 
    
         
            +
             
     | 
| 
      
 397 
     | 
    
         
            +
                def ref_resolver
         
     | 
| 
      
 398 
     | 
    
         
            +
                  @ref_resolver ||= @original_ref_resolver == 'net/http' ? CachedResolver.new(&NET_HTTP_REF_RESOLVER) : @original_ref_resolver
         
     | 
| 
      
 399 
     | 
    
         
            +
                end
         
     | 
| 
      
 400 
     | 
    
         
            +
             
     | 
| 
      
 401 
     | 
    
         
            +
                def regexp_resolver
         
     | 
| 
      
 402 
     | 
    
         
            +
                  @regexp_resolver ||= case @original_regexp_resolver
         
     | 
| 
      
 403 
     | 
    
         
            +
                  when 'ecma'
         
     | 
| 
      
 404 
     | 
    
         
            +
                    CachedResolver.new(&ECMA_REGEXP_RESOLVER)
         
     | 
| 
      
 405 
     | 
    
         
            +
                  when 'ruby'
         
     | 
| 
      
 406 
     | 
    
         
            +
                    CachedResolver.new(&RUBY_REGEXP_RESOLVER)
         
     | 
| 
      
 407 
     | 
    
         
            +
                  else
         
     | 
| 
      
 408 
     | 
    
         
            +
                    @original_regexp_resolver
         
     | 
| 
      
 409 
     | 
    
         
            +
                  end
         
     | 
| 
      
 410 
     | 
    
         
            +
                end
         
     | 
| 
      
 411 
     | 
    
         
            +
             
     | 
| 
      
 412 
     | 
    
         
            +
                def resolve_enumerators!(output)
         
     | 
| 
      
 413 
     | 
    
         
            +
                  case output
         
     | 
| 
      
 414 
     | 
    
         
            +
                  when Hash
         
     | 
| 
      
 415 
     | 
    
         
            +
                    output.transform_values! { |value| resolve_enumerators!(value) }
         
     | 
| 
      
 416 
     | 
    
         
            +
                  when Enumerator
         
     | 
| 
      
 417 
     | 
    
         
            +
                    output.map { |value| resolve_enumerators!(value) }
         
     | 
| 
      
 418 
     | 
    
         
            +
                  else
         
     | 
| 
      
 419 
     | 
    
         
            +
                    output
         
     | 
| 
      
 420 
     | 
    
         
            +
                  end
         
     | 
| 
      
 421 
     | 
    
         
            +
                end
         
     | 
| 
      
 422 
     | 
    
         
            +
              end
         
     | 
| 
      
 423 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/json_schemer/version.rb
    CHANGED