human_error 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/lib/human_error.rb +21 -19
  3. data/lib/human_error/configuration.rb +27 -7
  4. data/lib/human_error/error.rb +60 -28
  5. data/lib/human_error/errors/authentication_errors/duplicate_authentication_error.rb +8 -8
  6. data/lib/human_error/errors/authentication_errors/invalid_token_error.rb +8 -8
  7. data/lib/human_error/errors/authentication_errors/invalid_username_or_password_error.rb +8 -8
  8. data/lib/human_error/errors/crud_errors/association_error.rb +8 -9
  9. data/lib/human_error/errors/crud_errors/resource_not_found_error.rb +8 -8
  10. data/lib/human_error/errors/crud_errors/resource_persistence_error.rb +8 -8
  11. data/lib/human_error/errors/request_errors/parameter_missing_error.rb +40 -0
  12. data/lib/human_error/errors/request_errors/unpermitted_parameters_error.rb +46 -0
  13. data/lib/human_error/rescuable_resource.rb +30 -47
  14. data/lib/human_error/verifiable_resource.rb +36 -0
  15. data/lib/human_error/version.rb +1 -1
  16. data/spec/lib/human_error/configuration_spec.rb +13 -26
  17. data/spec/lib/human_error/error_spec.rb +135 -1
  18. data/spec/lib/human_error/errors/authentication_errors/duplicate_authentication_error_spec.rb +8 -13
  19. data/spec/lib/human_error/errors/authentication_errors/invalid_token_error_spec.rb +8 -12
  20. data/spec/lib/human_error/errors/authentication_errors/invalid_username_or_password_error_spec.rb +8 -13
  21. data/spec/lib/human_error/errors/crud_errors/association_error_spec.rb +8 -16
  22. data/spec/lib/human_error/errors/crud_errors/resource_not_found_error_spec.rb +9 -17
  23. data/spec/lib/human_error/errors/crud_errors/resource_persistence_error_spec.rb +9 -17
  24. data/spec/lib/human_error/errors/request_errors/parameter_missing_error_spec.rb +55 -0
  25. data/spec/lib/human_error/errors/request_errors/unpermitted_parameters_error_spec.rb +62 -0
  26. data/spec/lib/human_error_spec.rb +4 -57
  27. metadata +26 -12
  28. data/lib/human_error/error_code_directory.rb +0 -20
  29. data/lib/human_error/errors.rb +0 -9
  30. data/lib/human_error/errors/request_error.rb +0 -48
  31. data/lib/human_error/knowledgebase_id_directory.rb +0 -20
  32. data/lib/human_error/verifiable_model.rb +0 -30
  33. data/spec/lib/human_error/errors/request_error_spec.rb +0 -95
@@ -0,0 +1,40 @@
1
+ class HumanError
2
+ module Errors
3
+ class ParameterMissingError < RuntimeError
4
+ include Error
5
+
6
+ attr_accessor :parameter
7
+
8
+ def self.convert(original_error, overrides = {})
9
+ initialization_parameters = {}
10
+
11
+ case original_error.class.name
12
+ when 'ActionController::ParameterMissing'
13
+ initialization_parameters = {
14
+ parameter: original_error.param,
15
+ }
16
+ end
17
+
18
+ new(initialization_parameters.merge(overrides))
19
+ end
20
+
21
+ def http_status
22
+ 400
23
+ end
24
+
25
+ def title
26
+ 'Missing Parameter'
27
+ end
28
+
29
+ def detail
30
+ "#{parameter} is a required parameter, but you did not supply it."
31
+ end
32
+
33
+ def source
34
+ {
35
+ 'required_parameter' => parameter,
36
+ }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,46 @@
1
+ class HumanError
2
+ module Errors
3
+ class UnpermittedParametersError < RuntimeError
4
+ include Error
5
+
6
+ attr_accessor :parameters
7
+
8
+ def self.convert(original_error, overrides = {})
9
+ initialization_parameters = {}
10
+
11
+ case original_error.class.name
12
+ when 'ActionController::UnpermittedParameters'
13
+ initialization_parameters = {
14
+ parameters: Array(original_error.params),
15
+ }
16
+ end
17
+
18
+ new(initialization_parameters.merge(overrides))
19
+ end
20
+
21
+ def initialize(**attrs)
22
+ self.parameters = Array(attrs.delete(:parameters))
23
+
24
+ super(**attrs)
25
+ end
26
+
27
+ def http_status
28
+ 400
29
+ end
30
+
31
+ def title
32
+ 'Unpermitted Parameters'
33
+ end
34
+
35
+ def detail
36
+ "The following parameters passed are not allowed: #{parameters.join(', ')}"
37
+ end
38
+
39
+ def source
40
+ {
41
+ 'unpermitted_parameters' => parameters,
42
+ }
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,59 +1,42 @@
1
- require 'human_error/errors/crud_errors/resource_persistence_error'
2
- require 'human_error/errors/crud_errors/resource_not_found_error'
3
- require 'human_error/errors/crud_errors/association_error'
4
-
5
1
  class HumanError
6
2
  module RescuableResource
7
3
  module ClassMethods
8
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Style/GuardClause
9
- def rescue_resource(resource_name, from:, via:)
10
- nice_resource_name = resource_name.humanize.downcase
11
- lookup_library = via
12
-
13
- if from.include? 'persistence'
14
- rescue_from 'ActiveRecord::RecordInvalid',
15
- 'ActiveRecord::RecordNotSaved' do |exception|
16
- human_error = lookup_library.convert(exception,
17
- resource_name: nice_resource_name,
18
- action: action_name)
19
-
20
- render json: human_error,
21
- status: human_error.http_status
22
- end
23
- end
24
-
25
- if from.include? 'not_found'
26
- rescue_from 'ActiveRecord::RecordNotFound' do |exception|
27
- human_error = lookup_library.convert(exception,
28
- resource_name: nice_resource_name,
29
- action: action_name)
30
-
31
- render json: human_error,
32
- status: human_error.http_status
33
- end
34
- end
35
-
36
- if from.include? 'association'
37
- rescue_from 'ActiveRecord::InvalidForeignKey' do |exception|
38
- human_error = lookup_library.convert(exception,
39
- resource_name: nice_resource_name,
40
- action: action_name)
41
-
42
- render json: human_error,
43
- status: human_error.http_status
44
- end
45
- end
4
+ def plural_resource_name
5
+ name[/(\w+)Controller\z/, 1].
6
+ underscore.
7
+ pluralize.
8
+ downcase
9
+ end
46
10
 
47
- rescue_from 'HumanError::Error' do |exception|
48
- render json: exception,
49
- status: exception.http_status
50
- end
11
+ def singular_resource_name
12
+ name[/(\w+)Controller\z/, 1].
13
+ underscore.
14
+ singularize.
15
+ downcase
51
16
  end
52
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Style/GuardClause
53
17
  end
54
18
 
55
19
  def self.included(base)
56
20
  base.extend ClassMethods
21
+
22
+ base.rescue_from 'ActiveRecord::RecordInvalid',
23
+ 'ActiveRecord::RecordNotSaved',
24
+ 'ActiveRecord::RecordNotFound',
25
+ 'ActiveRecord::InvalidForeignKey',
26
+ 'ActionController::ParameterMissing',
27
+ 'ActionController::UnpermittedParameters' do |exception|
28
+ human_error = HumanError.convert(exception,
29
+ resource_name: self.class.singular_resource_name,
30
+ action: action_name)
31
+
32
+ render json: human_error,
33
+ status: human_error.http_status
34
+ end
35
+
36
+ base.rescue_from 'HumanError::Error' do |exception|
37
+ render json: exception,
38
+ status: exception.http_status
39
+ end
57
40
  end
58
41
  end
59
42
  end
@@ -0,0 +1,36 @@
1
+ class HumanError
2
+ module VerifiableResource
3
+ module ClassMethods
4
+ def plural_resource_name
5
+ name[/(\w+)Controller\z/, 1].
6
+ underscore.
7
+ pluralize.
8
+ downcase
9
+ end
10
+
11
+ def singular_resource_name
12
+ name[/(\w+)Controller\z/, 1].
13
+ underscore.
14
+ singularize.
15
+ downcase
16
+ end
17
+ end
18
+
19
+ def self.included(base)
20
+ base.extend ClassMethods
21
+
22
+ base.before_action except: %i{index create} do
23
+ model = public_send(self.class.singular_resource_name)
24
+
25
+ resource_not_found_error = HumanError.build(
26
+ 'ResourceNotFoundError',
27
+ resource_name: self.class.singular_resource_name,
28
+ resource_id: [params[:id]],
29
+ action: action_name,
30
+ )
31
+
32
+ fail resource_not_found_error unless model.persisted?
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,3 @@
1
1
  class HumanError
2
- VERSION = '2.0.0'
2
+ VERSION = '3.0.0'
3
3
  end
@@ -2,38 +2,25 @@ require 'rspectacular'
2
2
  require 'human_error/configuration'
3
3
 
4
4
  class HumanError
5
- describe Configuration do
6
- it 'can set the API version' do
7
- configuration = Configuration.new
8
- configuration.api_version = 'whateeeeeever'
5
+ describe Configuration, singletons: true do
6
+ it 'can set the url mappings' do
7
+ configuration = Configuration.instance
8
+ configuration.url_mappings = 'whateeeeeever'
9
9
 
10
- expect(configuration.api_version).to eql 'whateeeeeever'
11
- end
12
-
13
- it 'can set the API error documentation URL' do
14
- configuration = Configuration.new
15
- configuration.api_error_documentation_url = 'whateeeeeever'
16
-
17
- expect(configuration.api_error_documentation_url).to eql 'whateeeeeever'
18
- end
19
-
20
- it 'can set the knowledgebase URL' do
21
- configuration = Configuration.new
22
- configuration.knowledgebase_url = 'whateeeeeever'
23
-
24
- expect(configuration.knowledgebase_url).to eql 'whateeeeeever'
10
+ expect(configuration.url_mappings).to eql 'whateeeeeever'
25
11
  end
26
12
 
27
13
  it 'can convert itself into a hash' do
28
- configuration = Configuration.new
29
- configuration.knowledgebase_url = 'knowledgebase_url'
30
- configuration.api_error_documentation_url = 'api_error_documentation_url'
31
- configuration.api_version = 'api_version'
14
+ configuration = Configuration.instance
15
+ configuration.url_mappings = {
16
+ 'external_documentation_urls' => 'blah',
17
+ 'developer_documentation_urls' => 'asdf',
18
+ }
32
19
 
33
20
  expect(configuration.to_h).to eql(
34
- knowledgebase_url: 'knowledgebase_url',
35
- api_error_documentation_url: 'api_error_documentation_url',
36
- api_version: 'api_version')
21
+ external_documentation_urls: 'blah',
22
+ developer_documentation_urls: 'asdf',
23
+ )
37
24
  end
38
25
  end
39
26
  end
@@ -37,10 +37,144 @@ describe Error do
37
37
 
38
38
  it 'can have its message explicitly set when it is generated' do
39
39
  error = CustomError.new
40
- allow(error).to receive(:developer_message).
40
+ allow(error).to receive(:detail).
41
41
  and_return('My Developer Message')
42
42
 
43
43
  expect(error.message).to eql 'My Developer Message'
44
44
  end
45
+
46
+ it 'can generate error data' do
47
+ custom_error = CustomError.new(
48
+ id: 'identifier',
49
+ http_status: 'flibbity',
50
+ code: 'jibbit',
51
+ title: 'roll dem bones and stones',
52
+ detail: 'I cannot receive any satisfaction',
53
+ source: 'But perhaps if I attempt it one more time, I can',
54
+ developer_documentation_url: 'asimof/jibbit?version=janky',
55
+ external_documentation_url: 'jinkies/87654321')
56
+
57
+ expect(custom_error.as_json).to eql(
58
+ errors: [
59
+ {
60
+ id: 'identifier',
61
+ links: {
62
+ about: 'jinkies/87654321',
63
+ documentation: 'asimof/jibbit?version=janky',
64
+ },
65
+ status: 'flibbity',
66
+ code: 'jibbit',
67
+ title: 'roll dem bones and stones',
68
+ detail: 'I cannot receive any satisfaction',
69
+ source: 'But perhaps if I attempt it one more time, I can',
70
+ },
71
+ ])
72
+ end
73
+
74
+ it 'can extract configuration from the global config if it is not passed in',
75
+ singletons: HumanError::Configuration do
76
+
77
+ HumanError.configure do |config|
78
+ config.url_mappings = {
79
+ 'external_documentation_urls' => {
80
+ 'jibbit' => 'http://example.com/edu',
81
+ },
82
+ 'developer_documentation_urls' => {
83
+ 'jibbit' => 'http://example.com/ddu',
84
+ },
85
+ }
86
+ end
87
+
88
+ custom_error = CustomError.new(
89
+ id: 'identifier',
90
+ http_status: 'flibbity',
91
+ code: 'jibbit',
92
+ title: 'roll dem bones and stones',
93
+ detail: 'I cannot receive any satisfaction',
94
+ source: 'But perhaps if I attempt it one more time, I can')
95
+
96
+ expect(custom_error.as_json).to eql(
97
+ errors: [
98
+ {
99
+ id: 'identifier',
100
+ links: {
101
+ about: 'http://example.com/edu',
102
+ documentation: 'http://example.com/ddu',
103
+ },
104
+ status: 'flibbity',
105
+ code: 'jibbit',
106
+ title: 'roll dem bones and stones',
107
+ detail: 'I cannot receive any satisfaction',
108
+ source: 'But perhaps if I attempt it one more time, I can',
109
+ },
110
+ ])
111
+ end
112
+
113
+ it 'can override the global config if it is set, but an explicit value is passed in',
114
+ singletons: HumanError::Configuration do
115
+
116
+ HumanError.configure do |config|
117
+ config.url_mappings = {
118
+ 'external_documentation_urls' => {
119
+ 'jibbit' => 'http://example.com/edu',
120
+ },
121
+ 'developer_documentation_urls' => {
122
+ 'jibbit' => 'http://example.com/ddu',
123
+ },
124
+ }
125
+ end
126
+
127
+ custom_error = CustomError.new(
128
+ id: 'identifier',
129
+ http_status: 'flibbity',
130
+ code: 'jibbit',
131
+ title: 'roll dem bones and stones',
132
+ detail: 'I cannot receive any satisfaction',
133
+ source: 'But perhaps if I attempt it one more time, I can',
134
+ developer_documentation_url: 'hasimof/jibbit?version=hanky',
135
+ external_documentation_url: 'hinkies/87654321')
136
+
137
+ expect(custom_error.as_json).to eql(
138
+ errors: [
139
+ {
140
+ id: 'identifier',
141
+ links: {
142
+ about: 'hinkies/87654321',
143
+ documentation: 'hasimof/jibbit?version=hanky',
144
+ },
145
+ status: 'flibbity',
146
+ code: 'jibbit',
147
+ title: 'roll dem bones and stones',
148
+ detail: 'I cannot receive any satisfaction',
149
+ source: 'But perhaps if I attempt it one more time, I can',
150
+ },
151
+ ])
152
+ end
153
+
154
+ it 'can handle if it finds no URL mappings' do
155
+ custom_error = CustomError.new(
156
+ id: 'identifier',
157
+ http_status: 'flibbity',
158
+ code: 'jibbit',
159
+ title: 'roll dem bones and stones',
160
+ detail: 'I cannot receive any satisfaction',
161
+ source: 'But perhaps if I attempt it one more time, I can')
162
+
163
+ expect(custom_error.as_json).to eql(
164
+ errors: [
165
+ {
166
+ id: 'identifier',
167
+ links: {
168
+ about: nil,
169
+ documentation: nil,
170
+ },
171
+ status: 'flibbity',
172
+ code: 'jibbit',
173
+ title: 'roll dem bones and stones',
174
+ detail: 'I cannot receive any satisfaction',
175
+ source: 'But perhaps if I attempt it one more time, I can',
176
+ },
177
+ ])
178
+ end
45
179
  end
46
180
  end
@@ -14,34 +14,29 @@ describe DuplicateAuthenticationError do
14
14
  expect(error.http_status).to eql 409
15
15
  end
16
16
 
17
- it 'has a code of 1008' do
18
- expect(error.code).to eql 1008
17
+ it 'has a code' do
18
+ expect(error.code).to eql 'errors.duplicate_authentication_error'
19
19
  end
20
20
 
21
- it 'has a knowledgebase article ID of 1234567890' do
22
- expect(error.knowledgebase_article_id).to eql '1234567890'
21
+ it 'has a title' do
22
+ expect(error.title).to eql 'Duplicate Authentication'
23
23
  end
24
24
 
25
- it 'can output the developer message' do
26
- expect(error.developer_message).to eql 'The authentication you attempted to ' \
25
+ it 'can output the detail' do
26
+ expect(error.detail).to eql 'The authentication you attempted to ' \
27
27
  'register has already been registered by ' \
28
28
  'another user. We do not currently support ' \
29
29
  'allowing multiple users to be connected to ' \
30
30
  'the same authentication.'
31
31
  end
32
32
 
33
- it 'can output the developer details' do
34
- expect(error.developer_details).to eql(
33
+ it 'can output the source' do
34
+ expect(error.source).to eql(
35
35
  'provider' => 'flibbity',
36
36
  'provider_user_id' => '12345',
37
37
  'user_id' => '54321',
38
38
  )
39
39
  end
40
-
41
- it 'can output the friendly message' do
42
- expect(error.friendly_message).to eql 'Sorry! Someone else has already registered ' \
43
- 'this flibbity login.'
44
- end
45
40
  end
46
41
  end
47
42
  end