openapi_first 1.0.0.beta5 → 1.0.0.beta6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +2 -1
- data/CHANGELOG.md +8 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +6 -9
- data/Gemfile.rack2 +15 -0
- data/lib/openapi_first/{body_parser_middleware.rb → body_parser.rb} +3 -15
- data/lib/openapi_first/definition/cookie_parameters.rb +12 -0
- data/lib/openapi_first/definition/has_content.rb +37 -0
- data/lib/openapi_first/definition/header_parameters.rb +12 -0
- data/lib/openapi_first/definition/operation.rb +103 -0
- data/lib/openapi_first/definition/parameters.rb +47 -0
- data/lib/openapi_first/definition/path_item.rb +23 -0
- data/lib/openapi_first/definition/path_parameters.rb +13 -0
- data/lib/openapi_first/definition/query_parameters.rb +12 -0
- data/lib/openapi_first/definition/request_body.rb +32 -0
- data/lib/openapi_first/definition/response.rb +37 -0
- data/lib/openapi_first/{json_schema → definition/schema}/result.rb +1 -1
- data/lib/openapi_first/{json_schema.rb → definition/schema.rb} +2 -2
- data/lib/openapi_first/definition.rb +26 -6
- data/lib/openapi_first/error_response.rb +2 -0
- data/lib/openapi_first/request_body_validator.rb +17 -21
- data/lib/openapi_first/request_validation.rb +34 -30
- data/lib/openapi_first/response_validation.rb +31 -11
- data/lib/openapi_first/router.rb +19 -53
- data/lib/openapi_first/version.rb +1 -1
- data/openapi_first.gemspec +7 -4
- metadata +32 -52
- data/.rspec +0 -3
- data/.rubocop.yml +0 -14
- data/Rakefile +0 -15
- data/benchmarks/Gemfile +0 -16
- data/benchmarks/Gemfile.lock +0 -142
- data/benchmarks/README.md +0 -29
- data/benchmarks/apps/committee_with_hanami_api.ru +0 -26
- data/benchmarks/apps/committee_with_response_validation.ru +0 -29
- data/benchmarks/apps/committee_with_sinatra.ru +0 -31
- data/benchmarks/apps/grape.ru +0 -21
- data/benchmarks/apps/hanami_api.ru +0 -21
- data/benchmarks/apps/hanami_router.ru +0 -14
- data/benchmarks/apps/openapi.yaml +0 -268
- data/benchmarks/apps/openapi_first_with_hanami_api.ru +0 -24
- data/benchmarks/apps/openapi_first_with_plain_rack.ru +0 -32
- data/benchmarks/apps/openapi_first_with_response_validation.ru +0 -25
- data/benchmarks/apps/openapi_first_with_sinatra.ru +0 -29
- data/benchmarks/apps/roda.ru +0 -27
- data/benchmarks/apps/sinatra.ru +0 -26
- data/benchmarks/apps/syro.ru +0 -25
- data/benchmarks/benchmark-wrk.sh +0 -3
- data/benchmarks/benchmarks.rb +0 -48
- data/benchmarks/post.lua +0 -3
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/examples/README.md +0 -13
- data/examples/app.rb +0 -18
- data/examples/config.ru +0 -7
- data/examples/openapi.yaml +0 -29
- data/lib/openapi_first/operation.rb +0 -170
- data/lib/openapi_first/string_keyed_hash.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8173c11075add6c2c7a1a6cf024ea86ba448b185574e346fe288afa3aafcf677
|
4
|
+
data.tar.gz: f3dcd95b9c06adcfcada5430a8412a3ee6ea37c1c117e79546436ade496c5778
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d29578cdcc1573ff8903a7060a85e2b334b1ff908d4b3b7973e64b336ba414bfdf7b6fb762aff48cf0feaf78f5a29403d2d063b117fb1278bed628195363dd74
|
7
|
+
data.tar.gz: 0a6409930941ea9a1f4b734f49752ec18f8e9779ce6f5279e2db65022066500d8b7f9067af23625e780e20cf0d62f542c0a4b8388fea6f7cc37f63cd372baaf5
|
data/.github/workflows/ruby.yml
CHANGED
@@ -9,4 +9,5 @@ jobs:
|
|
9
9
|
with:
|
10
10
|
ruby-version: '3.1'
|
11
11
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
12
|
-
- run: bundle exec rake
|
12
|
+
- run: BUNDLE_GEMFILE=Gemfile bundle exec rake
|
13
|
+
- run: BUNDLE_GEMFILE=Gemfile.rack2 bundle lock --add-platform x86_64-linux && bundle exec rake
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,14 @@
|
|
2
2
|
|
3
3
|
## Unreleased
|
4
4
|
|
5
|
+
## 1.0.0.beta6
|
6
|
+
- Fix: Make response header validation work with rack 3
|
7
|
+
- Refactor router
|
8
|
+
- Remove dependency hanami-router
|
9
|
+
- PathItem and Operation for a request can be found by calling methods on the Definitnion
|
10
|
+
- Fixed https://github.com/ahx/openapi_first/issues/155
|
11
|
+
- Breaking / Regression: A paths like /pets/{from}-{to} if there is a path "/pets/{id}"
|
12
|
+
|
5
13
|
## 1.0.0.beta5
|
6
14
|
|
7
15
|
- Added: `OpenapiFirst::Config.default_options=` to set default options globally
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
openapi_first (1.0.0.
|
5
|
-
hanami-router (~> 2.0.0)
|
4
|
+
openapi_first (1.0.0.beta6)
|
6
5
|
json_refs (~> 0.1, >= 0.1.7)
|
7
6
|
json_schemer (~> 2.0.0)
|
8
7
|
multi_json (~> 1.15)
|
9
|
-
|
8
|
+
mustermann-contrib (~> 3.0.0)
|
9
|
+
openapi_parameters (>= 0.3.2, < 2.0)
|
10
10
|
rack (>= 2.2, < 4.0)
|
11
11
|
|
12
12
|
GEM
|
@@ -15,10 +15,6 @@ GEM
|
|
15
15
|
ast (2.4.2)
|
16
16
|
diff-lcs (1.5.0)
|
17
17
|
hana (1.3.7)
|
18
|
-
hanami-router (2.0.2)
|
19
|
-
mustermann (~> 3.0)
|
20
|
-
mustermann-contrib (~> 3.0)
|
21
|
-
rack (~> 2.0)
|
22
18
|
hansi (0.2.1)
|
23
19
|
json (2.6.3)
|
24
20
|
json_refs (0.1.8)
|
@@ -34,7 +30,7 @@ GEM
|
|
34
30
|
mustermann-contrib (3.0.0)
|
35
31
|
hansi (~> 0.2.0)
|
36
32
|
mustermann (= 3.0.0)
|
37
|
-
openapi_parameters (0.3.
|
33
|
+
openapi_parameters (0.3.2)
|
38
34
|
rack (>= 2.2)
|
39
35
|
zeitwerk (~> 2.6)
|
40
36
|
parallel (1.23.0)
|
@@ -42,7 +38,7 @@ GEM
|
|
42
38
|
ast (~> 2.4.1)
|
43
39
|
racc
|
44
40
|
racc (1.7.3)
|
45
|
-
rack (
|
41
|
+
rack (3.0.8)
|
46
42
|
rack-test (2.1.0)
|
47
43
|
rack (>= 1.3)
|
48
44
|
rainbow (3.1.1)
|
@@ -92,6 +88,7 @@ PLATFORMS
|
|
92
88
|
DEPENDENCIES
|
93
89
|
bundler
|
94
90
|
openapi_first!
|
91
|
+
rack (>= 3.0.0)
|
95
92
|
rack-test
|
96
93
|
rake
|
97
94
|
rspec
|
data/Gemfile.rack2
ADDED
@@ -3,21 +3,7 @@
|
|
3
3
|
require 'multi_json'
|
4
4
|
|
5
5
|
module OpenapiFirst
|
6
|
-
class
|
7
|
-
def initialize(app)
|
8
|
-
@app = app
|
9
|
-
end
|
10
|
-
|
11
|
-
ROUTER_PARSED_BODY = 'router.parsed_body'
|
12
|
-
private_constant :ROUTER_PARSED_BODY
|
13
|
-
|
14
|
-
def call(env)
|
15
|
-
env[ROUTER_PARSED_BODY] = parse_body(env)
|
16
|
-
@app.call(env)
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
6
|
+
class BodyParser
|
21
7
|
def parse_body(env)
|
22
8
|
request = Rack::Request.new(env)
|
23
9
|
body = read_body(request)
|
@@ -31,6 +17,8 @@ module OpenapiFirst
|
|
31
17
|
raise BodyParsingError, 'Failed to parse body as application/json'
|
32
18
|
end
|
33
19
|
|
20
|
+
private
|
21
|
+
|
34
22
|
def read_body(request)
|
35
23
|
body = request.body.read
|
36
24
|
request.body.rewind
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openapi_parameters'
|
4
|
+
require_relative 'parameters'
|
5
|
+
|
6
|
+
module OpenapiFirst
|
7
|
+
class CookieParameters < Parameters
|
8
|
+
def unpack(env)
|
9
|
+
OpenapiParameters::Cookie.new(@parameter_definitions).unpack(env['HTTP_COOKIE'])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,37 @@
|
|
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
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openapi_parameters'
|
4
|
+
require_relative 'parameters'
|
5
|
+
|
6
|
+
module OpenapiFirst
|
7
|
+
class HeaderParameters < Parameters
|
8
|
+
def unpack(env)
|
9
|
+
OpenapiParameters::Header.new(@parameter_definitions).unpack_env(env)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'set'
|
5
|
+
require_relative 'request_body'
|
6
|
+
require_relative 'response'
|
7
|
+
require_relative 'query_parameters'
|
8
|
+
require_relative 'header_parameters'
|
9
|
+
require_relative 'path_parameters'
|
10
|
+
require_relative 'cookie_parameters'
|
11
|
+
require_relative 'schema'
|
12
|
+
|
13
|
+
module OpenapiFirst
|
14
|
+
class Operation
|
15
|
+
extend Forwardable
|
16
|
+
def_delegators :operation_object,
|
17
|
+
:[],
|
18
|
+
:dig
|
19
|
+
|
20
|
+
WRITE_METHODS = Set.new(%w[post put patch delete]).freeze
|
21
|
+
private_constant :WRITE_METHODS
|
22
|
+
|
23
|
+
attr_reader :path, :method, :openapi_version
|
24
|
+
|
25
|
+
def initialize(path, request_method, path_item_object, openapi_version:)
|
26
|
+
@path = path
|
27
|
+
@method = request_method
|
28
|
+
@path_item_object = path_item_object
|
29
|
+
@openapi_version = openapi_version
|
30
|
+
@operation_object = @path_item_object[request_method]
|
31
|
+
end
|
32
|
+
|
33
|
+
def operation_id
|
34
|
+
operation_object['operationId']
|
35
|
+
end
|
36
|
+
|
37
|
+
def read?
|
38
|
+
!write?
|
39
|
+
end
|
40
|
+
|
41
|
+
def write?
|
42
|
+
WRITE_METHODS.include?(method)
|
43
|
+
end
|
44
|
+
|
45
|
+
def request_body
|
46
|
+
@request_body ||= RequestBody.new(operation_object['requestBody'], self) if operation_object['requestBody']
|
47
|
+
end
|
48
|
+
|
49
|
+
def response_for(status)
|
50
|
+
response_object = operation_object.dig('responses', status.to_s) ||
|
51
|
+
operation_object.dig('responses', "#{status / 100}XX") ||
|
52
|
+
operation_object.dig('responses', "#{status / 100}xx") ||
|
53
|
+
operation_object.dig('responses', 'default')
|
54
|
+
Response.new(status, response_object, self) if response_object
|
55
|
+
end
|
56
|
+
|
57
|
+
def name
|
58
|
+
@name ||= "#{method.upcase} #{path} (#{operation_id})"
|
59
|
+
end
|
60
|
+
|
61
|
+
def query_parameters
|
62
|
+
@query_parameters ||= build_parameters(all_parameters.filter { |p| p['in'] == 'query' }, QueryParameters)
|
63
|
+
end
|
64
|
+
|
65
|
+
def path_parameters
|
66
|
+
@path_parameters ||= build_parameters(all_parameters.filter { |p| p['in'] == 'path' }, PathParameters)
|
67
|
+
end
|
68
|
+
|
69
|
+
IGNORED_HEADERS = Set['Content-Type', 'Accept', 'Authorization'].freeze
|
70
|
+
private_constant :IGNORED_HEADERS
|
71
|
+
|
72
|
+
def header_parameters
|
73
|
+
@header_parameters ||= build_parameters(find_header_parameters, HeaderParameters)
|
74
|
+
end
|
75
|
+
|
76
|
+
def cookie_parameters
|
77
|
+
@cookie_parameters ||= build_parameters(all_parameters.filter { |p| p['in'] == 'cookie' }, CookieParameters)
|
78
|
+
end
|
79
|
+
|
80
|
+
def all_parameters
|
81
|
+
@all_parameters ||= begin
|
82
|
+
parameters = @path_item_object['parameters']&.dup || []
|
83
|
+
parameters_on_operation = operation_object['parameters']
|
84
|
+
parameters.concat(parameters_on_operation) if parameters_on_operation
|
85
|
+
parameters
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
attr_reader :operation_object
|
92
|
+
|
93
|
+
def build_parameters(parameters, klass)
|
94
|
+
klass.new(parameters, openapi_version:) if parameters.any?
|
95
|
+
end
|
96
|
+
|
97
|
+
def find_header_parameters
|
98
|
+
all_parameters.filter do |p|
|
99
|
+
p['in'] == 'header' && !IGNORED_HEADERS.include?(p['name'])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require_relative 'schema'
|
5
|
+
|
6
|
+
module OpenapiFirst
|
7
|
+
class Parameters
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def initialize(parameter_definitions, openapi_version:)
|
11
|
+
@parameter_definitions = parameter_definitions
|
12
|
+
@openapi_version = openapi_version
|
13
|
+
end
|
14
|
+
|
15
|
+
def_delegators :parameters, :map
|
16
|
+
|
17
|
+
def empty?
|
18
|
+
@parameter_definitions.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def schema
|
22
|
+
@schema ||= build_schema
|
23
|
+
end
|
24
|
+
|
25
|
+
def parameters
|
26
|
+
@parameter_definitions.map do |parameter_object|
|
27
|
+
OpenapiParameters::Parameter.new(parameter_object)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def build_schema
|
34
|
+
init_schema = {
|
35
|
+
'type' => 'object',
|
36
|
+
'properties' => {},
|
37
|
+
'required' => []
|
38
|
+
}
|
39
|
+
schema = @parameter_definitions.each_with_object(init_schema) do |parameter_def, result|
|
40
|
+
parameter = OpenapiParameters::Parameter.new(parameter_def)
|
41
|
+
result['properties'][parameter.name] = parameter.schema if parameter.schema
|
42
|
+
result['required'] << parameter.name if parameter.required?
|
43
|
+
end
|
44
|
+
Schema.new(schema, openapi_version: @openapi_version)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'operation'
|
4
|
+
|
5
|
+
module OpenapiFirst
|
6
|
+
class PathItem
|
7
|
+
def initialize(path, path_item_object, openapi_version:)
|
8
|
+
@path = path
|
9
|
+
@path_item_object = path_item_object
|
10
|
+
@openapi_version = openapi_version
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :path
|
14
|
+
|
15
|
+
def find_operation(request_method)
|
16
|
+
return unless @path_item_object[request_method]
|
17
|
+
|
18
|
+
Operation.new(
|
19
|
+
@path, request_method, @path_item_object, openapi_version: @openapi_version
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openapi_parameters'
|
4
|
+
require_relative 'parameters'
|
5
|
+
require_relative '../router'
|
6
|
+
|
7
|
+
module OpenapiFirst
|
8
|
+
class PathParameters < Parameters
|
9
|
+
def unpack(env)
|
10
|
+
OpenapiParameters::Path.new(@parameter_definitions).unpack(env[Router::RAW_PATH_PARAMS])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openapi_parameters'
|
4
|
+
require_relative 'parameters'
|
5
|
+
|
6
|
+
module OpenapiFirst
|
7
|
+
class QueryParameters < Parameters
|
8
|
+
def unpack(env)
|
9
|
+
OpenapiParameters::Query.new(@parameter_definitions).unpack(env['QUERY_STRING'])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'has_content'
|
4
|
+
|
5
|
+
module OpenapiFirst
|
6
|
+
class RequestBody
|
7
|
+
include HasContent
|
8
|
+
|
9
|
+
def initialize(request_body_object, operation)
|
10
|
+
@object = request_body_object
|
11
|
+
@operation = operation
|
12
|
+
end
|
13
|
+
|
14
|
+
def description
|
15
|
+
@object['description']
|
16
|
+
end
|
17
|
+
|
18
|
+
def required?
|
19
|
+
!!@object['required']
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def schema_write?
|
25
|
+
@operation.write?
|
26
|
+
end
|
27
|
+
|
28
|
+
def content
|
29
|
+
@object['content']
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'has_content'
|
4
|
+
|
5
|
+
module OpenapiFirst
|
6
|
+
class Response
|
7
|
+
include HasContent
|
8
|
+
|
9
|
+
def initialize(status, response_object, operation)
|
10
|
+
@status = status&.to_i
|
11
|
+
@object = response_object
|
12
|
+
@operation = operation
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :status
|
16
|
+
|
17
|
+
def description
|
18
|
+
@object['description']
|
19
|
+
end
|
20
|
+
|
21
|
+
def headers
|
22
|
+
@object['headers']
|
23
|
+
end
|
24
|
+
|
25
|
+
def content?
|
26
|
+
!!content&.any?
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def schema_write? = false
|
32
|
+
|
33
|
+
def content
|
34
|
+
@object['content']
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,19 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'mustermann/template'
|
4
|
+
require_relative 'definition/path_item'
|
4
5
|
|
5
6
|
module OpenapiFirst
|
6
7
|
# Represents an OpenAPI API Description document
|
7
8
|
class Definition
|
8
|
-
attr_reader :filepath, :
|
9
|
+
attr_reader :filepath, :paths, :openapi_version
|
9
10
|
|
10
11
|
def initialize(resolved, filepath)
|
11
12
|
@filepath = filepath
|
13
|
+
@paths = resolved['paths']
|
14
|
+
@openapi_version = detect_version(resolved)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param request_path String
|
18
|
+
def find_path_item_and_params(request_path)
|
19
|
+
matches = paths.each_with_object([]) do |kv, result|
|
20
|
+
path, path_item_object = kv
|
21
|
+
template = Mustermann::Template.new(path)
|
22
|
+
path_params = template.params(request_path)
|
23
|
+
next unless path_params
|
24
|
+
|
25
|
+
path_item = PathItem.new(path, path_item_object, openapi_version:)
|
26
|
+
result << [path_item, path_params]
|
27
|
+
end
|
28
|
+
# Thanks to open ota42y/openapi_parser for this part
|
29
|
+
matches.min_by { |match| match[1].size }
|
30
|
+
end
|
31
|
+
|
32
|
+
def operations
|
12
33
|
methods = %w[get head post put patch delete trace options]
|
13
|
-
@operations
|
14
|
-
path_item.
|
15
|
-
|
16
|
-
end
|
34
|
+
@operations ||= paths.flat_map do |path, path_item_object|
|
35
|
+
path_item = PathItem.new(path, path_item_object, openapi_version:)
|
36
|
+
path_item_object.slice(*methods).keys.map { |method| path_item.find_operation(method) }
|
17
37
|
end
|
18
38
|
end
|
19
39
|
|
@@ -5,37 +5,33 @@ module OpenapiFirst
|
|
5
5
|
def initialize(operation, env)
|
6
6
|
@operation = operation
|
7
7
|
@env = env
|
8
|
-
@parsed_request_body = env[REQUEST_BODY]
|
9
8
|
end
|
10
9
|
|
11
10
|
def validate!
|
12
|
-
|
13
|
-
|
14
|
-
validate_request_body!(@operation, @parsed_request_body, content_type)
|
15
|
-
end
|
11
|
+
request_body = @operation.request_body
|
12
|
+
return unless request_body
|
16
13
|
|
17
|
-
|
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
|
18
17
|
|
19
|
-
|
20
|
-
|
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)
|
21
25
|
end
|
22
26
|
|
23
|
-
|
24
|
-
validate_request_body_presence!(body, operation)
|
25
|
-
return if content_type.nil?
|
27
|
+
private
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
+
def validate_body!(parsed_request_body, schema)
|
30
|
+
request_body_schema = schema
|
31
|
+
return unless request_body_schema
|
29
32
|
|
30
|
-
schema_validation =
|
33
|
+
schema_validation = request_body_schema.validate(parsed_request_body)
|
31
34
|
RequestValidation.fail!(400, :body, schema_validation:) if schema_validation.error?
|
32
|
-
body
|
33
|
-
end
|
34
|
-
|
35
|
-
def validate_request_body_presence!(body, operation)
|
36
|
-
return unless operation.request_body['required'] && body.nil?
|
37
|
-
|
38
|
-
RequestValidation.fail!(400, :body)
|
39
35
|
end
|
40
36
|
end
|
41
37
|
end
|