openapi_first 1.2.1 → 1.3.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.
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