openapi_first 1.0.0.beta5 → 1.0.0.beta6
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/.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
|