openapi_first 1.0.0.beta5 → 1.0.0

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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +2 -1
  3. data/CHANGELOG.md +23 -2
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +16 -18
  6. data/Gemfile.rack2 +15 -0
  7. data/Gemfile.rack2.lock +99 -0
  8. data/README.md +99 -130
  9. data/lib/openapi_first/body_parser.rb +29 -0
  10. data/lib/openapi_first/configuration.rb +20 -0
  11. data/lib/openapi_first/definition/cookie_parameters.rb +12 -0
  12. data/lib/openapi_first/definition/header_parameters.rb +12 -0
  13. data/lib/openapi_first/definition/operation.rb +116 -0
  14. data/lib/openapi_first/definition/parameters.rb +47 -0
  15. data/lib/openapi_first/definition/path_item.rb +32 -0
  16. data/lib/openapi_first/definition/path_parameters.rb +12 -0
  17. data/lib/openapi_first/definition/query_parameters.rb +12 -0
  18. data/lib/openapi_first/definition/request_body.rb +43 -0
  19. data/lib/openapi_first/definition/response.rb +25 -0
  20. data/lib/openapi_first/definition/responses.rb +83 -0
  21. data/lib/openapi_first/definition.rb +61 -8
  22. data/lib/openapi_first/error_response.rb +22 -27
  23. data/lib/openapi_first/errors.rb +2 -14
  24. data/lib/openapi_first/failure.rb +55 -0
  25. data/lib/openapi_first/middlewares/request_validation.rb +52 -0
  26. data/lib/openapi_first/middlewares/response_validation.rb +35 -0
  27. data/lib/openapi_first/plugins/default/error_response.rb +74 -0
  28. data/lib/openapi_first/plugins/default.rb +11 -0
  29. data/lib/openapi_first/plugins/jsonapi/error_response.rb +58 -0
  30. data/lib/openapi_first/plugins/jsonapi.rb +11 -0
  31. data/lib/openapi_first/plugins.rb +9 -7
  32. data/lib/openapi_first/request_validation/request_body_validator.rb +41 -0
  33. data/lib/openapi_first/request_validation/validator.rb +81 -0
  34. data/lib/openapi_first/response_validation/validator.rb +101 -0
  35. data/lib/openapi_first/runtime_request.rb +84 -0
  36. data/lib/openapi_first/runtime_response.rb +31 -0
  37. data/lib/openapi_first/schema/validation_error.rb +18 -0
  38. data/lib/openapi_first/schema/validation_result.rb +32 -0
  39. data/lib/openapi_first/{json_schema.rb → schema.rb} +9 -5
  40. data/lib/openapi_first/version.rb +1 -1
  41. data/lib/openapi_first.rb +32 -28
  42. data/openapi_first.gemspec +10 -9
  43. metadata +55 -67
  44. data/.rspec +0 -3
  45. data/.rubocop.yml +0 -14
  46. data/Rakefile +0 -15
  47. data/benchmarks/Gemfile +0 -16
  48. data/benchmarks/Gemfile.lock +0 -142
  49. data/benchmarks/README.md +0 -29
  50. data/benchmarks/apps/committee_with_hanami_api.ru +0 -26
  51. data/benchmarks/apps/committee_with_response_validation.ru +0 -29
  52. data/benchmarks/apps/committee_with_sinatra.ru +0 -31
  53. data/benchmarks/apps/grape.ru +0 -21
  54. data/benchmarks/apps/hanami_api.ru +0 -21
  55. data/benchmarks/apps/hanami_router.ru +0 -14
  56. data/benchmarks/apps/openapi.yaml +0 -268
  57. data/benchmarks/apps/openapi_first_with_hanami_api.ru +0 -24
  58. data/benchmarks/apps/openapi_first_with_plain_rack.ru +0 -32
  59. data/benchmarks/apps/openapi_first_with_response_validation.ru +0 -25
  60. data/benchmarks/apps/openapi_first_with_sinatra.ru +0 -29
  61. data/benchmarks/apps/roda.ru +0 -27
  62. data/benchmarks/apps/sinatra.ru +0 -26
  63. data/benchmarks/apps/syro.ru +0 -25
  64. data/benchmarks/benchmark-wrk.sh +0 -3
  65. data/benchmarks/benchmarks.rb +0 -48
  66. data/benchmarks/post.lua +0 -3
  67. data/bin/console +0 -15
  68. data/bin/setup +0 -8
  69. data/examples/README.md +0 -13
  70. data/examples/app.rb +0 -18
  71. data/examples/config.ru +0 -7
  72. data/examples/openapi.yaml +0 -29
  73. data/lib/openapi_first/body_parser_middleware.rb +0 -40
  74. data/lib/openapi_first/config.rb +0 -20
  75. data/lib/openapi_first/error_responses/default.rb +0 -58
  76. data/lib/openapi_first/error_responses/json_api.rb +0 -58
  77. data/lib/openapi_first/json_schema/result.rb +0 -17
  78. data/lib/openapi_first/operation.rb +0 -170
  79. data/lib/openapi_first/request_body_validator.rb +0 -41
  80. data/lib/openapi_first/request_validation.rb +0 -118
  81. data/lib/openapi_first/request_validation_error.rb +0 -31
  82. data/lib/openapi_first/response_validation.rb +0 -93
  83. data/lib/openapi_first/response_validator.rb +0 -21
  84. data/lib/openapi_first/router.rb +0 -102
  85. data/lib/openapi_first/string_keyed_hash.rb +0 -20
  86. data/lib/openapi_first/use_router.rb +0 -18
@@ -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,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,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OpenapiFirst
4
- class JsonSchema
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,170 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'forwardable'
4
- require 'set'
5
- require_relative 'json_schema'
6
-
7
- module OpenapiFirst
8
- class Operation # rubocop:disable Metrics/ClassLength
9
- extend Forwardable
10
- def_delegators :operation_object,
11
- :[],
12
- :dig
13
-
14
- WRITE_METHODS = Set.new(%w[post put patch delete]).freeze
15
- private_constant :WRITE_METHODS
16
-
17
- attr_reader :path, :method, :openapi_version
18
-
19
- def initialize(path, request_method, path_item_object, openapi_version:)
20
- @path = path
21
- @method = request_method
22
- @path_item_object = path_item_object
23
- @openapi_version = openapi_version
24
- end
25
-
26
- def operation_id
27
- operation_object['operationId']
28
- end
29
-
30
- def read?
31
- !write?
32
- end
33
-
34
- def write?
35
- WRITE_METHODS.include?(method)
36
- end
37
-
38
- def request_body
39
- operation_object['requestBody']
40
- end
41
-
42
- def response_body_schema(status, content_type)
43
- content = response_for(status)['content']
44
- return if content.nil? || content.empty?
45
-
46
- raise ResponseInvalid, "Response has no content-type for '#{name}'" unless content_type
47
-
48
- media_type = find_content_for_content_type(content, content_type)
49
-
50
- unless media_type
51
- message = "Response content type not found '#{content_type}' for '#{name}'"
52
- raise ResponseContentTypeNotFoundError, message
53
- end
54
- schema = media_type['schema']
55
- return unless schema
56
-
57
- JsonSchema.new(schema, write: false, openapi_version:)
58
- end
59
-
60
- def request_body_schema(request_content_type)
61
- (@request_body_schema ||= {})[request_content_type] ||= begin
62
- content = operation_object.dig('requestBody', 'content')
63
- media_type = find_content_for_content_type(content, request_content_type)
64
- schema = media_type&.fetch('schema', nil)
65
- JsonSchema.new(schema, write: write?, openapi_version:) if schema
66
- end
67
- end
68
-
69
- def response_for(status)
70
- response_content = response_by_code(status)
71
- return response_content if response_content
72
-
73
- message = "Response status code or default not found: #{status} for '#{name}'"
74
- raise OpenapiFirst::ResponseCodeNotFoundError, message
75
- end
76
-
77
- def name
78
- @name ||= "#{method.upcase} #{path} (#{operation_id})"
79
- end
80
-
81
- def valid_request_content_type?(request_content_type)
82
- content = operation_object.dig('requestBody', 'content')
83
- return false unless content
84
-
85
- !!find_content_for_content_type(content, request_content_type)
86
- end
87
-
88
- def query_parameters
89
- @query_parameters ||= all_parameters.filter { |p| p['in'] == 'query' }
90
- end
91
-
92
- def path_parameters
93
- @path_parameters ||= all_parameters.filter { |p| p['in'] == 'path' }
94
- end
95
-
96
- IGNORED_HEADERS = Set['Content-Type', 'Accept', 'Authorization'].freeze
97
- private_constant :IGNORED_HEADERS
98
-
99
- def header_parameters
100
- @header_parameters ||= all_parameters.filter { |p| p['in'] == 'header' && !IGNORED_HEADERS.include?(p['name']) }
101
- end
102
-
103
- def cookie_parameters
104
- @cookie_parameters ||= all_parameters.filter { |p| p['in'] == 'cookie' }
105
- end
106
-
107
- def all_parameters
108
- @all_parameters ||= begin
109
- parameters = @path_item_object['parameters']&.dup || []
110
- parameters_on_operation = operation_object['parameters']
111
- parameters.concat(parameters_on_operation) if parameters_on_operation
112
- parameters
113
- end
114
- end
115
-
116
- # Return JSON Schema of for all query parameters
117
- def query_parameters_schema
118
- @query_parameters_schema ||= build_json_schema(query_parameters)
119
- end
120
-
121
- # Return JSON Schema of for all path parameters
122
- def path_parameters_schema
123
- @path_parameters_schema ||= build_json_schema(path_parameters)
124
- end
125
-
126
- def header_parameters_schema
127
- @header_parameters_schema ||= build_json_schema(header_parameters)
128
- end
129
-
130
- def cookie_parameters_schema
131
- @cookie_parameters_schema ||= build_json_schema(cookie_parameters)
132
- end
133
-
134
- private
135
-
136
- # Build JSON Schema for given parameter definitions
137
- # @parameter_defs [Array<Hash>] Parameter definitions
138
- def build_json_schema(parameter_defs)
139
- init_schema = {
140
- 'type' => 'object',
141
- 'properties' => {},
142
- 'required' => []
143
- }
144
- schema = parameter_defs.each_with_object(init_schema) do |parameter_def, result|
145
- parameter = OpenapiParameters::Parameter.new(parameter_def)
146
- result['properties'][parameter.name] = parameter.schema if parameter.schema
147
- result['required'] << parameter.name if parameter.required?
148
- end
149
- JsonSchema.new(schema, openapi_version:)
150
- end
151
-
152
- def response_by_code(status)
153
- operation_object.dig('responses', status.to_s) ||
154
- operation_object.dig('responses', "#{status / 100}XX") ||
155
- operation_object.dig('responses', "#{status / 100}xx") ||
156
- operation_object.dig('responses', 'default')
157
- end
158
-
159
- def operation_object
160
- @path_item_object[method]
161
- end
162
-
163
- def find_content_for_content_type(content, request_content_type)
164
- content.fetch(request_content_type) do |_|
165
- type = request_content_type.split(';')[0]
166
- content[type] || content["#{type.split('/')[0]}/*"] || content['*/*']
167
- end
168
- end
169
- end
170
- end
@@ -1,41 +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
- @parsed_request_body = env[REQUEST_BODY]
9
- end
10
-
11
- def validate!
12
- content_type = Rack::Request.new(@env).content_type
13
- validate_request_content_type!(@operation, content_type)
14
- validate_request_body!(@operation, @parsed_request_body, content_type)
15
- end
16
-
17
- private
18
-
19
- def validate_request_content_type!(operation, content_type)
20
- operation.valid_request_content_type?(content_type) || RequestValidation.fail!(415, :header)
21
- end
22
-
23
- def validate_request_body!(operation, body, content_type)
24
- validate_request_body_presence!(body, operation)
25
- return if content_type.nil?
26
-
27
- schema = operation&.request_body_schema(content_type)
28
- return unless schema
29
-
30
- schema_validation = schema.validate(body)
31
- 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
- end
40
- end
41
- end
@@ -1,118 +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 'string_keyed_hash'
9
- require_relative 'request_validation_error'
10
- require 'openapi_parameters'
11
-
12
- module OpenapiFirst
13
- # A Rack middleware to validate requests against an OpenAPI API description
14
- class RequestValidation
15
- prepend UseRouter
16
-
17
- FAIL = :request_validation_failed
18
- private_constant :FAIL
19
-
20
- # @param status [Integer] The intended HTTP status code (usually 400)
21
- # @param location [Symbol] One of :body, :header, :cookie, :query, :path
22
- # @param schema_validation [OpenapiFirst::JsonSchema::Result]
23
- def self.fail!(status, location, schema_validation: nil)
24
- throw FAIL, RequestValidationError.new(
25
- status:,
26
- location:,
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
- @app.call(env)
55
- end
56
-
57
- private
58
-
59
- def validate_request(operation, env)
60
- catch(FAIL) do
61
- env[PARAMS] = {}
62
- validate_query_params!(operation, env)
63
- validate_path_params!(operation, env)
64
- validate_cookie_params!(operation, env)
65
- validate_header_params!(operation, env)
66
- validate_request_body!(operation, env)
67
- nil
68
- end
69
- end
70
-
71
- def validate_path_params!(operation, env)
72
- path_parameters = operation.path_parameters
73
- return if path_parameters.empty?
74
-
75
- hashy = StringKeyedHash.new(env[Router::RAW_PATH_PARAMS])
76
- unpacked_path_params = OpenapiParameters::Path.new(path_parameters).unpack(hashy)
77
- schema_validation = operation.path_parameters_schema.validate(unpacked_path_params)
78
- RequestValidation.fail!(400, :path, schema_validation:) if schema_validation.error?
79
- env[PATH_PARAMS] = unpacked_path_params
80
- env[PARAMS].merge!(unpacked_path_params)
81
- end
82
-
83
- def validate_query_params!(operation, env)
84
- query_parameters = operation.query_parameters
85
- return if operation.query_parameters.empty?
86
-
87
- unpacked_query_params = OpenapiParameters::Query.new(query_parameters).unpack(env['QUERY_STRING'])
88
- schema_validation = operation.query_parameters_schema.validate(unpacked_query_params)
89
- RequestValidation.fail!(400, :query, schema_validation:) if schema_validation.error?
90
- env[QUERY_PARAMS] = unpacked_query_params
91
- env[PARAMS].merge!(unpacked_query_params)
92
- end
93
-
94
- def validate_cookie_params!(operation, env)
95
- cookie_parameters = operation.cookie_parameters
96
- return unless cookie_parameters&.any?
97
-
98
- unpacked_params = OpenapiParameters::Cookie.new(cookie_parameters).unpack(env['HTTP_COOKIE'])
99
- schema_validation = operation.cookie_parameters_schema.validate(unpacked_params)
100
- RequestValidation.fail!(400, :cookie, schema_validation:) if schema_validation.error?
101
- env[COOKIE_PARAMS] = unpacked_params
102
- end
103
-
104
- def validate_header_params!(operation, env)
105
- header_parameters = operation.header_parameters
106
- return if header_parameters.empty?
107
-
108
- unpacked_header_params = OpenapiParameters::Header.new(header_parameters).unpack_env(env)
109
- schema_validation = operation.header_parameters_schema.validate(unpacked_header_params)
110
- RequestValidation.fail!(400, :header, schema_validation:) if schema_validation.error?
111
- env[HEADER_PARAMS] = unpacked_header_params
112
- end
113
-
114
- def validate_request_body!(operation, env)
115
- RequestBodyValidator.new(operation, env).validate! if operation.request_body
116
- end
117
- end
118
- 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
@@ -1,93 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'multi_json'
4
- require_relative 'use_router'
5
-
6
- module OpenapiFirst
7
- class ResponseValidation
8
- prepend UseRouter
9
-
10
- def initialize(app, _options = {})
11
- @app = app
12
- end
13
-
14
- def call(env)
15
- operation = env[OPERATION]
16
- return @app.call(env) unless operation
17
-
18
- response = @app.call(env)
19
- validate(response, operation)
20
- response
21
- end
22
-
23
- def validate(response, operation)
24
- status, headers, body = response.to_a
25
- return validate_status_only(operation, status) if status == 204
26
-
27
- content_type = headers[Rack::CONTENT_TYPE]
28
- response_schema = operation.response_body_schema(status, content_type)
29
- validate_response_body(response_schema, body) if response_schema
30
- validate_response_headers(operation, status, headers)
31
- end
32
-
33
- private
34
-
35
- def validate_status_only(operation, status)
36
- operation.response_for(status)
37
- end
38
-
39
- def validate_response_body(schema, response)
40
- full_body = +''
41
- response.each { |chunk| full_body << chunk }
42
- data = full_body.empty? ? {} : load_json(full_body)
43
- validation = schema.validate(data)
44
- raise ResponseBodyInvalidError, validation.message if validation.error?
45
- end
46
-
47
- def validate_response_headers(operation, status, response_headers)
48
- response_header_definitions = operation.response_for(status)&.dig('headers')
49
- return unless response_header_definitions
50
-
51
- unpacked_headers = unpack_response_headers(response_header_definitions, response_headers)
52
- response_header_definitions.each do |name, definition|
53
- next if name == 'Content-Type'
54
-
55
- validate_response_header(name, definition, unpacked_headers, openapi_version: operation.openapi_version)
56
- end
57
- end
58
-
59
- def validate_response_header(name, definition, unpacked_headers, openapi_version:)
60
- unless unpacked_headers.key?(name)
61
- raise ResponseHeaderInvalidError, "Required response header '#{name}' is missing" if definition['required']
62
-
63
- return
64
- end
65
-
66
- return unless definition.key?('schema')
67
-
68
- validation = JsonSchema.new(definition['schema'], openapi_version:)
69
- value = unpacked_headers[name]
70
- schema_validation = validation.validate(value)
71
- raise ResponseHeaderInvalidError, schema_validation.message if schema_validation.error?
72
- end
73
-
74
- def unpack_response_headers(response_header_definitions, response_headers)
75
- headers_as_parameters = response_header_definitions.map do |name, definition|
76
- definition.merge('name' => name)
77
- end
78
- OpenapiParameters::Header.new(headers_as_parameters).unpack(response_headers)
79
- end
80
-
81
- def format_response_error(error)
82
- return "Write-only field appears in response: #{error['data_pointer']}" if error['type'] == 'writeOnly'
83
-
84
- JSONSchemer::Errors.pretty(error)
85
- end
86
-
87
- def load_json(string)
88
- MultiJson.load(string)
89
- rescue MultiJson::ParseError
90
- string
91
- end
92
- end
93
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'response_validation'
4
- require_relative 'router'
5
-
6
- module OpenapiFirst
7
- # A class to run manual response validation
8
- class ResponseValidator
9
- def initialize(spec)
10
- @spec = spec
11
- @router = Router.new(->(_env) {}, spec:, raise_error: true)
12
- @response_validation = ResponseValidation.new(->(response) { response.to_a })
13
- end
14
-
15
- def validate(request, response)
16
- env = request.env.dup
17
- @router.call(env)
18
- @response_validation.validate(response, env[OPERATION])
19
- end
20
- end
21
- end