scorpio 0.2.3 → 0.3.0
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/CHANGELOG.md +5 -0
- data/README.md +4 -2
- data/lib/scorpio.rb +9 -22
- data/lib/scorpio/google_api_document.rb +13 -16
- data/lib/scorpio/openapi.rb +141 -143
- data/lib/scorpio/openapi/document.rb +167 -0
- data/lib/scorpio/openapi/operation.rb +208 -0
- data/lib/scorpio/openapi/operations_scope.rb +29 -0
- data/lib/scorpio/openapi/v3/server.rb +32 -0
- data/lib/scorpio/request.rb +227 -0
- data/lib/scorpio/resource_base.rb +148 -182
- data/lib/scorpio/response.rb +34 -0
- data/lib/scorpio/ur.rb +33 -0
- data/lib/scorpio/version.rb +1 -1
- data/scorpio.gemspec +3 -4
- metadata +32 -36
- data/lib/scorpio/json-schema-fragments.rb +0 -191
- data/lib/scorpio/json.rb +0 -5
- data/lib/scorpio/json/node.rb +0 -256
- data/lib/scorpio/schema.rb +0 -249
- data/lib/scorpio/schema_instance_base.rb +0 -325
- data/lib/scorpio/schema_instance_base/to_rb.rb +0 -127
- data/lib/scorpio/schema_instance_json_coder.rb +0 -83
- data/lib/scorpio/struct_json_coder.rb +0 -30
- data/lib/scorpio/typelike_modules.rb +0 -164
- data/lib/scorpio/util.rb +0 -89
- data/lib/scorpio/util/faraday/response_media_type.rb +0 -15
@@ -0,0 +1,34 @@
|
|
1
|
+
module Scorpio
|
2
|
+
class Response < ::Ur::Response
|
3
|
+
def response_schema
|
4
|
+
ur.scorpio_request.operation.response_schema(status: status, media_type: media_type)
|
5
|
+
end
|
6
|
+
|
7
|
+
def body_object
|
8
|
+
# TODO handle media types like `application/schema-instance+json` or vendor things like github's
|
9
|
+
if media_type == 'application/json'
|
10
|
+
if body.empty?
|
11
|
+
# an empty body isn't valid json, of course, but we'll just return nil for it.
|
12
|
+
body_object = nil
|
13
|
+
else
|
14
|
+
begin
|
15
|
+
body_object = ::JSON.parse(body)
|
16
|
+
#rescue ::JSON::ParserError
|
17
|
+
# TODO
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if response_schema && (body_object.respond_to?(:to_hash) || body_object.respond_to?(:to_ary))
|
22
|
+
body_object = JSI.class_for_schema(response_schema).new(JSI::JSON::Node.new_doc(body_object))
|
23
|
+
end
|
24
|
+
|
25
|
+
body_object
|
26
|
+
elsif media_type == 'text/plain'
|
27
|
+
body
|
28
|
+
else
|
29
|
+
# we will return the body if we do not have a supported parsing. for now.
|
30
|
+
body
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/scorpio/ur.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Scorpio
|
2
|
+
class Ur < ::Ur
|
3
|
+
attr_accessor :scorpio_request
|
4
|
+
|
5
|
+
def class_for_schema(schema)
|
6
|
+
jsi_class_for_schema = super
|
7
|
+
if jsi_class_for_schema == ::Ur::Response
|
8
|
+
Scorpio::Response
|
9
|
+
else
|
10
|
+
jsi_class_for_schema
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def raise_on_http_error
|
15
|
+
error_class = Scorpio.error_classes_by_status[response.status]
|
16
|
+
error_class ||= if (400..499).include?(response.status)
|
17
|
+
ClientError
|
18
|
+
elsif (500..599).include?(response.status)
|
19
|
+
ServerError
|
20
|
+
elsif !response.success?
|
21
|
+
HTTPError
|
22
|
+
end
|
23
|
+
if error_class
|
24
|
+
message = "Error calling operation #{scorpio_request.operation.operationId} on #{self}:\n" + response.body
|
25
|
+
raise(error_class.new(message).tap do |e|
|
26
|
+
e.ur = self
|
27
|
+
e.response_object = response.body_object
|
28
|
+
end)
|
29
|
+
end
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/scorpio/version.rb
CHANGED
data/scorpio.gemspec
CHANGED
@@ -20,11 +20,9 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
+
spec.add_dependency "jsi", "~> 0.0.3"
|
24
|
+
spec.add_dependency "ur", "~> 0.0.2"
|
23
25
|
spec.add_dependency "faraday"
|
24
|
-
# we are monkey patching json-schema with a fix that has not been merged in a timely fashion.
|
25
|
-
spec.add_dependency "json-schema", "~> 2.8"
|
26
|
-
spec.add_dependency "api_hammer"
|
27
|
-
spec.add_development_dependency "bundler", "~> 1.12"
|
28
26
|
spec.add_development_dependency "rake", "~> 10.0"
|
29
27
|
spec.add_development_dependency "minitest", "~> 5.0"
|
30
28
|
spec.add_development_dependency "minitest-around"
|
@@ -33,6 +31,7 @@ Gem::Specification.new do |spec|
|
|
33
31
|
spec.add_development_dependency "rack", "~> 1.0"
|
34
32
|
spec.add_development_dependency "rack-accept"
|
35
33
|
spec.add_development_dependency "rack-test"
|
34
|
+
spec.add_development_dependency "api_hammer"
|
36
35
|
spec.add_development_dependency "activerecord"
|
37
36
|
spec.add_development_dependency "sqlite3"
|
38
37
|
spec.add_development_dependency "database_cleaner"
|
metadata
CHANGED
@@ -1,45 +1,45 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scorpio
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ethan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: jsi
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.0.3
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.0.3
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: ur
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 0.0.2
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 0.0.2
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: faraday
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: bundler
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '1.12'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '1.12'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: rake
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -178,6 +164,20 @@ dependencies:
|
|
178
164
|
- - ">="
|
179
165
|
- !ruby/object:Gem::Version
|
180
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: api_hammer
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
181
|
- !ruby/object:Gem::Dependency
|
182
182
|
name: activerecord
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -255,20 +255,16 @@ files:
|
|
255
255
|
- documents/www.googleapis.com/discovery/v1/apis/discovery/v1/rest.yml
|
256
256
|
- lib/scorpio.rb
|
257
257
|
- lib/scorpio/google_api_document.rb
|
258
|
-
- lib/scorpio/json-schema-fragments.rb
|
259
|
-
- lib/scorpio/json.rb
|
260
|
-
- lib/scorpio/json/node.rb
|
261
258
|
- lib/scorpio/openapi.rb
|
259
|
+
- lib/scorpio/openapi/document.rb
|
260
|
+
- lib/scorpio/openapi/operation.rb
|
261
|
+
- lib/scorpio/openapi/operations_scope.rb
|
262
|
+
- lib/scorpio/openapi/v3/server.rb
|
262
263
|
- lib/scorpio/pickle_adapter.rb
|
264
|
+
- lib/scorpio/request.rb
|
263
265
|
- lib/scorpio/resource_base.rb
|
264
|
-
- lib/scorpio/
|
265
|
-
- lib/scorpio/
|
266
|
-
- lib/scorpio/schema_instance_base/to_rb.rb
|
267
|
-
- lib/scorpio/schema_instance_json_coder.rb
|
268
|
-
- lib/scorpio/struct_json_coder.rb
|
269
|
-
- lib/scorpio/typelike_modules.rb
|
270
|
-
- lib/scorpio/util.rb
|
271
|
-
- lib/scorpio/util/faraday/response_media_type.rb
|
266
|
+
- lib/scorpio/response.rb
|
267
|
+
- lib/scorpio/ur.rb
|
272
268
|
- lib/scorpio/version.rb
|
273
269
|
- scorpio.gemspec
|
274
270
|
homepage: https://github.com/notEthan/scorpio
|
@@ -1,191 +0,0 @@
|
|
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.inspect} #{@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
|
data/lib/scorpio/json.rb
DELETED
data/lib/scorpio/json/node.rb
DELETED
@@ -1,256 +0,0 @@
|
|
1
|
-
module Scorpio
|
2
|
-
module JSON
|
3
|
-
# Scorpio::JSON::Node is an abstraction of a node within a JSON document.
|
4
|
-
# it aims to act like the underlying data type of the node's content
|
5
|
-
# (Hash or Array, generally) in most cases, defining methods of Hash
|
6
|
-
# and Array which delegate to the content. However, destructive methods
|
7
|
-
# are not defined, as modifying the content of a node would change it
|
8
|
-
# for any other nodes in the document that contain or refer to it.
|
9
|
-
#
|
10
|
-
# methods that return a modified copy such as #merge are defined, and
|
11
|
-
# return a copy of the document with the content of the node modified.
|
12
|
-
# the original node's document and content are untouched.
|
13
|
-
class Node
|
14
|
-
# if the content of the document at the given path is a Hash, returns
|
15
|
-
# a HashNode; if an Array, returns ArrayNode. otherwise returns a
|
16
|
-
# regular Node, though, for the most part this will be called with Hash
|
17
|
-
# or Array content.
|
18
|
-
def self.new_by_type(document, path)
|
19
|
-
node = Node.new(document, path)
|
20
|
-
content = node.content
|
21
|
-
if content.is_a?(Hash)
|
22
|
-
HashNode.new(document, path)
|
23
|
-
elsif content.is_a?(Array)
|
24
|
-
ArrayNode.new(document, path)
|
25
|
-
else
|
26
|
-
node
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# a Node represents the content of a document at a given path.
|
31
|
-
def initialize(document, path)
|
32
|
-
raise(ArgumentError, "path must be an array. got: #{path.pretty_inspect.chomp} (#{path.class})") unless path.is_a?(Array)
|
33
|
-
@document = document
|
34
|
-
@path = path.dup.freeze
|
35
|
-
@pointer = ::JSON::Schema::Pointer.new(:reference_tokens, path)
|
36
|
-
end
|
37
|
-
|
38
|
-
# the path of this Node within its document
|
39
|
-
attr_reader :path
|
40
|
-
# the document containing this Node at is path
|
41
|
-
attr_reader :document
|
42
|
-
# ::JSON::Schema::Pointer representing the path to this node within its document
|
43
|
-
attr_reader :pointer
|
44
|
-
|
45
|
-
# the raw content of this Node from the underlying document at this Node's path.
|
46
|
-
def content
|
47
|
-
pointer.evaluate(document)
|
48
|
-
end
|
49
|
-
|
50
|
-
def [](k)
|
51
|
-
node = self
|
52
|
-
content = node.content
|
53
|
-
if content.is_a?(Hash) && !content.key?(k)
|
54
|
-
node = node.deref
|
55
|
-
content = node.content
|
56
|
-
end
|
57
|
-
begin
|
58
|
-
el = content[k]
|
59
|
-
rescue TypeError => e
|
60
|
-
raise(e.class, e.message + "\nsubscripting with #{k.pretty_inspect.chomp} (#{k.class}) from #{content.class.inspect}. self is: #{pretty_inspect.chomp}", e.backtrace)
|
61
|
-
end
|
62
|
-
if el.is_a?(Hash) || el.is_a?(Array)
|
63
|
-
self.class.new_by_type(node.document, node.path + [k])
|
64
|
-
else
|
65
|
-
el
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def []=(k, v)
|
70
|
-
if v.is_a?(Node)
|
71
|
-
content[k] = v.content
|
72
|
-
else
|
73
|
-
content[k] = v
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def deref
|
78
|
-
content = self.content
|
79
|
-
|
80
|
-
return self unless content.is_a?(Hash) && content['$ref'].is_a?(String)
|
81
|
-
|
82
|
-
if content['$ref'][/\A#/]
|
83
|
-
return self.class.new_by_type(document, ::JSON::Schema::Pointer.parse_fragment(content['$ref'])).deref
|
84
|
-
end
|
85
|
-
|
86
|
-
# HAX for how google does refs and ids
|
87
|
-
if document_node['schemas'].respond_to?(:to_hash)
|
88
|
-
if document_node['schemas'][content['$ref']]
|
89
|
-
return document_node['schemas'][content['$ref']]
|
90
|
-
end
|
91
|
-
_, deref_by_id = document_node['schemas'].detect { |_k, schema| schema['id'] == content['$ref'] }
|
92
|
-
if deref_by_id
|
93
|
-
return deref_by_id
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
#raise(NotImplementedError, "cannot dereference #{content['$ref']}") # TODO
|
98
|
-
return self
|
99
|
-
end
|
100
|
-
|
101
|
-
# a Node at the root of the document
|
102
|
-
def document_node
|
103
|
-
Node.new_by_type(document, [])
|
104
|
-
end
|
105
|
-
|
106
|
-
# the parent of this node. if this node is the document root (its path is empty), raises
|
107
|
-
# ::JSON::Schema::Pointer::ReferenceError.
|
108
|
-
def parent_node
|
109
|
-
if path.empty?
|
110
|
-
raise(::JSON::Schema::Pointer::ReferenceError, "cannot access parent of root node: #{pretty_inspect.chomp}")
|
111
|
-
else
|
112
|
-
Node.new_by_type(document, path[0...-1])
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
# the pointer path to this node within the document, per RFC 6901 https://tools.ietf.org/html/rfc6901
|
117
|
-
def pointer_path
|
118
|
-
pointer.pointer
|
119
|
-
end
|
120
|
-
# the pointer fragment to this node within the document, per RFC 6901 https://tools.ietf.org/html/rfc6901
|
121
|
-
def fragment
|
122
|
-
pointer.fragment
|
123
|
-
end
|
124
|
-
|
125
|
-
def as_json(*opt)
|
126
|
-
Typelike.as_json(content, *opt)
|
127
|
-
end
|
128
|
-
|
129
|
-
# takes a block. the block is yielded the content of this node. the block MUST return a modified
|
130
|
-
# copy of that content (and NOT modify the object it is given).
|
131
|
-
def modified_copy
|
132
|
-
# we need to preserve the rest of the document, but modify the content at our path.
|
133
|
-
#
|
134
|
-
# this is actually a bit tricky. we can't modify the original document, obviously.
|
135
|
-
# we could do a deep copy, but that's expensive. instead, we make a copy of each array
|
136
|
-
# or hash in the path above this node. this node's content is modified by the caller, and
|
137
|
-
# that is recursively merged up to the document root. the recursion is done with a
|
138
|
-
# y combinator, for no other reason than that was a fun way to implement it.
|
139
|
-
modified_document = ycomb do |rec|
|
140
|
-
proc do |subdocument, subpath|
|
141
|
-
if subpath == []
|
142
|
-
yield(subdocument)
|
143
|
-
else
|
144
|
-
car = subpath[0]
|
145
|
-
cdr = subpath[1..-1]
|
146
|
-
if subdocument.respond_to?(:to_hash)
|
147
|
-
car_object = rec.call(subdocument[car], cdr)
|
148
|
-
if car_object.object_id == subdocument[car].object_id
|
149
|
-
subdocument
|
150
|
-
else
|
151
|
-
subdocument.merge({car => car_object})
|
152
|
-
end
|
153
|
-
elsif subdocument.respond_to?(:to_ary)
|
154
|
-
if car.is_a?(String) && car =~ /\A\d+\z/
|
155
|
-
car = car.to_i
|
156
|
-
end
|
157
|
-
unless car.is_a?(Integer)
|
158
|
-
raise(TypeError, "bad subscript #{car.pretty_inspect.chomp} with remaining subpath: #{cdr.inspect} for array: #{subdocument.pretty_inspect.chomp}")
|
159
|
-
end
|
160
|
-
car_object = rec.call(subdocument[car], cdr)
|
161
|
-
if car_object.object_id == subdocument[car].object_id
|
162
|
-
subdocument
|
163
|
-
else
|
164
|
-
subdocument.dup.tap do |arr|
|
165
|
-
arr[car] = car_object
|
166
|
-
end
|
167
|
-
end
|
168
|
-
else
|
169
|
-
raise(TypeError, "bad subscript: #{car.pretty_inspect.chomp} with remaining subpath: #{cdr.inspect} for content: #{subdocument.pretty_inspect.chomp}")
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end.call(document, path)
|
174
|
-
Node.new_by_type(modified_document, path)
|
175
|
-
end
|
176
|
-
|
177
|
-
def object_group_text
|
178
|
-
"fragment=#{fragment.inspect}"
|
179
|
-
end
|
180
|
-
def inspect
|
181
|
-
"\#<#{self.class.inspect} #{object_group_text} #{content.inspect}>"
|
182
|
-
end
|
183
|
-
def pretty_print(q)
|
184
|
-
q.instance_exec(self) do |obj|
|
185
|
-
text "\#<#{obj.class.inspect} #{obj.object_group_text}"
|
186
|
-
group_sub {
|
187
|
-
nest(2) {
|
188
|
-
breakable ' '
|
189
|
-
pp obj.content
|
190
|
-
}
|
191
|
-
}
|
192
|
-
breakable ''
|
193
|
-
text '>'
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
def fingerprint
|
198
|
-
{is_node: self.is_a?(Scorpio::JSON::Node), document: document, path: path}
|
199
|
-
end
|
200
|
-
include FingerprintHash
|
201
|
-
end
|
202
|
-
|
203
|
-
class ArrayNode < Node
|
204
|
-
def each
|
205
|
-
return to_enum(__method__) { content.size } unless block_given?
|
206
|
-
content.each_index { |i| yield self[i] }
|
207
|
-
self
|
208
|
-
end
|
209
|
-
|
210
|
-
def to_ary
|
211
|
-
to_a
|
212
|
-
end
|
213
|
-
|
214
|
-
include Enumerable
|
215
|
-
include Arraylike
|
216
|
-
|
217
|
-
def as_json(*opt) # needs redefined after including Enumerable
|
218
|
-
Typelike.as_json(content, *opt)
|
219
|
-
end
|
220
|
-
|
221
|
-
# methods that don't look at the value; can skip the overhead of #[] (invoked by #to_a).
|
222
|
-
# we override these methods from Arraylike
|
223
|
-
SAFE_INDEX_ONLY_METHODS.each do |method_name|
|
224
|
-
define_method(method_name) { |*a, &b| content.public_send(method_name, *a, &b) }
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
class HashNode < Node
|
229
|
-
def each(&block)
|
230
|
-
return to_enum(__method__) { content.size } unless block_given?
|
231
|
-
if block.arity > 1
|
232
|
-
content.each_key { |k| yield k, self[k] }
|
233
|
-
else
|
234
|
-
content.each_key { |k| yield [k, self[k]] }
|
235
|
-
end
|
236
|
-
self
|
237
|
-
end
|
238
|
-
|
239
|
-
def to_hash
|
240
|
-
inject({}) { |h, (k, v)| h[k] = v; h }
|
241
|
-
end
|
242
|
-
|
243
|
-
include Enumerable
|
244
|
-
include Hashlike
|
245
|
-
|
246
|
-
def as_json(*opt) # needs redefined after including Enumerable
|
247
|
-
Typelike.as_json(content, *opt)
|
248
|
-
end
|
249
|
-
|
250
|
-
# methods that don't look at the value; can skip the overhead of #[] (invoked by #to_hash)
|
251
|
-
SAFE_KEY_ONLY_METHODS.each do |method_name|
|
252
|
-
define_method(method_name) { |*a, &b| content.public_send(method_name, *a, &b) }
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
end
|