erratum 0.0.1
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
- 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
|