Skip to content

Commit e4d3499

Browse files
committed
add JSON::Schema::Pointer class to abstract handling of JSON pointer, dealing with pointers in fragments or in plain pointer syntax. use this in #schema_from_fragment.
1 parent 8b9bf7f commit e4d3499

File tree

2 files changed

+101
-28
lines changed

2 files changed

+101
-28
lines changed

lib/json-schema/pointer.rb

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
require 'addressable/uri'
2+
3+
module JSON
4+
class Schema
5+
class Pointer
6+
class Error < JSON::Schema::SchemaError
7+
end
8+
class PointerSyntaxError < Error
9+
end
10+
class ReferenceError < Error
11+
end
12+
13+
def self.parse_fragment(fragment)
14+
fragment = Addressable::URI.unescape(fragment)
15+
match = fragment.match(/\A#/)
16+
if match
17+
parse_pointer(match.post_match)
18+
else
19+
raise(PointerSyntaxError, "Invalid fragment syntax in #{fragment.inspect}: fragment must begin with #")
20+
end
21+
end
22+
23+
def self.parse_pointer(pointer_string)
24+
tokens = pointer_string.split('/', -1).map! do |piece|
25+
piece.gsub('~1', '/').gsub('~0', '~')
26+
end
27+
if tokens[0] == ''
28+
tokens[1..-1]
29+
elsif tokens.empty?
30+
tokens
31+
else
32+
raise(PointerSyntaxError, "Invalid pointer syntax in #{pointer_string.inspect}: pointer must begin with /")
33+
end
34+
end
35+
36+
def initialize(type, representation)
37+
@type = type
38+
if type == :reference_tokens
39+
reference_tokens = representation
40+
elsif type == :fragment
41+
reference_tokens = self.class.parse_fragment(representation)
42+
elsif type == :pointer
43+
reference_tokens = self.class.parse_pointer(representation)
44+
else
45+
raise ArgumentError, "invalid initialization type: #{type.inspect} with representation #{representation.inspect}"
46+
end
47+
@reference_tokens = reference_tokens.map(&:freeze).freeze
48+
end
49+
50+
attr_reader :reference_tokens
51+
52+
def evaluate(document)
53+
reference_tokens.inject(document) do |value, token|
54+
if value.is_a?(Array)
55+
if token.is_a?(String) && token =~ /\A\d|[1-9]\d+\z/
56+
token = token.to_i
57+
end
58+
unless token.is_a?(Integer)
59+
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not an integer and cannot be resolved in array #{value.inspect}")
60+
end
61+
unless (0...value.size).include?(token)
62+
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid index of #{value.inspect}")
63+
end
64+
elsif value.is_a?(Hash)
65+
unless value.key?(token)
66+
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid key of #{value.inspect}")
67+
end
68+
else
69+
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} cannot be resolved in #{value.inspect}")
70+
end
71+
value[token]
72+
end
73+
end
74+
75+
def to_s
76+
"#<#{self.class.name} #{@type} = #{representation_s}>"
77+
end
78+
79+
def representation_s
80+
if @type == :fragment
81+
fragment
82+
elsif @type == :pointer
83+
pointer
84+
else
85+
reference_tokens.inspect
86+
end
87+
end
88+
89+
def pointer
90+
reference_tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('')
91+
end
92+
93+
def fragment
94+
'#' + Addressable::URI.escape(pointer)
95+
end
96+
end
97+
end
98+
end

lib/json-schema/validator.rb

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
require 'json-schema/errors/json_load_error'
1313
require 'json-schema/errors/json_parse_error'
1414
require 'json-schema/util/uri'
15+
require 'json-schema/pointer'
1516

1617
module JSON
1718

@@ -73,36 +74,10 @@ def initialize(schema_data, data, opts={})
7374

7475
def schema_from_fragment(base_schema, fragment)
7576
schema_uri = base_schema.uri
76-
fragments = fragment.split("/")
7777

78-
# ensure the first element was a hash, per the fragment spec
79-
if fragments.shift != "#"
80-
raise JSON::Schema::SchemaError.new("Invalid fragment syntax in :fragment option")
81-
end
78+
pointer = JSON::Schema::Pointer.new(:fragment, fragment)
8279

83-
fragments.each do |f|
84-
if base_schema.is_a?(JSON::Schema) #test if fragment is a JSON:Schema instance
85-
if !base_schema.schema.has_key?(f)
86-
raise JSON::Schema::SchemaError.new("Invalid fragment resolution for :fragment option")
87-
end
88-
base_schema = base_schema.schema[f]
89-
elsif base_schema.is_a?(Hash)
90-
if !base_schema.has_key?(f)
91-
raise JSON::Schema::SchemaError.new("Invalid fragment resolution for :fragment option")
92-
end
93-
base_schema = base_schema[f]
94-
elsif base_schema.is_a?(Array)
95-
if base_schema[f.to_i].nil?
96-
raise JSON::Schema::SchemaError.new("Invalid fragment resolution for :fragment option")
97-
end
98-
base_schema = base_schema[f.to_i]
99-
else
100-
raise JSON::Schema::SchemaError.new("Invalid schema encountered when resolving :fragment option")
101-
end
102-
end
103-
unless base_schema.is_a?(JSON::Schema)
104-
base_schema = JSON::Schema.new(base_schema,schema_uri,@options[:version])
105-
end
80+
base_schema = JSON::Schema.new(pointer.evaluate(base_schema.schema), schema_uri, @options[:version])
10681

10782
if @options[:list]
10883
base_schema.to_array_schema

0 commit comments

Comments
 (0)