openapi_first 1.2.1 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openapi_first/body_parser.rb +1 -0
  3. data/lib/openapi_first/configuration.rb +3 -1
  4. data/lib/openapi_first/definition/operation.rb +66 -5
  5. data/lib/openapi_first/definition/path_item.rb +1 -0
  6. data/lib/openapi_first/definition/request_body.rb +2 -1
  7. data/lib/openapi_first/definition/response.rb +7 -0
  8. data/lib/openapi_first/definition.rb +40 -0
  9. data/lib/openapi_first/error_response.rb +1 -1
  10. data/lib/openapi_first/errors.rb +6 -0
  11. data/lib/openapi_first/failure.rb +13 -2
  12. data/lib/openapi_first/middlewares/request_validation.rb +2 -5
  13. data/lib/openapi_first/middlewares/response_validation.rb +1 -4
  14. data/lib/openapi_first/plugins/default/error_response.rb +4 -4
  15. data/lib/openapi_first/plugins/default.rb +1 -1
  16. data/lib/openapi_first/plugins/jsonapi/error_response.rb +3 -2
  17. data/lib/openapi_first/plugins/jsonapi.rb +1 -1
  18. data/lib/openapi_first/plugins.rb +1 -0
  19. data/lib/openapi_first/request_validation/request_body_validator.rb +1 -1
  20. data/lib/openapi_first/request_validation/validator.rb +1 -0
  21. data/lib/openapi_first/response_validation/validator.rb +1 -0
  22. data/lib/openapi_first/runtime_request.rb +63 -3
  23. data/lib/openapi_first/runtime_response.rb +43 -4
  24. data/lib/openapi_first/schema/validation_error.rb +2 -0
  25. data/lib/openapi_first/schema/validation_result.rb +2 -0
  26. data/lib/openapi_first/schema.rb +1 -0
  27. data/lib/openapi_first/version.rb +1 -1
  28. data/lib/openapi_first.rb +8 -0
  29. metadata +5 -16
  30. data/.github/CODEOWNERS +0 -1
  31. data/.github/workflows/ruby.yml +0 -13
  32. data/.gitignore +0 -11
  33. data/CHANGELOG.md +0 -279
  34. data/Gemfile +0 -18
  35. data/Gemfile.lock +0 -166
  36. data/Gemfile.rack2 +0 -15
  37. data/Gemfile.rack2.lock +0 -95
  38. data/LICENSE.txt +0 -21
  39. data/README.md +0 -225
  40. data/openapi_first.gemspec +0 -47
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ee379d839bec13bd899f690b3275d761b7425461f2241da4ccb62de384f0c94
4
- data.tar.gz: 6ea27d437c7cb443d5b5c84b8e6d0ef34782e4f5d8bb5ce7666acc78797ca523
3
+ metadata.gz: f7e9e0bad95211ba2b4f103089abc40d0e7fc46a24f7ce8e57318ba489655637
4
+ data.tar.gz: f1d28fbaa79a4eb7e00b242eb9bd89a78789bbb04742e0b87501ac454fb1cf47
5
5
  SHA512:
6
- metadata.gz: ab20a8bcd7d61961f4a1d654a36291f295ee0d69c50829cd8f1965998ca9a480a0636e2b0ca62d220a53c62b60d8bedb934fc8a57bed39a8e45683796a5cee8a
7
- data.tar.gz: 43206c1bf540193863bd1f02c14f79d172fff662974c8efbfd90dfd6334f11f23c0dee62c9c70c4bd45bbf202e68a76c9750a00aa4a45f5dd8682b6837a1177b
6
+ metadata.gz: f225e3a49d7f582e0f82822b2c9acbcd6008b6cc2464971e1485ef75ec12e508effc2165b6afc91535e27bc9bb9004f92fb160218f7295b0f45c3f58946ed616
7
+ data.tar.gz: 4c6ef209df4816361bed148f33b4e55ace50a5481172913c5760d014b09247adf74ffdbd2422ae930cd889494fe941c20d492835863d52a9dde67a75c1bd8622
@@ -3,6 +3,7 @@
3
3
  require 'multi_json'
4
4
 
5
5
  module OpenapiFirst
6
+ # @!visibility private
6
7
  class BodyParser
7
8
  def self.const_missing(const_name)
8
9
  super unless const_name == :ParsingError
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
+ # Global configuration. Currently only used for the request validation middleware.
4
5
  class Configuration
5
6
  def initialize
6
7
  @request_validation_error_response = OpenapiFirst.plugin(:default)::ErrorResponse
7
8
  @request_validation_raise_error = false
8
9
  end
9
10
 
10
- attr_reader :request_validation_error_response, :request_validation_raise_error
11
+ attr_reader :request_validation_error_response
12
+ attr_accessor :request_validation_raise_error
11
13
 
12
14
  def request_validation_error_response=(mod)
13
15
  @request_validation_error_response = if mod.is_a?(Symbol)
@@ -8,14 +8,15 @@ require_relative 'responses'
8
8
 
9
9
  module OpenapiFirst
10
10
  class Definition
11
+ # Represents an operation object in the OpenAPI 3.X specification.
12
+ # Use this class to access information about the operation. Use `#[key]` to read the raw data.
13
+ # When using the middleware you can access the operation object via `env[OpenapiFirst::REQUEST].operation`.
11
14
  class Operation
12
15
  extend Forwardable
16
+
13
17
  def_delegators :operation_object,
14
18
  :[]
15
19
 
16
- WRITE_METHODS = Set.new(%w[post put patch delete]).freeze
17
- private_constant :WRITE_METHODS
18
-
19
20
  def initialize(path, request_method, path_item_object, openapi_version:)
20
21
  @path = path
21
22
  @method = request_method
@@ -24,31 +25,63 @@ module OpenapiFirst
24
25
  @operation_object = @path_item_object[request_method]
25
26
  end
26
27
 
27
- attr_reader :path, :method, :openapi_version
28
+ # Returns the path of the operation as in the API description.
29
+ # @return [String] The path of the operation.
30
+ attr_reader :path
31
+
32
+ # Returns the (downcased) request method of the operation.
33
+ # Example: "get"
34
+ # @return [String] The request method of the operation.
35
+ attr_reader :method
28
36
  alias request_method method
29
37
 
38
+ attr_reader :openapi_version # :nodoc:
39
+
40
+ # Returns the operation ID as defined in the API description.
41
+ # @return [String, nil]
30
42
  def operation_id
31
43
  operation_object['operationId']
32
44
  end
33
45
 
46
+ # Checks if the operation is a read operation.
47
+ # This is the case for all request methods except POST, PUT, PATCH and DELETE.
48
+ # @return [Boolean] `true` if the operation is a read operation, `false` otherwise.
34
49
  def read?
35
50
  !write?
36
51
  end
37
52
 
53
+ # Checks if the operation is a write operation.
54
+ # This is the case for POST, PUT, PATCH and DELETE request methods.
55
+ # @return [Boolean] `true` if the operation is a write operation, `false` otherwise.
56
+ # @deprecated Use {#write?} instead.
38
57
  def write?
39
58
  WRITE_METHODS.include?(method)
40
59
  end
41
60
 
61
+ # Returns the request body definition if defined in the API description.
62
+ # @return [RequestBody, nil] The request body of the operation, or `nil` if not present.
42
63
  def request_body
43
64
  @request_body ||= RequestBody.new(operation_object['requestBody'], self) if operation_object['requestBody']
44
65
  end
45
66
 
67
+ # Checks if a response status is defined for this operation.
68
+ # @param status [Integer, String] The response status to check.
69
+ # @return [Boolean] `true` if the response status is defined, `false` otherwise.
46
70
  def response_status_defined?(status)
47
71
  responses.status_defined?(status)
48
72
  end
49
73
 
50
- def_delegators :responses, :response_for
74
+ # Returns the response object for a given status.
75
+ # @param status [Integer, String] The response status.
76
+ # @param content_type [String] Content-Type of the current response.
77
+ # @return [Response, nil] The response object for the given status, or `nil` if not found.
78
+ def response_for(status, content_type)
79
+ responses.response_for(status, content_type)
80
+ end
51
81
 
82
+ # Returns the schema for a given content type.
83
+ # @param content_type [String] The content type.
84
+ # @return [Schema, nil] The schema for the given content type, or `nil` if not found.
52
85
  def schema_for(content_type)
53
86
  content = @request_body_object['content']
54
87
  return unless content&.any?
@@ -59,44 +92,72 @@ module OpenapiFirst
59
92
  end
60
93
  end
61
94
 
95
+ # Returns a unique name for this operation. Used for generating error messages.
96
+ # @visibility private
62
97
  def name
63
98
  @name ||= "#{method.upcase} #{path} (#{operation_id})"
64
99
  end
65
100
 
101
+ # Returns the path parameters of the operation.
102
+ # @return [Array<Hash>] The path parameters of the operation.
66
103
  def path_parameters
67
104
  all_parameters['path']
68
105
  end
69
106
 
107
+ # Returns the query parameters of the operation.
108
+ # Returns parameters defined on the path and in the operation.
109
+ # @return [Array<Hash>] The query parameters of the operation.
70
110
  def query_parameters
71
111
  all_parameters['query']
72
112
  end
73
113
 
114
+ # Returns the header parameters of the operation.
115
+ # Returns parameters defined on the path and in the operation.
116
+ # @return [Array<Hash>] The header parameters of the operation.
74
117
  def header_parameters
75
118
  all_parameters['header']
76
119
  end
77
120
 
121
+ # Returns the cookie parameters of the operation.
122
+ # Returns parameters defined on the path and in the operation.
123
+ # @return [Array<Hash>] The cookie parameters of the operation.
78
124
  def cookie_parameters
79
125
  all_parameters['cookie']
80
126
  end
81
127
 
128
+ # Returns the schema for the path parameters.
129
+ # @visibility private
130
+ # @return [Schema, nil] The schema for the path parameters, or `nil` if not found.
82
131
  def path_parameters_schema
83
132
  @path_parameters_schema ||= build_schema(path_parameters)
84
133
  end
85
134
 
135
+ # Returns the schema for the query parameters.
136
+ # @visibility private
137
+ # @return [Schema, nil] The schema for the query parameters, or `nil` if not found.
86
138
  def query_parameters_schema
87
139
  @query_parameters_schema ||= build_schema(query_parameters)
88
140
  end
89
141
 
142
+ # Returns the schema for the header parameters.
143
+ # @visibility private
144
+ # @return [Schema, nil] The schema for the header parameters, or `nil` if not found.
90
145
  def header_parameters_schema
91
146
  @header_parameters_schema ||= build_schema(header_parameters)
92
147
  end
93
148
 
149
+ # Returns the schema for the cookie parameters.
150
+ # @visibility private
151
+ # @return [Schema, nil] The schema for the cookie parameters, or `nil` if not found.
94
152
  def cookie_parameters_schema
95
153
  @cookie_parameters_schema ||= build_schema(cookie_parameters)
96
154
  end
97
155
 
98
156
  private
99
157
 
158
+ WRITE_METHODS = Set.new(%w[post put patch delete]).freeze
159
+ private_constant :WRITE_METHODS
160
+
100
161
  IGNORED_HEADERS = Set['Content-Type', 'Accept', 'Authorization'].freeze
101
162
  private_constant :IGNORED_HEADERS
102
163
 
@@ -4,6 +4,7 @@ require_relative 'operation'
4
4
 
5
5
  module OpenapiFirst
6
6
  class Definition
7
+ # A pathItem as defined in the OpenAPI document.
7
8
  class PathItem
8
9
  def initialize(path, path_item_object, openapi_version:)
9
10
  @path = path
@@ -4,6 +4,7 @@ require_relative '../schema'
4
4
 
5
5
  module OpenapiFirst
6
6
  class Definition
7
+ # Represents a request body definition in the OpenAPI document that belongs to an operation.
7
8
  class RequestBody
8
9
  def initialize(request_body_object, operation)
9
10
  @request_body_object = request_body_object
@@ -36,7 +37,7 @@ module OpenapiFirst
36
37
  schema_object = media_type['schema']
37
38
  next unless schema_object
38
39
 
39
- result[type] = Schema.new(schema_object, write: @operation.write?,
40
+ result[type] = Schema.new(schema_object, write: true,
40
41
  openapi_version: @operation.openapi_version)
41
42
  end
42
43
  end
@@ -2,6 +2,9 @@
2
2
 
3
3
  module OpenapiFirst
4
4
  class Definition
5
+ # Represents a response definition in the OpenAPI document.
6
+ # This is not a direct reflecton of the OpenAPI 3.X response definition, but a combination of
7
+ # status, content type and content schema.
5
8
  class Response
6
9
  def initialize(operation:, status:, response_object:, content_type:, content_schema:)
7
10
  @operation = operation
@@ -11,6 +14,10 @@ module OpenapiFirst
11
14
  @content_schema = content_schema
12
15
  end
13
16
 
17
+ # @attr_reader [Operation] operation The operation this response belongs to.
18
+ # @attr_reader [Integer] status The HTTP status code of the response definition.
19
+ # @attr_reader [String, nil] content_type Content type of this response.
20
+ # @attr_reader [Schema, nil] content_schema the Schema of the response body.
14
21
  attr_reader :operation, :status, :content_type, :content_schema
15
22
 
16
23
  def headers
@@ -3,18 +3,45 @@
3
3
  require 'mustermann'
4
4
  require_relative 'definition/path_item'
5
5
  require_relative 'runtime_request'
6
+ require_relative 'request_validation/validator'
7
+ require_relative 'response_validation/validator'
6
8
 
7
9
  module OpenapiFirst
8
10
  # Represents an OpenAPI API Description document
11
+ # This is returned by OpenapiFirst.load.
9
12
  class Definition
10
13
  attr_reader :filepath, :paths, :openapi_version
11
14
 
15
+ # @param resolved [Hash] The resolved OpenAPI document.
16
+ # @param filepath [String] The file path of the OpenAPI document.
12
17
  def initialize(resolved, filepath = nil)
13
18
  @filepath = filepath
14
19
  @paths = resolved['paths']
15
20
  @openapi_version = detect_version(resolved)
16
21
  end
17
22
 
23
+ # Validates the request against the API description.
24
+ # @param rack_request [Rack::Request] The Rack request object.
25
+ # @param raise_error [Boolean] Whether to raise an error if validation fails.
26
+ # @return [RuntimeRequest] The validated request object.
27
+ def validate_request(rack_request, raise_error: false)
28
+ validated = request(rack_request).tap(&:validate)
29
+ validated.error&.raise! if raise_error
30
+ validated
31
+ end
32
+
33
+ # Validates the response against the API description.
34
+ # @param rack_request [Rack::Request] The Rack request object.
35
+ # @param rack_response [Rack::Response] The Rack response object.
36
+ # @param raise_error [Boolean] Whether to raise an error if validation fails.
37
+ # @return [RuntimeResponse] The validated response object.
38
+ def validate_response(rack_request, rack_response, raise_error: false)
39
+ request(rack_request).validate_response(rack_response, raise_error:)
40
+ end
41
+
42
+ # Builds a RuntimeRequest object based on the Rack request.
43
+ # @param rack_request [Rack::Request] The Rack request object.
44
+ # @return [RuntimeRequest] The RuntimeRequest object.
18
45
  def request(rack_request)
19
46
  path_item, path_params = find_path_item_and_params(rack_request.path)
20
47
  operation = path_item&.operation(rack_request.request_method.downcase)
@@ -26,14 +53,25 @@ module OpenapiFirst
26
53
  )
27
54
  end
28
55
 
56
+ # Builds a RuntimeResponse object based on the Rack request and response.
57
+ # @param rack_request [Rack::Request] The Rack request object.
58
+ # @param rack_response [Rack::Response] The Rack response object.
59
+ # @return [RuntimeResponse] The RuntimeResponse object.
29
60
  def response(rack_request, rack_response)
30
61
  request(rack_request).response(rack_response)
31
62
  end
32
63
 
64
+ # Gets all the operations defined in the API description.
65
+ # @return [Array<Operation>] An array of Operation objects.
33
66
  def operations
34
67
  @operations ||= path_items.flat_map(&:operations)
35
68
  end
36
69
 
70
+ # Gets the PathItem object for the specified path.
71
+ # @param pathname [String] The path template string.
72
+ # @return [PathItem] The PathItem object.
73
+ # Example:
74
+ # definition.path('/pets/{id}')
37
75
  def path(pathname)
38
76
  return unless paths.key?(pathname)
39
77
 
@@ -42,6 +80,8 @@ module OpenapiFirst
42
80
 
43
81
  private
44
82
 
83
+ # Gets all the PathItem objects defined in the API description.
84
+ # @return [Array] An array of PathItem objects.
45
85
  def path_items
46
86
  @path_items ||= paths.flat_map do |path, path_item_object|
47
87
  PathItem.new(path, path_item_object, openapi_version:)
@@ -29,7 +29,7 @@ module OpenapiFirst
29
29
 
30
30
  # The response status
31
31
  def status
32
- STATUS[failure.error_type] || 400
32
+ STATUS[failure.type] || 400
33
33
  end
34
34
 
35
35
  # Render this error response
@@ -1,10 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
+ # @!visibility private
4
5
  class Error < StandardError; end
6
+ # @!visibility private
5
7
  class ParseError < Error; end
8
+ # @!visibility private
6
9
  class NotFoundError < Error; end
10
+ # @!visibility private
7
11
  class RequestInvalidError < Error; end
12
+ # @!visibility private
8
13
  class ResponseNotFoundError < Error; end
14
+ # @!visibility private
9
15
  class ResponseInvalidError < Error; end
10
16
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
+ # A failure object returned when validation of request or response has failed.
4
5
  class Failure
5
6
  FAILURE = :openapi_first_validation_failure
6
7
 
@@ -29,7 +30,7 @@ module OpenapiFirst
29
30
  )
30
31
  end
31
32
 
32
- # @param type [Symbol] See TYPES.keys
33
+ # @param error_type [Symbol] See TYPES.keys
33
34
  # @param message [String] A generic error message
34
35
  # @param errors [Array<OpenapiFirst::Schema::ValidationError>]
35
36
  def initialize(error_type, message: nil, errors: nil)
@@ -43,7 +44,17 @@ module OpenapiFirst
43
44
  @errors = errors
44
45
  end
45
46
 
46
- attr_reader :error_type, :message, :errors
47
+ # @attr_reader [Symbol] error_type The type of the failure. See TYPES.keys.
48
+ # @alias type error_type
49
+ # Example: :invalid_body
50
+ attr_reader :error_type
51
+ alias type error_type
52
+
53
+ # @attr_reader [String] message A generic error message
54
+ attr_reader :message
55
+
56
+ # @attr_reader [Array<OpenapiFirst::Schema::ValidationError>] errors Schema validation errors
57
+ attr_reader :errors
47
58
 
48
59
  # Raise an exception that fits the failure.
49
60
  def raise!
@@ -26,11 +26,8 @@ module OpenapiFirst
26
26
  request = find_request(env)
27
27
  return @app.call(env) unless request
28
28
 
29
- failure = if @raise
30
- request.validate!
31
- else
32
- request.validate
33
- end
29
+ failure = request.validate
30
+ failure.raise! if failure && @raise
34
31
  return @error_response_class.new(failure:).render if failure
35
32
 
36
33
  @app.call(env)
@@ -20,11 +20,8 @@ module OpenapiFirst
20
20
  def call(env)
21
21
  request = find_request(env)
22
22
  status, headers, body = @app.call(env)
23
-
24
23
  body = body.to_ary if body.respond_to?(:to_ary)
25
-
26
- request.response(Rack::Response[status, headers, body]).validate!
27
-
24
+ request.validate_response(Rack::Response[status, headers, body], raise_error: true)
28
25
  [status, headers, body]
29
26
  end
30
27
 
@@ -29,10 +29,10 @@ module OpenapiFirst
29
29
  MultiJson.dump(result)
30
30
  end
31
31
 
32
- def error_type = failure.error_type
32
+ def type = failure.type
33
33
 
34
34
  def title
35
- TITLES.fetch(error_type)
35
+ TITLES.fetch(type)
36
36
  end
37
37
 
38
38
  def content_type
@@ -51,7 +51,7 @@ module OpenapiFirst
51
51
  end
52
52
 
53
53
  def pointer_key
54
- case error_type
54
+ case type
55
55
  when :invalid_body
56
56
  :pointer
57
57
  when :invalid_query, :invalid_path
@@ -64,7 +64,7 @@ module OpenapiFirst
64
64
  end
65
65
 
66
66
  def pointer(data_pointer)
67
- return data_pointer if error_type == :invalid_body
67
+ return data_pointer if type == :invalid_body
68
68
 
69
69
  data_pointer.delete_prefix('/')
70
70
  end
@@ -4,7 +4,7 @@ require_relative 'default/error_response'
4
4
 
5
5
  module OpenapiFirst
6
6
  module Plugins
7
- module Default
7
+ module Default # :nodoc:
8
8
  OpenapiFirst.register(:default, self)
9
9
  end
10
10
  end
@@ -3,6 +3,7 @@
3
3
  module OpenapiFirst
4
4
  module Plugins
5
5
  module Jsonapi
6
+ # A JSON:API conform error response. See https://jsonapi.org/.
6
7
  class ErrorResponse
7
8
  include OpenapiFirst::ErrorResponse
8
9
 
@@ -36,7 +37,7 @@ module OpenapiFirst
36
37
  end
37
38
 
38
39
  def pointer_key
39
- case failure.error_type
40
+ case failure.type
40
41
  when :invalid_body
41
42
  :pointer
42
43
  when :invalid_query, :invalid_path
@@ -49,7 +50,7 @@ module OpenapiFirst
49
50
  end
50
51
 
51
52
  def pointer(data_pointer)
52
- return data_pointer if failure.error_type == :invalid_body
53
+ return data_pointer if failure.type == :invalid_body
53
54
 
54
55
  data_pointer.delete_prefix('/')
55
56
  end
@@ -4,7 +4,7 @@ require_relative 'jsonapi/error_response'
4
4
 
5
5
  module OpenapiFirst
6
6
  module Plugins
7
- module Jsonapi
7
+ module Jsonapi # :nodoc:
8
8
  OpenapiFirst.register(:jsonapi, self)
9
9
  end
10
10
  end
@@ -4,6 +4,7 @@ module OpenapiFirst
4
4
  # Plugin System adapted from
5
5
  # Polished Ruby Programming by Jeremy Evans
6
6
  # https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewBook?id=0
7
+ # @!visibility private
7
8
  module Plugins
8
9
  PLUGINS = {} # rubocop:disable Style/MutableConstant
9
10
 
@@ -4,7 +4,7 @@ require_relative '../failure'
4
4
 
5
5
  module OpenapiFirst
6
6
  module RequestValidation
7
- class RequestBodyValidator
7
+ class RequestBodyValidator # :nodoc:
8
8
  def initialize(operation)
9
9
  @operation = operation
10
10
  end
@@ -5,6 +5,7 @@ require_relative 'request_body_validator'
5
5
 
6
6
  module OpenapiFirst
7
7
  module RequestValidation
8
+ # Validates a RuntimeRequest against an Operation.
8
9
  class Validator
9
10
  def initialize(operation)
10
11
  @operation = operation
@@ -4,6 +4,7 @@ require_relative '../failure'
4
4
 
5
5
  module OpenapiFirst
6
6
  module ResponseValidation
7
+ # Validates a RuntimeResponse against an Operation.
7
8
  class Validator
8
9
  def initialize(operation)
9
10
  @operation = operation
@@ -16,31 +16,59 @@ module OpenapiFirst
16
16
  @path_item = path_item
17
17
  @operation = operation
18
18
  @original_path_params = path_params
19
+ @error = nil
20
+ @validated = false
19
21
  end
20
22
 
21
23
  def_delegators :@request, :content_type, :media_type, :path
22
24
  def_delegators :@operation, :operation_id, :request_method
23
25
  def_delegator :@path_item, :path, :path_definition
24
26
 
25
- attr_reader :path_item, :operation
27
+ # Returns the path_item object.
28
+ # @return [PathItem, nil] The path_item object or nil if this request path is not known.
29
+ attr_reader :path_item
26
30
 
31
+ # Returns the operation object.
32
+ # @return [Operation, nil] The operation object or nil if this request method is not known.
33
+ attr_reader :operation
34
+
35
+ # Returns the error object if validation failed.
36
+ # @return [Failure, nil]
37
+ attr_reader :error
38
+
39
+ # Checks if the request is valid.
40
+ # @return [Boolean] true if the request is valid, false otherwise.
41
+ def valid?
42
+ validate unless @validated
43
+ error.nil?
44
+ end
45
+
46
+ # Checks if the path and request method are known.
47
+ # @return [Boolean] true if the path and request method are known, false otherwise.
27
48
  def known?
28
49
  known_path? && known_request_method?
29
50
  end
30
51
 
52
+ # Checks if the path is known.
53
+ # @return [Boolean] true if the path is known, false otherwise.
31
54
  def known_path?
32
55
  !!path_item
33
56
  end
34
57
 
58
+ # Checks if the request method is known.
59
+ # @return [Boolean] true if the request method is known, false otherwise.
35
60
  def known_request_method?
36
61
  !!operation
37
62
  end
38
63
 
39
- # Merged path and query parameters
64
+ # Returns the merged path and query parameters.
65
+ # @return [Hash] The merged path and query parameters.
40
66
  def params
41
67
  @params ||= query.merge(path_parameters)
42
68
  end
43
69
 
70
+ # Returns the parsed path parameters of the request.
71
+ # @return [Hash]
44
72
  def path_parameters
45
73
  return {} unless operation.path_parameters
46
74
 
@@ -48,6 +76,10 @@ module OpenapiFirst
48
76
  OpenapiParameters::Path.new(operation.path_parameters).unpack(@original_path_params) || {}
49
77
  end
50
78
 
79
+ # Returns the parsed query parameters.
80
+ # This only includes parameters that are defined in the API description.
81
+ # @note This method is aliased as query_parameters.
82
+ # @return [Hash]
51
83
  def query
52
84
  return {} unless operation.query_parameters
53
85
 
@@ -57,12 +89,18 @@ module OpenapiFirst
57
89
 
58
90
  alias query_parameters query
59
91
 
92
+ # Returns the parsed header parameters.
93
+ # This only includes parameters that are defined in the API description.
94
+ # @return [Hash]
60
95
  def headers
61
96
  return {} unless operation.header_parameters
62
97
 
63
98
  @headers ||= OpenapiParameters::Header.new(operation.header_parameters).unpack_env(request.env) || {}
64
99
  end
65
100
 
101
+ # Returns the parsed cookie parameters.
102
+ # This only includes parameters that are defined in the API description.
103
+ # @return [Hash]
66
104
  def cookies
67
105
  return {} unless operation.cookie_parameters
68
106
 
@@ -70,20 +108,42 @@ module OpenapiFirst
70
108
  OpenapiParameters::Cookie.new(operation.cookie_parameters).unpack(request.env[Rack::HTTP_COOKIE]) || {}
71
109
  end
72
110
 
111
+ # Returns the parsed request body.
112
+ # This returns the whole request body with default values applied as defined in the API description.
113
+ # This does not remove any fields that are not defined in the API description.
114
+ # @return [Hash, Array, String, nil] The parsed body of the request.
73
115
  def body
74
116
  @body ||= BodyParser.new.parse(request, request.media_type)
75
117
  end
118
+
76
119
  alias parsed_body body
77
120
 
121
+ # Validates the request.
122
+ # @return [Failure, nil] The Failure object if validation failed.
78
123
  def validate
79
- RequestValidation::Validator.new(operation).validate(self)
124
+ @validated = true
125
+ @error = RequestValidation::Validator.new(operation).validate(self)
80
126
  end
81
127
 
128
+ # Validates the request and raises an error if validation fails.
82
129
  def validate!
83
130
  error = validate
84
131
  error&.raise!
85
132
  end
86
133
 
134
+ # Validates the response.
135
+ # @param rack_response [Rack::Response] The rack response object.
136
+ # @param raise_error [Boolean] Whether to raise an error if validation fails.
137
+ # @return [RuntimeResponse] The validated response object.
138
+ def validate_response(rack_response, raise_error: false)
139
+ validated = response(rack_response).tap(&:validate)
140
+ validated.error&.raise! if raise_error
141
+ validated
142
+ end
143
+
144
+ # Creates a new RuntimeResponse object.
145
+ # @param rack_response [Rack::Response] The rack response object.
146
+ # @return [RuntimeResponse] The RuntimeResponse object.
87
147
  def response(rack_response)
88
148
  RuntimeResponse.new(operation, rack_response)
89
149
  end