Skip to content

Commit cfa0a95

Browse files
committed
monkey-patch application of voxpupuli/json-schema#382 until its merge
1 parent ae85934 commit cfa0a95

File tree

5 files changed

+194
-3
lines changed

5 files changed

+194
-3
lines changed

lib/scorpio.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require "scorpio/version"
22
require "pathname"
33
require "api_hammer/ycomb"
4+
require "scorpio/json-schema-fragments"
45

56
module Scorpio
67
def self.root
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
require "json-schema"
2+
3+
# apply the changes from https://github.com/ruby-json-schema/json-schema/pull/382
4+
5+
# json-schema/pointer.rb
6+
require 'addressable/uri'
7+
8+
module JSON
9+
class Schema
10+
# a JSON Pointer, as described by RFC 6901 https://tools.ietf.org/html/rfc6901
11+
class Pointer
12+
class Error < JSON::Schema::SchemaError
13+
end
14+
class PointerSyntaxError < Error
15+
end
16+
class ReferenceError < Error
17+
end
18+
19+
# parse a fragment to an array of reference tokens
20+
#
21+
# #/foo/bar
22+
#
23+
# => ['foo', 'bar']
24+
#
25+
# #/foo%20bar
26+
#
27+
# => ['foo bar']
28+
def self.parse_fragment(fragment)
29+
fragment = Addressable::URI.unescape(fragment)
30+
match = fragment.match(/\A#/)
31+
if match
32+
parse_pointer(match.post_match)
33+
else
34+
raise(PointerSyntaxError, "Invalid fragment syntax in #{fragment.inspect}: fragment must begin with #")
35+
end
36+
end
37+
38+
# parse a pointer to an array of reference tokens
39+
#
40+
# /foo
41+
#
42+
# => ['foo']
43+
#
44+
# /foo~0bar/baz~1qux
45+
#
46+
# => ['foo~bar', 'baz/qux']
47+
def self.parse_pointer(pointer_string)
48+
tokens = pointer_string.split('/', -1).map! do |piece|
49+
piece.gsub('~1', '/').gsub('~0', '~')
50+
end
51+
if tokens[0] == ''
52+
tokens[1..-1]
53+
elsif tokens.empty?
54+
tokens
55+
else
56+
raise(PointerSyntaxError, "Invalid pointer syntax in #{pointer_string.inspect}: pointer must begin with /")
57+
end
58+
end
59+
60+
# initializes a JSON::Schema::Pointer from the given representation.
61+
#
62+
# type may be one of:
63+
#
64+
# - :fragment - the representation is a fragment containing a pointer (starting with #)
65+
# - :pointer - the representation is a pointer (starting with /)
66+
# - :reference_tokens - the representation is an array of tokens referencing a path in a document
67+
def initialize(type, representation)
68+
@type = type
69+
if type == :reference_tokens
70+
reference_tokens = representation
71+
elsif type == :fragment
72+
reference_tokens = self.class.parse_fragment(representation)
73+
elsif type == :pointer
74+
reference_tokens = self.class.parse_pointer(representation)
75+
else
76+
raise ArgumentError, "invalid initialization type: #{type.inspect} with representation #{representation.inspect}"
77+
end
78+
@reference_tokens = reference_tokens.map(&:freeze).freeze
79+
end
80+
81+
attr_reader :reference_tokens
82+
83+
# takes a root json document and evaluates this pointer through the document, returning the value
84+
# pointed to by this pointer.
85+
def evaluate(document)
86+
reference_tokens.inject(document) do |value, token|
87+
if value.is_a?(Array)
88+
if token.is_a?(String) && token =~ /\A\d|[1-9]\d+\z/
89+
token = token.to_i
90+
end
91+
unless token.is_a?(Integer)
92+
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not an integer and cannot be resolved in array #{value.inspect}")
93+
end
94+
unless (0...value.size).include?(token)
95+
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid index of #{value.inspect}")
96+
end
97+
elsif value.is_a?(Hash)
98+
unless value.key?(token)
99+
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid key of #{value.inspect}")
100+
end
101+
else
102+
raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} cannot be resolved in #{value.inspect}")
103+
end
104+
value[token]
105+
end
106+
end
107+
108+
# the pointer string representation of this Pointer
109+
def pointer
110+
reference_tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('')
111+
end
112+
113+
# the fragment string representation of this Pointer
114+
def fragment
115+
'#' + Addressable::URI.escape(pointer)
116+
end
117+
118+
def to_s
119+
"#<#{self.class.name} #{@type} = #{representation_s}>"
120+
end
121+
122+
private
123+
124+
def representation_s
125+
if @type == :fragment
126+
fragment
127+
elsif @type == :pointer
128+
pointer
129+
else
130+
reference_tokens.inspect
131+
end
132+
end
133+
end
134+
end
135+
end
136+
137+
# json-schema/validator.rb
138+
139+
module JSON
140+
class Validator
141+
def initialize(schema_data, data, opts={})
142+
@options = @@default_opts.clone.merge(opts)
143+
@errors = []
144+
145+
validator = self.class.validator_for_name(@options[:version])
146+
@options[:version] = validator
147+
@options[:schema_reader] ||= self.class.schema_reader
148+
149+
@validation_options = @options[:record_errors] ? {:record_errors => true} : {}
150+
@validation_options[:insert_defaults] = true if @options[:insert_defaults]
151+
@validation_options[:strict] = true if @options[:strict] == true
152+
@validation_options[:clear_cache] = true if !@@cache_schemas || @options[:clear_cache]
153+
154+
@@mutex.synchronize { @base_schema = initialize_schema(schema_data) }
155+
@original_data = data
156+
@data = initialize_data(data)
157+
@@mutex.synchronize { build_schemas(@base_schema) }
158+
159+
# If the :fragment option is set, try and validate against the fragment
160+
if opts[:fragment]
161+
@base_schema = schema_from_fragment(@base_schema, opts[:fragment])
162+
end
163+
164+
# validate the schema, if requested
165+
if @options[:validate_schema]
166+
if @base_schema.schema["$schema"]
167+
base_validator = self.class.validator_for_name(@base_schema.schema["$schema"])
168+
end
169+
metaschema = base_validator ? base_validator.metaschema : validator.metaschema
170+
# Don't clear the cache during metaschema validation!
171+
self.class.validate!(metaschema, @base_schema.schema, {:clear_cache => false})
172+
end
173+
end
174+
175+
def schema_from_fragment(base_schema, fragment)
176+
schema_uri = base_schema.uri
177+
178+
pointer = JSON::Schema::Pointer.new(:fragment, fragment)
179+
180+
base_schema = JSON::Schema.new(pointer.evaluate(base_schema.schema), schema_uri, @options[:version])
181+
182+
if @options[:list]
183+
base_schema.to_array_schema
184+
elsif base_schema.is_a?(Hash)
185+
JSON::Schema.new(base_schema, schema_uri, @options[:version])
186+
else
187+
base_schema
188+
end
189+
end
190+
end
191+
end

lib/scorpio/model.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
require 'addressable/template'
2-
require 'json-schema'
32
require 'faraday_middleware'
43

54
module Scorpio

lib/scorpio/module_for_schema.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
require 'hana'
22
require 'json'
3-
require 'json-schema'
43

54
module Scorpio
65
CLASS_FOR_SCHEMA = Hash.new do |h, schema_node_|

scorpio.gemspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ Gem::Specification.new do |spec|
2222

2323
spec.add_dependency "faraday"
2424
spec.add_dependency "faraday_middleware"
25-
spec.add_dependency "json-schema"
25+
# we are monkey patching json-schema with a fix that has not been merged in a timely fashion.
26+
spec.add_dependency "json-schema", "~> 2.8"
2627
spec.add_dependency "hana"
2728
spec.add_development_dependency "bundler", "~> 1.12"
2829
spec.add_development_dependency "rake", "~> 10.0"

0 commit comments

Comments
 (0)