openapi_first 1.0.0.beta4 → 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 +13 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +17 -22
- data/Gemfile.rack2 +15 -0
- data/README.md +17 -7
- data/lib/openapi_first/body_parser.rb +28 -0
- data/lib/openapi_first/config.rb +4 -3
- 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/definition/schema/result.rb +17 -0
- data/lib/openapi_first/{schema_validation.rb → definition/schema.rb} +6 -6
- data/lib/openapi_first/definition.rb +26 -6
- data/lib/openapi_first/error_response.rb +28 -12
- data/lib/openapi_first/error_responses/default.rb +58 -0
- data/lib/openapi_first/error_responses/json_api.rb +58 -0
- data/lib/openapi_first/request_body_validator.rb +18 -22
- data/lib/openapi_first/request_validation.rb +68 -58
- data/lib/openapi_first/request_validation_error.rb +31 -0
- data/lib/openapi_first/response_validation.rb +33 -13
- data/lib/openapi_first/response_validator.rb +1 -0
- data/lib/openapi_first/router.rb +20 -62
- data/lib/openapi_first/version.rb +1 -1
- data/lib/openapi_first.rb +2 -13
- data/openapi_first.gemspec +8 -5
- metadata +44 -57
- data/.rspec +0 -3
- data/.rubocop.yml +0 -14
- data/Rakefile +0 -15
- data/benchmarks/Gemfile +0 -16
- data/benchmarks/Gemfile.lock +0 -131
- 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/body_parser_middleware.rb +0 -53
- data/lib/openapi_first/default_error_response.rb +0 -47
- data/lib/openapi_first/operation.rb +0 -142
- data/lib/openapi_first/operation_schemas.rb +0 -52
- data/lib/openapi_first/string_keyed_hash.rb +0 -20
- data/lib/openapi_first/validation_result.rb +0 -15
@@ -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
|
|
@@ -1,22 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'forwardable'
|
4
|
+
|
3
5
|
module OpenapiFirst
|
4
6
|
# This is the base class for error responses
|
5
7
|
class ErrorResponse
|
6
|
-
## @param
|
7
|
-
## @param
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
## @param request [Hash] The Rack request env
|
9
|
+
## @param request_validation_error [OpenapiFirst::RequestValidationError]
|
10
|
+
def initialize(env, request_validation_error)
|
11
|
+
@env = env
|
12
|
+
@request_validation_error = request_validation_error
|
13
|
+
end
|
14
|
+
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
attr_reader :env, :request_validation_error
|
18
|
+
|
19
|
+
def_delegators :@request_validation_error, :status, :location, :schema_validation
|
20
|
+
|
21
|
+
def validation_output
|
22
|
+
schema_validation&.output
|
17
23
|
end
|
18
24
|
|
19
|
-
|
25
|
+
def schema
|
26
|
+
schema_validation&.schema
|
27
|
+
end
|
28
|
+
|
29
|
+
def data
|
30
|
+
schema_validation&.data
|
31
|
+
end
|
32
|
+
|
33
|
+
def message
|
34
|
+
request_validation_error.message
|
35
|
+
end
|
20
36
|
|
21
37
|
def render
|
22
38
|
Rack::Response.new(body, status, Rack::CONTENT_TYPE => content_type).finish
|
@@ -0,0 +1,58 @@
|
|
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
|
@@ -0,0 +1,58 @@
|
|
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
|
@@ -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
|
16
|
-
|
17
|
-
private
|
11
|
+
request_body = @operation.request_body
|
12
|
+
return unless request_body
|
18
13
|
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
22
17
|
|
23
|
-
|
24
|
-
|
25
|
-
return if content_type.nil?
|
18
|
+
parsed_request_body = BodyParser.new.parse_body(@env)
|
19
|
+
RequestValidation.fail!(400, :body) if request_body.required? && parsed_request_body.nil?
|
26
20
|
|
27
|
-
schema
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
OpenapiFirst.error!(400, :request_body, validation_result:) if validation_result.error?
|
32
|
-
body
|
21
|
+
validate_body!(parsed_request_body, schema)
|
22
|
+
parsed_request_body
|
23
|
+
rescue BodyParsingError => e
|
24
|
+
RequestValidation.fail!(400, :body, message: e.message)
|
33
25
|
end
|
34
26
|
|
35
|
-
|
36
|
-
|
27
|
+
private
|
28
|
+
|
29
|
+
def validate_body!(parsed_request_body, schema)
|
30
|
+
request_body_schema = schema
|
31
|
+
return unless request_body_schema
|
37
32
|
|
38
|
-
|
33
|
+
schema_validation = request_body_schema.validate(parsed_request_body)
|
34
|
+
RequestValidation.fail!(400, :body, schema_validation:) if schema_validation.error?
|
39
35
|
end
|
40
36
|
end
|
41
37
|
end
|
@@ -5,16 +5,38 @@ require 'multi_json'
|
|
5
5
|
require_relative 'use_router'
|
6
6
|
require_relative 'error_response'
|
7
7
|
require_relative 'request_body_validator'
|
8
|
-
require_relative '
|
8
|
+
require_relative 'request_validation_error'
|
9
9
|
require 'openapi_parameters'
|
10
10
|
|
11
11
|
module OpenapiFirst
|
12
|
+
# A Rack middleware to validate requests against an OpenAPI API description
|
12
13
|
class RequestValidation
|
13
14
|
prepend UseRouter
|
14
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)
|
15
37
|
def initialize(app, options = {})
|
16
38
|
@app = app
|
17
|
-
@raise = options.fetch(:raise_error,
|
39
|
+
@raise = options.fetch(:raise_error, Config.default_options.request_validation_raise_error)
|
18
40
|
@error_response_class =
|
19
41
|
Plugins.find_error_response(options.fetch(:error_response, Config.default_options.error_response))
|
20
42
|
end
|
@@ -25,88 +47,76 @@ module OpenapiFirst
|
|
25
47
|
|
26
48
|
error = validate_request(operation, env)
|
27
49
|
if error
|
28
|
-
|
29
|
-
raise RequestInvalidError, error_message(title, location) if @raise
|
50
|
+
raise RequestInvalidError, error.error_message if @raise
|
30
51
|
|
31
|
-
return
|
52
|
+
return @error_response_class.new(env, error).render
|
32
53
|
end
|
54
|
+
|
33
55
|
@app.call(env)
|
34
56
|
end
|
35
57
|
|
36
58
|
private
|
37
59
|
|
38
|
-
def error_message(title, location)
|
39
|
-
return title unless location
|
40
|
-
|
41
|
-
"#{TOPICS.fetch(location)} #{title}"
|
42
|
-
end
|
43
|
-
|
44
|
-
TOPICS = {
|
45
|
-
request_body: 'Request body invalid:',
|
46
|
-
query: 'Query parameter invalid:',
|
47
|
-
header: 'Header parameter invalid:',
|
48
|
-
path: 'Path segment invalid:',
|
49
|
-
cookie: 'Cookie value invalid:'
|
50
|
-
}.freeze
|
51
|
-
private_constant :TOPICS
|
52
|
-
|
53
|
-
def error_response(error_object)
|
54
|
-
@error_response_class.new(**error_object)
|
55
|
-
end
|
56
|
-
|
57
60
|
def validate_request(operation, env)
|
58
|
-
catch(
|
61
|
+
catch(FAIL) do
|
59
62
|
env[PARAMS] = {}
|
60
|
-
|
61
|
-
|
62
|
-
validate_cookie_params!(operation, env)
|
63
|
-
validate_header_params!(operation, env)
|
64
|
-
RequestBodyValidator.new(operation, env).validate! if operation.request_body
|
63
|
+
validate_parameters!(operation, env)
|
64
|
+
validate_request_body!(operation, env)
|
65
65
|
nil
|
66
66
|
end
|
67
67
|
end
|
68
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
|
+
|
69
76
|
def validate_path_params!(operation, env)
|
70
|
-
|
71
|
-
return
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
env[
|
78
|
-
env[PARAMS].merge!(unpacked_path_params)
|
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)
|
79
85
|
end
|
80
86
|
|
81
87
|
def validate_query_params!(operation, env)
|
82
|
-
|
83
|
-
return
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
env[QUERY_PARAMS] =
|
89
|
-
env[PARAMS].merge!(
|
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)
|
90
96
|
end
|
91
97
|
|
92
98
|
def validate_cookie_params!(operation, env)
|
93
|
-
|
94
|
-
return unless
|
99
|
+
parameters = operation.cookie_parameters
|
100
|
+
return unless parameters
|
95
101
|
|
96
|
-
unpacked_params =
|
97
|
-
|
98
|
-
|
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?
|
99
105
|
env[COOKIE_PARAMS] = unpacked_params
|
100
106
|
end
|
101
107
|
|
102
108
|
def validate_header_params!(operation, env)
|
103
|
-
|
104
|
-
return
|
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
|
105
117
|
|
106
|
-
|
107
|
-
|
108
|
-
OpenapiFirst.error!(400, :header, validation_result:) if validation_result.error?
|
109
|
-
env[HEADER_PARAMS] = unpacked_header_params
|
118
|
+
def validate_request_body!(operation, env)
|
119
|
+
env[REQUEST_BODY] = RequestBodyValidator.new(operation, env).validate!
|
110
120
|
end
|
111
121
|
end
|
112
122
|
end
|
@@ -0,0 +1,31 @@
|
|
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
|
@@ -22,18 +22,39 @@ module OpenapiFirst
|
|
22
22
|
|
23
23
|
def validate(response, operation)
|
24
24
|
status, headers, body = response.to_a
|
25
|
-
|
25
|
+
response_definition = response_for(operation, status)
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
validate_response_headers(response_definition.headers, headers, openapi_version: operation.openapi_version)
|
28
|
+
|
29
|
+
return if no_content?(response_definition)
|
30
|
+
|
31
|
+
content_type = Rack::Response[status, headers, body].content_type
|
32
|
+
raise ResponseInvalid, "Response has no content-type for '#{operation.name}'" unless content_type
|
33
|
+
|
34
|
+
response_schema = response_definition.schema_for(content_type)
|
35
|
+
unless response_schema
|
36
|
+
message = "Response content type not found '#{content_type}' for '#{operation.name}'"
|
37
|
+
raise ResponseContentTypeNotFoundError, message
|
38
|
+
end
|
39
|
+
validate_response_body(response_schema, body)
|
31
40
|
end
|
32
41
|
|
33
42
|
private
|
34
43
|
|
44
|
+
def no_content?(response_definition)
|
45
|
+
response_definition.status == 204 || !response_definition.content?
|
46
|
+
end
|
47
|
+
|
48
|
+
def response_for(operation, status)
|
49
|
+
response = operation.response_for(status)
|
50
|
+
return response if response
|
51
|
+
|
52
|
+
message = "Response status code or default not found: #{status} for '#{operation.name}'"
|
53
|
+
raise OpenapiFirst::ResponseCodeNotFoundError, message
|
54
|
+
end
|
55
|
+
|
35
56
|
def validate_status_only(operation, status)
|
36
|
-
|
57
|
+
response_for(operation, status)
|
37
58
|
end
|
38
59
|
|
39
60
|
def validate_response_body(schema, response)
|
@@ -44,15 +65,14 @@ module OpenapiFirst
|
|
44
65
|
raise ResponseBodyInvalidError, validation.message if validation.error?
|
45
66
|
end
|
46
67
|
|
47
|
-
def validate_response_headers(
|
48
|
-
response_header_definitions = operation.response_for(status)&.dig('headers')
|
68
|
+
def validate_response_headers(response_header_definitions, response_headers, openapi_version:)
|
49
69
|
return unless response_header_definitions
|
50
70
|
|
51
71
|
unpacked_headers = unpack_response_headers(response_header_definitions, response_headers)
|
52
72
|
response_header_definitions.each do |name, definition|
|
53
73
|
next if name == 'Content-Type'
|
54
74
|
|
55
|
-
validate_response_header(name, definition, unpacked_headers, openapi_version:
|
75
|
+
validate_response_header(name, definition, unpacked_headers, openapi_version:)
|
56
76
|
end
|
57
77
|
end
|
58
78
|
|
@@ -65,15 +85,15 @@ module OpenapiFirst
|
|
65
85
|
|
66
86
|
return unless definition.key?('schema')
|
67
87
|
|
68
|
-
validation =
|
88
|
+
validation = Schema.new(definition['schema'], openapi_version:)
|
69
89
|
value = unpacked_headers[name]
|
70
|
-
|
71
|
-
raise ResponseHeaderInvalidError,
|
90
|
+
schema_validation = validation.validate(value)
|
91
|
+
raise ResponseHeaderInvalidError, schema_validation.message if schema_validation.error?
|
72
92
|
end
|
73
93
|
|
74
94
|
def unpack_response_headers(response_header_definitions, response_headers)
|
75
95
|
headers_as_parameters = response_header_definitions.map do |name, definition|
|
76
|
-
definition.merge('name' => name)
|
96
|
+
definition.merge('name' => name, 'in' => 'header')
|
77
97
|
end
|
78
98
|
OpenapiParameters::Header.new(headers_as_parameters).unpack(response_headers)
|
79
99
|
end
|