json_schemer 0.1.5 → 0.1.6
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/Gemfile.lock +2 -2
- data/README.md +10 -0
- data/lib/json_schemer.rb +34 -4
- data/lib/json_schemer/cached_ref_resolver.rb +15 -0
- data/lib/json_schemer/schema/base.rb +222 -106
- data/lib/json_schemer/schema/draft4.rb +8 -8
- data/lib/json_schemer/version.rb +1 -1
- metadata +4 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 8dc3f244ec43815a4207b9be2112cc76afee3cd37b96a3e1eb0fd9b189f8ad72
         | 
| 4 | 
            +
              data.tar.gz: 3dcede7d0418d30e98fe75054000657bf03201a1fbf8231810506a2a91de1bbb
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 1ea22d403dd91c7c555d7fe949a25840255a1ed55af658aad703b965b79e063fe2ae5a1d82b3b3ac7a6a56cc32b5720ec0639d734235e934ccaef04f87a11146
         | 
| 7 | 
            +
              data.tar.gz: 243e04ef109d923d9de753856943f2269548485de36f476e9d527be132c1f737abcd1ee6e74c6b3263bb95d9b03fcfc0a6f8a11c4249d538baab0d469c97652d
         | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -46,6 +46,16 @@ schemer.valid?({ 'abc' => 10 }) | |
| 46 46 |  | 
| 47 47 | 
             
            schemer.validate({ 'abc' => 10 }).to_a
         | 
| 48 48 | 
             
            # => [{"data"=>10, "schema"=>{"type"=>"integer", "minimum"=>11}, "pointer"=>"#/abc", "type"=>"minimum"}]
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            # schema files
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            schema = Pathname.new('/path/to/schema.json')
         | 
| 53 | 
            +
            schemer = JSONSchemer.schema(schema)
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            # schema json string
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            schema = '{ "type": "integer" }'
         | 
| 58 | 
            +
            schemer = JSONSchemer.schema(schema)
         | 
| 49 59 | 
             
            ```
         | 
| 50 60 |  | 
| 51 61 | 
             
            ## Options
         | 
    
        data/lib/json_schemer.rb
    CHANGED
    
    | @@ -2,6 +2,7 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'json_schemer/version'
         | 
| 4 4 | 
             
            require 'json_schemer/format'
         | 
| 5 | 
            +
            require 'json_schemer/cached_ref_resolver'
         | 
| 5 6 | 
             
            require 'json_schemer/schema/base'
         | 
| 6 7 | 
             
            require 'json_schemer/schema/draft4'
         | 
| 7 8 | 
             
            require 'json_schemer/schema/draft6'
         | 
| @@ -10,6 +11,8 @@ require 'json_schemer/schema/draft7' | |
| 10 11 | 
             
            module JSONSchemer
         | 
| 11 12 | 
             
              class UnsupportedMetaSchema < StandardError; end
         | 
| 12 13 | 
             
              class UnknownRef < StandardError; end
         | 
| 14 | 
            +
              class InvalidFileURI < StandardError; end
         | 
| 15 | 
            +
              class InvalidSymbolKey < StandardError; end
         | 
| 13 16 |  | 
| 14 17 | 
             
              DRAFT_CLASS_BY_META_SCHEMA = {
         | 
| 15 18 | 
             
                'http://json-schema.org/draft-04/schema#' => Schema::Draft4,
         | 
| @@ -19,9 +22,36 @@ module JSONSchemer | |
| 19 22 |  | 
| 20 23 | 
             
              DEFAULT_META_SCHEMA = 'http://json-schema.org/draft-07/schema#'
         | 
| 21 24 |  | 
| 22 | 
            -
               | 
| 23 | 
            -
                 | 
| 24 | 
            -
                 | 
| 25 | 
            -
                 | 
| 25 | 
            +
              FILE_URI_REF_RESOLVER = proc do |uri|
         | 
| 26 | 
            +
                raise InvalidFileURI, 'must use `file` scheme' unless uri.scheme == 'file'
         | 
| 27 | 
            +
                raise InvalidFileURI, 'cannot have a host (use `file:///`)' if uri.host
         | 
| 28 | 
            +
                JSON.parse(File.read(uri.path))
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              class << self
         | 
| 32 | 
            +
                def schema(schema, **options)
         | 
| 33 | 
            +
                  case schema
         | 
| 34 | 
            +
                  when String
         | 
| 35 | 
            +
                    schema = JSON.parse(schema)
         | 
| 36 | 
            +
                  when Pathname
         | 
| 37 | 
            +
                    uri = URI.parse("file://#{schema.realpath}")
         | 
| 38 | 
            +
                    if options.key?(:ref_resolver)
         | 
| 39 | 
            +
                      schema = FILE_URI_REF_RESOLVER.call(uri)
         | 
| 40 | 
            +
                    else
         | 
| 41 | 
            +
                      ref_resolver = CachedRefResolver.new(&FILE_URI_REF_RESOLVER)
         | 
| 42 | 
            +
                      schema = ref_resolver.call(uri)
         | 
| 43 | 
            +
                      options[:ref_resolver] = ref_resolver
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
                    schema[draft_class(schema)::ID_KEYWORD] ||= uri.to_s
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                  draft_class(schema).new(schema, **options)
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              private
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def draft_class(schema)
         | 
| 53 | 
            +
                  meta_schema = schema.is_a?(Hash) && schema.key?('$schema') ? schema['$schema'] : DEFAULT_META_SCHEMA
         | 
| 54 | 
            +
                  DRAFT_CLASS_BY_META_SCHEMA[meta_schema] || raise(UnsupportedMetaSchema, meta_schema)
         | 
| 55 | 
            +
                end
         | 
| 26 56 | 
             
              end
         | 
| 27 57 | 
             
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module JSONSchemer
         | 
| 4 | 
            +
              class CachedRefResolver
         | 
| 5 | 
            +
                def initialize(&ref_resolver)
         | 
| 6 | 
            +
                  @ref_resolver = ref_resolver
         | 
| 7 | 
            +
                  @cache = {}
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def call(uri)
         | 
| 11 | 
            +
                  @cache[uri] = @ref_resolver.call(uri) unless @cache.key?(uri)
         | 
| 12 | 
            +
                  @cache[uri]
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -12,6 +12,18 @@ module JSONSchemer | |
| 12 12 | 
             
                class Base
         | 
| 13 13 | 
             
                  include Format
         | 
| 14 14 |  | 
| 15 | 
            +
                  Instance = Struct.new(:data, :data_pointer, :schema, :schema_pointer, :parent_uri) do
         | 
| 16 | 
            +
                    def merge(
         | 
| 17 | 
            +
                      data: self.data,
         | 
| 18 | 
            +
                      data_pointer: self.data_pointer,
         | 
| 19 | 
            +
                      schema: self.schema,
         | 
| 20 | 
            +
                      schema_pointer: self.schema_pointer,
         | 
| 21 | 
            +
                      parent_uri: self.parent_uri
         | 
| 22 | 
            +
                    )
         | 
| 23 | 
            +
                      self.class.new(data, data_pointer, schema, schema_pointer, parent_uri)
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 15 27 | 
             
                  ID_KEYWORD = '$id'
         | 
| 16 28 | 
             
                  DEFAULT_REF_RESOLVER = proc { |uri| raise UnknownRef, uri.to_s }.freeze
         | 
| 17 29 | 
             
                  NET_HTTP_REF_RESOLVER = proc { |uri| JSON.parse(Net::HTTP.get(uri)) }.freeze
         | 
| @@ -24,23 +36,36 @@ module JSONSchemer | |
| 24 36 | 
             
                    keywords: nil,
         | 
| 25 37 | 
             
                    ref_resolver: DEFAULT_REF_RESOLVER
         | 
| 26 38 | 
             
                  )
         | 
| 39 | 
            +
                    raise InvalidSymbolKey, 'schemas must use string keys' if schema.is_a?(Hash) && schema.first.first.is_a?(Symbol)
         | 
| 27 40 | 
             
                    @root = schema
         | 
| 28 41 | 
             
                    @format = format
         | 
| 29 42 | 
             
                    @formats = formats
         | 
| 30 43 | 
             
                    @keywords = keywords
         | 
| 31 | 
            -
                    @ref_resolver = ref_resolver == 'net/http' ? NET_HTTP_REF_RESOLVER : ref_resolver
         | 
| 44 | 
            +
                    @ref_resolver = ref_resolver == 'net/http' ? CachedRefResolver.new(&NET_HTTP_REF_RESOLVER) : ref_resolver
         | 
| 32 45 | 
             
                  end
         | 
| 33 46 |  | 
| 34 | 
            -
                  def valid?(data | 
| 35 | 
            -
                     | 
| 47 | 
            +
                  def valid?(data)
         | 
| 48 | 
            +
                    valid_instance?(Instance.new(data, '', root, '', nil))
         | 
| 36 49 | 
             
                  end
         | 
| 37 50 |  | 
| 38 | 
            -
                  def validate(data | 
| 39 | 
            -
                     | 
| 51 | 
            +
                  def validate(data)
         | 
| 52 | 
            +
                    validate_instance(Instance.new(data, '', root, '', nil))
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                protected
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def valid_instance?(instance)
         | 
| 58 | 
            +
                    validate_instance(instance).none?
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  def validate_instance(instance)
         | 
| 62 | 
            +
                    return enum_for(:validate_instance, instance) unless block_given?
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                    schema = instance.schema
         | 
| 40 65 |  | 
| 41 66 | 
             
                    return if schema == true
         | 
| 42 67 | 
             
                    if schema == false
         | 
| 43 | 
            -
                      yield error( | 
| 68 | 
            +
                      yield error(instance, 'schema')
         | 
| 44 69 | 
             
                      return
         | 
| 45 70 | 
             
                    end
         | 
| 46 71 |  | 
| @@ -59,60 +84,60 @@ module JSONSchemer | |
| 59 84 | 
             
                    ref = schema['$ref']
         | 
| 60 85 | 
             
                    id = schema[id_keyword]
         | 
| 61 86 |  | 
| 62 | 
            -
                    parent_uri = join_uri(parent_uri, id)
         | 
| 87 | 
            +
                    instance.parent_uri = join_uri(instance.parent_uri, id)
         | 
| 63 88 |  | 
| 64 89 | 
             
                    if ref
         | 
| 65 | 
            -
                      validate_ref( | 
| 90 | 
            +
                      validate_ref(instance, ref, &Proc.new)
         | 
| 66 91 | 
             
                      return
         | 
| 67 92 | 
             
                    end
         | 
| 68 93 |  | 
| 69 94 | 
             
                    if format? && custom_format?(format)
         | 
| 70 | 
            -
                      validate_custom_format( | 
| 95 | 
            +
                      validate_custom_format(instance, formats.fetch(format), &Proc.new)
         | 
| 71 96 | 
             
                    end
         | 
| 72 97 |  | 
| 73 98 | 
             
                    if keywords
         | 
| 74 99 | 
             
                      keywords.each do |keyword, callable|
         | 
| 75 100 | 
             
                        if schema.key?(keyword)
         | 
| 76 | 
            -
                          result = callable.call(data, schema, pointer)
         | 
| 101 | 
            +
                          result = callable.call(data, schema, instance.pointer)
         | 
| 77 102 | 
             
                          if result.is_a?(Array)
         | 
| 78 103 | 
             
                            result.each { |error| yield error }
         | 
| 79 104 | 
             
                          elsif !result
         | 
| 80 | 
            -
                            yield error( | 
| 105 | 
            +
                            yield error(instance, keyword)
         | 
| 81 106 | 
             
                          end
         | 
| 82 107 | 
             
                        end
         | 
| 83 108 | 
             
                      end
         | 
| 84 109 | 
             
                    end
         | 
| 85 110 |  | 
| 86 | 
            -
                     | 
| 87 | 
            -
             | 
| 111 | 
            +
                    data = instance.data
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                    yield error(instance, 'enum') if enum && !enum.include?(data)
         | 
| 114 | 
            +
                    yield error(instance, 'const') if schema.key?('const') && schema['const'] != data
         | 
| 88 115 |  | 
| 89 | 
            -
                    yield error( | 
| 90 | 
            -
                    yield error( | 
| 91 | 
            -
                    yield error( | 
| 92 | 
            -
                    yield error( | 
| 116 | 
            +
                    yield error(instance, 'allOf') if all_of && !all_of.all? { |subschema| valid_instance?(instance.merge(schema: subschema)) }
         | 
| 117 | 
            +
                    yield error(instance, 'anyOf') if any_of && !any_of.any? { |subschema| valid_instance?(instance.merge(schema: subschema)) }
         | 
| 118 | 
            +
                    yield error(instance, 'oneOf') if one_of && !one_of.one? { |subschema| valid_instance?(instance.merge(schema: subschema)) }
         | 
| 119 | 
            +
                    yield error(instance, 'not') if !not_schema.nil? && valid_instance?(instance.merge(schema: not_schema))
         | 
| 93 120 |  | 
| 94 | 
            -
                    if if_schema &&  | 
| 95 | 
            -
                      yield error( | 
| 121 | 
            +
                    if if_schema && valid_instance?(instance.merge(schema: if_schema))
         | 
| 122 | 
            +
                      yield error(instance, 'then') if !then_schema.nil? && !valid_instance?(instance.merge(schema: then_schema))
         | 
| 96 123 | 
             
                    elsif if_schema
         | 
| 97 | 
            -
                      yield error( | 
| 124 | 
            +
                      yield error(instance, 'else') if !else_schema.nil? && !valid_instance?(instance.merge(schema: else_schema))
         | 
| 98 125 | 
             
                    end
         | 
| 99 126 |  | 
| 100 127 | 
             
                    case type
         | 
| 101 128 | 
             
                    when nil
         | 
| 102 | 
            -
                      validate_class( | 
| 129 | 
            +
                      validate_class(instance, &Proc.new)
         | 
| 103 130 | 
             
                    when String
         | 
| 104 | 
            -
                      validate_type( | 
| 131 | 
            +
                      validate_type(instance, type, &Proc.new)
         | 
| 105 132 | 
             
                    when Array
         | 
| 106 | 
            -
                      if valid_type = type.find { |subtype|  | 
| 107 | 
            -
                        validate_type( | 
| 133 | 
            +
                      if valid_type = type.find { |subtype| valid_instance?(instance.merge(schema: { 'type' => subtype })) }
         | 
| 134 | 
            +
                        validate_type(instance, valid_type, &Proc.new)
         | 
| 108 135 | 
             
                      else
         | 
| 109 | 
            -
                        yield error( | 
| 136 | 
            +
                        yield error(instance, 'type')
         | 
| 110 137 | 
             
                      end
         | 
| 111 138 | 
             
                    end
         | 
| 112 139 | 
             
                  end
         | 
| 113 140 |  | 
| 114 | 
            -
                protected
         | 
| 115 | 
            -
             | 
| 116 141 | 
             
                  def ids
         | 
| 117 142 | 
             
                    @ids ||= resolve_ids(root)
         | 
| 118 143 | 
             
                  end
         | 
| @@ -147,125 +172,157 @@ module JSONSchemer | |
| 147 172 | 
             
                    )
         | 
| 148 173 | 
             
                  end
         | 
| 149 174 |  | 
| 150 | 
            -
                  def error( | 
| 175 | 
            +
                  def error(instance, type)
         | 
| 151 176 | 
             
                    {
         | 
| 152 | 
            -
                      'data' => data,
         | 
| 153 | 
            -
                      ' | 
| 154 | 
            -
                      ' | 
| 177 | 
            +
                      'data' => instance.data,
         | 
| 178 | 
            +
                      'data_pointer' => instance.data_pointer,
         | 
| 179 | 
            +
                      'schema' => instance.schema,
         | 
| 180 | 
            +
                      'schema_pointer' => instance.schema_pointer,
         | 
| 181 | 
            +
                      'root_schema' => root,
         | 
| 155 182 | 
             
                      'type' => type,
         | 
| 156 183 | 
             
                    }
         | 
| 157 184 | 
             
                  end
         | 
| 158 185 |  | 
| 159 | 
            -
                  def validate_class( | 
| 160 | 
            -
                    case data
         | 
| 186 | 
            +
                  def validate_class(instance)
         | 
| 187 | 
            +
                    case instance.data
         | 
| 161 188 | 
             
                    when Integer
         | 
| 162 | 
            -
                      validate_integer( | 
| 189 | 
            +
                      validate_integer(instance, &Proc.new)
         | 
| 163 190 | 
             
                    when Numeric
         | 
| 164 | 
            -
                      validate_number( | 
| 191 | 
            +
                      validate_number(instance, &Proc.new)
         | 
| 165 192 | 
             
                    when String
         | 
| 166 | 
            -
                      validate_string( | 
| 193 | 
            +
                      validate_string(instance, &Proc.new)
         | 
| 167 194 | 
             
                    when Array
         | 
| 168 | 
            -
                      validate_array( | 
| 195 | 
            +
                      validate_array(instance, &Proc.new)
         | 
| 169 196 | 
             
                    when Hash
         | 
| 170 | 
            -
                      validate_object( | 
| 197 | 
            +
                      validate_object(instance, &Proc.new)
         | 
| 171 198 | 
             
                    end
         | 
| 172 199 | 
             
                  end
         | 
| 173 200 |  | 
| 174 | 
            -
                  def validate_type( | 
| 201 | 
            +
                  def validate_type(instance, type)
         | 
| 175 202 | 
             
                    case type
         | 
| 176 203 | 
             
                    when 'null'
         | 
| 177 | 
            -
                      yield error( | 
| 204 | 
            +
                      yield error(instance, 'null') unless instance.data.nil?
         | 
| 178 205 | 
             
                    when 'boolean'
         | 
| 179 | 
            -
                      yield error( | 
| 206 | 
            +
                      yield error(instance, 'boolean') unless BOOLEANS.include?(instance.data)
         | 
| 180 207 | 
             
                    when 'number'
         | 
| 181 | 
            -
                      validate_number( | 
| 208 | 
            +
                      validate_number(instance, &Proc.new)
         | 
| 182 209 | 
             
                    when 'integer'
         | 
| 183 | 
            -
                      validate_integer( | 
| 210 | 
            +
                      validate_integer(instance, &Proc.new)
         | 
| 184 211 | 
             
                    when 'string'
         | 
| 185 | 
            -
                      validate_string( | 
| 212 | 
            +
                      validate_string(instance, &Proc.new)
         | 
| 186 213 | 
             
                    when 'array'
         | 
| 187 | 
            -
                      validate_array( | 
| 214 | 
            +
                      validate_array(instance, &Proc.new)
         | 
| 188 215 | 
             
                    when 'object'
         | 
| 189 | 
            -
                      validate_object( | 
| 216 | 
            +
                      validate_object(instance, &Proc.new)
         | 
| 190 217 | 
             
                    end
         | 
| 191 218 | 
             
                  end
         | 
| 192 219 |  | 
| 193 | 
            -
                  def validate_ref( | 
| 194 | 
            -
                    ref_uri = join_uri(parent_uri, ref)
         | 
| 220 | 
            +
                  def validate_ref(instance, ref)
         | 
| 221 | 
            +
                    ref_uri = join_uri(instance.parent_uri, ref)
         | 
| 195 222 |  | 
| 196 223 | 
             
                    if valid_json_pointer?(ref_uri.fragment)
         | 
| 197 | 
            -
                      ref_pointer = Hana::Pointer.new(URI.unescape(ref_uri.fragment | 
| 224 | 
            +
                      ref_pointer = Hana::Pointer.new(URI.unescape(ref_uri.fragment))
         | 
| 198 225 | 
             
                      if ref.start_with?('#')
         | 
| 199 | 
            -
                         | 
| 226 | 
            +
                        subinstance = instance.merge(
         | 
| 227 | 
            +
                          schema: ref_pointer.eval(root),
         | 
| 228 | 
            +
                          schema_pointer: ref_uri.fragment,
         | 
| 229 | 
            +
                          parent_uri: pointer_uri(root, ref_pointer)
         | 
| 230 | 
            +
                        )
         | 
| 231 | 
            +
                        validate_instance(subinstance, &Proc.new)
         | 
| 200 232 | 
             
                      else
         | 
| 201 233 | 
             
                        ref_root = ref_resolver.call(ref_uri)
         | 
| 202 234 | 
             
                        ref_object = child(ref_root)
         | 
| 203 | 
            -
                         | 
| 235 | 
            +
                        subinstance = instance.merge(
         | 
| 236 | 
            +
                          schema: ref_pointer.eval(ref_root),
         | 
| 237 | 
            +
                          schema_pointer: ref_uri.fragment,
         | 
| 238 | 
            +
                          parent_uri: pointer_uri(ref_root, ref_pointer)
         | 
| 239 | 
            +
                        )
         | 
| 240 | 
            +
                        ref_object.validate_instance(subinstance, &Proc.new)
         | 
| 204 241 | 
             
                      end
         | 
| 205 | 
            -
                    elsif ids | 
| 206 | 
            -
                       | 
| 242 | 
            +
                    elsif id = ids[ref_uri.to_s]
         | 
| 243 | 
            +
                      subinstance = instance.merge(
         | 
| 244 | 
            +
                        schema: id.fetch(:schema),
         | 
| 245 | 
            +
                        schema_pointer: id.fetch(:pointer),
         | 
| 246 | 
            +
                        parent_uri: ref_uri
         | 
| 247 | 
            +
                      )
         | 
| 248 | 
            +
                      validate_instance(subinstance, &Proc.new)
         | 
| 207 249 | 
             
                    else
         | 
| 208 250 | 
             
                      ref_root = ref_resolver.call(ref_uri)
         | 
| 209 251 | 
             
                      ref_object = child(ref_root)
         | 
| 210 | 
            -
                       | 
| 252 | 
            +
                      id = ref_object.ids[ref_uri.to_s] || { schema: ref_root, pointer: '' }
         | 
| 253 | 
            +
                      subinstance = instance.merge(
         | 
| 254 | 
            +
                        schema: id.fetch(:schema),
         | 
| 255 | 
            +
                        schema_pointer: id.fetch(:pointer),
         | 
| 256 | 
            +
                        parent_uri: ref_uri
         | 
| 257 | 
            +
                      )
         | 
| 258 | 
            +
                      ref_object.validate_instance(subinstance, &Proc.new)
         | 
| 211 259 | 
             
                    end
         | 
| 212 260 | 
             
                  end
         | 
| 213 261 |  | 
| 214 | 
            -
                  def validate_custom_format( | 
| 215 | 
            -
                    yield error( | 
| 262 | 
            +
                  def validate_custom_format(instance, custom_format)
         | 
| 263 | 
            +
                    yield error(instance, 'format') if custom_format != false && !custom_format.call(instance.data, instance.schema)
         | 
| 216 264 | 
             
                  end
         | 
| 217 265 |  | 
| 218 | 
            -
                  def validate_exclusive_maximum( | 
| 219 | 
            -
                    yield error( | 
| 266 | 
            +
                  def validate_exclusive_maximum(instance, exclusive_maximum, maximum)
         | 
| 267 | 
            +
                    yield error(instance, 'exclusiveMaximum') if instance.data >= exclusive_maximum
         | 
| 220 268 | 
             
                  end
         | 
| 221 269 |  | 
| 222 | 
            -
                  def validate_exclusive_minimum( | 
| 223 | 
            -
                    yield error( | 
| 270 | 
            +
                  def validate_exclusive_minimum(instance, exclusive_minimum, minimum)
         | 
| 271 | 
            +
                    yield error(instance, 'exclusiveMinimum') if instance.data <= exclusive_minimum
         | 
| 224 272 | 
             
                  end
         | 
| 225 273 |  | 
| 226 | 
            -
                  def validate_numeric( | 
| 274 | 
            +
                  def validate_numeric(instance)
         | 
| 275 | 
            +
                    schema = instance.schema
         | 
| 276 | 
            +
                    data = instance.data
         | 
| 277 | 
            +
             | 
| 227 278 | 
             
                    multiple_of = schema['multipleOf']
         | 
| 228 279 | 
             
                    maximum = schema['maximum']
         | 
| 229 280 | 
             
                    exclusive_maximum = schema['exclusiveMaximum']
         | 
| 230 281 | 
             
                    minimum = schema['minimum']
         | 
| 231 282 | 
             
                    exclusive_minimum = schema['exclusiveMinimum']
         | 
| 232 283 |  | 
| 233 | 
            -
                    yield error( | 
| 234 | 
            -
                    yield error( | 
| 284 | 
            +
                    yield error(instance, 'maximum') if maximum && data > maximum
         | 
| 285 | 
            +
                    yield error(instance, 'minimum') if minimum && data < minimum
         | 
| 235 286 |  | 
| 236 | 
            -
                    validate_exclusive_maximum( | 
| 237 | 
            -
                    validate_exclusive_minimum( | 
| 287 | 
            +
                    validate_exclusive_maximum(instance, exclusive_maximum, maximum, &Proc.new) if exclusive_maximum
         | 
| 288 | 
            +
                    validate_exclusive_minimum(instance, exclusive_minimum, minimum, &Proc.new) if exclusive_minimum
         | 
| 238 289 |  | 
| 239 290 | 
             
                    if multiple_of
         | 
| 240 291 | 
             
                      quotient = data / multiple_of.to_f
         | 
| 241 | 
            -
                      yield error( | 
| 292 | 
            +
                      yield error(instance, 'multipleOf') unless quotient.floor == quotient
         | 
| 242 293 | 
             
                    end
         | 
| 243 294 | 
             
                  end
         | 
| 244 295 |  | 
| 245 | 
            -
                  def validate_number( | 
| 246 | 
            -
                    unless data.is_a?(Numeric)
         | 
| 247 | 
            -
                      yield error( | 
| 296 | 
            +
                  def validate_number(instance)
         | 
| 297 | 
            +
                    unless instance.data.is_a?(Numeric)
         | 
| 298 | 
            +
                      yield error(instance, 'number')
         | 
| 248 299 | 
             
                      return
         | 
| 249 300 | 
             
                    end
         | 
| 250 301 |  | 
| 251 | 
            -
                    validate_numeric( | 
| 302 | 
            +
                    validate_numeric(instance, &Proc.new)
         | 
| 252 303 | 
             
                  end
         | 
| 253 304 |  | 
| 254 | 
            -
                  def validate_integer( | 
| 305 | 
            +
                  def validate_integer(instance)
         | 
| 306 | 
            +
                    data = instance.data
         | 
| 307 | 
            +
             | 
| 255 308 | 
             
                    if !data.is_a?(Numeric) || (!data.is_a?(Integer) && data.floor != data)
         | 
| 256 | 
            -
                      yield error( | 
| 309 | 
            +
                      yield error(instance, 'integer')
         | 
| 257 310 | 
             
                      return
         | 
| 258 311 | 
             
                    end
         | 
| 259 312 |  | 
| 260 | 
            -
                    validate_numeric( | 
| 313 | 
            +
                    validate_numeric(instance, &Proc.new)
         | 
| 261 314 | 
             
                  end
         | 
| 262 315 |  | 
| 263 | 
            -
                  def validate_string( | 
| 316 | 
            +
                  def validate_string(instance)
         | 
| 317 | 
            +
                    data = instance.data
         | 
| 318 | 
            +
             | 
| 264 319 | 
             
                    unless data.is_a?(String)
         | 
| 265 | 
            -
                      yield error( | 
| 320 | 
            +
                      yield error(instance, 'string')
         | 
| 266 321 | 
             
                      return
         | 
| 267 322 | 
             
                    end
         | 
| 268 323 |  | 
| 324 | 
            +
                    schema = instance.schema
         | 
| 325 | 
            +
             | 
| 269 326 | 
             
                    max_length = schema['maxLength']
         | 
| 270 327 | 
             
                    min_length = schema['minLength']
         | 
| 271 328 | 
             
                    pattern = schema['pattern']
         | 
| @@ -273,10 +330,10 @@ module JSONSchemer | |
| 273 330 | 
             
                    content_encoding = schema['contentEncoding']
         | 
| 274 331 | 
             
                    content_media_type = schema['contentMediaType']
         | 
| 275 332 |  | 
| 276 | 
            -
                    yield error( | 
| 277 | 
            -
                    yield error( | 
| 278 | 
            -
                    yield error( | 
| 279 | 
            -
                    yield error( | 
| 333 | 
            +
                    yield error(instance, 'maxLength') if max_length && data.size > max_length
         | 
| 334 | 
            +
                    yield error(instance, 'minLength') if min_length && data.size < min_length
         | 
| 335 | 
            +
                    yield error(instance, 'pattern') if pattern && Regexp.new(pattern) !~ data
         | 
| 336 | 
            +
                    yield error(instance, 'format') if format? && spec_format?(format) && !valid_spec_format?(data, format)
         | 
| 280 337 |  | 
| 281 338 | 
             
                    if content_encoding || content_media_type
         | 
| 282 339 | 
             
                      decoded_data = data
         | 
| @@ -288,13 +345,13 @@ module JSONSchemer | |
| 288 345 | 
             
                        else # '7bit', '8bit', 'binary', 'quoted-printable'
         | 
| 289 346 | 
             
                          raise NotImplementedError
         | 
| 290 347 | 
             
                        end
         | 
| 291 | 
            -
                        yield error( | 
| 348 | 
            +
                        yield error(instance, 'contentEncoding') unless decoded_data
         | 
| 292 349 | 
             
                      end
         | 
| 293 350 |  | 
| 294 351 | 
             
                      if content_media_type && decoded_data
         | 
| 295 352 | 
             
                        case content_media_type.downcase
         | 
| 296 353 | 
             
                        when 'application/json'
         | 
| 297 | 
            -
                          yield error( | 
| 354 | 
            +
                          yield error(instance, 'contentMediaType') unless valid_json?(decoded_data)
         | 
| 298 355 | 
             
                        else
         | 
| 299 356 | 
             
                          raise NotImplementedError
         | 
| 300 357 | 
             
                        end
         | 
| @@ -302,12 +359,16 @@ module JSONSchemer | |
| 302 359 | 
             
                    end
         | 
| 303 360 | 
             
                  end
         | 
| 304 361 |  | 
| 305 | 
            -
                  def validate_array( | 
| 362 | 
            +
                  def validate_array(instance, &block)
         | 
| 363 | 
            +
                    data = instance.data
         | 
| 364 | 
            +
             | 
| 306 365 | 
             
                    unless data.is_a?(Array)
         | 
| 307 | 
            -
                      yield error( | 
| 366 | 
            +
                      yield error(instance, 'array')
         | 
| 308 367 | 
             
                      return
         | 
| 309 368 | 
             
                    end
         | 
| 310 369 |  | 
| 370 | 
            +
                    schema = instance.schema
         | 
| 371 | 
            +
             | 
| 311 372 | 
             
                    items = schema['items']
         | 
| 312 373 | 
             
                    additional_items = schema['additionalItems']
         | 
| 313 374 | 
             
                    max_items = schema['maxItems']
         | 
| @@ -315,34 +376,56 @@ module JSONSchemer | |
| 315 376 | 
             
                    unique_items = schema['uniqueItems']
         | 
| 316 377 | 
             
                    contains = schema['contains']
         | 
| 317 378 |  | 
| 318 | 
            -
                    yield error( | 
| 319 | 
            -
                    yield error( | 
| 320 | 
            -
                    yield error( | 
| 321 | 
            -
                    yield error( | 
| 379 | 
            +
                    yield error(instance, 'maxItems') if max_items && data.size > max_items
         | 
| 380 | 
            +
                    yield error(instance, 'minItems') if min_items && data.size < min_items
         | 
| 381 | 
            +
                    yield error(instance, 'uniqueItems') if unique_items && data.size != data.uniq.size
         | 
| 382 | 
            +
                    yield error(instance, 'contains') if !contains.nil? && data.all? { |item| !valid_instance?(instance.merge(data: item, schema: contains)) }
         | 
| 322 383 |  | 
| 323 384 | 
             
                    if items.is_a?(Array)
         | 
| 324 385 | 
             
                      data.each_with_index do |item, index|
         | 
| 325 386 | 
             
                        if index < items.size
         | 
| 326 | 
            -
                           | 
| 387 | 
            +
                          subinstance = instance.merge(
         | 
| 388 | 
            +
                            data: item,
         | 
| 389 | 
            +
                            data_pointer: "#{instance.data_pointer}/#{index}",
         | 
| 390 | 
            +
                            schema: items[index],
         | 
| 391 | 
            +
                            schema_pointer: "#{instance.schema_pointer}/items/#{index}"
         | 
| 392 | 
            +
                          )
         | 
| 393 | 
            +
                          validate_instance(subinstance, &block)
         | 
| 327 394 | 
             
                        elsif !additional_items.nil?
         | 
| 328 | 
            -
                           | 
| 395 | 
            +
                          subinstance = instance.merge(
         | 
| 396 | 
            +
                            data: item,
         | 
| 397 | 
            +
                            data_pointer: "#{instance.data_pointer}/#{index}",
         | 
| 398 | 
            +
                            schema: additional_items,
         | 
| 399 | 
            +
                            schema_pointer: "#{instance.schema_pointer}/additionalItems"
         | 
| 400 | 
            +
                          )
         | 
| 401 | 
            +
                          validate_instance(subinstance, &block)
         | 
| 329 402 | 
             
                        else
         | 
| 330 403 | 
             
                          break
         | 
| 331 404 | 
             
                        end
         | 
| 332 405 | 
             
                      end
         | 
| 333 406 | 
             
                    elsif !items.nil?
         | 
| 334 407 | 
             
                      data.each_with_index do |item, index|
         | 
| 335 | 
            -
                         | 
| 408 | 
            +
                        subinstance = instance.merge(
         | 
| 409 | 
            +
                          data: item,
         | 
| 410 | 
            +
                          data_pointer: "#{instance.data_pointer}/#{index}",
         | 
| 411 | 
            +
                          schema: items,
         | 
| 412 | 
            +
                          schema_pointer: "#{instance.schema_pointer}/items"
         | 
| 413 | 
            +
                        )
         | 
| 414 | 
            +
                        validate_instance(subinstance, &block)
         | 
| 336 415 | 
             
                      end
         | 
| 337 416 | 
             
                    end
         | 
| 338 417 | 
             
                  end
         | 
| 339 418 |  | 
| 340 | 
            -
                  def validate_object( | 
| 419 | 
            +
                  def validate_object(instance, &block)
         | 
| 420 | 
            +
                    data = instance.data
         | 
| 421 | 
            +
             | 
| 341 422 | 
             
                    unless data.is_a?(Hash)
         | 
| 342 | 
            -
                      yield error( | 
| 423 | 
            +
                      yield error(instance, 'object')
         | 
| 343 424 | 
             
                      return
         | 
| 344 425 | 
             
                    end
         | 
| 345 426 |  | 
| 427 | 
            +
                    schema = instance.schema
         | 
| 428 | 
            +
             | 
| 346 429 | 
             
                    max_properties = schema['maxProperties']
         | 
| 347 430 | 
             
                    min_properties = schema['minProperties']
         | 
| 348 431 | 
             
                    required = schema['required']
         | 
| @@ -356,32 +439,52 @@ module JSONSchemer | |
| 356 439 | 
             
                      dependencies.each do |key, value|
         | 
| 357 440 | 
             
                        next unless data.key?(key)
         | 
| 358 441 | 
             
                        subschema = value.is_a?(Array) ? { 'required' => value } : value
         | 
| 359 | 
            -
                         | 
| 442 | 
            +
                        subinstance = instance.merge(schema: subschema, schema_pointer: "#{instance.schema_pointer}/dependencies/#{key}")
         | 
| 443 | 
            +
                        validate_instance(subinstance, &block)
         | 
| 360 444 | 
             
                      end
         | 
| 361 445 | 
             
                    end
         | 
| 362 446 |  | 
| 363 | 
            -
                    yield error( | 
| 364 | 
            -
                    yield error( | 
| 365 | 
            -
                    yield error( | 
| 447 | 
            +
                    yield error(instance, 'maxProperties') if max_properties && data.size > max_properties
         | 
| 448 | 
            +
                    yield error(instance, 'minProperties') if min_properties && data.size < min_properties
         | 
| 449 | 
            +
                    yield error(instance, 'required') if required && required.any? { |key| !data.key?(key) }
         | 
| 366 450 |  | 
| 367 451 | 
             
                    regex_pattern_properties = nil
         | 
| 368 452 | 
             
                    data.each do |key, value|
         | 
| 369 | 
            -
                       | 
| 453 | 
            +
                      unless property_names.nil?
         | 
| 454 | 
            +
                        subinstance = instance.merge(
         | 
| 455 | 
            +
                          data: key,
         | 
| 456 | 
            +
                          schema: property_names,
         | 
| 457 | 
            +
                          schema_pointer: "#{instance.schema_pointer}/propertyNames"
         | 
| 458 | 
            +
                        )
         | 
| 459 | 
            +
                        validate_instance(subinstance, &block)
         | 
| 460 | 
            +
                      end
         | 
| 370 461 |  | 
| 371 462 | 
             
                      matched_key = false
         | 
| 372 463 |  | 
| 373 464 | 
             
                      if properties && properties.key?(key)
         | 
| 374 | 
            -
                         | 
| 465 | 
            +
                        subinstance = instance.merge(
         | 
| 466 | 
            +
                          data: value,
         | 
| 467 | 
            +
                          data_pointer: "#{instance.data_pointer}/#{key}",
         | 
| 468 | 
            +
                          schema: properties[key],
         | 
| 469 | 
            +
                          schema_pointer: "#{instance.schema_pointer}/properties/#{key}"
         | 
| 470 | 
            +
                        )
         | 
| 471 | 
            +
                        validate_instance(subinstance, &block)
         | 
| 375 472 | 
             
                        matched_key = true
         | 
| 376 473 | 
             
                      end
         | 
| 377 474 |  | 
| 378 475 | 
             
                      if pattern_properties
         | 
| 379 476 | 
             
                        regex_pattern_properties ||= pattern_properties.map do |pattern, property_schema|
         | 
| 380 | 
            -
                          [Regexp.new(pattern), property_schema]
         | 
| 477 | 
            +
                          [pattern, Regexp.new(pattern), property_schema]
         | 
| 381 478 | 
             
                        end
         | 
| 382 | 
            -
                        regex_pattern_properties.each do |regex, property_schema|
         | 
| 479 | 
            +
                        regex_pattern_properties.each do |pattern, regex, property_schema|
         | 
| 383 480 | 
             
                          if regex =~ key
         | 
| 384 | 
            -
                             | 
| 481 | 
            +
                            subinstance = instance.merge(
         | 
| 482 | 
            +
                              data: value,
         | 
| 483 | 
            +
                              data_pointer: "#{instance.data_pointer}/#{key}",
         | 
| 484 | 
            +
                              schema: property_schema,
         | 
| 485 | 
            +
                              schema_pointer: "#{instance.schema_pointer}/patternProperties/#{pattern}"
         | 
| 486 | 
            +
                            )
         | 
| 487 | 
            +
                            validate_instance(subinstance, &block)
         | 
| 385 488 | 
             
                            matched_key = true
         | 
| 386 489 | 
             
                          end
         | 
| 387 490 | 
             
                        end
         | 
| @@ -389,7 +492,15 @@ module JSONSchemer | |
| 389 492 |  | 
| 390 493 | 
             
                      next if matched_key
         | 
| 391 494 |  | 
| 392 | 
            -
                       | 
| 495 | 
            +
                      unless additional_properties.nil?
         | 
| 496 | 
            +
                        subinstance = instance.merge(
         | 
| 497 | 
            +
                          data: value,
         | 
| 498 | 
            +
                          data_pointer: "#{instance.data_pointer}/#{key}",
         | 
| 499 | 
            +
                          schema: additional_properties,
         | 
| 500 | 
            +
                          schema_pointer: "#{instance.schema_pointer}/additionalProperties"
         | 
| 501 | 
            +
                        )
         | 
| 502 | 
            +
                        validate_instance(subinstance, &block)
         | 
| 503 | 
            +
                      end
         | 
| 393 504 | 
             
                    end
         | 
| 394 505 | 
             
                  end
         | 
| 395 506 |  | 
| @@ -425,15 +536,20 @@ module JSONSchemer | |
| 425 536 | 
             
                    uri_parts ? URI.join(*uri_parts) : nil
         | 
| 426 537 | 
             
                  end
         | 
| 427 538 |  | 
| 428 | 
            -
                  def resolve_ids(schema, ids = {}, parent_uri = nil)
         | 
| 539 | 
            +
                  def resolve_ids(schema, ids = {}, parent_uri = nil, pointer = '')
         | 
| 429 540 | 
             
                    if schema.is_a?(Array)
         | 
| 430 | 
            -
                      schema. | 
| 541 | 
            +
                      schema.each_with_index { |subschema, index| resolve_ids(subschema, ids, parent_uri, "#{pointer}/#{index}") }
         | 
| 431 542 | 
             
                    elsif schema.is_a?(Hash)
         | 
| 432 543 | 
             
                      id = schema[id_keyword]
         | 
| 433 544 | 
             
                      uri = join_uri(parent_uri, id)
         | 
| 434 | 
            -
                       | 
| 545 | 
            +
                      unless uri == parent_uri
         | 
| 546 | 
            +
                        ids[uri.to_s] = {
         | 
| 547 | 
            +
                          schema: schema,
         | 
| 548 | 
            +
                          pointer: pointer
         | 
| 549 | 
            +
                        }
         | 
| 550 | 
            +
                      end
         | 
| 435 551 | 
             
                      if definitions = schema['definitions']
         | 
| 436 | 
            -
                        definitions. | 
| 552 | 
            +
                        definitions.each { |key, subschema| resolve_ids(subschema, ids, uri, "#{pointer}/definitions/#{key}") }
         | 
| 437 553 | 
             
                      end
         | 
| 438 554 | 
             
                    end
         | 
| 439 555 | 
             
                    ids
         | 
| @@ -24,21 +24,21 @@ module JSONSchemer | |
| 24 24 | 
             
                    SUPPORTED_FORMATS.include?(format)
         | 
| 25 25 | 
             
                  end
         | 
| 26 26 |  | 
| 27 | 
            -
                  def validate_exclusive_maximum( | 
| 28 | 
            -
                    yield error( | 
| 27 | 
            +
                  def validate_exclusive_maximum(instance, exclusive_maximum, maximum)
         | 
| 28 | 
            +
                    yield error(instance, 'exclusiveMaximum') if exclusive_maximum && instance.data >= maximum
         | 
| 29 29 | 
             
                  end
         | 
| 30 30 |  | 
| 31 | 
            -
                  def validate_exclusive_minimum( | 
| 32 | 
            -
                    yield error( | 
| 31 | 
            +
                  def validate_exclusive_minimum(instance, exclusive_minimum, minimum)
         | 
| 32 | 
            +
                    yield error(instance, 'exclusiveMinimum') if exclusive_minimum && instance.data <= minimum
         | 
| 33 33 | 
             
                  end
         | 
| 34 34 |  | 
| 35 | 
            -
                  def validate_integer( | 
| 36 | 
            -
                    if !data.is_a?(Integer)
         | 
| 37 | 
            -
                      yield error( | 
| 35 | 
            +
                  def validate_integer(instance)
         | 
| 36 | 
            +
                    if !instance.data.is_a?(Integer)
         | 
| 37 | 
            +
                      yield error(instance, 'integer')
         | 
| 38 38 | 
             
                      return
         | 
| 39 39 | 
             
                    end
         | 
| 40 40 |  | 
| 41 | 
            -
                    validate_numeric( | 
| 41 | 
            +
                    validate_numeric(instance, &Proc.new)
         | 
| 42 42 | 
             
                  end
         | 
| 43 43 | 
             
                end
         | 
| 44 44 | 
             
              end
         | 
    
        data/lib/json_schemer/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: json_schemer
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1. | 
| 4 | 
            +
              version: 0.1.6
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - David Harsha
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2018- | 
| 11 | 
            +
            date: 2018-08-27 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: bundler
         | 
| @@ -113,6 +113,7 @@ files: | |
| 113 113 | 
             
            - bin/setup
         | 
| 114 114 | 
             
            - json_schemer.gemspec
         | 
| 115 115 | 
             
            - lib/json_schemer.rb
         | 
| 116 | 
            +
            - lib/json_schemer/cached_ref_resolver.rb
         | 
| 116 117 | 
             
            - lib/json_schemer/format.rb
         | 
| 117 118 | 
             
            - lib/json_schemer/schema/base.rb
         | 
| 118 119 | 
             
            - lib/json_schemer/schema/draft4.rb
         | 
| @@ -139,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 139 140 | 
             
                  version: '0'
         | 
| 140 141 | 
             
            requirements: []
         | 
| 141 142 | 
             
            rubyforge_project: 
         | 
| 142 | 
            -
            rubygems_version: 2.7. | 
| 143 | 
            +
            rubygems_version: 2.7.6
         | 
| 143 144 | 
             
            signing_key: 
         | 
| 144 145 | 
             
            specification_version: 4
         | 
| 145 146 | 
             
            summary: JSON Schema validator. Supports drafts 4, 6, and 7.
         |