openapi_first 1.0.0.beta6 → 1.0.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 +16 -3
- data/Gemfile +1 -0
- data/Gemfile.lock +11 -10
- data/Gemfile.rack2.lock +99 -0
- data/README.md +99 -130
- data/lib/openapi_first/body_parser.rb +5 -4
- data/lib/openapi_first/configuration.rb +20 -0
- data/lib/openapi_first/definition/operation.rb +84 -71
- data/lib/openapi_first/definition/parameters.rb +1 -1
- data/lib/openapi_first/definition/path_item.rb +21 -12
- data/lib/openapi_first/definition/path_parameters.rb +2 -3
- data/lib/openapi_first/definition/request_body.rb +22 -11
- data/lib/openapi_first/definition/response.rb +19 -31
- data/lib/openapi_first/definition/responses.rb +83 -0
- data/lib/openapi_first/definition.rb +50 -17
- data/lib/openapi_first/error_response.rb +22 -29
- data/lib/openapi_first/errors.rb +2 -14
- data/lib/openapi_first/failure.rb +55 -0
- data/lib/openapi_first/middlewares/request_validation.rb +52 -0
- data/lib/openapi_first/middlewares/response_validation.rb +35 -0
- data/lib/openapi_first/plugins/default/error_response.rb +74 -0
- data/lib/openapi_first/plugins/default.rb +11 -0
- data/lib/openapi_first/plugins/jsonapi/error_response.rb +58 -0
- data/lib/openapi_first/plugins/jsonapi.rb +11 -0
- data/lib/openapi_first/plugins.rb +9 -7
- data/lib/openapi_first/request_validation/request_body_validator.rb +41 -0
- data/lib/openapi_first/request_validation/validator.rb +81 -0
- data/lib/openapi_first/response_validation/validator.rb +101 -0
- data/lib/openapi_first/runtime_request.rb +84 -0
- data/lib/openapi_first/runtime_response.rb +31 -0
- data/lib/openapi_first/schema/validation_error.rb +18 -0
- data/lib/openapi_first/schema/validation_result.rb +32 -0
- data/lib/openapi_first/{definition/schema.rb → schema.rb} +8 -4
- data/lib/openapi_first/version.rb +1 -1
- data/lib/openapi_first.rb +32 -28
- data/openapi_first.gemspec +3 -5
- metadata +28 -20
- data/lib/openapi_first/config.rb +0 -20
- data/lib/openapi_first/definition/has_content.rb +0 -37
- data/lib/openapi_first/definition/schema/result.rb +0 -17
- data/lib/openapi_first/error_responses/default.rb +0 -58
- data/lib/openapi_first/error_responses/json_api.rb +0 -58
- data/lib/openapi_first/request_body_validator.rb +0 -37
- data/lib/openapi_first/request_validation.rb +0 -122
- data/lib/openapi_first/request_validation_error.rb +0 -31
- data/lib/openapi_first/response_validation.rb +0 -113
- data/lib/openapi_first/response_validator.rb +0 -21
- data/lib/openapi_first/router.rb +0 -68
- data/lib/openapi_first/use_router.rb +0 -18
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'validation_error'
|
4
|
+
|
5
|
+
module OpenapiFirst
|
6
|
+
class Schema
|
7
|
+
class ValidationResult
|
8
|
+
def initialize(validation, schema:, data:)
|
9
|
+
@validation = validation
|
10
|
+
@schema = schema
|
11
|
+
@data = data
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :schema, :data
|
15
|
+
|
16
|
+
def error? = @validation.any?
|
17
|
+
|
18
|
+
def errors
|
19
|
+
@errors ||= @validation.map do |err|
|
20
|
+
ValidationError.new(err)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns a message that is used in exception messages.
|
25
|
+
def message
|
26
|
+
return unless error?
|
27
|
+
|
28
|
+
errors.map(&:error).join('. ')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'json_schemer'
|
4
|
-
require_relative 'schema/
|
4
|
+
require_relative 'schema/validation_result'
|
5
5
|
|
6
6
|
module OpenapiFirst
|
7
7
|
class Schema
|
@@ -19,19 +19,23 @@ module OpenapiFirst
|
|
19
19
|
access_mode: write ? 'write' : 'read',
|
20
20
|
meta_schema: SCHEMAS.fetch(openapi_version),
|
21
21
|
insert_property_defaults: true,
|
22
|
-
output_format: '
|
22
|
+
output_format: 'classic',
|
23
23
|
before_property_validation: method(:before_property_validation)
|
24
24
|
)
|
25
25
|
end
|
26
26
|
|
27
27
|
def validate(data)
|
28
|
-
|
29
|
-
|
28
|
+
ValidationResult.new(
|
29
|
+
@schemer.validate(data),
|
30
30
|
schema:,
|
31
31
|
data:
|
32
32
|
)
|
33
33
|
end
|
34
34
|
|
35
|
+
def [](key)
|
36
|
+
@schema[key]
|
37
|
+
end
|
38
|
+
|
35
39
|
private
|
36
40
|
|
37
41
|
def before_property_validation(data, property, property_schema, parent)
|
data/lib/openapi_first.rb
CHANGED
@@ -1,47 +1,51 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'yaml'
|
4
|
+
require 'multi_json'
|
4
5
|
require 'json_refs'
|
5
|
-
require_relative 'openapi_first/
|
6
|
+
require_relative 'openapi_first/errors'
|
7
|
+
require_relative 'openapi_first/configuration'
|
6
8
|
require_relative 'openapi_first/plugins'
|
7
9
|
require_relative 'openapi_first/definition'
|
8
10
|
require_relative 'openapi_first/version'
|
9
|
-
require_relative 'openapi_first/
|
10
|
-
require_relative 'openapi_first/
|
11
|
-
require_relative 'openapi_first/request_validation'
|
12
|
-
require_relative 'openapi_first/response_validator'
|
13
|
-
require_relative 'openapi_first/response_validation'
|
14
|
-
require_relative 'openapi_first/error_responses/default'
|
15
|
-
require_relative 'openapi_first/error_responses/json_api'
|
11
|
+
require_relative 'openapi_first/error_response'
|
12
|
+
require_relative 'openapi_first/middlewares/response_validation'
|
13
|
+
require_relative 'openapi_first/middlewares/request_validation'
|
16
14
|
|
17
15
|
module OpenapiFirst
|
18
|
-
|
19
|
-
OPERATION = 'openapi.operation'
|
16
|
+
extend Plugins
|
20
17
|
|
21
|
-
|
22
|
-
|
18
|
+
class << self
|
19
|
+
def configuration
|
20
|
+
@configuration ||= Configuration.new
|
21
|
+
end
|
23
22
|
|
24
|
-
|
25
|
-
|
23
|
+
def configure
|
24
|
+
yield configuration
|
25
|
+
end
|
26
|
+
end
|
26
27
|
|
27
|
-
#
|
28
|
-
|
28
|
+
# Key in rack to find instance of RuntimeRequest
|
29
|
+
REQUEST = 'openapi.request'
|
29
30
|
|
30
|
-
|
31
|
-
|
31
|
+
def self.load(spec_path, only: nil)
|
32
|
+
resolved = Bundle.resolve(spec_path)
|
33
|
+
resolved['paths'].filter!(&->(key, _) { only.call(key) }) if only
|
34
|
+
Definition.new(resolved, spec_path)
|
35
|
+
end
|
32
36
|
|
33
|
-
|
34
|
-
|
37
|
+
module Bundle
|
38
|
+
def self.resolve(spec_path)
|
39
|
+
Dir.chdir(File.dirname(spec_path)) do
|
40
|
+
content = load_file(File.basename(spec_path))
|
41
|
+
JsonRefs.call(content, resolve_local_ref: true, resolve_file_ref: true)
|
42
|
+
end
|
43
|
+
end
|
35
44
|
|
36
|
-
|
37
|
-
|
45
|
+
def self.load_file(spec_path)
|
46
|
+
return MultiJson.load(File.read(spec_path)) if File.extname(spec_path) == '.json'
|
38
47
|
|
39
|
-
|
40
|
-
resolved = Dir.chdir(File.dirname(spec_path)) do
|
41
|
-
content = YAML.load_file(File.basename(spec_path))
|
42
|
-
JsonRefs.call(content, resolve_local_ref: true, resolve_file_ref: true)
|
48
|
+
YAML.unsafe_load_file(spec_path)
|
43
49
|
end
|
44
|
-
resolved['paths'].filter!(&->(key, _) { only.call(key) }) if only
|
45
|
-
Definition.new(resolved, spec_path)
|
46
50
|
end
|
47
51
|
end
|
data/openapi_first.gemspec
CHANGED
@@ -17,7 +17,8 @@ Gem::Specification.new do |spec|
|
|
17
17
|
if spec.respond_to?(:metadata)
|
18
18
|
spec.metadata['https://github.com/ahx/openapi_first'] = spec.homepage
|
19
19
|
spec.metadata['source_code_uri'] = 'https://github.com/ahx/openapi_first'
|
20
|
-
spec.metadata['changelog_uri'] = 'https://github.com/ahx/openapi_first/blob/
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/ahx/openapi_first/blob/main/CHANGELOG.md'
|
21
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
21
22
|
else
|
22
23
|
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
23
24
|
'public gem pushes.'
|
@@ -38,12 +39,9 @@ Gem::Specification.new do |spec|
|
|
38
39
|
spec.required_ruby_version = '>= 3.1.1'
|
39
40
|
|
40
41
|
spec.add_runtime_dependency 'json_refs', '~> 0.1', '>= 0.1.7'
|
41
|
-
spec.add_runtime_dependency 'json_schemer', '~> 2.
|
42
|
+
spec.add_runtime_dependency 'json_schemer', '~> 2.1.0'
|
42
43
|
spec.add_runtime_dependency 'multi_json', '~> 1.15'
|
43
44
|
spec.add_runtime_dependency 'mustermann-contrib', '~> 3.0.0'
|
44
45
|
spec.add_runtime_dependency 'openapi_parameters', '>= 0.3.2', '< 2.0'
|
45
46
|
spec.add_runtime_dependency 'rack', '>= 2.2', '< 4.0'
|
46
|
-
spec.metadata = {
|
47
|
-
'rubygems_mfa_required' => 'true'
|
48
|
-
}
|
49
47
|
end
|
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: 1.0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Haller
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json_refs
|
@@ -36,14 +36,14 @@ dependencies:
|
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: 2.
|
39
|
+
version: 2.1.0
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 2.
|
46
|
+
version: 2.1.0
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: multi_json
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -126,14 +126,14 @@ files:
|
|
126
126
|
- Gemfile
|
127
127
|
- Gemfile.lock
|
128
128
|
- Gemfile.rack2
|
129
|
+
- Gemfile.rack2.lock
|
129
130
|
- LICENSE.txt
|
130
131
|
- README.md
|
131
132
|
- lib/openapi_first.rb
|
132
133
|
- lib/openapi_first/body_parser.rb
|
133
|
-
- lib/openapi_first/
|
134
|
+
- lib/openapi_first/configuration.rb
|
134
135
|
- lib/openapi_first/definition.rb
|
135
136
|
- lib/openapi_first/definition/cookie_parameters.rb
|
136
|
-
- lib/openapi_first/definition/has_content.rb
|
137
137
|
- lib/openapi_first/definition/header_parameters.rb
|
138
138
|
- lib/openapi_first/definition/operation.rb
|
139
139
|
- lib/openapi_first/definition/parameters.rb
|
@@ -142,26 +142,34 @@ files:
|
|
142
142
|
- lib/openapi_first/definition/query_parameters.rb
|
143
143
|
- lib/openapi_first/definition/request_body.rb
|
144
144
|
- lib/openapi_first/definition/response.rb
|
145
|
-
- lib/openapi_first/definition/
|
146
|
-
- lib/openapi_first/definition/schema/result.rb
|
145
|
+
- lib/openapi_first/definition/responses.rb
|
147
146
|
- lib/openapi_first/error_response.rb
|
148
|
-
- lib/openapi_first/error_responses/default.rb
|
149
|
-
- lib/openapi_first/error_responses/json_api.rb
|
150
147
|
- lib/openapi_first/errors.rb
|
148
|
+
- lib/openapi_first/failure.rb
|
149
|
+
- lib/openapi_first/middlewares/request_validation.rb
|
150
|
+
- lib/openapi_first/middlewares/response_validation.rb
|
151
151
|
- lib/openapi_first/plugins.rb
|
152
|
-
- lib/openapi_first/
|
153
|
-
- lib/openapi_first/
|
154
|
-
- lib/openapi_first/
|
155
|
-
- lib/openapi_first/
|
156
|
-
- lib/openapi_first/
|
157
|
-
- lib/openapi_first/
|
158
|
-
- lib/openapi_first/
|
152
|
+
- lib/openapi_first/plugins/default.rb
|
153
|
+
- lib/openapi_first/plugins/default/error_response.rb
|
154
|
+
- lib/openapi_first/plugins/jsonapi.rb
|
155
|
+
- lib/openapi_first/plugins/jsonapi/error_response.rb
|
156
|
+
- lib/openapi_first/request_validation/request_body_validator.rb
|
157
|
+
- lib/openapi_first/request_validation/validator.rb
|
158
|
+
- lib/openapi_first/response_validation/validator.rb
|
159
|
+
- lib/openapi_first/runtime_request.rb
|
160
|
+
- lib/openapi_first/runtime_response.rb
|
161
|
+
- lib/openapi_first/schema.rb
|
162
|
+
- lib/openapi_first/schema/validation_error.rb
|
163
|
+
- lib/openapi_first/schema/validation_result.rb
|
159
164
|
- lib/openapi_first/version.rb
|
160
165
|
- openapi_first.gemspec
|
161
166
|
homepage: https://github.com/ahx/openapi_first
|
162
167
|
licenses:
|
163
168
|
- MIT
|
164
169
|
metadata:
|
170
|
+
https://github.com/ahx/openapi_first: https://github.com/ahx/openapi_first
|
171
|
+
source_code_uri: https://github.com/ahx/openapi_first
|
172
|
+
changelog_uri: https://github.com/ahx/openapi_first/blob/main/CHANGELOG.md
|
165
173
|
rubygems_mfa_required: 'true'
|
166
174
|
post_install_message:
|
167
175
|
rdoc_options: []
|
@@ -174,11 +182,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
174
182
|
version: 3.1.1
|
175
183
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
176
184
|
requirements:
|
177
|
-
- - "
|
185
|
+
- - ">="
|
178
186
|
- !ruby/object:Gem::Version
|
179
|
-
version:
|
187
|
+
version: '0'
|
180
188
|
requirements: []
|
181
|
-
rubygems_version: 3.3
|
189
|
+
rubygems_version: 3.5.3
|
182
190
|
signing_key:
|
183
191
|
specification_version: 4
|
184
192
|
summary: Implement REST APIs based on OpenApi 3.x
|
data/lib/openapi_first/config.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OpenapiFirst
|
4
|
-
class Config
|
5
|
-
def initialize(error_response: :default, request_validation_raise_error: false)
|
6
|
-
@error_response = Plugins.find_error_response(error_response)
|
7
|
-
@request_validation_raise_error = request_validation_raise_error
|
8
|
-
end
|
9
|
-
|
10
|
-
attr_reader :error_response, :request_validation_raise_error
|
11
|
-
|
12
|
-
def self.default_options
|
13
|
-
@default_options ||= new
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.default_options=(options)
|
17
|
-
@default_options = new(**options)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'schema'
|
4
|
-
|
5
|
-
module OpenapiFirst
|
6
|
-
module HasContent
|
7
|
-
def schema_for(content_type)
|
8
|
-
return unless content&.any?
|
9
|
-
|
10
|
-
content_schemas&.fetch(content_type) do
|
11
|
-
type = content_type.split(';')[0]
|
12
|
-
content_schemas[type] || content_schemas["#{type.split('/')[0]}/*"] || content_schemas['*/*']
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def content
|
19
|
-
raise NotImplementedError
|
20
|
-
end
|
21
|
-
|
22
|
-
def schema_write?
|
23
|
-
raise NotImplementedError
|
24
|
-
end
|
25
|
-
|
26
|
-
def content_schemas
|
27
|
-
@content_schemas ||= content&.each_with_object({}) do |kv, result|
|
28
|
-
type, media_type = kv
|
29
|
-
schema_object = media_type['schema']
|
30
|
-
next unless schema_object
|
31
|
-
|
32
|
-
result[type] = Schema.new(schema_object, write: schema_write?,
|
33
|
-
openapi_version: @operation.openapi_version)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OpenapiFirst
|
4
|
-
class Schema
|
5
|
-
Result = Struct.new(:output, :schema, :data, keyword_init: true) do
|
6
|
-
def valid? = output['valid']
|
7
|
-
def error? = !output['valid']
|
8
|
-
|
9
|
-
# Returns a message that is used in exception messages.
|
10
|
-
def message
|
11
|
-
return if valid?
|
12
|
-
|
13
|
-
(output['errors']&.map { |e| e['error'] }&.join('. ') || output['error'])
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,58 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OpenapiFirst
|
4
|
-
module ErrorResponses
|
5
|
-
class Default < ErrorResponse
|
6
|
-
OpenapiFirst::Plugins.register_error_response(:default, self)
|
7
|
-
|
8
|
-
def body
|
9
|
-
MultiJson.dump({ errors: serialized_errors })
|
10
|
-
end
|
11
|
-
|
12
|
-
def content_type
|
13
|
-
'application/json'
|
14
|
-
end
|
15
|
-
|
16
|
-
def serialized_errors
|
17
|
-
return default_errors unless validation_output
|
18
|
-
|
19
|
-
key = pointer_key
|
20
|
-
validation_errors&.map do |error|
|
21
|
-
{
|
22
|
-
status: status.to_s,
|
23
|
-
source: { key => pointer(error['instanceLocation']) },
|
24
|
-
title: error['error']
|
25
|
-
}
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def validation_errors
|
30
|
-
validation_output['errors'] || [validation_output]
|
31
|
-
end
|
32
|
-
|
33
|
-
def default_errors
|
34
|
-
[{
|
35
|
-
status: status.to_s,
|
36
|
-
title: message
|
37
|
-
}]
|
38
|
-
end
|
39
|
-
|
40
|
-
def pointer_key
|
41
|
-
case location
|
42
|
-
when :body
|
43
|
-
:pointer
|
44
|
-
when :query, :path
|
45
|
-
:parameter
|
46
|
-
else
|
47
|
-
location
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def pointer(data_pointer)
|
52
|
-
return data_pointer if location == :body
|
53
|
-
|
54
|
-
data_pointer.delete_prefix('/')
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
@@ -1,58 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OpenapiFirst
|
4
|
-
module ErrorResponses
|
5
|
-
class JsonApi < ErrorResponse
|
6
|
-
OpenapiFirst::Plugins.register_error_response(:json_api, self)
|
7
|
-
|
8
|
-
def body
|
9
|
-
MultiJson.dump({ errors: serialized_errors })
|
10
|
-
end
|
11
|
-
|
12
|
-
def content_type
|
13
|
-
'application/vnd.api+json'
|
14
|
-
end
|
15
|
-
|
16
|
-
def serialized_errors
|
17
|
-
return default_errors unless validation_output
|
18
|
-
|
19
|
-
key = pointer_key
|
20
|
-
validation_errors&.map do |error|
|
21
|
-
{
|
22
|
-
status: status.to_s,
|
23
|
-
source: { key => pointer(error['instanceLocation']) },
|
24
|
-
title: error['error']
|
25
|
-
}
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def validation_errors
|
30
|
-
validation_output['errors'] || [validation_output]
|
31
|
-
end
|
32
|
-
|
33
|
-
def default_errors
|
34
|
-
[{
|
35
|
-
status: status.to_s,
|
36
|
-
title: message
|
37
|
-
}]
|
38
|
-
end
|
39
|
-
|
40
|
-
def pointer_key
|
41
|
-
case location
|
42
|
-
when :body
|
43
|
-
:pointer
|
44
|
-
when :query, :path
|
45
|
-
:parameter
|
46
|
-
else
|
47
|
-
location
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def pointer(data_pointer)
|
52
|
-
return data_pointer if location == :body
|
53
|
-
|
54
|
-
data_pointer.delete_prefix('/')
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OpenapiFirst
|
4
|
-
class RequestBodyValidator
|
5
|
-
def initialize(operation, env)
|
6
|
-
@operation = operation
|
7
|
-
@env = env
|
8
|
-
end
|
9
|
-
|
10
|
-
def validate!
|
11
|
-
request_body = @operation.request_body
|
12
|
-
return unless request_body
|
13
|
-
|
14
|
-
request_content_type = Rack::Request.new(@env).content_type
|
15
|
-
schema = request_body.schema_for(request_content_type)
|
16
|
-
RequestValidation.fail!(415, :header) unless schema
|
17
|
-
|
18
|
-
parsed_request_body = BodyParser.new.parse_body(@env)
|
19
|
-
RequestValidation.fail!(400, :body) if request_body.required? && parsed_request_body.nil?
|
20
|
-
|
21
|
-
validate_body!(parsed_request_body, schema)
|
22
|
-
parsed_request_body
|
23
|
-
rescue BodyParsingError => e
|
24
|
-
RequestValidation.fail!(400, :body, message: e.message)
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def validate_body!(parsed_request_body, schema)
|
30
|
-
request_body_schema = schema
|
31
|
-
return unless request_body_schema
|
32
|
-
|
33
|
-
schema_validation = request_body_schema.validate(parsed_request_body)
|
34
|
-
RequestValidation.fail!(400, :body, schema_validation:) if schema_validation.error?
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,122 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'rack'
|
4
|
-
require 'multi_json'
|
5
|
-
require_relative 'use_router'
|
6
|
-
require_relative 'error_response'
|
7
|
-
require_relative 'request_body_validator'
|
8
|
-
require_relative 'request_validation_error'
|
9
|
-
require 'openapi_parameters'
|
10
|
-
|
11
|
-
module OpenapiFirst
|
12
|
-
# A Rack middleware to validate requests against an OpenAPI API description
|
13
|
-
class RequestValidation
|
14
|
-
prepend UseRouter
|
15
|
-
|
16
|
-
FAIL = :request_validation_failed
|
17
|
-
private_constant :FAIL
|
18
|
-
|
19
|
-
# @param status [Integer] The intended HTTP status code (usually 400)
|
20
|
-
# @param location [Symbol] One of :body, :header, :cookie, :query, :path
|
21
|
-
# @param schema_validation [OpenapiFirst::Schema::Result]
|
22
|
-
def self.fail!(status, location, message: nil, schema_validation: nil)
|
23
|
-
throw FAIL, RequestValidationError.new(
|
24
|
-
status:,
|
25
|
-
location:,
|
26
|
-
message:,
|
27
|
-
schema_validation:
|
28
|
-
)
|
29
|
-
end
|
30
|
-
|
31
|
-
# @param app The parent Rack application
|
32
|
-
# @param options An optional Hash of configuration options to override defaults
|
33
|
-
# :error_response A Boolean indicating whether to raise an error if validation fails.
|
34
|
-
# default: OpenapiFirst::ErrorResponses::Default (Config.default_options.error_response)
|
35
|
-
# :raise_error The Class to use for error responses.
|
36
|
-
# default: false (Config.default_options.request_validation_raise_error)
|
37
|
-
def initialize(app, options = {})
|
38
|
-
@app = app
|
39
|
-
@raise = options.fetch(:raise_error, Config.default_options.request_validation_raise_error)
|
40
|
-
@error_response_class =
|
41
|
-
Plugins.find_error_response(options.fetch(:error_response, Config.default_options.error_response))
|
42
|
-
end
|
43
|
-
|
44
|
-
def call(env)
|
45
|
-
operation = env[OPERATION]
|
46
|
-
return @app.call(env) unless operation
|
47
|
-
|
48
|
-
error = validate_request(operation, env)
|
49
|
-
if error
|
50
|
-
raise RequestInvalidError, error.error_message if @raise
|
51
|
-
|
52
|
-
return @error_response_class.new(env, error).render
|
53
|
-
end
|
54
|
-
|
55
|
-
@app.call(env)
|
56
|
-
end
|
57
|
-
|
58
|
-
private
|
59
|
-
|
60
|
-
def validate_request(operation, env)
|
61
|
-
catch(FAIL) do
|
62
|
-
env[PARAMS] = {}
|
63
|
-
validate_parameters!(operation, env)
|
64
|
-
validate_request_body!(operation, env)
|
65
|
-
nil
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def validate_parameters!(operation, env)
|
70
|
-
validate_query_params!(operation, env)
|
71
|
-
validate_path_params!(operation, env)
|
72
|
-
validate_cookie_params!(operation, env)
|
73
|
-
validate_header_params!(operation, env)
|
74
|
-
end
|
75
|
-
|
76
|
-
def validate_path_params!(operation, env)
|
77
|
-
parameters = operation.path_parameters
|
78
|
-
return unless parameters
|
79
|
-
|
80
|
-
unpacked_params = parameters.unpack(env)
|
81
|
-
schema_validation = parameters.schema.validate(unpacked_params)
|
82
|
-
RequestValidation.fail!(400, :path, schema_validation:) if schema_validation.error?
|
83
|
-
env[PATH_PARAMS] = unpacked_params
|
84
|
-
env[PARAMS].merge!(unpacked_params)
|
85
|
-
end
|
86
|
-
|
87
|
-
def validate_query_params!(operation, env)
|
88
|
-
parameters = operation.query_parameters
|
89
|
-
return unless parameters
|
90
|
-
|
91
|
-
unpacked_params = parameters.unpack(env)
|
92
|
-
schema_validation = parameters.schema.validate(unpacked_params)
|
93
|
-
RequestValidation.fail!(400, :query, schema_validation:) if schema_validation.error?
|
94
|
-
env[QUERY_PARAMS] = unpacked_params
|
95
|
-
env[PARAMS].merge!(unpacked_params)
|
96
|
-
end
|
97
|
-
|
98
|
-
def validate_cookie_params!(operation, env)
|
99
|
-
parameters = operation.cookie_parameters
|
100
|
-
return unless parameters
|
101
|
-
|
102
|
-
unpacked_params = parameters.unpack(env)
|
103
|
-
schema_validation = parameters.schema.validate(unpacked_params)
|
104
|
-
RequestValidation.fail!(400, :cookie, schema_validation:) if schema_validation.error?
|
105
|
-
env[COOKIE_PARAMS] = unpacked_params
|
106
|
-
end
|
107
|
-
|
108
|
-
def validate_header_params!(operation, env)
|
109
|
-
parameters = operation.header_parameters
|
110
|
-
return unless parameters
|
111
|
-
|
112
|
-
unpacked_params = parameters.unpack(env)
|
113
|
-
schema_validation = parameters.schema.validate(unpacked_params)
|
114
|
-
RequestValidation.fail!(400, :header, schema_validation:) if schema_validation.error?
|
115
|
-
env[HEADER_PARAMS] = unpacked_params
|
116
|
-
end
|
117
|
-
|
118
|
-
def validate_request_body!(operation, env)
|
119
|
-
env[REQUEST_BODY] = RequestBodyValidator.new(operation, env).validate!
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module OpenapiFirst
|
4
|
-
class RequestValidationError
|
5
|
-
def initialize(status:, location:, message: nil, schema_validation: nil)
|
6
|
-
@status = status
|
7
|
-
@location = location
|
8
|
-
@message = message
|
9
|
-
@schema_validation = schema_validation
|
10
|
-
end
|
11
|
-
|
12
|
-
attr_reader :status, :request, :location, :schema_validation
|
13
|
-
|
14
|
-
def message
|
15
|
-
@message || schema_validation&.message || Rack::Utils::HTTP_STATUS_CODES[status]
|
16
|
-
end
|
17
|
-
|
18
|
-
def error_message
|
19
|
-
"#{TOPICS.fetch(location)} #{message}"
|
20
|
-
end
|
21
|
-
|
22
|
-
TOPICS = {
|
23
|
-
body: 'Request body invalid:',
|
24
|
-
query: 'Query parameter invalid:',
|
25
|
-
header: 'Header parameter invalid:',
|
26
|
-
path: 'Path segment invalid:',
|
27
|
-
cookie: 'Cookie value invalid:'
|
28
|
-
}.freeze
|
29
|
-
private_constant :TOPICS
|
30
|
-
end
|
31
|
-
end
|