apia 3.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.
- checksums.yaml +7 -0
- data/VERSION +1 -0
- data/lib/apia.rb +21 -0
- data/lib/apia/api.rb +100 -0
- data/lib/apia/argument_set.rb +221 -0
- data/lib/apia/authenticator.rb +57 -0
- data/lib/apia/callable_with_environment.rb +43 -0
- data/lib/apia/controller.rb +32 -0
- data/lib/apia/defineable.rb +60 -0
- data/lib/apia/definition.rb +27 -0
- data/lib/apia/definitions/api.rb +51 -0
- data/lib/apia/definitions/argument.rb +77 -0
- data/lib/apia/definitions/argument_set.rb +33 -0
- data/lib/apia/definitions/authenticator.rb +46 -0
- data/lib/apia/definitions/controller.rb +41 -0
- data/lib/apia/definitions/endpoint.rb +74 -0
- data/lib/apia/definitions/enum.rb +31 -0
- data/lib/apia/definitions/error.rb +59 -0
- data/lib/apia/definitions/field.rb +117 -0
- data/lib/apia/definitions/lookup_argument_set.rb +27 -0
- data/lib/apia/definitions/object.rb +29 -0
- data/lib/apia/definitions/polymorph.rb +29 -0
- data/lib/apia/definitions/polymorph_option.rb +53 -0
- data/lib/apia/definitions/scalar.rb +23 -0
- data/lib/apia/definitions/type.rb +109 -0
- data/lib/apia/dsl.rb +23 -0
- data/lib/apia/dsls/api.rb +37 -0
- data/lib/apia/dsls/argument.rb +27 -0
- data/lib/apia/dsls/argument_set.rb +35 -0
- data/lib/apia/dsls/authenticator.rb +38 -0
- data/lib/apia/dsls/concerns/has_fields.rb +38 -0
- data/lib/apia/dsls/controller.rb +34 -0
- data/lib/apia/dsls/endpoint.rb +79 -0
- data/lib/apia/dsls/enum.rb +19 -0
- data/lib/apia/dsls/error.rb +26 -0
- data/lib/apia/dsls/field.rb +27 -0
- data/lib/apia/dsls/lookup_argument_set.rb +24 -0
- data/lib/apia/dsls/object.rb +19 -0
- data/lib/apia/dsls/polymorph.rb +19 -0
- data/lib/apia/dsls/route_group.rb +43 -0
- data/lib/apia/dsls/route_set.rb +40 -0
- data/lib/apia/dsls/scalar.rb +23 -0
- data/lib/apia/dsls/scope_descriptions.rb +17 -0
- data/lib/apia/endpoint.rb +110 -0
- data/lib/apia/enum.rb +43 -0
- data/lib/apia/environment_error_handling.rb +74 -0
- data/lib/apia/error.rb +61 -0
- data/lib/apia/error_set.rb +15 -0
- data/lib/apia/errors/error_exception_error.rb +32 -0
- data/lib/apia/errors/field_spec_parse_error.rb +23 -0
- data/lib/apia/errors/invalid_argument_error.rb +68 -0
- data/lib/apia/errors/invalid_enum_option_error.rb +21 -0
- data/lib/apia/errors/invalid_helper_error.rb +6 -0
- data/lib/apia/errors/invalid_json_error.rb +23 -0
- data/lib/apia/errors/invalid_polymorph_value_error.rb +21 -0
- data/lib/apia/errors/invalid_scalar_value_error.rb +21 -0
- data/lib/apia/errors/manifest_error.rb +43 -0
- data/lib/apia/errors/missing_argument_error.rb +40 -0
- data/lib/apia/errors/null_field_value_error.rb +37 -0
- data/lib/apia/errors/parse_error.rb +10 -0
- data/lib/apia/errors/runtime_error.rb +30 -0
- data/lib/apia/errors/scope_not_granted_error.rb +15 -0
- data/lib/apia/errors/standard_error.rb +6 -0
- data/lib/apia/field_set.rb +76 -0
- data/lib/apia/field_spec.rb +155 -0
- data/lib/apia/helpers.rb +34 -0
- data/lib/apia/hook_set.rb +30 -0
- data/lib/apia/lookup_argument_set.rb +57 -0
- data/lib/apia/lookup_environment.rb +27 -0
- data/lib/apia/manifest_errors.rb +62 -0
- data/lib/apia/mock_request.rb +18 -0
- data/lib/apia/object.rb +68 -0
- data/lib/apia/object_set.rb +21 -0
- data/lib/apia/pagination_object.rb +34 -0
- data/lib/apia/polymorph.rb +50 -0
- data/lib/apia/rack.rb +184 -0
- data/lib/apia/rack_error.rb +17 -0
- data/lib/apia/request.rb +67 -0
- data/lib/apia/request_environment.rb +84 -0
- data/lib/apia/request_headers.rb +42 -0
- data/lib/apia/response.rb +64 -0
- data/lib/apia/route.rb +61 -0
- data/lib/apia/route_group.rb +20 -0
- data/lib/apia/route_set.rb +89 -0
- data/lib/apia/scalar.rb +52 -0
- data/lib/apia/scalars.rb +25 -0
- data/lib/apia/scalars/base64.rb +31 -0
- data/lib/apia/scalars/boolean.rb +37 -0
- data/lib/apia/scalars/date.rb +45 -0
- data/lib/apia/scalars/decimal.rb +36 -0
- data/lib/apia/scalars/integer.rb +34 -0
- data/lib/apia/scalars/string.rb +24 -0
- data/lib/apia/scalars/unix_time.rb +40 -0
- data/lib/apia/schema/api_controller_schema_type.rb +17 -0
- data/lib/apia/schema/api_schema_type.rb +43 -0
- data/lib/apia/schema/argument_schema_type.rb +28 -0
- data/lib/apia/schema/argument_set_schema_type.rb +21 -0
- data/lib/apia/schema/authenticator_schema_type.rb +22 -0
- data/lib/apia/schema/controller.rb +39 -0
- data/lib/apia/schema/controller_endpoint_schema_type.rb +17 -0
- data/lib/apia/schema/controller_schema_type.rb +32 -0
- data/lib/apia/schema/endpoint_schema_type.rb +35 -0
- data/lib/apia/schema/enum_schema_type.rb +20 -0
- data/lib/apia/schema/enum_value_schema_type.rb +14 -0
- data/lib/apia/schema/error_schema_type.rb +23 -0
- data/lib/apia/schema/field_schema_type.rb +38 -0
- data/lib/apia/schema/field_spec_options_schema_type.rb +16 -0
- data/lib/apia/schema/lookup_argument_set_schema_type.rb +25 -0
- data/lib/apia/schema/object_schema_polymorph.rb +31 -0
- data/lib/apia/schema/object_schema_type.rb +21 -0
- data/lib/apia/schema/polymorph_option_schema_type.rb +16 -0
- data/lib/apia/schema/polymorph_schema_type.rb +20 -0
- data/lib/apia/schema/request_method_enum.rb +21 -0
- data/lib/apia/schema/route_group_schema_type.rb +19 -0
- data/lib/apia/schema/route_schema_type.rb +31 -0
- data/lib/apia/schema/route_set_schema_type.rb +20 -0
- data/lib/apia/schema/scalar_schema_type.rb +15 -0
- data/lib/apia/schema/scope_type.rb +14 -0
- data/lib/apia/version.rb +12 -0
- metadata +188 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apia/dsl'
|
4
|
+
require 'apia/definitions/polymorph_option'
|
5
|
+
require 'apia/helpers'
|
6
|
+
|
7
|
+
module Apia
|
8
|
+
module DSLs
|
9
|
+
class Polymorph < DSL
|
10
|
+
|
11
|
+
def option(name, type: nil, matcher: nil)
|
12
|
+
id = "#{@definition.id}/#{Helpers.camelize(name)}Option"
|
13
|
+
option = Definitions::PolymorphOption.new(id, name, type: type, matcher: matcher)
|
14
|
+
@definition.options[name.to_sym] = option
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apia
|
4
|
+
module DSLs
|
5
|
+
class RouteGroup
|
6
|
+
|
7
|
+
def initialize(route_set, group)
|
8
|
+
@route_set = route_set
|
9
|
+
@group = group
|
10
|
+
end
|
11
|
+
|
12
|
+
def route(path, **options)
|
13
|
+
@route_set.dsl.route(path, controller: options[:controller] || @group.default_controller, group: @group, **options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def group(id, &block)
|
17
|
+
group = Apia::RouteGroup.new("#{@group.id}.#{id}", @group)
|
18
|
+
dsl = Apia::DSLs::RouteGroup.new(@route_set, group)
|
19
|
+
dsl.instance_eval(&block)
|
20
|
+
@group.groups << group
|
21
|
+
end
|
22
|
+
|
23
|
+
Route::REQUEST_METHODS.each do |method_name|
|
24
|
+
define_method method_name do |path, **options|
|
25
|
+
route(path, request_method: method_name, **options)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def name(name)
|
30
|
+
@group.name = name
|
31
|
+
end
|
32
|
+
|
33
|
+
def description(description)
|
34
|
+
@group.description = description
|
35
|
+
end
|
36
|
+
|
37
|
+
def controller(controller)
|
38
|
+
@group.default_controller = controller
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apia/route'
|
4
|
+
require 'apia/route_group'
|
5
|
+
require 'apia/dsls/route_group'
|
6
|
+
|
7
|
+
module Apia
|
8
|
+
module DSLs
|
9
|
+
class RouteSet
|
10
|
+
|
11
|
+
def initialize(route_set)
|
12
|
+
@route_set = route_set
|
13
|
+
end
|
14
|
+
|
15
|
+
def schema(path: 'schema')
|
16
|
+
require 'apia/schema/controller'
|
17
|
+
get path, controller: Schema::Controller, endpoint: :schema
|
18
|
+
end
|
19
|
+
|
20
|
+
def route(path, request_method: nil, **options)
|
21
|
+
route = Route.new(path, request_method: request_method, **options)
|
22
|
+
@route_set.add(route)
|
23
|
+
end
|
24
|
+
|
25
|
+
Route::REQUEST_METHODS.each do |method_name|
|
26
|
+
define_method method_name do |path, **options|
|
27
|
+
route(path, request_method: method_name, **options)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def group(id, &block)
|
32
|
+
group = Apia::RouteGroup.new(id.to_s, nil)
|
33
|
+
dsl = Apia::DSLs::RouteGroup.new(@route_set, group)
|
34
|
+
dsl.instance_eval(&block)
|
35
|
+
@route_set.groups << group
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apia/dsl'
|
4
|
+
|
5
|
+
module Apia
|
6
|
+
module DSLs
|
7
|
+
class Scalar < DSL
|
8
|
+
|
9
|
+
def cast(&block)
|
10
|
+
@definition.cast = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(&block)
|
14
|
+
@definition.parse = block
|
15
|
+
end
|
16
|
+
|
17
|
+
def validator(&block)
|
18
|
+
@definition.validator = block
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apia
|
4
|
+
module DSLs
|
5
|
+
class ScopeDescriptions
|
6
|
+
|
7
|
+
def initialize(api)
|
8
|
+
@api = api
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(name, description)
|
12
|
+
@api.scopes[name.to_s] = { description: description }
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apia/helpers'
|
4
|
+
require 'apia/defineable'
|
5
|
+
require 'apia/definitions/endpoint'
|
6
|
+
require 'apia/request_environment'
|
7
|
+
require 'apia/errors/scope_not_granted_error'
|
8
|
+
require 'apia/callable_with_environment'
|
9
|
+
|
10
|
+
module Apia
|
11
|
+
class Endpoint
|
12
|
+
|
13
|
+
extend Defineable
|
14
|
+
include CallableWithEnvironment
|
15
|
+
|
16
|
+
class << self
|
17
|
+
|
18
|
+
# Return the definition object for the endpoint
|
19
|
+
#
|
20
|
+
# @return [Apia::Definitions::Endpoint]
|
21
|
+
def definition
|
22
|
+
@definition ||= Definitions::Endpoint.new(Helpers.class_name_to_id(name))
|
23
|
+
end
|
24
|
+
|
25
|
+
# Collate all objects that this endpoint references and add them to the
|
26
|
+
# given object set
|
27
|
+
#
|
28
|
+
# @param set [Apia::ObjectSet]
|
29
|
+
# @return [void]
|
30
|
+
def collate_objects(set)
|
31
|
+
set.add_object(definition.argument_set)
|
32
|
+
|
33
|
+
definition.potential_errors.each do |error|
|
34
|
+
set.add_object(error)
|
35
|
+
end
|
36
|
+
|
37
|
+
definition.fields.each_value do |field|
|
38
|
+
set.add_object(field.type.klass) if field.type.usable_for_field?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Run this request by providing a request to execute it with.
|
43
|
+
#
|
44
|
+
# @param request [Apia::Request]
|
45
|
+
# @return [Apia::Response]
|
46
|
+
def execute(request)
|
47
|
+
response = Response.new(request, self)
|
48
|
+
environment = RequestEnvironment.new(request, response)
|
49
|
+
|
50
|
+
catch_errors(response) do
|
51
|
+
# Determine an authenticator and execute it before the request happens
|
52
|
+
request.authenticator = definition.authenticator || request.controller&.definition&.authenticator || request.api&.definition&.authenticator
|
53
|
+
request.authenticator&.execute(environment)
|
54
|
+
|
55
|
+
# Determine if we're permitted to run the action based on the endpoint's scopes
|
56
|
+
if request.authenticator && !request.authenticator.authorized_scope?(environment, definition.scopes)
|
57
|
+
environment.raise_error Apia::ScopeNotGrantedError, scopes: definition.scopes
|
58
|
+
end
|
59
|
+
|
60
|
+
# Process arguments into the request. This happens after the authentication
|
61
|
+
# stage because a) authenticators shouldn't be using endpoint specific args
|
62
|
+
# and b) the argument conditions may need to know the identity.
|
63
|
+
request.arguments = definition.argument_set.create_from_request(request)
|
64
|
+
|
65
|
+
# Call the action for the endpoint
|
66
|
+
endpoint_instance = new(environment)
|
67
|
+
endpoint_instance.call_with_error_handling
|
68
|
+
|
69
|
+
# We're going to call this here because we want to cache the actual values of
|
70
|
+
# the output within the catch_errors block.
|
71
|
+
response.hash
|
72
|
+
end
|
73
|
+
|
74
|
+
response
|
75
|
+
end
|
76
|
+
|
77
|
+
# Catch any runtime errors and update the given response with the appropriate
|
78
|
+
# values.
|
79
|
+
#
|
80
|
+
# @param response [Apia::Response]
|
81
|
+
# @return [void]
|
82
|
+
def catch_errors(response)
|
83
|
+
yield
|
84
|
+
rescue Apia::RuntimeError => e
|
85
|
+
catch_errors(response) do
|
86
|
+
response.body = { error: e.hash }
|
87
|
+
response.status = e.http_status
|
88
|
+
response.headers['x-api-schema'] = 'json-error'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Should a given field be included
|
93
|
+
#
|
94
|
+
def include_field?(*args)
|
95
|
+
definition.fields.spec.include_field?(*args)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Allow an endpoint to be executed with a mocked request.
|
99
|
+
#
|
100
|
+
def test
|
101
|
+
request = Apia::MockRequest.empty
|
102
|
+
request.endpoint = self
|
103
|
+
yield request if block_given?
|
104
|
+
execute(request)
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
data/lib/apia/enum.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apia/helpers'
|
4
|
+
require 'apia/defineable'
|
5
|
+
require 'apia/definitions/enum'
|
6
|
+
require 'apia/errors/invalid_enum_option_error'
|
7
|
+
|
8
|
+
module Apia
|
9
|
+
class Enum
|
10
|
+
|
11
|
+
extend Defineable
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
# Return the definition object for the enum
|
16
|
+
#
|
17
|
+
# @return [Apia::Definitions::Enum]
|
18
|
+
def definition
|
19
|
+
@definition ||= Definitions::Enum.new(Helpers.class_name_to_id(name))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Cast a value or define a new block for casting (for DSL purposes)
|
23
|
+
#
|
24
|
+
# @param value [Object?]
|
25
|
+
# @return [Object?]
|
26
|
+
def cast(value = nil, &block)
|
27
|
+
if block_given? && value.nil?
|
28
|
+
return definition.dsl.cast(&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
value = definition.cast.call(value) if definition.cast
|
32
|
+
|
33
|
+
if definition.values[value].nil?
|
34
|
+
raise InvalidEnumOptionError.new(self, value)
|
35
|
+
end
|
36
|
+
|
37
|
+
value
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apia
|
4
|
+
module EnvironmentErrorHandling
|
5
|
+
|
6
|
+
# Raise an error
|
7
|
+
#
|
8
|
+
# @param error [String, Class] an error class or the name of a defined error
|
9
|
+
def raise_error(error, fields = {})
|
10
|
+
if error.respond_to?(:ancestors) && error.ancestors.include?(Apia::Error)
|
11
|
+
raise error.exception(fields)
|
12
|
+
end
|
13
|
+
|
14
|
+
if found_error = find_error_by_name(error)
|
15
|
+
raise found_error.exception(fields)
|
16
|
+
end
|
17
|
+
|
18
|
+
raise Apia::RuntimeError, "No error defined named #{error}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Return an error instance for a given exception class
|
22
|
+
#
|
23
|
+
# @param exception_class [Class] any error class
|
24
|
+
# @return [Class, nil] any class that inherits from Apia::Error or nil if no error is found
|
25
|
+
def error_for_exception(exception_class)
|
26
|
+
potential_error_sources.each do |source|
|
27
|
+
source.definition.potential_errors.each do |error|
|
28
|
+
if error.definition.catchable_exceptions.key?(exception_class)
|
29
|
+
return {
|
30
|
+
error: error,
|
31
|
+
block: error.definition.catchable_exceptions[exception_class]
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def raise_exception(exception)
|
40
|
+
error = error_for_exception(exception.class)
|
41
|
+
raise exception if error.nil?
|
42
|
+
|
43
|
+
fields = {}
|
44
|
+
error[:block]&.call(fields, exception)
|
45
|
+
raise error[:error].exception(fields)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def find_error_by_name(error_name)
|
51
|
+
return nil if potential_error_sources.nil?
|
52
|
+
|
53
|
+
potential_error_sources.each do |source|
|
54
|
+
error = find_potential_error(source, error_name)
|
55
|
+
return error if error
|
56
|
+
end
|
57
|
+
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_potential_error(source, name)
|
62
|
+
return nil if source.nil?
|
63
|
+
|
64
|
+
unless name =~ /\//
|
65
|
+
name = source.definition.id + '/' + name
|
66
|
+
end
|
67
|
+
|
68
|
+
source.definition.potential_errors.find do |error|
|
69
|
+
error.definition.id == name
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
data/lib/apia/error.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apia/helpers'
|
4
|
+
require 'apia/defineable'
|
5
|
+
require 'apia/definitions/error'
|
6
|
+
require 'apia/object'
|
7
|
+
require 'apia/scalar'
|
8
|
+
require 'apia/errors/error_exception_error'
|
9
|
+
|
10
|
+
module Apia
|
11
|
+
# An Error represents a specific failure that can be raised by any
|
12
|
+
# action within the API.
|
13
|
+
#
|
14
|
+
# An error can specify a `code` which is textual description of the
|
15
|
+
# error which will be returned to the user.
|
16
|
+
#
|
17
|
+
# An HTTP status code can be provided which will be sent to the user
|
18
|
+
# if the error is incurred. If no HTTP status code is provided, the
|
19
|
+
# a 500 error code will be used.
|
20
|
+
#
|
21
|
+
# You can also define an array of additional fields that can
|
22
|
+
# included when the error is raised. This works in the same way as
|
23
|
+
# any type and an object implementing those methods should be provided
|
24
|
+
# when the error is raised.
|
25
|
+
class Error
|
26
|
+
|
27
|
+
extend Defineable
|
28
|
+
|
29
|
+
class << self
|
30
|
+
|
31
|
+
# Return the definition object for errors
|
32
|
+
#
|
33
|
+
# @return [Apia::Definitions::Error]
|
34
|
+
def definition
|
35
|
+
@definition ||= Definitions::Error.new(Helpers.class_name_to_id(name))
|
36
|
+
end
|
37
|
+
|
38
|
+
# Collate all objects that this error references and add them to the
|
39
|
+
# given object set
|
40
|
+
#
|
41
|
+
# @param set [Apia::ObjectSet]
|
42
|
+
# @return [void]
|
43
|
+
def collate_objects(set)
|
44
|
+
definition.fields.each_value do |field|
|
45
|
+
set.add_object(field.type.klass) if field.type.usable_for_field?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return an exception that should be raised to represent this error
|
50
|
+
# when it is actually invoked
|
51
|
+
#
|
52
|
+
# @param fields [Hash]
|
53
|
+
# @return [ErrorExceptionError]
|
54
|
+
def exception(fields = {})
|
55
|
+
ErrorExceptionError.new(self, fields)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apia
|
4
|
+
class ErrorSet < Array
|
5
|
+
|
6
|
+
def validate(errors, object)
|
7
|
+
each_with_index do |error, index|
|
8
|
+
unless error.respond_to?(:ancestors) && error.ancestors.include?(Apia::Error)
|
9
|
+
errors.add object, 'InvalidPotentialError', "Potential error at index #{index} must be a class that inherits from Apia::Error"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|