openapi_first 1.0.0.beta6 → 1.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 +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
|