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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +2 -1
  3. data/CHANGELOG.md +13 -0
  4. data/Gemfile +2 -1
  5. data/Gemfile.lock +17 -22
  6. data/Gemfile.rack2 +15 -0
  7. data/README.md +17 -7
  8. data/lib/openapi_first/body_parser.rb +28 -0
  9. data/lib/openapi_first/config.rb +4 -3
  10. data/lib/openapi_first/definition/cookie_parameters.rb +12 -0
  11. data/lib/openapi_first/definition/has_content.rb +37 -0
  12. data/lib/openapi_first/definition/header_parameters.rb +12 -0
  13. data/lib/openapi_first/definition/operation.rb +103 -0
  14. data/lib/openapi_first/definition/parameters.rb +47 -0
  15. data/lib/openapi_first/definition/path_item.rb +23 -0
  16. data/lib/openapi_first/definition/path_parameters.rb +13 -0
  17. data/lib/openapi_first/definition/query_parameters.rb +12 -0
  18. data/lib/openapi_first/definition/request_body.rb +32 -0
  19. data/lib/openapi_first/definition/response.rb +37 -0
  20. data/lib/openapi_first/definition/schema/result.rb +17 -0
  21. data/lib/openapi_first/{schema_validation.rb → definition/schema.rb} +6 -6
  22. data/lib/openapi_first/definition.rb +26 -6
  23. data/lib/openapi_first/error_response.rb +28 -12
  24. data/lib/openapi_first/error_responses/default.rb +58 -0
  25. data/lib/openapi_first/error_responses/json_api.rb +58 -0
  26. data/lib/openapi_first/request_body_validator.rb +18 -22
  27. data/lib/openapi_first/request_validation.rb +68 -58
  28. data/lib/openapi_first/request_validation_error.rb +31 -0
  29. data/lib/openapi_first/response_validation.rb +33 -13
  30. data/lib/openapi_first/response_validator.rb +1 -0
  31. data/lib/openapi_first/router.rb +20 -62
  32. data/lib/openapi_first/version.rb +1 -1
  33. data/lib/openapi_first.rb +2 -13
  34. data/openapi_first.gemspec +8 -5
  35. metadata +44 -57
  36. data/.rspec +0 -3
  37. data/.rubocop.yml +0 -14
  38. data/Rakefile +0 -15
  39. data/benchmarks/Gemfile +0 -16
  40. data/benchmarks/Gemfile.lock +0 -131
  41. data/benchmarks/README.md +0 -29
  42. data/benchmarks/apps/committee_with_hanami_api.ru +0 -26
  43. data/benchmarks/apps/committee_with_response_validation.ru +0 -29
  44. data/benchmarks/apps/committee_with_sinatra.ru +0 -31
  45. data/benchmarks/apps/grape.ru +0 -21
  46. data/benchmarks/apps/hanami_api.ru +0 -21
  47. data/benchmarks/apps/hanami_router.ru +0 -14
  48. data/benchmarks/apps/openapi.yaml +0 -268
  49. data/benchmarks/apps/openapi_first_with_hanami_api.ru +0 -24
  50. data/benchmarks/apps/openapi_first_with_plain_rack.ru +0 -32
  51. data/benchmarks/apps/openapi_first_with_response_validation.ru +0 -25
  52. data/benchmarks/apps/openapi_first_with_sinatra.ru +0 -29
  53. data/benchmarks/apps/roda.ru +0 -27
  54. data/benchmarks/apps/sinatra.ru +0 -26
  55. data/benchmarks/apps/syro.ru +0 -25
  56. data/benchmarks/benchmark-wrk.sh +0 -3
  57. data/benchmarks/benchmarks.rb +0 -48
  58. data/benchmarks/post.lua +0 -3
  59. data/bin/console +0 -15
  60. data/bin/setup +0 -8
  61. data/examples/README.md +0 -13
  62. data/examples/app.rb +0 -18
  63. data/examples/config.ru +0 -7
  64. data/examples/openapi.yaml +0 -29
  65. data/lib/openapi_first/body_parser_middleware.rb +0 -53
  66. data/lib/openapi_first/default_error_response.rb +0 -47
  67. data/lib/openapi_first/operation.rb +0 -142
  68. data/lib/openapi_first/operation_schemas.rb +0 -52
  69. data/lib/openapi_first/string_keyed_hash.rb +0 -20
  70. data/lib/openapi_first/validation_result.rb +0 -15
@@ -1,19 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'operation'
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, :operations
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 = resolved['paths'].flat_map do |path, path_item|
14
- path_item.slice(*methods).map do |request_method, _operation_object|
15
- Operation.new(path, request_method, path_item, openapi_version: detect_version(resolved))
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 status [Integer] The HTTP status code.
7
- ## @param title [String] The title of the error. Usually the name of the HTTP status code.
8
- ## @param location [Symbol] The location of the error (:request_body, :query, :header, :cookie, :path).
9
- ## @param validation_result [ValidationResult]
10
- def initialize(status:, location:, title:, validation_result:)
11
- @status = status
12
- @title = title
13
- @location = location
14
- @validation_output = validation_result&.output
15
- @schema = validation_result&.schema
16
- @data = validation_result&.data
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
- attr_reader :status, :location, :title, :schema, :data, :validation_output
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
- 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
11
+ request_body = @operation.request_body
12
+ return unless request_body
18
13
 
19
- def validate_request_content_type!(operation, content_type)
20
- operation.valid_request_content_type?(content_type) || OpenapiFirst.error!(415)
21
- end
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
- def validate_request_body!(operation, body, content_type)
24
- validate_request_body_presence!(body, operation)
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 = operation&.request_body_schema(content_type)
28
- return unless schema
29
-
30
- validation_result = schema.validate(body)
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
- def validate_request_body_presence!(body, operation)
36
- return unless operation.request_body['required'] && body.nil?
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
- OpenapiFirst.error!(400, :request_body, title: 'Request body is required')
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 'string_keyed_hash'
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, false)
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
- location, title = error.values_at(:location, :title)
29
- raise RequestInvalidError, error_message(title, location) if @raise
50
+ raise RequestInvalidError, error.error_message if @raise
30
51
 
31
- return error_response(error).render
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(:error) do
61
+ catch(FAIL) do
59
62
  env[PARAMS] = {}
60
- validate_query_params!(operation, env)
61
- validate_path_params!(operation, env)
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
- path_parameters = operation.path_parameters
71
- return if path_parameters.empty?
72
-
73
- hashy = StringKeyedHash.new(env[Router::RAW_PATH_PARAMS])
74
- unpacked_path_params = OpenapiParameters::Path.new(path_parameters).unpack(hashy)
75
- validation_result = operation.schemas.path_parameters_schema.validate(unpacked_path_params)
76
- OpenapiFirst.error!(400, :path, validation_result:) if validation_result.error?
77
- env[PATH_PARAMS] = unpacked_path_params
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
- query_parameters = operation.query_parameters
83
- return if operation.query_parameters.empty?
84
-
85
- unpacked_query_params = OpenapiParameters::Query.new(query_parameters).unpack(env['QUERY_STRING'])
86
- validation_result = operation.schemas.query_parameters_schema.validate(unpacked_query_params)
87
- OpenapiFirst.error!(400, :query, validation_result:) if validation_result.error?
88
- env[QUERY_PARAMS] = unpacked_query_params
89
- env[PARAMS].merge!(unpacked_query_params)
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
- cookie_parameters = operation.cookie_parameters
94
- return unless cookie_parameters&.any?
99
+ parameters = operation.cookie_parameters
100
+ return unless parameters
95
101
 
96
- unpacked_params = OpenapiParameters::Cookie.new(cookie_parameters).unpack(env['HTTP_COOKIE'])
97
- validation_result = operation.schemas.cookie_parameters_schema.validate(unpacked_params)
98
- OpenapiFirst.error!(400, :cookie, validation_result:) if validation_result.error?
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
- header_parameters = operation.header_parameters
104
- return if header_parameters.empty?
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
- unpacked_header_params = OpenapiParameters::Header.new(header_parameters).unpack_env(env)
107
- validation_result = operation.schemas.header_parameters_schema.validate(unpacked_header_params)
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
- return validate_status_only(operation, status) if status == 204
25
+ response_definition = response_for(operation, status)
26
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)
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
- operation.response_for(status)
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(operation, status, 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: operation.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 = SchemaValidation.new(definition['schema'], openapi_version:)
88
+ validation = Schema.new(definition['schema'], openapi_version:)
69
89
  value = unpacked_headers[name]
70
- validation_result = validation.validate(value)
71
- raise ResponseHeaderInvalidError, validation_result.message if validation_result.error?
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
@@ -4,6 +4,7 @@ require_relative 'response_validation'
4
4
  require_relative 'router'
5
5
 
6
6
  module OpenapiFirst
7
+ # A class to run manual response validation
7
8
  class ResponseValidator
8
9
  def initialize(spec)
9
10
  @spec = spec