openapi_first 1.0.0.beta6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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