openapi_first 1.4.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -1
- data/README.md +105 -28
- data/lib/openapi_first/body_parser.rb +8 -11
- data/lib/openapi_first/builder.rb +81 -0
- data/lib/openapi_first/configuration.rb +24 -3
- data/lib/openapi_first/definition.rb +44 -100
- data/lib/openapi_first/error_response.rb +2 -2
- data/lib/openapi_first/error_responses/default.rb +73 -0
- data/lib/openapi_first/error_responses/jsonapi.rb +59 -0
- data/lib/openapi_first/errors.rb +26 -4
- data/lib/openapi_first/failure.rb +29 -26
- data/lib/openapi_first/json_refs.rb +1 -3
- data/lib/openapi_first/middlewares/request_validation.rb +2 -2
- data/lib/openapi_first/middlewares/response_validation.rb +4 -3
- data/lib/openapi_first/request.rb +92 -0
- data/lib/openapi_first/request_parser.rb +35 -0
- data/lib/openapi_first/request_validator.rb +25 -0
- data/lib/openapi_first/response.rb +57 -0
- data/lib/openapi_first/response_parser.rb +49 -0
- data/lib/openapi_first/response_validator.rb +27 -0
- data/lib/openapi_first/router/find_content.rb +17 -0
- data/lib/openapi_first/router/find_response.rb +45 -0
- data/lib/openapi_first/{definition → router}/path_template.rb +9 -1
- data/lib/openapi_first/router.rb +100 -0
- data/lib/openapi_first/schema/validation_error.rb +16 -10
- data/lib/openapi_first/schema/validation_result.rb +8 -6
- data/lib/openapi_first/schema.rb +4 -8
- data/lib/openapi_first/test/methods.rb +21 -0
- data/lib/openapi_first/test.rb +19 -0
- data/lib/openapi_first/validated_request.rb +81 -0
- data/lib/openapi_first/validated_response.rb +33 -0
- data/lib/openapi_first/validators/request_body.rb +39 -0
- data/lib/openapi_first/validators/request_parameters.rb +61 -0
- data/lib/openapi_first/validators/response_body.rb +30 -0
- data/lib/openapi_first/validators/response_headers.rb +25 -0
- data/lib/openapi_first/version.rb +1 -1
- data/lib/openapi_first.rb +40 -21
- metadata +35 -24
- data/lib/openapi_first/definition/operation.rb +0 -197
- data/lib/openapi_first/definition/path_item.rb +0 -40
- data/lib/openapi_first/definition/request_body.rb +0 -46
- data/lib/openapi_first/definition/response.rb +0 -32
- data/lib/openapi_first/definition/responses.rb +0 -87
- data/lib/openapi_first/plugins/default/error_response.rb +0 -74
- data/lib/openapi_first/plugins/default.rb +0 -11
- data/lib/openapi_first/plugins/jsonapi/error_response.rb +0 -60
- data/lib/openapi_first/plugins/jsonapi.rb +0 -11
- data/lib/openapi_first/plugins.rb +0 -25
- data/lib/openapi_first/request_validation/request_body_validator.rb +0 -41
- data/lib/openapi_first/request_validation/validator.rb +0 -82
- data/lib/openapi_first/response_validation/validator.rb +0 -98
- data/lib/openapi_first/runtime_request.rb +0 -166
- data/lib/openapi_first/runtime_response.rb +0 -124
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openapi_first
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Haller
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-06-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hana
|
@@ -28,16 +28,22 @@ dependencies:
|
|
28
28
|
name: json_schemer
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.1'
|
34
|
+
- - "<"
|
32
35
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
36
|
+
version: '3.0'
|
34
37
|
type: :runtime
|
35
38
|
prerelease: false
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
37
40
|
requirements:
|
38
|
-
- - "
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '2.1'
|
44
|
+
- - "<"
|
39
45
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
46
|
+
version: '3.0'
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
48
|
name: multi_json
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -104,33 +110,38 @@ files:
|
|
104
110
|
- README.md
|
105
111
|
- lib/openapi_first.rb
|
106
112
|
- lib/openapi_first/body_parser.rb
|
113
|
+
- lib/openapi_first/builder.rb
|
107
114
|
- lib/openapi_first/configuration.rb
|
108
115
|
- lib/openapi_first/definition.rb
|
109
|
-
- lib/openapi_first/definition/operation.rb
|
110
|
-
- lib/openapi_first/definition/path_item.rb
|
111
|
-
- lib/openapi_first/definition/path_template.rb
|
112
|
-
- lib/openapi_first/definition/request_body.rb
|
113
|
-
- lib/openapi_first/definition/response.rb
|
114
|
-
- lib/openapi_first/definition/responses.rb
|
115
116
|
- lib/openapi_first/error_response.rb
|
117
|
+
- lib/openapi_first/error_responses/default.rb
|
118
|
+
- lib/openapi_first/error_responses/jsonapi.rb
|
116
119
|
- lib/openapi_first/errors.rb
|
117
120
|
- lib/openapi_first/failure.rb
|
118
121
|
- lib/openapi_first/json_refs.rb
|
119
122
|
- lib/openapi_first/middlewares/request_validation.rb
|
120
123
|
- lib/openapi_first/middlewares/response_validation.rb
|
121
|
-
- lib/openapi_first/
|
122
|
-
- lib/openapi_first/
|
123
|
-
- lib/openapi_first/
|
124
|
-
- lib/openapi_first/
|
125
|
-
- lib/openapi_first/
|
126
|
-
- lib/openapi_first/
|
127
|
-
- lib/openapi_first/
|
128
|
-
- lib/openapi_first/
|
129
|
-
- lib/openapi_first/
|
130
|
-
- lib/openapi_first/
|
124
|
+
- lib/openapi_first/request.rb
|
125
|
+
- lib/openapi_first/request_parser.rb
|
126
|
+
- lib/openapi_first/request_validator.rb
|
127
|
+
- lib/openapi_first/response.rb
|
128
|
+
- lib/openapi_first/response_parser.rb
|
129
|
+
- lib/openapi_first/response_validator.rb
|
130
|
+
- lib/openapi_first/router.rb
|
131
|
+
- lib/openapi_first/router/find_content.rb
|
132
|
+
- lib/openapi_first/router/find_response.rb
|
133
|
+
- lib/openapi_first/router/path_template.rb
|
131
134
|
- lib/openapi_first/schema.rb
|
132
135
|
- lib/openapi_first/schema/validation_error.rb
|
133
136
|
- lib/openapi_first/schema/validation_result.rb
|
137
|
+
- lib/openapi_first/test.rb
|
138
|
+
- lib/openapi_first/test/methods.rb
|
139
|
+
- lib/openapi_first/validated_request.rb
|
140
|
+
- lib/openapi_first/validated_response.rb
|
141
|
+
- lib/openapi_first/validators/request_body.rb
|
142
|
+
- lib/openapi_first/validators/request_parameters.rb
|
143
|
+
- lib/openapi_first/validators/response_body.rb
|
144
|
+
- lib/openapi_first/validators/response_headers.rb
|
134
145
|
- lib/openapi_first/version.rb
|
135
146
|
homepage: https://github.com/ahx/openapi_first
|
136
147
|
licenses:
|
@@ -149,14 +160,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
149
160
|
requirements:
|
150
161
|
- - ">="
|
151
162
|
- !ruby/object:Gem::Version
|
152
|
-
version: 3.
|
163
|
+
version: 3.2.0
|
153
164
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
165
|
requirements:
|
155
166
|
- - ">="
|
156
167
|
- !ruby/object:Gem::Version
|
157
168
|
version: '0'
|
158
169
|
requirements: []
|
159
|
-
rubygems_version: 3.5.
|
170
|
+
rubygems_version: 3.5.11
|
160
171
|
signing_key:
|
161
172
|
specification_version: 4
|
162
173
|
summary: Implement HTTP APIs based on OpenApi 3.x
|
@@ -1,197 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'forwardable'
|
4
|
-
require 'set'
|
5
|
-
require 'openapi_parameters'
|
6
|
-
require_relative 'request_body'
|
7
|
-
require_relative 'responses'
|
8
|
-
|
9
|
-
module OpenapiFirst
|
10
|
-
class Definition
|
11
|
-
# Represents an operation object in the OpenAPI 3.X specification.
|
12
|
-
# Use this class to access information about the operation. Use `#[key]` to read the raw data.
|
13
|
-
# When using the middleware you can access the operation object via `env[OpenapiFirst::REQUEST].operation`.
|
14
|
-
class Operation
|
15
|
-
extend Forwardable
|
16
|
-
|
17
|
-
def_delegators :operation_object,
|
18
|
-
:[]
|
19
|
-
|
20
|
-
def initialize(path, request_method, path_item_object, openapi_version:)
|
21
|
-
@path = path
|
22
|
-
@method = request_method
|
23
|
-
@path_item_object = path_item_object
|
24
|
-
@openapi_version = openapi_version
|
25
|
-
@operation_object = @path_item_object[request_method]
|
26
|
-
end
|
27
|
-
|
28
|
-
# Returns the path of the operation as in the API description.
|
29
|
-
# @return [String] The path of the operation.
|
30
|
-
attr_reader :path
|
31
|
-
|
32
|
-
# Returns the (downcased) request method of the operation.
|
33
|
-
# Example: "get"
|
34
|
-
# @return [String] The request method of the operation.
|
35
|
-
attr_reader :method
|
36
|
-
alias request_method method
|
37
|
-
|
38
|
-
attr_reader :openapi_version # :nodoc:
|
39
|
-
|
40
|
-
# Returns the operation ID as defined in the API description.
|
41
|
-
# @return [String, nil]
|
42
|
-
def operation_id
|
43
|
-
operation_object['operationId']
|
44
|
-
end
|
45
|
-
|
46
|
-
# Checks if the operation is a read operation.
|
47
|
-
# This is the case for all request methods except POST, PUT, PATCH and DELETE.
|
48
|
-
# @return [Boolean] `true` if the operation is a read operation, `false` otherwise.
|
49
|
-
def read?
|
50
|
-
!write?
|
51
|
-
end
|
52
|
-
|
53
|
-
# Checks if the operation is a write operation.
|
54
|
-
# This is the case for POST, PUT, PATCH and DELETE request methods.
|
55
|
-
# @return [Boolean] `true` if the operation is a write operation, `false` otherwise.
|
56
|
-
# @deprecated Use {#write?} instead.
|
57
|
-
def write?
|
58
|
-
WRITE_METHODS.include?(method)
|
59
|
-
end
|
60
|
-
|
61
|
-
# Returns the request body definition if defined in the API description.
|
62
|
-
# @return [RequestBody, nil] The request body of the operation, or `nil` if not present.
|
63
|
-
def request_body
|
64
|
-
@request_body ||= RequestBody.new(operation_object['requestBody'], self) if operation_object['requestBody']
|
65
|
-
end
|
66
|
-
|
67
|
-
# Checks if a response status is defined for this operation.
|
68
|
-
# @param status [Integer, String] The response status to check.
|
69
|
-
# @return [Boolean] `true` if the response status is defined, `false` otherwise.
|
70
|
-
def response_status_defined?(status)
|
71
|
-
responses.status_defined?(status)
|
72
|
-
end
|
73
|
-
|
74
|
-
# Returns the response object for a given status.
|
75
|
-
# @param status [Integer, String] The response status.
|
76
|
-
# @param content_type [String] Content-Type of the current response.
|
77
|
-
# @return [Response, nil] The response object for the given status, or `nil` if not found.
|
78
|
-
def response_for(status, content_type)
|
79
|
-
responses.response_for(status, content_type)
|
80
|
-
end
|
81
|
-
|
82
|
-
# Returns the schema for a given content type.
|
83
|
-
# @param content_type [String] The content type.
|
84
|
-
# @return [Schema, nil] The schema for the given content type, or `nil` if not found.
|
85
|
-
def schema_for(content_type)
|
86
|
-
content = @request_body_object['content']
|
87
|
-
return unless content&.any?
|
88
|
-
|
89
|
-
content_schemas&.fetch(content_type) do
|
90
|
-
type = content_type.split(';')[0]
|
91
|
-
content_schemas[type] || content_schemas["#{type.split('/')[0]}/*"] || content_schemas['*/*']
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
# Returns a unique name for this operation. Used for generating error messages.
|
96
|
-
# @visibility private
|
97
|
-
def name
|
98
|
-
@name ||= "#{method.upcase} #{path}"
|
99
|
-
end
|
100
|
-
|
101
|
-
# Returns the path parameters of the operation.
|
102
|
-
# @return [Array<Hash>] The path parameters of the operation.
|
103
|
-
def path_parameters
|
104
|
-
all_parameters['path']
|
105
|
-
end
|
106
|
-
|
107
|
-
# Returns the query parameters of the operation.
|
108
|
-
# Returns parameters defined on the path and in the operation.
|
109
|
-
# @return [Array<Hash>] The query parameters of the operation.
|
110
|
-
def query_parameters
|
111
|
-
all_parameters['query']
|
112
|
-
end
|
113
|
-
|
114
|
-
# Returns the header parameters of the operation.
|
115
|
-
# Returns parameters defined on the path and in the operation.
|
116
|
-
# @return [Array<Hash>] The header parameters of the operation.
|
117
|
-
def header_parameters
|
118
|
-
all_parameters['header']
|
119
|
-
end
|
120
|
-
|
121
|
-
# Returns the cookie parameters of the operation.
|
122
|
-
# Returns parameters defined on the path and in the operation.
|
123
|
-
# @return [Array<Hash>] The cookie parameters of the operation.
|
124
|
-
def cookie_parameters
|
125
|
-
all_parameters['cookie']
|
126
|
-
end
|
127
|
-
|
128
|
-
# Returns the schema for the path parameters.
|
129
|
-
# @visibility private
|
130
|
-
# @return [Schema, nil] The schema for the path parameters, or `nil` if not found.
|
131
|
-
def path_parameters_schema
|
132
|
-
@path_parameters_schema ||= build_schema(path_parameters)
|
133
|
-
end
|
134
|
-
|
135
|
-
# Returns the schema for the query parameters.
|
136
|
-
# @visibility private
|
137
|
-
# @return [Schema, nil] The schema for the query parameters, or `nil` if not found.
|
138
|
-
def query_parameters_schema
|
139
|
-
@query_parameters_schema ||= build_schema(query_parameters)
|
140
|
-
end
|
141
|
-
|
142
|
-
# Returns the schema for the header parameters.
|
143
|
-
# @visibility private
|
144
|
-
# @return [Schema, nil] The schema for the header parameters, or `nil` if not found.
|
145
|
-
def header_parameters_schema
|
146
|
-
@header_parameters_schema ||= build_schema(header_parameters)
|
147
|
-
end
|
148
|
-
|
149
|
-
# Returns the schema for the cookie parameters.
|
150
|
-
# @visibility private
|
151
|
-
# @return [Schema, nil] The schema for the cookie parameters, or `nil` if not found.
|
152
|
-
def cookie_parameters_schema
|
153
|
-
@cookie_parameters_schema ||= build_schema(cookie_parameters)
|
154
|
-
end
|
155
|
-
|
156
|
-
private
|
157
|
-
|
158
|
-
WRITE_METHODS = Set.new(%w[post put patch delete]).freeze
|
159
|
-
private_constant :WRITE_METHODS
|
160
|
-
|
161
|
-
IGNORED_HEADERS = Set['Content-Type', 'Accept', 'Authorization'].freeze
|
162
|
-
private_constant :IGNORED_HEADERS
|
163
|
-
|
164
|
-
def all_parameters
|
165
|
-
@all_parameters ||= (@path_item_object.fetch('parameters', []) + operation_object.fetch('parameters', []))
|
166
|
-
.reject { |p| p['in'] == 'header' && IGNORED_HEADERS.include?(p['name']) }
|
167
|
-
.group_by { _1['in'] }
|
168
|
-
end
|
169
|
-
|
170
|
-
def build_schema(parameters)
|
171
|
-
return unless parameters&.any?
|
172
|
-
|
173
|
-
init_schema = {
|
174
|
-
'type' => 'object',
|
175
|
-
'properties' => {},
|
176
|
-
'required' => []
|
177
|
-
}
|
178
|
-
schema = parameters.each_with_object(init_schema) do |parameter_def, result|
|
179
|
-
parameter = OpenapiParameters::Parameter.new(parameter_def)
|
180
|
-
result['properties'][parameter.name] = parameter.schema if parameter.schema
|
181
|
-
result['required'] << parameter.name if parameter.required?
|
182
|
-
end
|
183
|
-
Schema.new(schema, openapi_version: @openapi_version)
|
184
|
-
end
|
185
|
-
|
186
|
-
def responses
|
187
|
-
@responses ||= Responses.new(self, operation_object['responses'])
|
188
|
-
end
|
189
|
-
|
190
|
-
attr_reader :operation_object
|
191
|
-
|
192
|
-
def build_parameters(parameters, klass)
|
193
|
-
klass.new(parameters, openapi_version:) if parameters.any?
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'forwardable'
|
4
|
-
require_relative 'operation'
|
5
|
-
require_relative 'path_template'
|
6
|
-
|
7
|
-
module OpenapiFirst
|
8
|
-
class Definition
|
9
|
-
# A pathItem as defined in the OpenAPI document.
|
10
|
-
class PathItem
|
11
|
-
extend Forwardable
|
12
|
-
|
13
|
-
def initialize(path, path_item_object, openapi_version:)
|
14
|
-
@path = path
|
15
|
-
@path_item_object = path_item_object
|
16
|
-
@openapi_version = openapi_version
|
17
|
-
@path_template = PathTemplate.new(path)
|
18
|
-
end
|
19
|
-
|
20
|
-
attr_reader :path
|
21
|
-
|
22
|
-
def_delegator :@path_template, :match
|
23
|
-
|
24
|
-
def operation(request_method)
|
25
|
-
return unless @path_item_object[request_method]
|
26
|
-
|
27
|
-
Operation.new(
|
28
|
-
@path, request_method, @path_item_object, openapi_version: @openapi_version
|
29
|
-
)
|
30
|
-
end
|
31
|
-
|
32
|
-
METHODS = %w[get head post put patch delete trace options].freeze
|
33
|
-
private_constant :METHODS
|
34
|
-
|
35
|
-
def operations
|
36
|
-
@operations ||= @path_item_object.slice(*METHODS).keys.map { |method| operation(method) }
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../schema'
|
4
|
-
|
5
|
-
module OpenapiFirst
|
6
|
-
class Definition
|
7
|
-
# Represents a request body definition in the OpenAPI document that belongs to an operation.
|
8
|
-
class RequestBody
|
9
|
-
def initialize(request_body_object, operation)
|
10
|
-
@request_body_object = request_body_object
|
11
|
-
@operation = operation
|
12
|
-
end
|
13
|
-
|
14
|
-
def description
|
15
|
-
@request_body_object['description']
|
16
|
-
end
|
17
|
-
|
18
|
-
def required?
|
19
|
-
!!@request_body_object['required']
|
20
|
-
end
|
21
|
-
|
22
|
-
def schema_for(content_type)
|
23
|
-
content = @request_body_object['content']
|
24
|
-
return unless content&.any?
|
25
|
-
|
26
|
-
content_schemas&.fetch(content_type) do
|
27
|
-
type = content_type.split(';')[0]
|
28
|
-
content_schemas[type] || content_schemas["#{type.split('/')[0]}/*"] || content_schemas['*/*']
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def content_schemas
|
35
|
-
@content_schemas ||= @request_body_object['content']&.each_with_object({}) do |kv, result|
|
36
|
-
type, media_type = kv
|
37
|
-
schema_object = media_type['schema']
|
38
|
-
next unless schema_object
|
39
|
-
|
40
|
-
result[type] = Schema.new(schema_object, write: true,
|
41
|
-
openapi_version: @operation.openapi_version)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OpenapiFirst
|
4
|
-
class Definition
|
5
|
-
# Represents a response definition in the OpenAPI document.
|
6
|
-
# This is not a direct reflecton of the OpenAPI 3.X response definition, but a combination of
|
7
|
-
# status, content type and content schema.
|
8
|
-
class Response
|
9
|
-
def initialize(operation:, status:, response_object:, content_type:, content_schema:)
|
10
|
-
@operation = operation
|
11
|
-
@response_object = response_object
|
12
|
-
@status = status
|
13
|
-
@content_type = content_type
|
14
|
-
@content_schema = content_schema
|
15
|
-
end
|
16
|
-
|
17
|
-
# @attr_reader [Operation] operation The operation this response belongs to.
|
18
|
-
# @attr_reader [Integer] status The HTTP status code of the response definition.
|
19
|
-
# @attr_reader [String, nil] content_type Content type of this response.
|
20
|
-
# @attr_reader [Schema, nil] content_schema the Schema of the response body.
|
21
|
-
attr_reader :operation, :status, :content_type, :content_schema
|
22
|
-
|
23
|
-
def headers
|
24
|
-
@response_object['headers']
|
25
|
-
end
|
26
|
-
|
27
|
-
def description
|
28
|
-
@response_object['description']
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,87 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'response'
|
4
|
-
|
5
|
-
module OpenapiFirst
|
6
|
-
class Definition
|
7
|
-
# @visibility private
|
8
|
-
class Responses
|
9
|
-
def initialize(operation, responses_object)
|
10
|
-
@operation = operation
|
11
|
-
@responses_object = responses_object
|
12
|
-
end
|
13
|
-
|
14
|
-
def status_defined?(status)
|
15
|
-
!!find_response_object(status)
|
16
|
-
end
|
17
|
-
|
18
|
-
def response_for(status, response_content_type)
|
19
|
-
response_object = find_response_object(status)
|
20
|
-
return unless response_object
|
21
|
-
return response_without_content(status, response_object) unless content_defined?(response_object)
|
22
|
-
|
23
|
-
defined_content_type = find_defined_content_type(response_object, response_content_type)
|
24
|
-
return unless defined_content_type
|
25
|
-
|
26
|
-
content_schema = find_content_schema(response_object, response_content_type)
|
27
|
-
Response.new(operation:, status:, response_object:, content_type: defined_content_type, content_schema:)
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
attr_reader :openapi_version, :operation
|
33
|
-
|
34
|
-
def response_without_content(status, response_object)
|
35
|
-
Response.new(operation:, status:, response_object:, content_type: nil, content_schema: nil)
|
36
|
-
end
|
37
|
-
|
38
|
-
def find_defined_content_type(response_object, content_type)
|
39
|
-
return if content_type.nil?
|
40
|
-
|
41
|
-
content = response_object['content']
|
42
|
-
return content_type if content.key?(content_type)
|
43
|
-
|
44
|
-
type = content_type.split(';')[0]
|
45
|
-
return type if content.key?(type)
|
46
|
-
|
47
|
-
key = "#{type.split('/')[0]}/*"
|
48
|
-
return key if content.key?(key)
|
49
|
-
|
50
|
-
key = '*/*'
|
51
|
-
key if content.key?(key)
|
52
|
-
end
|
53
|
-
|
54
|
-
def content_defined?(response_object)
|
55
|
-
response_object['content']&.any?
|
56
|
-
end
|
57
|
-
|
58
|
-
def find_content_schema(response_object, response_content_type)
|
59
|
-
return unless response_content_type
|
60
|
-
|
61
|
-
content_object = find_response_body(response_object['content'], response_content_type)
|
62
|
-
content_schema_object = content_object&.fetch('schema', nil)
|
63
|
-
return unless content_schema_object
|
64
|
-
|
65
|
-
Schema.new(content_schema_object, write: false, openapi_version: operation.openapi_version)
|
66
|
-
end
|
67
|
-
|
68
|
-
def find_response_object(status)
|
69
|
-
# According to OAS status has to be a string,
|
70
|
-
# but there are a few API descriptions out there that use integers because of YAML.
|
71
|
-
return @responses_object[status] if @responses_object.key?(status)
|
72
|
-
|
73
|
-
@responses_object[status.to_s] ||
|
74
|
-
@responses_object["#{status / 100}XX"] ||
|
75
|
-
@responses_object["#{status / 100}xx"] ||
|
76
|
-
@responses_object['default']
|
77
|
-
end
|
78
|
-
|
79
|
-
def find_response_body(content, content_type)
|
80
|
-
content&.fetch(content_type) do |_|
|
81
|
-
type = content_type.split(';')[0]
|
82
|
-
content[type] || content["#{type.split('/')[0]}/*"] || content['*/*']
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
@@ -1,74 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OpenapiFirst
|
4
|
-
module Plugins
|
5
|
-
module Default
|
6
|
-
# An error reponse that returns application/problem+json with a list of "errors"
|
7
|
-
# See also https://www.rfc-editor.org/rfc/rfc9457.html
|
8
|
-
class ErrorResponse
|
9
|
-
include OpenapiFirst::ErrorResponse
|
10
|
-
|
11
|
-
TITLES = {
|
12
|
-
not_found: 'Not Found',
|
13
|
-
method_not_allowed: 'Request Method Not Allowed',
|
14
|
-
unsupported_media_type: 'Unsupported Media Type',
|
15
|
-
invalid_body: 'Bad Request Body',
|
16
|
-
invalid_query: 'Bad Query Parameter',
|
17
|
-
invalid_header: 'Bad Request Header',
|
18
|
-
invalid_path: 'Bad Request Path',
|
19
|
-
invalid_cookie: 'Bad Request Cookie'
|
20
|
-
}.freeze
|
21
|
-
private_constant :TITLES
|
22
|
-
|
23
|
-
def body
|
24
|
-
result = {
|
25
|
-
title:,
|
26
|
-
status:
|
27
|
-
}
|
28
|
-
result[:errors] = errors if failure.errors
|
29
|
-
MultiJson.dump(result)
|
30
|
-
end
|
31
|
-
|
32
|
-
def type = failure.type
|
33
|
-
|
34
|
-
def title
|
35
|
-
TITLES.fetch(type)
|
36
|
-
end
|
37
|
-
|
38
|
-
def content_type
|
39
|
-
'application/problem+json'
|
40
|
-
end
|
41
|
-
|
42
|
-
def errors
|
43
|
-
key = pointer_key
|
44
|
-
failure.errors.map do |error|
|
45
|
-
{
|
46
|
-
message: error.error,
|
47
|
-
key => pointer(error.instance_location),
|
48
|
-
code: error.type
|
49
|
-
}
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def pointer_key
|
54
|
-
case type
|
55
|
-
when :invalid_body
|
56
|
-
:pointer
|
57
|
-
when :invalid_query, :invalid_path
|
58
|
-
:parameter
|
59
|
-
when :invalid_header
|
60
|
-
:header
|
61
|
-
when :invalid_cookie
|
62
|
-
:cookie
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def pointer(data_pointer)
|
67
|
-
return data_pointer if type == :invalid_body
|
68
|
-
|
69
|
-
data_pointer.delete_prefix('/')
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
@@ -1,60 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OpenapiFirst
|
4
|
-
module Plugins
|
5
|
-
module Jsonapi
|
6
|
-
# A JSON:API conform error response. See https://jsonapi.org/.
|
7
|
-
class ErrorResponse
|
8
|
-
include OpenapiFirst::ErrorResponse
|
9
|
-
|
10
|
-
def body
|
11
|
-
MultiJson.dump({ errors: serialized_errors })
|
12
|
-
end
|
13
|
-
|
14
|
-
def content_type
|
15
|
-
'application/vnd.api+json'
|
16
|
-
end
|
17
|
-
|
18
|
-
def serialized_errors
|
19
|
-
return default_errors unless failure.errors
|
20
|
-
|
21
|
-
key = pointer_key
|
22
|
-
failure.errors.map do |error|
|
23
|
-
{
|
24
|
-
status: status.to_s,
|
25
|
-
source: { key => pointer(error.instance_location) },
|
26
|
-
title: error.error,
|
27
|
-
code: error.type
|
28
|
-
}
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def default_errors
|
33
|
-
[{
|
34
|
-
status: status.to_s,
|
35
|
-
title: Rack::Utils::HTTP_STATUS_CODES[status]
|
36
|
-
}]
|
37
|
-
end
|
38
|
-
|
39
|
-
def pointer_key
|
40
|
-
case failure.type
|
41
|
-
when :invalid_body
|
42
|
-
:pointer
|
43
|
-
when :invalid_query, :invalid_path
|
44
|
-
:parameter
|
45
|
-
when :invalid_header
|
46
|
-
:header
|
47
|
-
when :invalid_cookie
|
48
|
-
:cookie
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def pointer(data_pointer)
|
53
|
-
return data_pointer if failure.type == :invalid_body
|
54
|
-
|
55
|
-
data_pointer.delete_prefix('/')
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|