openapi_first 0.11.0.alpha → 0.12.1
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/.rubocop.yml +6 -0
- data/CHANGELOG.md +19 -1
- data/Gemfile.lock +8 -7
- data/README.md +122 -89
- data/benchmarks/Gemfile.lock +6 -6
- data/benchmarks/apps/openapi_first.ru +1 -1
- data/examples/app.rb +6 -1
- data/lib/openapi_first.rb +46 -8
- data/lib/openapi_first/app.rb +5 -8
- data/lib/openapi_first/definition.rb +0 -12
- data/lib/openapi_first/find_handler.rb +25 -21
- data/lib/openapi_first/operation.rb +12 -7
- data/lib/openapi_first/request_validation.rb +24 -13
- data/lib/openapi_first/{operation_resolver.rb → responder.rb} +18 -4
- data/lib/openapi_first/response_validation.rb +25 -78
- data/lib/openapi_first/response_validator.rb +6 -47
- data/lib/openapi_first/router.rb +29 -31
- data/lib/openapi_first/router_required.rb +13 -0
- data/lib/openapi_first/validation_format.rb +3 -1
- data/lib/openapi_first/version.rb +1 -1
- data/openapi_first.gemspec +7 -7
- metadata +6 -5
data/examples/app.rb
CHANGED
data/lib/openapi_first.rb
CHANGED
@@ -9,7 +9,7 @@ require 'openapi_first/router'
|
|
9
9
|
require 'openapi_first/request_validation'
|
10
10
|
require 'openapi_first/response_validator'
|
11
11
|
require 'openapi_first/response_validation'
|
12
|
-
require 'openapi_first/
|
12
|
+
require 'openapi_first/responder'
|
13
13
|
require 'openapi_first/app'
|
14
14
|
|
15
15
|
module OpenapiFirst
|
@@ -19,6 +19,10 @@ module OpenapiFirst
|
|
19
19
|
INBOX = 'openapi_first.inbox'
|
20
20
|
HANDLER = 'openapi_first.handler'
|
21
21
|
|
22
|
+
def self.env
|
23
|
+
ENV['RACK_ENV'] || ENV['HANAMI_ENV'] || ENV['RAILS_ENV']
|
24
|
+
end
|
25
|
+
|
22
26
|
def self.load(spec_path, only: nil)
|
23
27
|
content = YAML.load_file(spec_path)
|
24
28
|
raw = OasParser::Parser.new(spec_path, content).resolve
|
@@ -27,14 +31,14 @@ module OpenapiFirst
|
|
27
31
|
Definition.new(parsed)
|
28
32
|
end
|
29
33
|
|
30
|
-
def self.app(spec, namespace:)
|
34
|
+
def self.app(spec, namespace:, raise_error: false)
|
31
35
|
spec = OpenapiFirst.load(spec) if spec.is_a?(String)
|
32
|
-
App.new(nil, spec, namespace: namespace)
|
36
|
+
App.new(nil, spec, namespace: namespace, raise_error: raise_error)
|
33
37
|
end
|
34
38
|
|
35
|
-
def self.middleware(spec, namespace:)
|
39
|
+
def self.middleware(spec, namespace:, raise_error: false)
|
36
40
|
spec = OpenapiFirst.load(spec) if spec.is_a?(String)
|
37
|
-
AppWithOptions.new(spec, namespace: namespace)
|
41
|
+
AppWithOptions.new(spec, namespace: namespace, raise_error: raise_error)
|
38
42
|
end
|
39
43
|
|
40
44
|
class AppWithOptions
|
@@ -50,7 +54,41 @@ module OpenapiFirst
|
|
50
54
|
|
51
55
|
class Error < StandardError; end
|
52
56
|
class NotFoundError < Error; end
|
53
|
-
class
|
54
|
-
class
|
55
|
-
class
|
57
|
+
class NotImplementedError < RuntimeError; end
|
58
|
+
class ResponseInvalid < Error; end
|
59
|
+
class ResponseCodeNotFoundError < ResponseInvalid; end
|
60
|
+
class ResponseContentTypeNotFoundError < ResponseInvalid; end
|
61
|
+
class ResponseBodyInvalidError < ResponseInvalid; end
|
62
|
+
|
63
|
+
class RequestInvalidError < Error
|
64
|
+
def initialize(serialized_errors)
|
65
|
+
message = error_message(serialized_errors)
|
66
|
+
super message
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def error_message(errors)
|
72
|
+
errors.map do |error|
|
73
|
+
[human_source(error), human_error(error)].compact.join(' ')
|
74
|
+
end.join(', ')
|
75
|
+
end
|
76
|
+
|
77
|
+
def human_source(error)
|
78
|
+
return unless error[:source]
|
79
|
+
|
80
|
+
source_key = error[:source].keys.first
|
81
|
+
source = {
|
82
|
+
pointer: 'Request body invalid:',
|
83
|
+
parameter: 'Query parameter invalid:'
|
84
|
+
}.fetch(source_key, source_key)
|
85
|
+
name = error[:source].values.first
|
86
|
+
source += " #{name}" unless name.nil? || name.empty?
|
87
|
+
source
|
88
|
+
end
|
89
|
+
|
90
|
+
def human_error(error)
|
91
|
+
error[:title]
|
92
|
+
end
|
93
|
+
end
|
56
94
|
end
|
data/lib/openapi_first/app.rb
CHANGED
@@ -5,16 +5,13 @@ require 'logger'
|
|
5
5
|
|
6
6
|
module OpenapiFirst
|
7
7
|
class App
|
8
|
-
def initialize(
|
9
|
-
parent_app,
|
10
|
-
spec,
|
11
|
-
namespace:
|
12
|
-
)
|
8
|
+
def initialize(parent_app, spec, namespace:, raise_error:)
|
13
9
|
@stack = Rack::Builder.app do
|
14
10
|
freeze_app
|
15
|
-
use OpenapiFirst::Router, spec: spec, parent_app: parent_app
|
16
|
-
use OpenapiFirst::RequestValidation
|
17
|
-
|
11
|
+
use OpenapiFirst::Router, spec: spec, raise_error: raise_error, parent_app: parent_app
|
12
|
+
use OpenapiFirst::RequestValidation, raise_error: raise_error
|
13
|
+
use OpenapiFirst::ResponseValidation if raise_error
|
14
|
+
run OpenapiFirst::Responder.new(
|
18
15
|
spec: spec,
|
19
16
|
namespace: namespace
|
20
17
|
)
|
@@ -14,17 +14,5 @@ module OpenapiFirst
|
|
14
14
|
def operations
|
15
15
|
@spec.endpoints.map { |e| Operation.new(e) }
|
16
16
|
end
|
17
|
-
|
18
|
-
def find_operation!(request)
|
19
|
-
@spec
|
20
|
-
.path_by_path(request.path)
|
21
|
-
.endpoint_by_method(request.request_method.downcase)
|
22
|
-
end
|
23
|
-
|
24
|
-
def find_operation(request)
|
25
|
-
find_operation!(request)
|
26
|
-
rescue OasParser::PathNotFound, OasParser::MethodNotFound
|
27
|
-
nil
|
28
|
-
end
|
29
17
|
end
|
30
18
|
end
|
@@ -5,14 +5,10 @@ require_relative 'utils'
|
|
5
5
|
module OpenapiFirst
|
6
6
|
class FindHandler
|
7
7
|
def initialize(spec, namespace)
|
8
|
-
@spec = spec
|
9
8
|
@namespace = namespace
|
10
|
-
|
11
|
-
|
12
|
-
def all
|
13
|
-
@spec.operations.each_with_object({}) do |operation, hash|
|
9
|
+
@handlers = spec.operations.each_with_object({}) do |operation, hash|
|
14
10
|
operation_id = operation.operation_id
|
15
|
-
handler =
|
11
|
+
handler = find_handler(operation_id)
|
16
12
|
if handler.nil?
|
17
13
|
warn "#{self.class.name} cannot not find handler for '#{operation.operation_id}' (#{operation.method} #{operation.path}). This operation will be ignored." # rubocop:disable Layout/LineLength
|
18
14
|
next
|
@@ -21,22 +17,17 @@ module OpenapiFirst
|
|
21
17
|
end
|
22
18
|
end
|
23
19
|
|
24
|
-
def
|
20
|
+
def [](operation_id)
|
21
|
+
@handlers[operation_id]
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_handler(operation_id)
|
25
25
|
name = operation_id.match(/:*(.*)/)&.to_a&.at(1)
|
26
26
|
return if name.nil?
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
return klass&.method(Utils.underscore(method_name))
|
32
|
-
end
|
33
|
-
if name.include?('#')
|
34
|
-
module_name, klass_name = name.split('#')
|
35
|
-
const = find_const(@namespace, module_name)
|
36
|
-
klass = find_const(const, klass_name)
|
37
|
-
return ->(params, res) { klass.new.call(params, res) } if klass.instance_method(:initialize).arity.zero?
|
38
|
-
|
39
|
-
return ->(params, res) { klass.new(params.env).call(params, res) }
|
28
|
+
catch :halt do
|
29
|
+
return find_class_method_handler(name) if name.include?('.')
|
30
|
+
return find_instance_method_handler(name) if name.include?('#')
|
40
31
|
end
|
41
32
|
method_name = Utils.underscore(name)
|
42
33
|
return unless @namespace.respond_to?(method_name)
|
@@ -44,11 +35,24 @@ module OpenapiFirst
|
|
44
35
|
@namespace.method(method_name)
|
45
36
|
end
|
46
37
|
|
47
|
-
|
38
|
+
def find_class_method_handler(name)
|
39
|
+
module_name, method_name = name.split('.')
|
40
|
+
klass = find_const(@namespace, module_name)
|
41
|
+
klass.method(Utils.underscore(method_name))
|
42
|
+
end
|
43
|
+
|
44
|
+
def find_instance_method_handler(name)
|
45
|
+
module_name, klass_name = name.split('#')
|
46
|
+
const = find_const(@namespace, module_name)
|
47
|
+
klass = find_const(const, klass_name)
|
48
|
+
return ->(params, res) { klass.new.call(params, res) } if klass.instance_method(:initialize).arity.zero?
|
49
|
+
|
50
|
+
->(params, res) { klass.new(params.env).call(params, res) }
|
51
|
+
end
|
48
52
|
|
49
53
|
def find_const(parent, name)
|
50
54
|
name = Utils.classify(name)
|
51
|
-
|
55
|
+
throw :halt unless parent.const_defined?(name, false)
|
52
56
|
|
53
57
|
parent.const_get(name, false)
|
54
58
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'forwardable'
|
4
|
+
require 'json_schemer'
|
4
5
|
require_relative 'utils'
|
5
6
|
require_relative 'response_object'
|
6
7
|
|
@@ -25,6 +26,10 @@ module OpenapiFirst
|
|
25
26
|
@parameters_json_schema ||= build_parameters_json_schema
|
26
27
|
end
|
27
28
|
|
29
|
+
def parameters_schema
|
30
|
+
@parameters_schema ||= parameters_json_schema && JSONSchemer.schema(parameters_json_schema)
|
31
|
+
end
|
32
|
+
|
28
33
|
def content_type_for(status)
|
29
34
|
content = response_for(status)['content']
|
30
35
|
content.keys[0] if content
|
@@ -36,8 +41,8 @@ module OpenapiFirst
|
|
36
41
|
|
37
42
|
media_type = content[content_type]
|
38
43
|
unless media_type
|
39
|
-
message = "Response
|
40
|
-
raise
|
44
|
+
message = "Response content type not found '#{content_type}' for '#{name}'"
|
45
|
+
raise ResponseContentTypeNotFoundError, message
|
41
46
|
end
|
42
47
|
media_type['schema']
|
43
48
|
end
|
@@ -45,16 +50,16 @@ module OpenapiFirst
|
|
45
50
|
def response_for(status)
|
46
51
|
@operation.response_by_code(status.to_s, use_default: true).raw
|
47
52
|
rescue OasParser::ResponseCodeNotFound
|
48
|
-
message = "Response status code or default not found: #{status} for '#{
|
53
|
+
message = "Response status code or default not found: #{status} for '#{name}'"
|
49
54
|
raise OpenapiFirst::ResponseCodeNotFoundError, message
|
50
55
|
end
|
51
56
|
|
52
|
-
|
53
|
-
|
54
|
-
def operation_name
|
55
|
-
"#{method.upcase} #{path}"
|
57
|
+
def name
|
58
|
+
"#{method.upcase} #{path} (#{operation_id})"
|
56
59
|
end
|
57
60
|
|
61
|
+
private
|
62
|
+
|
58
63
|
def build_parameters_json_schema
|
59
64
|
return unless @operation.parameters&.any?
|
60
65
|
|
@@ -4,12 +4,16 @@ require 'rack'
|
|
4
4
|
require 'json_schemer'
|
5
5
|
require 'multi_json'
|
6
6
|
require_relative 'inbox'
|
7
|
+
require_relative 'router_required'
|
7
8
|
require_relative 'validation_format'
|
8
9
|
|
9
10
|
module OpenapiFirst
|
10
11
|
class RequestValidation # rubocop:disable Metrics/ClassLength
|
11
|
-
|
12
|
+
prepend RouterRequired
|
13
|
+
|
14
|
+
def initialize(app, raise_error: false)
|
12
15
|
@app = app
|
16
|
+
@raise = raise_error
|
13
17
|
end
|
14
18
|
|
15
19
|
def call(env) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
@@ -46,32 +50,32 @@ module OpenapiFirst
|
|
46
50
|
|
47
51
|
parsed_request_body = parse_request_body!(body)
|
48
52
|
errors = validate_json_schema(schema, parsed_request_body)
|
49
|
-
|
53
|
+
halt_with_error(400, serialize_request_body_errors(errors)) if errors.any?
|
50
54
|
env[INBOX].merge! env[REQUEST_BODY] = parsed_request_body
|
51
55
|
end
|
52
56
|
|
53
57
|
def parse_request_body!(body)
|
54
|
-
MultiJson.load(body)
|
58
|
+
MultiJson.load(body, symbolize_keys: true)
|
55
59
|
rescue MultiJson::ParseError => e
|
56
60
|
err = { title: 'Failed to parse body as JSON' }
|
57
61
|
err[:detail] = e.cause unless ENV['RACK_ENV'] == 'production'
|
58
|
-
|
62
|
+
halt_with_error(400, [err])
|
59
63
|
end
|
60
64
|
|
61
65
|
def validate_request_content_type!(content_type, operation)
|
62
66
|
return if operation.request_body.content[content_type]
|
63
67
|
|
64
|
-
|
68
|
+
halt_with_error(415)
|
65
69
|
end
|
66
70
|
|
67
71
|
def validate_request_body_presence!(body, operation)
|
68
72
|
return unless operation.request_body.required && body.empty?
|
69
73
|
|
70
|
-
|
74
|
+
halt_with_error(415, 'Request body is required')
|
71
75
|
end
|
72
76
|
|
73
77
|
def validate_json_schema(schema, object)
|
74
|
-
|
78
|
+
schema.validate(Utils.deep_stringify(object))
|
75
79
|
end
|
76
80
|
|
77
81
|
def default_error(status, title = Rack::Utils::HTTP_STATUS_CODES[status])
|
@@ -81,8 +85,10 @@ module OpenapiFirst
|
|
81
85
|
}
|
82
86
|
end
|
83
87
|
|
84
|
-
def
|
85
|
-
|
88
|
+
def halt_with_error(status, errors = [default_error(status)])
|
89
|
+
raise RequestInvalidError, errors if @raise
|
90
|
+
|
91
|
+
halt Rack::Response.new(
|
86
92
|
MultiJson.dump(errors: errors),
|
87
93
|
status,
|
88
94
|
Rack::CONTENT_TYPE => 'application/vnd.api+json'
|
@@ -92,7 +98,8 @@ module OpenapiFirst
|
|
92
98
|
def request_body_schema(content_type, operation)
|
93
99
|
return unless operation
|
94
100
|
|
95
|
-
operation.request_body.content[content_type]&.fetch('schema')
|
101
|
+
schema = operation.request_body.content[content_type]&.fetch('schema')
|
102
|
+
JSONSchemer.schema(schema) if schema
|
96
103
|
end
|
97
104
|
|
98
105
|
def serialize_request_body_errors(validation_errors)
|
@@ -110,8 +117,11 @@ module OpenapiFirst
|
|
110
117
|
return unless json_schema
|
111
118
|
|
112
119
|
params = filtered_params(json_schema, params)
|
113
|
-
errors =
|
114
|
-
|
120
|
+
errors = validate_json_schema(
|
121
|
+
operation.parameters_schema,
|
122
|
+
params
|
123
|
+
)
|
124
|
+
halt_with_error(400, serialize_query_parameter_errors(errors)) if errors.any?
|
115
125
|
env[PARAMETERS] = params
|
116
126
|
env[INBOX].merge! params
|
117
127
|
end
|
@@ -119,7 +129,8 @@ module OpenapiFirst
|
|
119
129
|
def filtered_params(json_schema, params)
|
120
130
|
json_schema['properties']
|
121
131
|
.each_with_object({}) do |key_value, result|
|
122
|
-
parameter_name
|
132
|
+
parameter_name = key_value[0].to_sym
|
133
|
+
schema = key_value[1]
|
123
134
|
next unless params.key?(parameter_name)
|
124
135
|
|
125
136
|
value = params[parameter_name]
|
@@ -5,16 +5,16 @@ require_relative 'inbox'
|
|
5
5
|
require_relative 'find_handler'
|
6
6
|
|
7
7
|
module OpenapiFirst
|
8
|
-
class
|
9
|
-
def initialize(spec:, namespace:)
|
10
|
-
@
|
8
|
+
class Responder
|
9
|
+
def initialize(spec:, namespace:, resolver: FindHandler.new(spec, namespace))
|
10
|
+
@resolver = resolver
|
11
11
|
@namespace = namespace
|
12
12
|
end
|
13
13
|
|
14
14
|
def call(env)
|
15
15
|
operation = env[OpenapiFirst::OPERATION]
|
16
16
|
res = Rack::Response.new
|
17
|
-
handler =
|
17
|
+
handler = find_handler(operation)
|
18
18
|
result = handler.call(env[INBOX], res)
|
19
19
|
res.write serialize(result) if result && res.body.empty?
|
20
20
|
res[Rack::CONTENT_TYPE] ||= operation.content_type_for(res.status)
|
@@ -23,10 +23,24 @@ module OpenapiFirst
|
|
23
23
|
|
24
24
|
private
|
25
25
|
|
26
|
+
def find_handler(operation)
|
27
|
+
handler = @resolver[operation.operation_id]
|
28
|
+
raise NotImplementedError, "Could not find handler for #{operation.name}" unless handler
|
29
|
+
|
30
|
+
handler
|
31
|
+
end
|
32
|
+
|
26
33
|
def serialize(result)
|
27
34
|
return result if result.is_a?(String)
|
28
35
|
|
29
36
|
MultiJson.dump(result)
|
30
37
|
end
|
31
38
|
end
|
39
|
+
|
40
|
+
class OperationResolver < Responder
|
41
|
+
def initialize(spec:, namespace:)
|
42
|
+
warn "#{self.class.name} was renamed to #{OpenapiFirst::Responder.name}"
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
32
46
|
end
|
@@ -2,10 +2,13 @@
|
|
2
2
|
|
3
3
|
require 'json_schemer'
|
4
4
|
require 'multi_json'
|
5
|
+
require_relative 'router_required'
|
5
6
|
require_relative 'validation'
|
6
7
|
|
7
8
|
module OpenapiFirst
|
8
9
|
class ResponseValidation
|
10
|
+
prepend RouterRequired
|
11
|
+
|
9
12
|
def initialize(app)
|
10
13
|
@app = app
|
11
14
|
end
|
@@ -14,103 +17,47 @@ module OpenapiFirst
|
|
14
17
|
operation = env[OPERATION]
|
15
18
|
return @app.call(env) unless operation
|
16
19
|
|
17
|
-
|
20
|
+
response = @app.call(env)
|
21
|
+
validate(response, operation)
|
22
|
+
response
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate(response, operation)
|
26
|
+
status, headers, body = response.to_a
|
27
|
+
return validate_status_only(operation, status) if status == 204
|
28
|
+
|
18
29
|
content_type = headers[Rack::CONTENT_TYPE]
|
30
|
+
raise ResponseInvalid, "Response has no content-type for '#{operation.name}'" unless content_type
|
31
|
+
|
19
32
|
response_schema = operation.response_schema_for(status, content_type)
|
20
33
|
validate_response_body(response_schema, body) if response_schema
|
21
|
-
|
22
|
-
[status, headers, body]
|
23
34
|
end
|
24
35
|
|
25
36
|
private
|
26
37
|
|
27
|
-
def
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
def error(message)
|
32
|
-
{ title: message }
|
33
|
-
end
|
34
|
-
|
35
|
-
def error_response(status, errors)
|
36
|
-
Rack::Response.new(
|
37
|
-
MultiJson.dump(errors: errors),
|
38
|
-
status,
|
39
|
-
Rack::CONTENT_TYPE => 'application/vnd.api+json'
|
40
|
-
).finish
|
38
|
+
def validate_status_only(operation, status)
|
39
|
+
operation.response_for(status)
|
41
40
|
end
|
42
41
|
|
43
42
|
def validate_response_body(schema, response)
|
44
43
|
full_body = +''
|
45
44
|
response.each { |chunk| full_body << chunk }
|
46
|
-
data = full_body.empty? ? {} :
|
45
|
+
data = full_body.empty? ? {} : load_json(full_body)
|
47
46
|
errors = JSONSchemer.schema(schema).validate(data).to_a.map do |error|
|
48
|
-
|
47
|
+
error_message_for(error)
|
49
48
|
end
|
50
49
|
raise ResponseBodyInvalidError, errors.join(', ') if errors.any?
|
51
50
|
end
|
52
51
|
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
# frozen_string_literal: true
|
61
|
-
|
62
|
-
require 'json_schemer'
|
63
|
-
require 'multi_json'
|
64
|
-
require_relative 'validation'
|
65
|
-
|
66
|
-
module OpenapiFirst
|
67
|
-
class ResponseValidator
|
68
|
-
def initialize(spec)
|
69
|
-
@spec = spec
|
70
|
-
end
|
71
|
-
|
72
|
-
def validate(request, response)
|
73
|
-
errors = validation_errors(request, response)
|
74
|
-
Validation.new(errors || [])
|
75
|
-
rescue OasParser::ResponseCodeNotFound, OasParser::MethodNotFound => e
|
76
|
-
Validation.new([e.message])
|
52
|
+
def load_json(string)
|
53
|
+
MultiJson.load(string)
|
54
|
+
rescue MultiJson::ParseError
|
55
|
+
string
|
77
56
|
end
|
78
57
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
content = response_for(request, response)&.content
|
83
|
-
return unless content
|
84
|
-
|
85
|
-
content_type = content[response.content_type]
|
86
|
-
return ["Content type not found: '#{response.content_type}'"] unless content_type
|
87
|
-
|
88
|
-
response_schema = content_type['schema']
|
89
|
-
return unless response_schema
|
90
|
-
|
91
|
-
response_data = MultiJson.load(response.body)
|
92
|
-
validate_json_schema(response_schema, response_data)
|
93
|
-
end
|
94
|
-
|
95
|
-
def validate_json_schema(schema, data)
|
96
|
-
JSONSchemer.schema(schema).validate(data).to_a.map do |error|
|
97
|
-
format_error(error)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def format_error(error)
|
102
|
-
ValidationFormat.error_details(error)
|
103
|
-
.merge!(
|
104
|
-
data_pointer: error['data_pointer'],
|
105
|
-
schema_pointer: error['schema_pointer']
|
106
|
-
).tap do |formatted|
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def response_for(request, response)
|
111
|
-
@spec
|
112
|
-
.find_operation!(request)
|
113
|
-
&.response_by_code(response.status.to_s, use_default: true)
|
58
|
+
def error_message_for(error)
|
59
|
+
err = ValidationFormat.error_details(error)
|
60
|
+
[err[:title], error['data_pointer'], err[:detail]].compact.join(' ')
|
114
61
|
end
|
115
62
|
end
|
116
63
|
end
|