openapi_first 2.1.1 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +0 -3
- data/lib/openapi_first/body_parser.rb +29 -16
- data/lib/openapi_first/builder.rb +143 -30
- data/lib/openapi_first/definition.rb +5 -5
- data/lib/openapi_first/error_responses/default.rb +1 -1
- data/lib/openapi_first/error_responses/jsonapi.rb +1 -1
- data/lib/openapi_first/file_loader.rb +21 -0
- data/lib/openapi_first/json.rb +25 -0
- data/lib/openapi_first/json_pointer.rb +22 -0
- data/lib/openapi_first/ref_resolver.rb +142 -0
- data/lib/openapi_first/request.rb +17 -56
- data/lib/openapi_first/request_body_parsers.rb +47 -0
- data/lib/openapi_first/request_parser.rb +11 -9
- data/lib/openapi_first/request_validator.rb +16 -9
- data/lib/openapi_first/response.rb +3 -21
- data/lib/openapi_first/response_body_parsers.rb +29 -0
- data/lib/openapi_first/response_parser.rb +9 -26
- data/lib/openapi_first/response_validator.rb +2 -2
- data/lib/openapi_first/test/methods.rb +9 -10
- data/lib/openapi_first/test/minitest_helpers.rb +28 -0
- data/lib/openapi_first/test/plain_helpers.rb +26 -0
- data/lib/openapi_first/test.rb +6 -0
- data/lib/openapi_first/validated_request.rb +13 -29
- data/lib/openapi_first/validators/request_body.rb +9 -23
- data/lib/openapi_first/validators/request_parameters.rb +17 -25
- data/lib/openapi_first/validators/response_body.rb +7 -3
- data/lib/openapi_first/validators/response_headers.rb +6 -4
- data/lib/openapi_first/version.rb +1 -1
- data/lib/openapi_first.rb +10 -17
- metadata +11 -23
- data/lib/openapi_first/json_refs.rb +0 -151
- data/lib/openapi_first/schema.rb +0 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9458fb5b815dcc64722a1829dc2082f60a92b36ce7af9fa8529c041544b190d4
|
4
|
+
data.tar.gz: 5f5982e1e71f5f1a150860be23291863ff3c431b3bce332899b01150e2c9fc11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c92c5cbda9bcd884299a265fcc713c0370e0766670e2b3199af9169dfe29855ed78ae5ad1a70fd98326a4f764403e0b9429132b97ee9f3b887d5d9af772acd69
|
7
|
+
data.tar.gz: '05083e92a954621b54667d8f99c133b2489e70eaa2b7332885c867053c81667a0eba0779ad210c150c8402814dff6d144044349271e8fe06808bafe0e5ad94ca'
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,21 @@
|
|
2
2
|
|
3
3
|
## Unreleased
|
4
4
|
|
5
|
+
## 2.2.1
|
6
|
+
|
7
|
+
- Fix issue with $ref resolving paths poiting outside directories `$ref: '../a/b.yaml'` (https://github.com/ahx/openapi_first/issues/313)
|
8
|
+
- Remove warning about missing assertions when using assert_api_conform (https://github.com/ahx/openapi_first/issues/313)
|
9
|
+
|
10
|
+
## 2.2.0
|
11
|
+
|
12
|
+
- Fix support for discriminator in response bodies if no mapping is defined (https://github.com/ahx/openapi_first/issues/285)
|
13
|
+
- Fix support for discriminator in request bodies if no mapping is defined
|
14
|
+
- Replace bundled json_refs fork with own code
|
15
|
+
- Better error messages when OpenAPI file has invalid references ("$ref")
|
16
|
+
- Autoload OpenapiFirst::Test module. There is no need to `require 'openapi_first/test'` anymore.
|
17
|
+
- Remove multi_json dependency. openapi_first uses multi_json if available or the default json gem otherwise.
|
18
|
+
If you want to use multi_json, make sure to add it to your Gemfile.
|
19
|
+
|
5
20
|
## 2.1.1
|
6
21
|
|
7
22
|
- Fix issue with non file downloads / JSON responses https://github.com/ahx/openapi_first/issues/281
|
data/README.md
CHANGED
@@ -79,8 +79,6 @@ validated_response.parsed_headers
|
|
79
79
|
definition.validate_response(rack_request,rack_response, raise_error: true) # Raises OpenapiFirst::ResponseInvalidError or OpenapiFirst::ResponseNotFoundError
|
80
80
|
```
|
81
81
|
|
82
|
-
OpenapiFirst uses [`multi_json`](https://rubygems.org/gems/multi_json).
|
83
|
-
|
84
82
|
## Rack Middlewares
|
85
83
|
|
86
84
|
### Request validation
|
@@ -210,7 +208,6 @@ Here is how to set it up for Rails integration tests:
|
|
210
208
|
|
211
209
|
```ruby
|
212
210
|
# test_helper.rb
|
213
|
-
require 'openapi_first/test'
|
214
211
|
OpenapiFirst::Test.register('openapi/v1.openapi.yaml')
|
215
212
|
```
|
216
213
|
|
@@ -1,32 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'multi_json'
|
4
|
-
|
5
3
|
module OpenapiFirst
|
6
4
|
# @!visibility private
|
7
|
-
|
8
|
-
def
|
9
|
-
|
5
|
+
module BodyParser
|
6
|
+
def self.[](content_type)
|
7
|
+
case content_type
|
8
|
+
when /json/i
|
9
|
+
JsonBodyParser
|
10
|
+
when %r{multipart/form-data}i
|
11
|
+
MultipartBodyParser
|
12
|
+
else
|
13
|
+
DefaultBodyParser
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.read_body(request)
|
18
|
+
body = request.body&.read
|
19
|
+
request.body.rewind if request.body.respond_to?(:rewind)
|
20
|
+
body
|
10
21
|
end
|
11
22
|
|
12
|
-
|
23
|
+
JsonBodyParser = lambda do |request|
|
13
24
|
body = read_body(request)
|
14
25
|
return if body.nil? || body.empty?
|
15
26
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
body
|
20
|
-
rescue MultiJson::ParseError
|
27
|
+
JSON.parse(body)
|
28
|
+
rescue JSON::ParserError
|
21
29
|
Failure.fail!(:invalid_body, message: 'Failed to parse request body as JSON')
|
22
30
|
end
|
23
31
|
|
24
|
-
|
32
|
+
MultipartBodyParser = lambda do |request|
|
33
|
+
request.POST.transform_values do |value|
|
34
|
+
value.is_a?(Hash) && value[:tempfile] ? value[:tempfile].read : value
|
35
|
+
end
|
36
|
+
end
|
25
37
|
|
26
|
-
|
27
|
-
|
28
|
-
request.
|
29
|
-
|
38
|
+
# This returns the post data parsed by rack or the raw body
|
39
|
+
DefaultBodyParser = lambda do |request|
|
40
|
+
return request.POST if request.form_data?
|
41
|
+
|
42
|
+
read_body(request)
|
30
43
|
end
|
31
44
|
end
|
32
45
|
end
|
@@ -1,33 +1,59 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json_schemer'
|
4
|
+
require_relative 'json_pointer'
|
5
|
+
require_relative 'ref_resolver'
|
6
|
+
|
3
7
|
module OpenapiFirst
|
4
8
|
# Builds parts of a Definition
|
5
9
|
# This knows how to read a resolved OpenAPI document and build {Request} and {Response} objects.
|
6
|
-
class Builder
|
10
|
+
class Builder # rubocop:disable Metrics/ClassLength
|
7
11
|
REQUEST_METHODS = %w[get head post put patch delete trace options].freeze
|
8
12
|
|
9
13
|
# Builds a router from a resolved OpenAPI document.
|
10
|
-
# @param
|
14
|
+
# @param contents [Hash] The OpenAPI document Hash.
|
11
15
|
# @param config [OpenapiFirst::Configuration] The configuration object.
|
12
|
-
def self.build_router(
|
13
|
-
|
14
|
-
new(resolved, config, openapi_version).router
|
16
|
+
def self.build_router(contents, filepath:, config:)
|
17
|
+
new(contents, filepath:, config:).router
|
15
18
|
end
|
16
19
|
|
17
|
-
def initialize(
|
18
|
-
@
|
20
|
+
def initialize(contents, filepath:, config:)
|
21
|
+
@schemer_configuration = JSONSchemer::Configuration.new(
|
22
|
+
meta_schema: detect_meta_schema(contents, filepath),
|
23
|
+
insert_property_defaults: true
|
24
|
+
)
|
19
25
|
@config = config
|
20
|
-
@
|
26
|
+
@contents = RefResolver.for(contents, dir: filepath && File.dirname(filepath))
|
21
27
|
end
|
22
28
|
|
23
|
-
attr_reader :
|
29
|
+
attr_reader :config
|
30
|
+
private attr_reader :schemer_configuration
|
31
|
+
|
32
|
+
def detect_meta_schema(document, filepath)
|
33
|
+
# Copied from JSONSchemer 🙇🏻♂️
|
34
|
+
version = document['openapi']
|
35
|
+
case version
|
36
|
+
when /\A3\.1\.\d+\z/
|
37
|
+
@document_schema = JSONSchemer.openapi31_document
|
38
|
+
document.fetch('jsonSchemaDialect') { JSONSchemer::OpenAPI31::BASE_URI.to_s }
|
39
|
+
when /\A3\.0\.\d+\z/
|
40
|
+
@document_schema = JSONSchemer.openapi30_document
|
41
|
+
JSONSchemer::OpenAPI30::BASE_URI.to_s
|
42
|
+
else
|
43
|
+
raise Error, "Unsupported OpenAPI version #{version.inspect} #{filepath}"
|
44
|
+
end
|
45
|
+
end
|
24
46
|
|
25
47
|
def router # rubocop:disable Metrics/MethodLength
|
26
48
|
router = OpenapiFirst::Router.new
|
27
|
-
|
28
|
-
path_item_object.
|
49
|
+
@contents.fetch('paths').each do |path, path_item_object|
|
50
|
+
path_item_object.resolved.keys.intersection(REQUEST_METHODS).map do |request_method|
|
29
51
|
operation_object = path_item_object[request_method]
|
30
|
-
|
52
|
+
parameters = operation_object['parameters']&.resolved.to_a.chain(path_item_object['parameters']&.resolved.to_a)
|
53
|
+
parameters = parse_parameters(parameters)
|
54
|
+
|
55
|
+
build_requests(path:, request_method:, operation_object:,
|
56
|
+
parameters:).each do |request|
|
31
57
|
router.add_request(
|
32
58
|
request,
|
33
59
|
request_method:,
|
@@ -35,7 +61,7 @@ module OpenapiFirst
|
|
35
61
|
content_type: request.content_type
|
36
62
|
)
|
37
63
|
end
|
38
|
-
build_responses(operation_object).each do |response|
|
64
|
+
build_responses(responses: operation_object['responses']).each do |response|
|
39
65
|
router.add_response(
|
40
66
|
response,
|
41
67
|
request_method:,
|
@@ -49,33 +75,120 @@ module OpenapiFirst
|
|
49
75
|
router
|
50
76
|
end
|
51
77
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
78
|
+
def parse_parameters(parameters)
|
79
|
+
grouped_parameters = group_parameters(parameters)
|
80
|
+
ParsedParameters.new(
|
81
|
+
query: grouped_parameters[:query],
|
82
|
+
path: grouped_parameters[:path],
|
83
|
+
cookie: grouped_parameters[:cookie],
|
84
|
+
header: grouped_parameters[:header],
|
85
|
+
query_schema: build_parameter_schema(grouped_parameters[:query]),
|
86
|
+
path_schema: build_parameter_schema(grouped_parameters[:path]),
|
87
|
+
cookie_schema: build_parameter_schema(grouped_parameters[:cookie]),
|
88
|
+
header_schema: build_parameter_schema(grouped_parameters[:header])
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
def build_parameter_schema(parameters)
|
93
|
+
schema = build_parameters_schema(parameters)
|
94
|
+
|
95
|
+
JSONSchemer.schema(schema,
|
96
|
+
configuration: schemer_configuration,
|
97
|
+
after_property_validation: config.hooks[:after_request_parameter_property_validation])
|
98
|
+
end
|
99
|
+
|
100
|
+
def build_requests(path:, request_method:, operation_object:, parameters:)
|
101
|
+
required_body = operation_object['requestBody']&.resolved&.fetch('required', false) == true
|
102
|
+
result = operation_object.dig('requestBody', 'content')&.map do |content_type, content_object|
|
103
|
+
content_schema = content_object['schema'].schema(
|
104
|
+
configuration: schemer_configuration,
|
105
|
+
after_property_validation: config.hooks[:after_request_body_property_validation]
|
106
|
+
)
|
107
|
+
Request.new(path:, request_method:,
|
108
|
+
operation_object: operation_object.resolved,
|
109
|
+
parameters:, content_type:,
|
110
|
+
content_schema:,
|
111
|
+
required_body:)
|
60
112
|
end || []
|
61
113
|
return result if required_body
|
62
114
|
|
63
115
|
result << Request.new(
|
64
|
-
path:, request_method:, operation_object
|
116
|
+
path:, request_method:, operation_object: operation_object.resolved,
|
65
117
|
parameters:, content_type: nil, content_schema: nil,
|
66
|
-
required_body
|
118
|
+
required_body:
|
67
119
|
)
|
68
120
|
end
|
69
121
|
|
70
|
-
def build_responses(
|
71
|
-
|
72
|
-
|
122
|
+
def build_responses(responses:)
|
123
|
+
return [] unless responses
|
124
|
+
|
125
|
+
responses.flat_map do |status, response_object|
|
126
|
+
headers = response_object['headers']&.resolved
|
127
|
+
headers_schema = JSONSchemer::Schema.new(
|
128
|
+
build_headers_schema(headers),
|
129
|
+
configuration: schemer_configuration
|
130
|
+
)
|
73
131
|
response_object['content']&.map do |content_type, content_object|
|
74
|
-
content_schema = content_object['schema']
|
75
|
-
Response.new(status:,
|
76
|
-
|
77
|
-
|
132
|
+
content_schema = content_object['schema'].schema(configuration: schemer_configuration)
|
133
|
+
Response.new(status:,
|
134
|
+
headers:,
|
135
|
+
headers_schema:,
|
136
|
+
content_type:,
|
137
|
+
content_schema:)
|
138
|
+
end || Response.new(status:, headers:, headers_schema:, content_type: nil,
|
139
|
+
content_schema: nil)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
IGNORED_HEADER_PARAMETERS = Set['Content-Type', 'Accept', 'Authorization'].freeze
|
144
|
+
private_constant :IGNORED_HEADER_PARAMETERS
|
145
|
+
|
146
|
+
def group_parameters(parameter_definitions)
|
147
|
+
result = {}
|
148
|
+
parameter_definitions&.each do |parameter|
|
149
|
+
(result[parameter['in'].to_sym] ||= []) << parameter
|
150
|
+
end
|
151
|
+
result[:header]&.reject! { IGNORED_HEADER_PARAMETERS.include?(_1['name']) }
|
152
|
+
result
|
153
|
+
end
|
154
|
+
|
155
|
+
def build_headers_schema(headers_object)
|
156
|
+
return unless headers_object&.any?
|
157
|
+
|
158
|
+
properties = {}
|
159
|
+
required = []
|
160
|
+
headers_object.each do |name, header|
|
161
|
+
schema = header['schema']
|
162
|
+
next if name.casecmp('content-type').zero?
|
163
|
+
|
164
|
+
properties[name] = schema if schema
|
165
|
+
required << name if header['required']
|
78
166
|
end
|
167
|
+
{
|
168
|
+
'properties' => properties,
|
169
|
+
'required' => required
|
170
|
+
}
|
79
171
|
end
|
172
|
+
|
173
|
+
def build_parameters_schema(parameters)
|
174
|
+
return unless parameters
|
175
|
+
|
176
|
+
properties = {}
|
177
|
+
required = []
|
178
|
+
parameters.each do |parameter|
|
179
|
+
schema = parameter['schema']
|
180
|
+
name = parameter['name']
|
181
|
+
properties[name] = schema if schema
|
182
|
+
required << name if parameter['required']
|
183
|
+
end
|
184
|
+
|
185
|
+
{
|
186
|
+
'properties' => properties,
|
187
|
+
'required' => required
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
ParsedParameters = Data.define(:path, :query, :header, :cookie, :path_schema, :query_schema, :header_schema,
|
192
|
+
:cookie_schema)
|
80
193
|
end
|
81
194
|
end
|
@@ -22,16 +22,16 @@ module OpenapiFirst
|
|
22
22
|
# @return [Router]
|
23
23
|
attr_reader :router
|
24
24
|
|
25
|
-
# @param
|
25
|
+
# @param contents [Hash] The OpenAPI document.
|
26
26
|
# @param filepath [String] The file path of the OpenAPI document.
|
27
|
-
def initialize(
|
27
|
+
def initialize(contents, filepath = nil)
|
28
28
|
@filepath = filepath
|
29
29
|
@config = OpenapiFirst.configuration.clone
|
30
30
|
yield @config if block_given?
|
31
31
|
@config.freeze
|
32
|
-
@router = Builder.build_router(
|
33
|
-
@resolved =
|
34
|
-
@paths =
|
32
|
+
@router = Builder.build_router(contents, filepath:, config:)
|
33
|
+
@resolved = contents
|
34
|
+
@paths = @router.routes.map(&:path).to_a.uniq # TODO: Refactor
|
35
35
|
end
|
36
36
|
|
37
37
|
# Gives access to the raw resolved Hash. Like `mydefinition['components'].dig('schemas', 'Stations')`
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module OpenapiFirst
|
6
|
+
# @!visibility private
|
7
|
+
module FileLoader
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def load(file_path)
|
11
|
+
raise FileNotFoundError, "File not found #{file_path}" unless File.exist?(file_path)
|
12
|
+
|
13
|
+
body = File.read(file_path)
|
14
|
+
extname = File.extname(file_path)
|
15
|
+
return JSON.parse(body) if extname == '.json'
|
16
|
+
return YAML.unsafe_load(body) if ['.yaml', '.yml'].include?(extname)
|
17
|
+
|
18
|
+
body
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This tries to load MultiJson if available to keep compatibility
|
4
|
+
# with MultiJson until next major version
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'multi_json'
|
8
|
+
module OpenapiFirst
|
9
|
+
# Compatibility with MultiJson
|
10
|
+
# @visibility private
|
11
|
+
module JSON
|
12
|
+
ParserError = MultiJson::ParseError
|
13
|
+
|
14
|
+
def self.parse(string)
|
15
|
+
MultiJson.load(string)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.generate(object)
|
19
|
+
MultiJson.dump(object)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
rescue LoadError
|
24
|
+
require 'json'
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenapiFirst
|
4
|
+
# Functions to handle JSON Pointers
|
5
|
+
# @!visibility private
|
6
|
+
module JsonPointer
|
7
|
+
ESCAPE_CHARS = { '~' => '~0', '/' => '~1', '+' => '%2B' }.freeze
|
8
|
+
ESCAPE_REGEX = Regexp.union(ESCAPE_CHARS.keys)
|
9
|
+
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def append(root, *tokens)
|
13
|
+
"#{root}/" + tokens.map do |token|
|
14
|
+
escape_json_pointer_token(token)
|
15
|
+
end.join('/')
|
16
|
+
end
|
17
|
+
|
18
|
+
def escape_json_pointer_token(token)
|
19
|
+
token.to_s.gsub(ESCAPE_REGEX, ESCAPE_CHARS)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json_schemer'
|
4
|
+
|
5
|
+
module OpenapiFirst
|
6
|
+
# This is here to give traverse an OAD while keeping $refs intact
|
7
|
+
# @visibility private
|
8
|
+
module RefResolver
|
9
|
+
def self.load(file_path)
|
10
|
+
contents = OpenapiFirst::FileLoader.load(file_path)
|
11
|
+
self.for(contents, dir: File.dirname(File.expand_path(file_path)))
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.for(value, context: value, dir: Dir.pwd)
|
15
|
+
case value
|
16
|
+
when ::Hash
|
17
|
+
Hash.new(value, context:, dir:)
|
18
|
+
when ::Array
|
19
|
+
Array.new(value, context:, dir:)
|
20
|
+
when ::NilClass
|
21
|
+
nil
|
22
|
+
else
|
23
|
+
Simple.new(value)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @visibility private
|
28
|
+
module Diggable
|
29
|
+
def dig(*keys)
|
30
|
+
keys.inject(self) do |result, key|
|
31
|
+
break unless result.respond_to?(:[])
|
32
|
+
|
33
|
+
result[key]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @visibility private
|
39
|
+
module Resolvable
|
40
|
+
def initialize(value, context: value, dir: nil)
|
41
|
+
@value = value
|
42
|
+
@context = context
|
43
|
+
@dir = (dir && File.absolute_path(dir)) || Dir.pwd
|
44
|
+
end
|
45
|
+
|
46
|
+
# The value of this node
|
47
|
+
attr_reader :value
|
48
|
+
# The path of the file sytem directory where this was loaded from
|
49
|
+
attr_reader :dir
|
50
|
+
# The object where this node was found in
|
51
|
+
attr_reader :context
|
52
|
+
|
53
|
+
def resolve_ref(pointer)
|
54
|
+
if pointer.start_with?('#')
|
55
|
+
value = Hana::Pointer.new(pointer[1..]).eval(context)
|
56
|
+
raise "Unknown reference #{pointer} in #{context}" unless value
|
57
|
+
|
58
|
+
return RefResolver.for(value, dir:)
|
59
|
+
end
|
60
|
+
|
61
|
+
relative_path, file_pointer = pointer.split('#')
|
62
|
+
full_path = File.expand_path(relative_path, dir)
|
63
|
+
return RefResolver.load(full_path) unless file_pointer
|
64
|
+
|
65
|
+
file_contents = FileLoader.load(full_path)
|
66
|
+
new_dir = File.dirname(full_path)
|
67
|
+
value = Hana::Pointer.new(file_pointer).eval(file_contents)
|
68
|
+
RefResolver.for(value, dir: new_dir)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# @visibility private
|
73
|
+
class Simple
|
74
|
+
include Resolvable
|
75
|
+
|
76
|
+
def resolved = value
|
77
|
+
end
|
78
|
+
|
79
|
+
# @visibility private
|
80
|
+
class Hash
|
81
|
+
include Resolvable
|
82
|
+
include Diggable
|
83
|
+
include Enumerable
|
84
|
+
|
85
|
+
def resolved
|
86
|
+
return resolve_ref(value['$ref']).value if value.key?('$ref')
|
87
|
+
|
88
|
+
value
|
89
|
+
end
|
90
|
+
|
91
|
+
def [](key)
|
92
|
+
return resolve_ref(@value['$ref'])[key] if !@value.key?(key) && @value.key?('$ref')
|
93
|
+
|
94
|
+
RefResolver.for(@value[key], dir:, context:)
|
95
|
+
end
|
96
|
+
|
97
|
+
def fetch(key)
|
98
|
+
return resolve_ref(@value['$ref']).fetch(key) if !@value.key?(key) && @value.key?('$ref')
|
99
|
+
|
100
|
+
RefResolver.for(@value.fetch(key), dir:, context:)
|
101
|
+
end
|
102
|
+
|
103
|
+
def each
|
104
|
+
resolved.each do |key, value|
|
105
|
+
yield key, RefResolver.for(value, dir:, context:)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def schema(options = {})
|
110
|
+
ref_resolver = JSONSchemer::CachedResolver.new do |uri|
|
111
|
+
FileLoader.load(uri.path)
|
112
|
+
end
|
113
|
+
base_uri = URI::File.build({ path: "#{dir}/" })
|
114
|
+
root = JSONSchemer::Schema.new(context, base_uri:, ref_resolver:, **options)
|
115
|
+
JSONSchemer::Schema.new(value, nil, root, base_uri:, **options)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# @visibility private
|
120
|
+
class Array
|
121
|
+
include Resolvable
|
122
|
+
include Diggable
|
123
|
+
|
124
|
+
def [](index)
|
125
|
+
item = @value[index]
|
126
|
+
return resolve_ref(item['$ref']) if item.is_a?(::Hash) && item.key?('$ref')
|
127
|
+
|
128
|
+
RefResolver.for(item, dir:, context:)
|
129
|
+
end
|
130
|
+
|
131
|
+
def resolved
|
132
|
+
value.map do |item|
|
133
|
+
if item.respond_to?(:key?) && item.key?('$ref')
|
134
|
+
resolve_ref(item['$ref']).resolved
|
135
|
+
else
|
136
|
+
item
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|