human_error 2.0.0 → 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.
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