openapi_first 1.0.0.beta6 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -3
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +11 -10
  5. data/Gemfile.rack2.lock +99 -0
  6. data/README.md +99 -130
  7. data/lib/openapi_first/body_parser.rb +5 -4
  8. data/lib/openapi_first/configuration.rb +20 -0
  9. data/lib/openapi_first/definition/operation.rb +84 -71
  10. data/lib/openapi_first/definition/parameters.rb +1 -1
  11. data/lib/openapi_first/definition/path_item.rb +21 -12
  12. data/lib/openapi_first/definition/path_parameters.rb +2 -3
  13. data/lib/openapi_first/definition/request_body.rb +22 -11
  14. data/lib/openapi_first/definition/response.rb +19 -31
  15. data/lib/openapi_first/definition/responses.rb +83 -0
  16. data/lib/openapi_first/definition.rb +50 -17
  17. data/lib/openapi_first/error_response.rb +22 -29
  18. data/lib/openapi_first/errors.rb +2 -14
  19. data/lib/openapi_first/failure.rb +55 -0
  20. data/lib/openapi_first/middlewares/request_validation.rb +52 -0
  21. data/lib/openapi_first/middlewares/response_validation.rb +35 -0
  22. data/lib/openapi_first/plugins/default/error_response.rb +74 -0
  23. data/lib/openapi_first/plugins/default.rb +11 -0
  24. data/lib/openapi_first/plugins/jsonapi/error_response.rb +58 -0
  25. data/lib/openapi_first/plugins/jsonapi.rb +11 -0
  26. data/lib/openapi_first/plugins.rb +9 -7
  27. data/lib/openapi_first/request_validation/request_body_validator.rb +41 -0
  28. data/lib/openapi_first/request_validation/validator.rb +81 -0
  29. data/lib/openapi_first/response_validation/validator.rb +101 -0
  30. data/lib/openapi_first/runtime_request.rb +84 -0
  31. data/lib/openapi_first/runtime_response.rb +31 -0
  32. data/lib/openapi_first/schema/validation_error.rb +18 -0
  33. data/lib/openapi_first/schema/validation_result.rb +32 -0
  34. data/lib/openapi_first/{definition/schema.rb → schema.rb} +8 -4
  35. data/lib/openapi_first/version.rb +1 -1
  36. data/lib/openapi_first.rb +32 -28
  37. data/openapi_first.gemspec +3 -5
  38. metadata +28 -20
  39. data/lib/openapi_first/config.rb +0 -20
  40. data/lib/openapi_first/definition/has_content.rb +0 -37
  41. data/lib/openapi_first/definition/schema/result.rb +0 -17
  42. data/lib/openapi_first/error_responses/default.rb +0 -58
  43. data/lib/openapi_first/error_responses/json_api.rb +0 -58
  44. data/lib/openapi_first/request_body_validator.rb +0 -37
  45. data/lib/openapi_first/request_validation.rb +0 -122
  46. data/lib/openapi_first/request_validation_error.rb +0 -31
  47. data/lib/openapi_first/response_validation.rb +0 -113
  48. data/lib/openapi_first/response_validator.rb +0 -21
  49. data/lib/openapi_first/router.rb +0 -68
  50. data/lib/openapi_first/use_router.rb +0 -18
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiFirst
4
+ class Failure
5
+ FAILURE = :openapi_first_validation_failure
6
+
7
+ TYPES = {
8
+ not_found: [NotFoundError, 'Request path is not defined.'],
9
+ method_not_allowed: [RequestInvalidError, 'Request method is not defined.'],
10
+ unsupported_media_type: [RequestInvalidError, 'Request content type is not defined.'],
11
+ invalid_body: [RequestInvalidError, 'Request body invalid:'],
12
+ invalid_query: [RequestInvalidError, 'Query parameter is invalid:'],
13
+ invalid_header: [RequestInvalidError, 'Request header is invalid:'],
14
+ invalid_path: [RequestInvalidError, 'Path segment is invalid:'],
15
+ invalid_cookie: [RequestInvalidError, 'Cookie value is invalid:'],
16
+ response_not_found: [ResponseNotFoundError, 'Response is not defined.'],
17
+ invalid_response_body: [ResponseInvalidError, 'Response body is invalid:'],
18
+ invalid_response_header: [ResponseInvalidError, 'Response header is invalid:']
19
+ }.freeze
20
+ private_constant :TYPES
21
+
22
+ # @param error_type [Symbol] See Failure::TYPES.keys
23
+ # @param errors [Array<OpenapiFirst::Schema::ValidationResult>]
24
+ def self.fail!(error_type, message: nil, errors: nil)
25
+ throw FAILURE, new(
26
+ error_type,
27
+ message:,
28
+ errors:
29
+ )
30
+ end
31
+
32
+ # @param type [Symbol] See TYPES.keys
33
+ # @param message [String] A generic error message
34
+ # @param errors [Array<OpenapiFirst::Schema::ValidationError>]
35
+ def initialize(error_type, message: nil, errors: nil)
36
+ unless TYPES.key?(error_type)
37
+ raise ArgumentError,
38
+ "error_type must be one of #{TYPES.keys} but was #{error_type.inspect}"
39
+ end
40
+
41
+ @error_type = error_type
42
+ @message = message
43
+ @errors = errors
44
+ end
45
+
46
+ attr_reader :error_type, :message, :errors
47
+
48
+ # Raise an exception that fits the failure.
49
+ def raise!
50
+ exception, message_prefix = TYPES.fetch(error_type)
51
+
52
+ raise exception, "#{message_prefix} #{@message || errors&.map(&:error)&.join('. ')}"
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack'
4
+ module OpenapiFirst
5
+ module Middlewares
6
+ # A Rack middleware to validate requests against an OpenAPI API description
7
+ class RequestValidation
8
+ # @param app The parent Rack application
9
+ # @param options An optional Hash of configuration options to override defaults
10
+ # :raise_error A Boolean indicating whether to raise an error if validation fails.
11
+ # default: false
12
+ # :error_response The Class to use for error responses.
13
+ # default: OpenapiFirst::Plugins::Default::ErrorResponse (Config.default_options.error_response)
14
+ def initialize(app, options = {})
15
+ @app = app
16
+ @raise = options.fetch(:raise_error, OpenapiFirst.configuration.request_validation_raise_error)
17
+ @error_response_class = error_response(options[:error_response])
18
+
19
+ spec = options.fetch(:spec)
20
+ raise "You have to pass spec: when initializing #{self.class}" unless spec
21
+
22
+ @definition = spec.is_a?(Definition) ? spec : OpenapiFirst.load(spec)
23
+ end
24
+
25
+ def call(env)
26
+ request = find_request(env)
27
+ return @app.call(env) unless request
28
+
29
+ failure = if @raise
30
+ request.validate!
31
+ else
32
+ request.validate
33
+ end
34
+ return @error_response_class.new(failure:).render if failure
35
+
36
+ @app.call(env)
37
+ end
38
+
39
+ private
40
+
41
+ def find_request(env)
42
+ env[REQUEST] ||= @definition.request(Rack::Request.new(env))
43
+ end
44
+
45
+ def error_response(mod)
46
+ return OpenapiFirst.plugin(mod)::ErrorResponse if mod.is_a?(Symbol)
47
+
48
+ mod || OpenapiFirst.configuration.request_validation_error_response
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack'
4
+ module OpenapiFirst
5
+ module Middlewares
6
+ # A Rack middleware to validate requests against an OpenAPI API description
7
+ class ResponseValidation
8
+ # @param app The parent Rack application
9
+ # @param options Hash
10
+ # :spec Path to the OpenAPI file or an instance of Definition
11
+ def initialize(app, options = {})
12
+ @app = app
13
+
14
+ spec = options.fetch(:spec)
15
+ raise "You have to pass spec: when initializing #{self.class}" unless spec
16
+
17
+ @definition = spec.is_a?(Definition) ? spec : OpenapiFirst.load(spec)
18
+ end
19
+
20
+ def call(env)
21
+ request = find_request(env)
22
+ response = @app.call(env)
23
+ request.response(response).validate!
24
+
25
+ response
26
+ end
27
+
28
+ private
29
+
30
+ def find_request(env)
31
+ env[REQUEST] ||= @definition.request(Rack::Request.new(env))
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiFirst
4
+ module Plugins
5
+ module Default
6
+ # An error reponse that returns application/problem+json with a list of "errors"
7
+ # See also https://www.rfc-editor.org/rfc/rfc9457.html
8
+ class ErrorResponse
9
+ include OpenapiFirst::ErrorResponse
10
+
11
+ TITLES = {
12
+ not_found: 'Not Found',
13
+ method_not_allowed: 'Request Method Not Allowed',
14
+ unsupported_media_type: 'Unsupported Media Type',
15
+ invalid_body: 'Bad Request Body',
16
+ invalid_query: 'Bad Query Parameter',
17
+ invalid_header: 'Bad Request Header',
18
+ invalid_path: 'Bad Request Path',
19
+ invalid_cookie: 'Bod Request Cookie'
20
+ }.freeze
21
+ private_constant :TITLES
22
+
23
+ def body
24
+ result = {
25
+ title:,
26
+ status:
27
+ }
28
+ result[:errors] = errors if failure.errors
29
+ MultiJson.dump(result)
30
+ end
31
+
32
+ def error_type = failure.error_type
33
+
34
+ def title
35
+ TITLES.fetch(error_type)
36
+ end
37
+
38
+ def content_type
39
+ 'application/problem+json'
40
+ end
41
+
42
+ def errors
43
+ key = pointer_key
44
+ failure.errors.map do |error|
45
+ {
46
+ message: error.error,
47
+ key => pointer(error.instance_location),
48
+ code: error.type
49
+ }
50
+ end
51
+ end
52
+
53
+ def pointer_key
54
+ case error_type
55
+ when :invalid_body
56
+ :pointer
57
+ when :invalid_query, :invalid_path
58
+ :parameter
59
+ when :invalid_header
60
+ :header
61
+ when :invalid_cookie
62
+ :cookie
63
+ end
64
+ end
65
+
66
+ def pointer(data_pointer)
67
+ return data_pointer if error_type == :invalid_body
68
+
69
+ data_pointer.delete_prefix('/')
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'default/error_response'
4
+
5
+ module OpenapiFirst
6
+ module Plugins
7
+ module Default
8
+ OpenapiFirst.register(:default, self)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiFirst
4
+ module Plugins
5
+ module Jsonapi
6
+ class ErrorResponse
7
+ include OpenapiFirst::ErrorResponse
8
+
9
+ def body
10
+ MultiJson.dump({ errors: serialized_errors })
11
+ end
12
+
13
+ def content_type
14
+ 'application/vnd.api+json'
15
+ end
16
+
17
+ def serialized_errors
18
+ return default_errors unless failure.errors
19
+
20
+ key = pointer_key
21
+ failure.errors.map do |error|
22
+ {
23
+ status: status.to_s,
24
+ source: { key => pointer(error.instance_location) },
25
+ title: error.error
26
+ }
27
+ end
28
+ end
29
+
30
+ def default_errors
31
+ [{
32
+ status: status.to_s,
33
+ title: Rack::Utils::HTTP_STATUS_CODES[status]
34
+ }]
35
+ end
36
+
37
+ def pointer_key
38
+ case failure.error_type
39
+ when :invalid_body
40
+ :pointer
41
+ when :invalid_query, :invalid_path
42
+ :parameter
43
+ when :invalid_header
44
+ :header
45
+ when :invalid_cookie
46
+ :cookie
47
+ end
48
+ end
49
+
50
+ def pointer(data_pointer)
51
+ return data_pointer if failure.error_type == :invalid_body
52
+
53
+ data_pointer.delete_prefix('/')
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'jsonapi/error_response'
4
+
5
+ module OpenapiFirst
6
+ module Plugins
7
+ module Jsonapi
8
+ OpenapiFirst.register(:jsonapi, self)
9
+ end
10
+ end
11
+ end
@@ -1,17 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenapiFirst
4
+ # Plugin System adapted from
5
+ # Polished Ruby Programming by Jeremy Evans
6
+ # https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewBook?id=0
4
7
  module Plugins
5
- ERROR_RESPONSES = {} # rubocop:disable Style/MutableConstant
8
+ PLUGINS = {} # rubocop:disable Style/MutableConstant
6
9
 
7
- def self.register_error_response(name, klass)
8
- ERROR_RESPONSES[name] = klass
10
+ def register(name, klass)
11
+ PLUGINS[name] = klass
9
12
  end
10
13
 
11
- def self.find_error_response(name)
12
- return name if name.is_a?(Class)
13
-
14
- ERROR_RESPONSES.fetch(name)
14
+ def plugin(name)
15
+ require "openapi_first/plugins/#{name}"
16
+ PLUGINS.fetch(name)
15
17
  end
16
18
  end
17
19
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../failure'
4
+
5
+ module OpenapiFirst
6
+ module RequestValidation
7
+ class RequestBodyValidator
8
+ def initialize(operation)
9
+ @operation = operation
10
+ end
11
+
12
+ def validate!(parsed_request_body, request_content_type)
13
+ request_body = operation.request_body
14
+ schema = request_body.schema_for(request_content_type)
15
+ unless schema
16
+ Failure.fail!(:unsupported_media_type,
17
+ message: "Unsupported Media Type '#{request_content_type}'")
18
+ end
19
+
20
+ if request_body.required? && parsed_request_body.nil?
21
+ Failure.fail!(:invalid_body,
22
+ message: 'Request body is not defined')
23
+ end
24
+
25
+ validate_body!(parsed_request_body, schema)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :operation
31
+
32
+ def validate_body!(parsed_request_body, schema)
33
+ request_body_schema = schema
34
+ return unless request_body_schema
35
+
36
+ validation = request_body_schema.validate(parsed_request_body)
37
+ Failure.fail!(:invalid_body, errors: validation.errors) if validation.error?
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../failure'
4
+ require_relative 'request_body_validator'
5
+
6
+ module OpenapiFirst
7
+ module RequestValidation
8
+ class Validator
9
+ def initialize(operation)
10
+ @operation = operation
11
+ end
12
+
13
+ def validate(runtime_request)
14
+ catch Failure::FAILURE do
15
+ validate_defined(runtime_request)
16
+ validate_parameters!(runtime_request)
17
+ validate_request_body!(runtime_request)
18
+ nil
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :operation, :raw_path_params
25
+
26
+ def validate_defined(request)
27
+ return if request.known?
28
+ return Failure.fail!(:not_found) unless request.known_path?
29
+
30
+ Failure.fail!(:method_not_allowed) unless request.known_request_method?
31
+ end
32
+
33
+ def validate_parameters!(request)
34
+ validate_query_params!(request)
35
+ validate_path_params!(request)
36
+ validate_cookie_params!(request)
37
+ validate_header_params!(request)
38
+ end
39
+
40
+ def validate_path_params!(request)
41
+ parameters = operation.path_parameters
42
+ return unless parameters
43
+
44
+ validation = parameters.schema.validate(request.path_parameters)
45
+ Failure.fail!(:invalid_path, errors: validation.errors) if validation.error?
46
+ end
47
+
48
+ def validate_query_params!(request)
49
+ parameters = operation.query_parameters
50
+ return unless parameters
51
+
52
+ validation = parameters.schema.validate(request.query)
53
+ Failure.fail!(:invalid_query, errors: validation.errors) if validation.error?
54
+ end
55
+
56
+ def validate_cookie_params!(request)
57
+ parameters = operation.cookie_parameters
58
+ return unless parameters
59
+
60
+ validation = parameters.schema.validate(request.cookies)
61
+ Failure.fail!(:invalid_cookie, errors: validation.errors) if validation.error?
62
+ end
63
+
64
+ def validate_header_params!(request)
65
+ parameters = operation.header_parameters
66
+ return unless parameters
67
+
68
+ validation = parameters.schema.validate(request.headers)
69
+ Failure.fail!(:invalid_header, errors: validation.errors) if validation.error?
70
+ end
71
+
72
+ def validate_request_body!(request)
73
+ return unless operation.request_body
74
+
75
+ RequestBodyValidator.new(operation).validate!(request.body, request.content_type)
76
+ rescue BodyParser::ParsingError => e
77
+ Failure.fail!(:invalid_body, message: e.message)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../failure'
4
+
5
+ module OpenapiFirst
6
+ module ResponseValidation
7
+ class Validator
8
+ def initialize(operation)
9
+ @operation = operation
10
+ end
11
+
12
+ def validate(rack_response)
13
+ return unless operation
14
+
15
+ response = Rack::Response[*rack_response.to_a]
16
+ catch Failure::FAILURE do
17
+ response_definition = response_for(operation, response.status, response.content_type)
18
+ validate_response_body(response_definition.content_schema, response.body)
19
+ validate_response_headers(response_definition.headers, response.headers)
20
+ nil
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :operation
27
+
28
+ def response_for(operation, status, content_type)
29
+ response = operation.response_for(status, content_type)
30
+ return response if response
31
+
32
+ unless operation.response_status_defined?(status)
33
+ message = "Response status '#{status}' not found for '#{operation.name}'"
34
+ Failure.fail!(:response_not_found, message:)
35
+ end
36
+ if content_type.nil? || content_type.empty?
37
+ message = "Content-Type for '#{operation.name}' must not be empty"
38
+ Failure.fail!(:invalid_response_header, message:)
39
+ end
40
+
41
+ message = "Content-Type '#{content_type}' is not defined for '#{operation.name}'"
42
+ Failure.fail!(:invalid_response_header, message:)
43
+ end
44
+
45
+ def validate_response_body(schema, response)
46
+ return unless schema
47
+
48
+ full_body = +''
49
+ response.each { |chunk| full_body << chunk }
50
+ data = full_body.empty? ? {} : load_json(full_body)
51
+ validation = schema.validate(data)
52
+ Failure.fail!(:invalid_response_body, errors: validation.errors) if validation.error?
53
+ end
54
+
55
+ def validate_response_headers(response_header_definitions, response_headers)
56
+ return unless response_header_definitions
57
+
58
+ unpacked_headers = unpack_response_headers(response_header_definitions, response_headers)
59
+ response_header_definitions.each do |name, definition|
60
+ next if name == 'Content-Type'
61
+
62
+ validate_response_header(name, definition, unpacked_headers, openapi_version: operation.openapi_version)
63
+ end
64
+ end
65
+
66
+ def validate_response_header(name, definition, unpacked_headers, openapi_version:)
67
+ unless unpacked_headers.key?(name)
68
+ if definition['required']
69
+ Failure.fail!(:invalid_response_header,
70
+ message: "Required response header '#{name}' is missing")
71
+ end
72
+
73
+ return
74
+ end
75
+
76
+ return unless definition.key?('schema')
77
+
78
+ validation = Schema.new(definition['schema'], openapi_version:)
79
+ value = unpacked_headers[name]
80
+ validation_result = validation.validate(value)
81
+ return unless validation_result.error?
82
+
83
+ Failure.fail!(:invalid_response_header,
84
+ errors: validation_result.errors)
85
+ end
86
+
87
+ def unpack_response_headers(response_header_definitions, response_headers)
88
+ headers_as_parameters = response_header_definitions.map do |name, definition|
89
+ definition.merge('name' => name, 'in' => 'header')
90
+ end
91
+ OpenapiParameters::Header.new(headers_as_parameters).unpack(response_headers)
92
+ end
93
+
94
+ def load_json(string)
95
+ MultiJson.load(string)
96
+ rescue MultiJson::ParseError
97
+ string
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require_relative 'runtime_response'
5
+ require_relative 'body_parser'
6
+ require_relative 'request_validation/validator'
7
+
8
+ module OpenapiFirst
9
+ # RuntimeRequest represents how an incoming request (Rack::Request) matches a request definition.
10
+ class RuntimeRequest
11
+ extend Forwardable
12
+
13
+ def initialize(request:, path_item:, operation:, path_params:)
14
+ @request = request
15
+ @path_item = path_item
16
+ @operation = operation
17
+ @original_path_params = path_params
18
+ end
19
+
20
+ def_delegators :@request, :content_type, :media_type
21
+ def_delegators :@operation, :operation_id
22
+
23
+ def known?
24
+ known_path? && known_request_method?
25
+ end
26
+
27
+ def known_path?
28
+ !!path_item
29
+ end
30
+
31
+ def known_request_method?
32
+ !!operation
33
+ end
34
+
35
+ # Merged path and query parameters
36
+ def params
37
+ @params ||= query.merge(path_parameters)
38
+ end
39
+
40
+ def path_parameters
41
+ @path_parameters ||=
42
+ operation.path_parameters&.unpack(@original_path_params) || {}
43
+ end
44
+
45
+ def query
46
+ @query ||=
47
+ operation.query_parameters&.unpack(request.env) || {}
48
+ end
49
+
50
+ alias query_parameters query
51
+
52
+ def headers
53
+ @headers ||=
54
+ operation.header_parameters&.unpack(request.env) || {}
55
+ end
56
+
57
+ def cookies
58
+ @cookies ||=
59
+ operation.cookie_parameters&.unpack(request.env) || {}
60
+ end
61
+
62
+ def body
63
+ @body ||= BodyParser.new.parse(request, request.media_type)
64
+ end
65
+ alias parsed_body body
66
+
67
+ def validate
68
+ RequestValidation::Validator.new(operation).validate(self)
69
+ end
70
+
71
+ def validate!
72
+ error = validate
73
+ error&.raise!
74
+ end
75
+
76
+ def response(rack_response)
77
+ RuntimeResponse.new(operation, rack_response)
78
+ end
79
+
80
+ private
81
+
82
+ attr_reader :request, :operation, :path_item
83
+ end
84
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'response_validation/validator'
4
+
5
+ module OpenapiFirst
6
+ class RuntimeResponse
7
+ def initialize(operation, rack_response)
8
+ @operation = operation
9
+ @rack_response = rack_response
10
+ end
11
+
12
+ def description
13
+ response_definition&.description
14
+ end
15
+
16
+ def validate
17
+ ResponseValidation::Validator.new(@operation).validate(@rack_response)
18
+ end
19
+
20
+ def validate!
21
+ error = validate
22
+ error&.raise!
23
+ end
24
+
25
+ private
26
+
27
+ def response_definition
28
+ @response_definition ||= @operation.response_for(@rack_response.status, @rack_response.content_type)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenapiFirst
4
+ class Schema
5
+ class ValidationError
6
+ def initialize(json_schemer_error)
7
+ @error = json_schemer_error
8
+ end
9
+
10
+ def error = @error['error']
11
+ def schemer_error = @error
12
+ def instance_location = @error['data_pointer']
13
+ def schema_location = @error['schema_pointer']
14
+ def type = @error['type']
15
+ def details = @error['details']
16
+ end
17
+ end
18
+ end