erratum 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/LICENSE.txt +19 -0
- data/README.md +2 -0
- data/Rakefile +2 -0
- data/lib/erratum/configuration.rb +44 -0
- data/lib/erratum/error.rb +110 -0
- data/lib/erratum/errors/authentication_error.rb +9 -0
- data/lib/erratum/errors/authentication_errors/duplicate_authentication_error.rb +37 -0
- data/lib/erratum/errors/authentication_errors/invalid_token_error.rb +30 -0
- data/lib/erratum/errors/authentication_errors/invalid_username_or_password_error.rb +30 -0
- data/lib/erratum/errors/authorization_error.rb +9 -0
- data/lib/erratum/errors/authorization_errors/forbidden_error.rb +48 -0
- data/lib/erratum/errors/crud_error.rb +24 -0
- data/lib/erratum/errors/crud_errors/association_error.rb +54 -0
- data/lib/erratum/errors/crud_errors/resource_not_found_error.rb +52 -0
- data/lib/erratum/errors/crud_errors/resource_persistence_error.rb +50 -0
- data/lib/erratum/errors/request_errors/parameter_missing_error.rb +43 -0
- data/lib/erratum/errors/request_errors/unpermitted_parameters_error.rb +49 -0
- data/lib/erratum/rescuable_resource.rb +29 -0
- data/lib/erratum/resource_naming.rb +31 -0
- data/lib/erratum/utilities/string.rb +18 -0
- data/lib/erratum/verifiable_resource.rb +23 -0
- data/lib/erratum/version.rb +4 -0
- data/lib/erratum.rb +48 -0
- data/spec/lib/erratum/configuration_spec.rb +27 -0
- data/spec/lib/erratum/error_spec.rb +189 -0
- data/spec/lib/erratum/errors/authentication_errors/duplicate_authentication_error_spec.rb +43 -0
- data/spec/lib/erratum/errors/authentication_errors/invalid_token_error_spec.rb +33 -0
- data/spec/lib/erratum/errors/authentication_errors/invalid_username_or_password_error_spec.rb +35 -0
- data/spec/lib/erratum/errors/authorization_errors/forbidden_error_spec.rb +52 -0
- data/spec/lib/erratum/errors/crud_errors/association_error_spec.rb +69 -0
- data/spec/lib/erratum/errors/crud_errors/resource_not_found_error_spec.rb +89 -0
- data/spec/lib/erratum/errors/crud_errors/resource_persistence_error_spec.rb +84 -0
- data/spec/lib/erratum/errors/request_errors/parameter_missing_error_spec.rb +58 -0
- data/spec/lib/erratum/errors/request_errors/unpermitted_parameters_error_spec.rb +67 -0
- data/spec/lib/erratum/rescuable_resource_spec.rb +8 -0
- data/spec/lib/human_error_spec.rb +20 -0
- data.tar.gz.sig +0 -0
- metadata +171 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Erratum
|
3
|
+
module ResourceNaming
|
4
|
+
CONTROLLER_RESOURCE_NAME_PATTERN = /\A((.*?::)?.*?)(\w+)Controller\z/
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def plural_resource_name
|
8
|
+
@plural_resource_name ||= name[CONTROLLER_RESOURCE_NAME_PATTERN, 3].
|
9
|
+
underscore.
|
10
|
+
pluralize.
|
11
|
+
downcase
|
12
|
+
end
|
13
|
+
|
14
|
+
def singular_resource_name
|
15
|
+
@singular_resource_name ||= name[CONTROLLER_RESOURCE_NAME_PATTERN, 3].
|
16
|
+
underscore.
|
17
|
+
singularize.
|
18
|
+
downcase
|
19
|
+
end
|
20
|
+
|
21
|
+
def resource_class_name
|
22
|
+
@resource_class_name ||= singular_resource_name.
|
23
|
+
camelize
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.included(base)
|
28
|
+
base.extend ClassMethods
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Erratum
|
3
|
+
module Utilities
|
4
|
+
class String
|
5
|
+
def self.underscore(other)
|
6
|
+
word = other.to_s.gsub('::', '/')
|
7
|
+
word.gsub!(/(?:([A-Za-z\d])|^)(?=\b|[^a-z])/) do
|
8
|
+
"#{Regexp.last_match(1)}#{Regexp.last_match(1) && ''}"
|
9
|
+
end
|
10
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
11
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
12
|
+
word.tr!('-', '_')
|
13
|
+
word.downcase!
|
14
|
+
word
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'erratum/resource_naming'
|
3
|
+
|
4
|
+
class Erratum
|
5
|
+
module ResourceNaming
|
6
|
+
def self.included(base)
|
7
|
+
base.include ResourceNaming
|
8
|
+
|
9
|
+
base.before_action except: %i{index create} do
|
10
|
+
model = public_send(self.class.singular_resource_name)
|
11
|
+
|
12
|
+
resource_not_found_error = Erratum.build(
|
13
|
+
'ResourceNotFoundError',
|
14
|
+
resource_name: self.class.singular_resource_name,
|
15
|
+
resource_id: [params[:id]],
|
16
|
+
action: action_name,
|
17
|
+
)
|
18
|
+
|
19
|
+
fail resource_not_found_error unless model.persisted?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/erratum.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'erratum/configuration'
|
3
|
+
require 'erratum/error'
|
4
|
+
require 'erratum/errors/authorization_error'
|
5
|
+
require 'erratum/errors/authorization_errors/forbidden_error'
|
6
|
+
require 'erratum/errors/authentication_error'
|
7
|
+
require 'erratum/errors/authentication_errors/duplicate_authentication_error'
|
8
|
+
require 'erratum/errors/authentication_errors/invalid_token_error'
|
9
|
+
require 'erratum/errors/authentication_errors/invalid_username_or_password_error'
|
10
|
+
require 'erratum/errors/crud_error'
|
11
|
+
require 'erratum/errors/crud_errors/association_error'
|
12
|
+
require 'erratum/errors/crud_errors/resource_not_found_error'
|
13
|
+
require 'erratum/errors/crud_errors/resource_persistence_error'
|
14
|
+
require 'erratum/errors/request_errors/parameter_missing_error'
|
15
|
+
require 'erratum/errors/request_errors/unpermitted_parameters_error'
|
16
|
+
require 'erratum/rescuable_resource'
|
17
|
+
require 'erratum/verifiable_resource'
|
18
|
+
require 'erratum/version'
|
19
|
+
|
20
|
+
class Erratum
|
21
|
+
def self.fetch(error_type)
|
22
|
+
Object.const_get("Erratum::Errors::#{error_type}")
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.build(error_type, overrides = {})
|
26
|
+
fetch(error_type).new(overrides)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.convert(original_error, overrides = {})
|
30
|
+
case original_error.class.name
|
31
|
+
when 'ActiveRecord::InvalidForeignKey'
|
32
|
+
fetch('AssociationError').convert(original_error, overrides)
|
33
|
+
when 'ActiveRecord::RecordNotFound'
|
34
|
+
fetch('ResourceNotFoundError').convert(original_error, overrides)
|
35
|
+
when 'ActiveRecord::RecordInvalid',
|
36
|
+
'ActiveRecord::RecordNotSaved'
|
37
|
+
fetch('ResourcePersistenceError').convert(original_error, overrides)
|
38
|
+
when 'ActionController::ParameterMissing',
|
39
|
+
fetch('ParameterMissingError').convert(original_error, overrides)
|
40
|
+
when 'ActionController::UnpermittedParameters'
|
41
|
+
fetch('UnpermittedParametersError').convert(original_error, overrides)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.raise(error_type, **args)
|
46
|
+
Kernel.fail build(error_type, **args)
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rspeckled'
|
3
|
+
require 'erratum/configuration'
|
4
|
+
|
5
|
+
class Erratum
|
6
|
+
RSpec.describe Configuration, singletons: true do
|
7
|
+
it 'can set the url mappings' do
|
8
|
+
configuration = Configuration.instance
|
9
|
+
configuration.url_mappings = 'foobarbaz'
|
10
|
+
|
11
|
+
expect(configuration.url_mappings).to eql 'foobarbaz'
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can convert itself into a hash' do
|
15
|
+
configuration = Configuration.instance
|
16
|
+
configuration.url_mappings = {
|
17
|
+
'external_documentation_urls' => 'blah',
|
18
|
+
'developer_documentation_urls' => 'asdf',
|
19
|
+
}
|
20
|
+
|
21
|
+
expect(configuration.to_h).to eql(
|
22
|
+
external_documentation_urls: 'blah',
|
23
|
+
developer_documentation_urls: 'asdf',
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rspeckled'
|
3
|
+
require 'erratum/error'
|
4
|
+
|
5
|
+
class CustomError < RuntimeError
|
6
|
+
include Erratum::Error
|
7
|
+
end
|
8
|
+
|
9
|
+
class Erratum
|
10
|
+
RSpec.describe Error do
|
11
|
+
it 'can wrap an error with another error' do
|
12
|
+
original_error = RuntimeError.new('My Runtime Error Message')
|
13
|
+
wrapped_error = CustomError.wrap(original_error)
|
14
|
+
|
15
|
+
expect(wrapped_error.class.name).to eql 'CustomError'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'can wrap the message of an error by identifying the original error type' do
|
19
|
+
original_error = RuntimeError.new('My Runtime Error Message')
|
20
|
+
wrapped_error = CustomError.wrap(original_error)
|
21
|
+
|
22
|
+
expect(wrapped_error.message).to eql 'RuntimeError: My Runtime Error Message'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'can transfer the backtrace of the original error to the wrapped error' do
|
26
|
+
original_error = RuntimeError.new('My Runtime Error Message')
|
27
|
+
original_error.set_backtrace %w{foo bar baz}
|
28
|
+
wrapped_error = CustomError.wrap(original_error)
|
29
|
+
|
30
|
+
expect(wrapped_error.backtrace).to eql %w{foo bar baz}
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'can have its message explicitly set when it is generated' do
|
34
|
+
error = CustomError.new(message: 'My Message')
|
35
|
+
|
36
|
+
expect(error.message).to eql 'My Message'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'can have its message explicitly set when it is generated' do
|
40
|
+
error = CustomError.new
|
41
|
+
allow(error).to receive(:detail).
|
42
|
+
and_return('My Developer Message')
|
43
|
+
|
44
|
+
expect(error.message).to eql 'My Developer Message'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'can generate error data' do
|
48
|
+
custom_error = CustomError.new(
|
49
|
+
id: 'identifier',
|
50
|
+
http_status: 'flibbity',
|
51
|
+
code: 'jibbit',
|
52
|
+
title: 'roll dem bones and stones',
|
53
|
+
detail: 'I cannot receive any satisfaction',
|
54
|
+
source: 'But perhaps if I attempt it one more time, I can',
|
55
|
+
developer_documentation_url: 'asimof/jibbit?version=janky',
|
56
|
+
external_documentation_url: 'jinkies/87654321',
|
57
|
+
)
|
58
|
+
|
59
|
+
expect(custom_error.as_json).to eql(
|
60
|
+
errors: [
|
61
|
+
{
|
62
|
+
id: 'identifier',
|
63
|
+
links: {
|
64
|
+
about: 'jinkies/87654321',
|
65
|
+
documentation: 'asimof/jibbit?version=janky',
|
66
|
+
},
|
67
|
+
status: 'flibbity',
|
68
|
+
code: 'jibbit',
|
69
|
+
title: 'roll dem bones and stones',
|
70
|
+
detail: 'I cannot receive any satisfaction',
|
71
|
+
source: 'But perhaps if I attempt it one more time, I can',
|
72
|
+
},
|
73
|
+
],
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'can extract configuration from the global config if it is not passed in',
|
78
|
+
singletons: Erratum::Configuration do
|
79
|
+
|
80
|
+
Erratum.configure do |config|
|
81
|
+
config.url_mappings = {
|
82
|
+
'external_documentation_urls' => {
|
83
|
+
'jibbit' => 'http://example.com/edu',
|
84
|
+
},
|
85
|
+
'developer_documentation_urls' => {
|
86
|
+
'jibbit' => 'http://example.com/ddu',
|
87
|
+
},
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
custom_error = CustomError.new(
|
92
|
+
id: 'identifier',
|
93
|
+
http_status: 'flibbity',
|
94
|
+
code: 'jibbit',
|
95
|
+
title: 'roll dem bones and stones',
|
96
|
+
detail: 'I cannot receive any satisfaction',
|
97
|
+
source: 'But perhaps if I attempt it one more time, I can',
|
98
|
+
)
|
99
|
+
|
100
|
+
expect(custom_error.as_json).to eql(
|
101
|
+
errors: [
|
102
|
+
{
|
103
|
+
id: 'identifier',
|
104
|
+
links: {
|
105
|
+
about: 'http://example.com/edu',
|
106
|
+
documentation: 'http://example.com/ddu',
|
107
|
+
},
|
108
|
+
status: 'flibbity',
|
109
|
+
code: 'jibbit',
|
110
|
+
title: 'roll dem bones and stones',
|
111
|
+
detail: 'I cannot receive any satisfaction',
|
112
|
+
source: 'But perhaps if I attempt it one more time, I can',
|
113
|
+
},
|
114
|
+
],
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'can override the global config if it is set, but an explicit value is passed in',
|
119
|
+
singletons: Erratum::Configuration do
|
120
|
+
|
121
|
+
Erratum.configure do |config|
|
122
|
+
config.url_mappings = {
|
123
|
+
'external_documentation_urls' => {
|
124
|
+
'jibbit' => 'http://example.com/edu',
|
125
|
+
},
|
126
|
+
'developer_documentation_urls' => {
|
127
|
+
'jibbit' => 'http://example.com/ddu',
|
128
|
+
},
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
custom_error = CustomError.new(
|
133
|
+
id: 'identifier',
|
134
|
+
http_status: 'flibbity',
|
135
|
+
code: 'jibbit',
|
136
|
+
title: 'roll dem bones and stones',
|
137
|
+
detail: 'I cannot receive any satisfaction',
|
138
|
+
source: 'But perhaps if I attempt it one more time, I can',
|
139
|
+
developer_documentation_url: 'hasimof/jibbit?version=hanky',
|
140
|
+
external_documentation_url: 'hinkies/87654321',
|
141
|
+
)
|
142
|
+
|
143
|
+
expect(custom_error.as_json).to eql(
|
144
|
+
errors: [
|
145
|
+
{
|
146
|
+
id: 'identifier',
|
147
|
+
links: {
|
148
|
+
about: 'hinkies/87654321',
|
149
|
+
documentation: 'hasimof/jibbit?version=hanky',
|
150
|
+
},
|
151
|
+
status: 'flibbity',
|
152
|
+
code: 'jibbit',
|
153
|
+
title: 'roll dem bones and stones',
|
154
|
+
detail: 'I cannot receive any satisfaction',
|
155
|
+
source: 'But perhaps if I attempt it one more time, I can',
|
156
|
+
},
|
157
|
+
],
|
158
|
+
)
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'can handle if it finds no URL mappings' do
|
162
|
+
custom_error = CustomError.new(
|
163
|
+
id: 'identifier',
|
164
|
+
http_status: 'flibbity',
|
165
|
+
code: 'jibbit',
|
166
|
+
title: 'roll dem bones and stones',
|
167
|
+
detail: 'I cannot receive any satisfaction',
|
168
|
+
source: 'But perhaps if I attempt it one more time, I can',
|
169
|
+
)
|
170
|
+
|
171
|
+
expect(custom_error.as_json).to eql(
|
172
|
+
errors: [
|
173
|
+
{
|
174
|
+
id: 'identifier',
|
175
|
+
links: {
|
176
|
+
about: nil,
|
177
|
+
documentation: nil,
|
178
|
+
},
|
179
|
+
status: 'flibbity',
|
180
|
+
code: 'jibbit',
|
181
|
+
title: 'roll dem bones and stones',
|
182
|
+
detail: 'I cannot receive any satisfaction',
|
183
|
+
source: 'But perhaps if I attempt it one more time, I can',
|
184
|
+
},
|
185
|
+
],
|
186
|
+
)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rspeckled'
|
3
|
+
require 'erratum/errors/authentication_errors/duplicate_authentication_error'
|
4
|
+
|
5
|
+
class Erratum
|
6
|
+
module Errors
|
7
|
+
RSpec.describe DuplicateAuthenticationError do
|
8
|
+
let(:error) do
|
9
|
+
DuplicateAuthenticationError.new(provider: 'flibbity',
|
10
|
+
provider_user_id: '12345',
|
11
|
+
user_id: '54321')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'has a status of 409' do
|
15
|
+
expect(error.http_status).to eql 409
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'has a code' do
|
19
|
+
expect(error.code).to eql 'errors.duplicate_authentication_error'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'has a title' do
|
23
|
+
expect(error.title).to eql 'Duplicate Authentication'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'can output the detail' do
|
27
|
+
expect(error.detail).to eql 'The authentication you attempted to ' \
|
28
|
+
'register has already been registered by ' \
|
29
|
+
'another user. We do not currently support ' \
|
30
|
+
'allowing multiple users to be connected to ' \
|
31
|
+
'the same authentication.'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'can output the source' do
|
35
|
+
expect(error.source).to eql(
|
36
|
+
'provider' => 'flibbity',
|
37
|
+
'provider_user_id' => '12345',
|
38
|
+
'user_id' => '54321',
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rspeckled'
|
3
|
+
require 'erratum/errors/authentication_errors/invalid_token_error'
|
4
|
+
|
5
|
+
class Erratum
|
6
|
+
module Errors
|
7
|
+
RSpec.describe InvalidTokenError do
|
8
|
+
let(:error) { InvalidTokenError.new }
|
9
|
+
|
10
|
+
it 'has a status of 401' do
|
11
|
+
expect(error.http_status).to eql 401
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'has a code' do
|
15
|
+
expect(error.code).to eql 'errors.invalid_token_error'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'has a title' do
|
19
|
+
expect(error.title).to eql 'Invalid Token'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'can output the detail' do
|
23
|
+
expect(error.detail).to eql 'The token you attempted to use for this ' \
|
24
|
+
'request is invalid for this resource. ' \
|
25
|
+
'Please double-check and try again.'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'can output the source' do
|
29
|
+
expect(error.source).to eql(token: '[FILTERED]')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rspeckled'
|
3
|
+
require 'erratum/errors/authentication_errors/invalid_username_or_password_error'
|
4
|
+
|
5
|
+
class Erratum
|
6
|
+
module Errors
|
7
|
+
RSpec.describe InvalidUsernameOrPasswordError do
|
8
|
+
let(:error) { InvalidUsernameOrPasswordError.new }
|
9
|
+
|
10
|
+
it 'has a status of 401' do
|
11
|
+
expect(error.http_status).to eql 401
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'has a code' do
|
15
|
+
expect(error.code).to eql 'errors.invalid_username_or_password_error'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'has a title' do
|
19
|
+
expect(error.title).to eql 'Invalid Username/Password'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'can output the detail' do
|
23
|
+
expect(error.detail).to eql 'Either the username or password passed in ' \
|
24
|
+
'or this request is invalid. Please ' \
|
25
|
+
'double-check and try again.'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'can output the source' do
|
29
|
+
error = InvalidUsernameOrPasswordError.new username: 'neo'
|
30
|
+
|
31
|
+
expect(error.source).to eql(username: 'neo', password: '[FILTERED]')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rspeckled'
|
3
|
+
require 'erratum/errors/authorization_errors/forbidden_error'
|
4
|
+
|
5
|
+
class Erratum
|
6
|
+
module Errors
|
7
|
+
RSpec.describe ForbiddenError do
|
8
|
+
let(:error) { ForbiddenError.new }
|
9
|
+
|
10
|
+
it 'has a status of 403' do
|
11
|
+
expect(error.http_status).to eql 403
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'has a code' do
|
15
|
+
expect(error.code).to eql 'errors.forbidden_error'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'has a title' do
|
19
|
+
expect(error.title).to eql 'Forbidden'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'can output the detail for actions affecting a specific resource' do
|
23
|
+
error = ForbiddenError.new(resource_name: 'resource',
|
24
|
+
resource_id: '123',
|
25
|
+
action: 'show')
|
26
|
+
|
27
|
+
expect(error.detail).to eql \
|
28
|
+
'You do not have access to show the resource with ID 123. Providing a different ' \
|
29
|
+
'set of credentials may potentially allow you access to this resource.'
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'can output the detail for actions affecting multiple resources' do
|
33
|
+
error = ForbiddenError.new(resource_name: 'resource',
|
34
|
+
action: 'create')
|
35
|
+
|
36
|
+
expect(error.detail).to eql \
|
37
|
+
'You do not have access to create a resource. Providing a different ' \
|
38
|
+
'set of credentials may potentially allow you access to this resource.'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'can output the source' do
|
42
|
+
error = ForbiddenError.new(resource_name: 'resource',
|
43
|
+
resource_id: '123',
|
44
|
+
action: 'show')
|
45
|
+
|
46
|
+
expect(error.source).to eql('resource_name' => 'resource',
|
47
|
+
'resource_id' => '123',
|
48
|
+
'action' => 'show')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rspeckled'
|
3
|
+
require 'erratum/errors/crud_errors/association_error'
|
4
|
+
require 'active_record/errors'
|
5
|
+
|
6
|
+
class Erratum
|
7
|
+
module Errors
|
8
|
+
RSpec.describe AssociationError do
|
9
|
+
let(:foreign_key_error) do
|
10
|
+
ActiveRecord::InvalidForeignKey.new('DETAIL: Key (resource_id)=(123)')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has a status of 422' do
|
14
|
+
error = AssociationError.new
|
15
|
+
|
16
|
+
expect(error.http_status).to eql 422
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'has a code' do
|
20
|
+
error = AssociationError.new
|
21
|
+
|
22
|
+
expect(error.code).to eql 'errors.association_error'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'has a title' do
|
26
|
+
error = AssociationError.new
|
27
|
+
|
28
|
+
expect(error.title).to eql 'Association Error'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'includes the resource name and action in the detail' do
|
32
|
+
error = AssociationError.new association_name: 'dragon',
|
33
|
+
resource_name: 'Daenerys'
|
34
|
+
|
35
|
+
expect(error.detail).to eql 'The dragon that you ' \
|
36
|
+
'attempted to associate with the Daenerys was ' \
|
37
|
+
'not valid.'
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'includes the resource name and action in the source' do
|
41
|
+
error = AssociationError.new association_name: 'dragon',
|
42
|
+
resource_name: 'Daenerys',
|
43
|
+
attributes: 'winter is coming',
|
44
|
+
association_id: '123'
|
45
|
+
|
46
|
+
expect(error.source).to eql(
|
47
|
+
'Daenerys' => 'winter is coming',
|
48
|
+
'dragon id' => '123',
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'can convert an "ActiveRecord::InvalidForeignKey"' do
|
53
|
+
error = AssociationError.convert(foreign_key_error)
|
54
|
+
|
55
|
+
expect(error.resource_name).to eql nil
|
56
|
+
expect(error.association_name).to eql 'resource'
|
57
|
+
expect(error.association_id).to eql '123'
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'can convert an "ActiveRecord::InvalidForeignKey" while overriding attributes' do
|
61
|
+
error = AssociationError.convert(foreign_key_error, resource_name: 'my_resource')
|
62
|
+
|
63
|
+
expect(error.resource_name).to eql 'my_resource'
|
64
|
+
expect(error.association_name).to eql 'resource'
|
65
|
+
expect(error.association_id).to eql '123'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rspeckled'
|
3
|
+
require 'erratum/errors/crud_errors/resource_not_found_error'
|
4
|
+
require 'active_record/errors'
|
5
|
+
|
6
|
+
class Erratum
|
7
|
+
module Errors
|
8
|
+
RSpec.describe ResourceNotFoundError do
|
9
|
+
it 'has a status of 404' do
|
10
|
+
error = ResourceNotFoundError.new
|
11
|
+
|
12
|
+
expect(error.http_status).to eql 404
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'has a code' do
|
16
|
+
error = ResourceNotFoundError.new
|
17
|
+
|
18
|
+
expect(error.code).to eql 'errors.resource_not_found_error'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'has a title' do
|
22
|
+
error = ResourceNotFoundError.new
|
23
|
+
|
24
|
+
expect(error.title).to eql 'Resource Not Found'
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'includes the resource name and action in the detail' do
|
28
|
+
error = ResourceNotFoundError.new resource_name: 'dragon',
|
29
|
+
action: 'ride'
|
30
|
+
|
31
|
+
expect(error.detail).to eql 'The dragon you attempted ' \
|
32
|
+
'to ride for this request is either ' \
|
33
|
+
'not authorized for the authenticated user ' \
|
34
|
+
'or does not exist.'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'includes the resource name and action in the source' do
|
38
|
+
error = ResourceNotFoundError.new resource_name: 'dragon',
|
39
|
+
resource_id: 123
|
40
|
+
|
41
|
+
expect(error.source).to eql('dragon_id' => 123)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'can accept an array of IDs' do
|
45
|
+
error = ResourceNotFoundError.new resource_name: 'dragon',
|
46
|
+
resource_id: %w{123 456}
|
47
|
+
|
48
|
+
expect(error.source).to eql('dragon_id' => %w{123 456})
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'can convert an "ActiveRecord::RecordNotFound" with no IDs' do
|
52
|
+
record_not_found_error = ActiveRecord::RecordNotFound.new \
|
53
|
+
"Couldn't find resource without an ID"
|
54
|
+
error = ResourceNotFoundError.convert(record_not_found_error)
|
55
|
+
|
56
|
+
expect(error.resource_name).to eql nil
|
57
|
+
expect(error.resource_id).to eql []
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'can convert an "ActiveRecord::RecordNotFound" with one ID' do
|
61
|
+
record_not_found_error = ActiveRecord::RecordNotFound.new \
|
62
|
+
"Couldn't find resource with 'id'=123"
|
63
|
+
error = ResourceNotFoundError.convert(record_not_found_error)
|
64
|
+
|
65
|
+
expect(error.resource_name).to eql nil
|
66
|
+
expect(error.resource_id).to eql %w{123}
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'can convert an "ActiveRecord::RecordNotFound" with multiple IDs' do
|
70
|
+
record_not_found_error = ActiveRecord::RecordNotFound.new \
|
71
|
+
"Couldn't find all resource with 'id': 123, 456, 789"
|
72
|
+
error = ResourceNotFoundError.convert(record_not_found_error)
|
73
|
+
|
74
|
+
expect(error.resource_name).to eql nil
|
75
|
+
expect(error.resource_id).to eql %w{123 456 789}
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'can convert an "ActiveRecord::RecordNotFound" while overriding attributes' do
|
79
|
+
record_not_found_error = ActiveRecord::RecordNotFound.new \
|
80
|
+
"Couldn't find resource with 'id'=123"
|
81
|
+
error = ResourceNotFoundError.convert(record_not_found_error,
|
82
|
+
resource_name: 'my_resource')
|
83
|
+
|
84
|
+
expect(error.resource_name).to eql 'my_resource'
|
85
|
+
expect(error.resource_id).to eql %w{123}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|