openapi_first 1.4.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -1
  3. data/README.md +105 -28
  4. data/lib/openapi_first/body_parser.rb +8 -11
  5. data/lib/openapi_first/builder.rb +81 -0
  6. data/lib/openapi_first/configuration.rb +24 -3
  7. data/lib/openapi_first/definition.rb +44 -100
  8. data/lib/openapi_first/error_response.rb +2 -2
  9. data/lib/openapi_first/error_responses/default.rb +73 -0
  10. data/lib/openapi_first/error_responses/jsonapi.rb +59 -0
  11. data/lib/openapi_first/errors.rb +26 -4
  12. data/lib/openapi_first/failure.rb +29 -26
  13. data/lib/openapi_first/json_refs.rb +1 -3
  14. data/lib/openapi_first/middlewares/request_validation.rb +2 -2
  15. data/lib/openapi_first/middlewares/response_validation.rb +4 -3
  16. data/lib/openapi_first/request.rb +92 -0
  17. data/lib/openapi_first/request_parser.rb +35 -0
  18. data/lib/openapi_first/request_validator.rb +25 -0
  19. data/lib/openapi_first/response.rb +57 -0
  20. data/lib/openapi_first/response_parser.rb +49 -0
  21. data/lib/openapi_first/response_validator.rb +27 -0
  22. data/lib/openapi_first/router/find_content.rb +17 -0
  23. data/lib/openapi_first/router/find_response.rb +45 -0
  24. data/lib/openapi_first/{definition → router}/path_template.rb +9 -1
  25. data/lib/openapi_first/router.rb +100 -0
  26. data/lib/openapi_first/schema/validation_error.rb +16 -10
  27. data/lib/openapi_first/schema/validation_result.rb +8 -6
  28. data/lib/openapi_first/schema.rb +4 -8
  29. data/lib/openapi_first/test/methods.rb +21 -0
  30. data/lib/openapi_first/test.rb +19 -0
  31. data/lib/openapi_first/validated_request.rb +81 -0
  32. data/lib/openapi_first/validated_response.rb +33 -0
  33. data/lib/openapi_first/validators/request_body.rb +39 -0
  34. data/lib/openapi_first/validators/request_parameters.rb +61 -0
  35. data/lib/openapi_first/validators/response_body.rb +30 -0
  36. data/lib/openapi_first/validators/response_headers.rb +25 -0
  37. data/lib/openapi_first/version.rb +1 -1
  38. data/lib/openapi_first.rb +40 -21
  39. metadata +25 -20
  40. data/lib/openapi_first/definition/operation.rb +0 -197
  41. data/lib/openapi_first/definition/path_item.rb +0 -40
  42. data/lib/openapi_first/definition/request_body.rb +0 -46
  43. data/lib/openapi_first/definition/response.rb +0 -32
  44. data/lib/openapi_first/definition/responses.rb +0 -87
  45. data/lib/openapi_first/plugins/default/error_response.rb +0 -74
  46. data/lib/openapi_first/plugins/default.rb +0 -11
  47. data/lib/openapi_first/plugins/jsonapi/error_response.rb +0 -60
  48. data/lib/openapi_first/plugins/jsonapi.rb +0 -11
  49. data/lib/openapi_first/plugins.rb +0 -25
  50. data/lib/openapi_first/request_validation/request_body_validator.rb +0 -41
  51. data/lib/openapi_first/request_validation/validator.rb +0 -82
  52. data/lib/openapi_first/response_validation/validator.rb +0 -98
  53. data/lib/openapi_first/runtime_request.rb +0 -166
  54. data/lib/openapi_first/runtime_response.rb +0 -124
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
- class Definition
4
+ class Router
5
5
  # @visibility private
6
6
  class PathTemplate
7
7
  # See also https://spec.openapis.org/oas/v3.1.0#path-templating
@@ -9,12 +9,20 @@ module OpenapiFirst
9
9
  TEMPLATE_EXPRESSION_NAME = /\{([^}]+)\}/
10
10
  ALLOWED_PARAMETER_CHARACTERS = %r{([^/?#]+)}
11
11
 
12
+ def self.template?(string)
13
+ string.include?('{')
14
+ end
15
+
12
16
  def initialize(template)
13
17
  @template = template
14
18
  @names = template.scan(TEMPLATE_EXPRESSION_NAME).flatten
15
19
  @pattern = build_pattern(template)
16
20
  end
17
21
 
22
+ def to_s
23
+ @template
24
+ end
25
+
18
26
  def match(path)
19
27
  return {} if path == @template
20
28
  return if @names.empty?
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'router/path_template'
4
+ require_relative 'router/find_content'
5
+ require_relative 'router/find_response'
6
+
7
+ module OpenapiFirst
8
+ # Router can map requests / responses to their API definition
9
+ class Router
10
+ # Returned by {#match}
11
+ RequestMatch = Data.define(:request_definition, :params, :error, :responses) do
12
+ def match_response(status:, content_type:)
13
+ FindResponse.call(responses, status, content_type, request_method: request_definition.request_method,
14
+ path: request_definition.path)
15
+ end
16
+ end
17
+
18
+ # Returned by {#routes} to introspect all routes
19
+ Route = Data.define(:path, :request_method, :requests, :responses)
20
+
21
+ NOT_FOUND = RequestMatch.new(request_definition: nil, params: nil, responses: nil, error: Failure.new(:not_found))
22
+ private_constant :NOT_FOUND
23
+
24
+ def initialize
25
+ @static = {}
26
+ @dynamic = {} # TODO: use a trie or similar
27
+ end
28
+
29
+ # Returns an enumerator of all routes
30
+ def routes
31
+ @static.chain(@dynamic).lazy.flat_map do |path, request_methods|
32
+ request_methods.filter_map do |request_method, content|
33
+ next if request_method == :template
34
+
35
+ Route.new(path:, request_method:, requests: content[:requests].each_value,
36
+ responses: content[:responses].each_value.lazy.flat_map(&:values))
37
+ end
38
+ end
39
+ end
40
+
41
+ # Add a request definition
42
+ def add_request(request, request_method:, path:, content_type: nil)
43
+ route_at(path, request_method)[:requests][content_type] = request
44
+ end
45
+
46
+ # Add a response definition
47
+ def add_response(response, request_method:, path:, status:, response_content_type: nil)
48
+ (route_at(path, request_method)[:responses][status] ||= {})[response_content_type] = response
49
+ end
50
+
51
+ # Return all request objects that match the given path and request method
52
+ def match(request_method, path, content_type: nil)
53
+ path_item, params = find_path_item(path)
54
+ return NOT_FOUND unless path_item
55
+
56
+ contents = path_item.dig(request_method, :requests)
57
+ return NOT_FOUND.with(error: Failure.new(:method_not_allowed)) unless contents
58
+
59
+ request_definition = FindContent.call(contents, content_type)
60
+ unless request_definition
61
+ message = "#{content_type_err(content_type)} Content-Type should be #{contents.keys.join(' or ')}."
62
+ return NOT_FOUND.with(error: Failure.new(:unsupported_media_type, message:))
63
+ end
64
+
65
+ responses = path_item.dig(request_method, :responses)
66
+ RequestMatch.new(request_definition:, params:, error: nil, responses:)
67
+ end
68
+
69
+ private
70
+
71
+ def route_at(path, request_method)
72
+ request_method = request_method.upcase
73
+ path_item = if PathTemplate.template?(path)
74
+ @dynamic[path] ||= { template: PathTemplate.new(path) }
75
+ else
76
+ @static[path] ||= {}
77
+ end
78
+ path_item[request_method] ||= {
79
+ requests: {},
80
+ responses: {}
81
+ }
82
+ end
83
+
84
+ def content_type_err(content_type)
85
+ return 'Content-Type must not be empty.' if content_type.nil? || content_type.empty?
86
+
87
+ "Content-Type #{content_type} is not defined."
88
+ end
89
+
90
+ def find_path_item(request_path)
91
+ found = @static[request_path]
92
+ return [found, {}] if found
93
+
94
+ @dynamic.find do |_path, path_item|
95
+ params = path_item[:template].match(request_path)
96
+ return [path_item, params] if params
97
+ end
98
+ end
99
+ end
100
+ end
@@ -3,18 +3,24 @@
3
3
  module OpenapiFirst
4
4
  class Schema
5
5
  # One of multiple validation errors. Returned by Schema::ValidationResult#errors.
6
- class ValidationError
7
- def initialize(json_schemer_error)
8
- @error = json_schemer_error
6
+ ValidationError = Data.define(:message, :data_pointer, :schema_pointer, :type, :details) do
7
+ # @deprecated Please use {#message} instead
8
+ def error
9
+ warn 'OpenapiFirst::Schema::ValidationError#error is deprecated. Use #message instead.'
10
+ message
9
11
  end
10
12
 
11
- def error = @error['error']
12
- alias message error
13
- def schemer_error = @error
14
- def instance_location = @error['data_pointer']
15
- def schema_location = @error['schema_pointer']
16
- def type = @error['type']
17
- def details = @error['details']
13
+ # @deprecated Please use {#data_pointer} instead
14
+ def instance_location
15
+ warn 'OpenapiFirst::Schema::ValidationError#instance_location is deprecated. Use #data_pointer instead.'
16
+ data_pointer
17
+ end
18
+
19
+ # @deprecated Please use {#schema_pointer} instead
20
+ def schema_location
21
+ warn 'OpenapiFirst::Schema::ValidationError#schema_location is deprecated. Use #schema_pointer instead.'
22
+ schema_pointer
23
+ end
18
24
  end
19
25
  end
20
26
  end
@@ -6,20 +6,22 @@ module OpenapiFirst
6
6
  class Schema
7
7
  # Result of validating data against a schema. Return value of Schema#validate.
8
8
  class ValidationResult
9
- def initialize(validation, schema:, data:)
9
+ def initialize(validation)
10
10
  @validation = validation
11
- @schema = schema
12
- @data = data
13
11
  end
14
12
 
15
- attr_reader :schema, :data
16
-
17
13
  def error? = @validation.any?
18
14
 
19
15
  # Returns an array of ValidationError objects.
20
16
  def errors
21
17
  @errors ||= @validation.map do |err|
22
- ValidationError.new(err)
18
+ ValidationError.new(
19
+ message: err['error'],
20
+ data_pointer: err['data_pointer'],
21
+ schema_pointer: err['schema_pointer'],
22
+ type: err['type'],
23
+ details: err['details']
24
+ )
23
25
  end
24
26
  end
25
27
  end
@@ -13,24 +13,20 @@ module OpenapiFirst
13
13
  '3.0' => 'json-schemer://openapi30/schema'
14
14
  }.freeze
15
15
 
16
- def initialize(schema, openapi_version:, write: true)
17
- @schema = schema
16
+ def initialize(schema, openapi_version: '3.1', write: true, after_property_validation: nil)
18
17
  @schemer = JSONSchemer.schema(
19
18
  schema,
20
19
  access_mode: write ? 'write' : 'read',
21
20
  meta_schema: SCHEMAS.fetch(openapi_version),
22
21
  insert_property_defaults: true,
23
22
  output_format: 'classic',
24
- before_property_validation: method(:before_property_validation)
23
+ before_property_validation: method(:before_property_validation),
24
+ after_property_validation:
25
25
  )
26
26
  end
27
27
 
28
28
  def validate(data)
29
- ValidationResult.new(
30
- @schemer.validate(data),
31
- schema:,
32
- data:
33
- )
29
+ ValidationResult.new(@schemer.validate(data))
34
30
  end
35
31
 
36
32
  private
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiFirst
4
+ module Test
5
+ # Methods to use in integration tests
6
+ module Methods
7
+ def assert_api_conform(status: nil, api: :default)
8
+ api = OpenapiFirst::Test[api]
9
+ request = respond_to?(:last_request) ? last_request : @request
10
+ response = respond_to?(:last_response) ? last_response : @response
11
+ if status && status != response.status
12
+ raise OpenapiFirst::Error,
13
+ "Expected status #{status}, but got #{response.status} " \
14
+ "from #{request.request_method.upcase} #{request.path}."
15
+ end
16
+ api.validate_request(request, raise_error: true)
17
+ api.validate_response(request, response, raise_error: true)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test/methods'
4
+
5
+ module OpenapiFirst
6
+ # Test integration
7
+ module Test
8
+ def self.register(path, as: :default)
9
+ @registry ||= {}
10
+ @registry[as] = OpenapiFirst.load(path)
11
+ end
12
+
13
+ def self.[](api)
14
+ @registry[api] || raise(ArgumentError,
15
+ "API description #{api} not found to be used via assert_api_conform. " \
16
+ 'Use OpenapiFirst::Test.register to load an API description first.')
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'delegate'
5
+
6
+ module OpenapiFirst
7
+ # A validated request. It can be valid or not.
8
+ class ValidatedRequest < SimpleDelegator
9
+ extend Forwardable
10
+
11
+ def initialize(original_request, error:, parsed_values: {}, request_definition: nil)
12
+ super(original_request)
13
+ @parsed_values = Hash.new({}).merge(parsed_values)
14
+ @error = error
15
+ @request_definition = request_definition
16
+ end
17
+
18
+ # @!method error
19
+ # @return [Failure, nil] The error that occurred during validation.
20
+ # @!method request_definition
21
+ # @return [Request, nil]
22
+ attr_reader :parsed_values, :error, :request_definition
23
+
24
+ # Openapi 3 specific
25
+ # @!method operation
26
+ # @return [Hash] The OpenAPI 3 operation object
27
+ # @!method operation_id
28
+ # @return [String, nil] The OpenAPI 3 operationId
29
+ def_delegators :request_definition, :operation_id, :operation
30
+
31
+ # Parsed path parameters
32
+ # @return [Hash] A string keyed hash of path parameters
33
+ def parsed_path_parameters
34
+ parsed_values[:path]
35
+ end
36
+
37
+ # Parsed query parameters. This only returns the query parameters that are defined in the OpenAPI spec.
38
+ def parsed_query
39
+ parsed_values[:query]
40
+ end
41
+
42
+ # Parsed headers. This only returns the query parameters that are defined in the OpenAPI spec.
43
+ def parsed_headers
44
+ parsed_values[:headers]
45
+ end
46
+
47
+ # Parsed cookies. This only returns the query parameters that are defined in the OpenAPI spec.
48
+ def parsed_cookies
49
+ parsed_values[:cookies]
50
+ end
51
+
52
+ # Parsed body. This parses the body according to the content type.
53
+ # Note that this returns the hole body, not only the fields that are defined in the OpenAPI spec.
54
+ # You can use JSON Schemas `additionalProperties` or `unevaluatedProperties` to
55
+ # returns a validation error if the body contains unknown fields.
56
+ def parsed_body
57
+ parsed_values[:body]
58
+ end
59
+
60
+ # Checks if the request is valid.
61
+ def valid?
62
+ error.nil?
63
+ end
64
+
65
+ # Checks if the request is invalid.
66
+ def invalid?
67
+ !valid?
68
+ end
69
+
70
+ # Returns true if the request is defined.
71
+ def known?
72
+ request_definition != nil
73
+ end
74
+
75
+ # Merged path, query, body parameters.
76
+ # Here path has the highest precedence, then query, then body.
77
+ def parsed_params
78
+ @parsed_params ||= parsed_body.merge(parsed_query, parsed_path_parameters)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'delegate'
5
+
6
+ module OpenapiFirst
7
+ # A validated response. It can be valid or not.
8
+ class ValidatedResponse < SimpleDelegator
9
+ extend Forwardable
10
+
11
+ def initialize(original_response, error:, parsed_values: nil, response_definition: nil)
12
+ super(original_response)
13
+ @error = error
14
+ @parsed_values = parsed_values
15
+ @response_definition = response_definition
16
+ end
17
+
18
+ attr_reader :parsed_values, :error, :response_definition
19
+
20
+ def_delegator :parsed_values, :headers, :parsed_headers
21
+ def_delegator :parsed_values, :body, :parsed_body
22
+
23
+ # Checks if the response is valid.
24
+ # @return [Boolean] true if the response is valid, false otherwise.
25
+ def valid?
26
+ error.nil?
27
+ end
28
+
29
+ def invalid?
30
+ !valid?
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiFirst
4
+ module Validators
5
+ class RequestBody
6
+ def self.for(request_definition, openapi_version:, hooks: {})
7
+ schema = request_definition.content_schema
8
+ return unless schema
9
+
10
+ after_property_validation = hooks[:after_request_body_property_validation]
11
+
12
+ new(Schema.new(schema, after_property_validation:, openapi_version:),
13
+ required: request_definition.required_request_body?)
14
+ end
15
+
16
+ def initialize(schema, required:)
17
+ @schema = schema
18
+ @required = required
19
+ end
20
+
21
+ def call(request)
22
+ request_body = read_body(request)
23
+ if request_body.nil?
24
+ Failure.fail!(:invalid_body, message: 'Request body is not defined') if @required
25
+ return
26
+ end
27
+
28
+ validation = @schema.validate(request_body)
29
+ Failure.fail!(:invalid_body, errors: validation.errors) if validation.error?
30
+ end
31
+
32
+ private
33
+
34
+ def read_body(request)
35
+ request[:body]
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiFirst
4
+ module Validators
5
+ class RequestParameters
6
+ RequestHeaders = Data.define(:schema) do
7
+ def call(parsed_values)
8
+ validation = schema.validate(parsed_values[:headers])
9
+ Failure.fail!(:invalid_header, errors: validation.errors) if validation.error?
10
+ end
11
+ end
12
+
13
+ Path = Data.define(:schema) do
14
+ def call(parsed_values)
15
+ validation = schema.validate(parsed_values[:path])
16
+ Failure.fail!(:invalid_path, errors: validation.errors) if validation.error?
17
+ end
18
+ end
19
+
20
+ Query = Data.define(:schema) do
21
+ def call(parsed_values)
22
+ validation = schema.validate(parsed_values[:query])
23
+ Failure.fail!(:invalid_query, errors: validation.errors) if validation.error?
24
+ end
25
+ end
26
+
27
+ RequestCookies = Data.define(:schema) do
28
+ def call(parsed_values)
29
+ validation = schema.validate(parsed_values[:cookies])
30
+ Failure.fail!(:invalid_cookie, errors: validation.errors) if validation.error?
31
+ end
32
+ end
33
+
34
+ VALIDATORS = {
35
+ path_schema: Path,
36
+ query_schema: Query,
37
+ header_schema: RequestHeaders,
38
+ cookie_schema: RequestCookies
39
+ }.freeze
40
+
41
+ def self.for(operation, openapi_version:, hooks: {})
42
+ after_property_validation = hooks[:after_request_parameter_property_validation]
43
+ validators = VALIDATORS.filter_map do |key, klass|
44
+ schema = operation.send(key)
45
+ klass.new(Schema.new(schema, after_property_validation:, openapi_version:)) if schema
46
+ end
47
+ return if validators.empty?
48
+
49
+ new(validators)
50
+ end
51
+
52
+ def initialize(validators)
53
+ @validators = validators
54
+ end
55
+
56
+ def call(parsed_values)
57
+ @validators.each { |validator| validator.call(parsed_values) }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiFirst
4
+ module Validators
5
+ class ResponseBody
6
+ def self.for(response_definition, openapi_version:)
7
+ schema = response_definition&.content_schema
8
+ return unless schema
9
+
10
+ new(Schema.new(schema, write: false, openapi_version:))
11
+ end
12
+
13
+ def initialize(schema)
14
+ @schema = schema
15
+ end
16
+
17
+ attr_reader :schema
18
+
19
+ def call(response)
20
+ begin
21
+ parsed_body = response.body
22
+ rescue ParseError => e
23
+ Failure.fail!(:invalid_response_body, message: e.message)
24
+ end
25
+ validation = schema.validate(parsed_body)
26
+ Failure.fail!(:invalid_response_body, errors: validation.errors) if validation.error?
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiFirst
4
+ module Validators
5
+ class ResponseHeaders
6
+ def self.for(response_definition, openapi_version:)
7
+ schema = response_definition&.headers_schema
8
+ return unless schema
9
+
10
+ new(Schema.new(schema, openapi_version:))
11
+ end
12
+
13
+ def initialize(schema)
14
+ @schema = schema
15
+ end
16
+
17
+ attr_reader :schema
18
+
19
+ def call(parsed_request)
20
+ validation = schema.validate(parsed_request.headers)
21
+ Failure.fail!(:invalid_response_header, errors: validation.errors) if validation.error?
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
- VERSION = '1.4.3'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/openapi_first.rb CHANGED
@@ -5,45 +5,63 @@ require 'multi_json'
5
5
  require_relative 'openapi_first/json_refs'
6
6
  require_relative 'openapi_first/errors'
7
7
  require_relative 'openapi_first/configuration'
8
- require_relative 'openapi_first/plugins'
9
8
  require_relative 'openapi_first/definition'
10
9
  require_relative 'openapi_first/version'
11
- require_relative 'openapi_first/error_response'
10
+ require_relative 'openapi_first/schema'
12
11
  require_relative 'openapi_first/middlewares/response_validation'
13
12
  require_relative 'openapi_first/middlewares/request_validation'
14
13
 
15
14
  # OpenapiFirst is a toolchain to build HTTP APIS based on OpenAPI API descriptions.
16
15
  module OpenapiFirst
17
- extend Plugins
16
+ # Key in rack to find instance of Request
17
+ REQUEST = 'openapi.request'
18
+ FAILURE = :openapi_first_validation_failure
18
19
 
19
- class << self
20
- # @return [Configuration]
21
- def configuration
22
- @configuration ||= Configuration.new
23
- end
20
+ # @return [Configuration]
21
+ def self.configuration
22
+ @configuration ||= Configuration.new
23
+ end
24
24
 
25
- # @return [Configuration]
26
- # @yield [Configuration]
27
- def configure
28
- yield configuration
29
- end
25
+ # @return [Configuration]
26
+ # @yield [Configuration]
27
+ def self.configure
28
+ yield configuration
30
29
  end
31
30
 
32
- # Key in rack to find instance of RuntimeRequest
33
- REQUEST = 'openapi.request'
31
+ ERROR_RESPONSES = {} # rubocop:disable Style/MutableConstant
32
+ private_constant :ERROR_RESPONSES
33
+
34
+ # Register an error response class
35
+ # @param name [Symbol]
36
+ # @param klass [Class] A class that includes / implements OpenapiFirst::ErrorResponse
37
+ def self.register_error_response(name, klass)
38
+ ERROR_RESPONSES[name.to_sym] = klass
39
+ end
40
+
41
+ # @param name [Symbol]
42
+ # @return [Class] The error response class
43
+ def self.find_error_response(name)
44
+ ERROR_RESPONSES.fetch(name) do
45
+ raise "Unknown error response: #{name}. " /
46
+ 'Register your error response class via `OpenapiFirst.register_error_response(name, klass)`. ' /
47
+ "Registered error responses are: #{ERROR_RESPONSES.keys.join(', ')}."
48
+ end
49
+ end
34
50
 
35
51
  # Load and dereference an OpenAPI spec file
36
52
  # @return [Definition]
37
- def self.load(filepath, only: nil)
53
+ def self.load(filepath, only: nil, &)
54
+ raise FileNotFoundError, "File not found: #{filepath}" unless File.exist?(filepath)
55
+
38
56
  resolved = Bundle.resolve(filepath)
39
- parse(resolved, only:, filepath:)
57
+ parse(resolved, only:, filepath:, &)
40
58
  end
41
59
 
42
60
  # Parse a dereferenced Hash
43
61
  # @return [Definition]
44
- def self.parse(resolved, only: nil, filepath: nil)
62
+ def self.parse(resolved, only: nil, filepath: nil, &)
45
63
  resolved['paths'].filter!(&->(key, _) { only.call(key) }) if only
46
- Definition.new(resolved, filepath)
64
+ Definition.new(resolved, filepath, &)
47
65
  end
48
66
 
49
67
  # @!visibility private
@@ -55,5 +73,6 @@ module OpenapiFirst
55
73
  end
56
74
  end
57
75
 
58
- OpenapiFirst.plugin(:default)
59
- OpenapiFirst.plugin(:jsonapi)
76
+ require_relative 'openapi_first/error_response'
77
+ require_relative 'openapi_first/error_responses/default'
78
+ require_relative 'openapi_first/error_responses/jsonapi'