glia-errors 0.0.1 → 0.6.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 +4 -4
- data/.ruby-version +1 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +3 -2
- data/README.md +14 -16
- data/gemfiles/dry_validation_v0.gemfile +1 -1
- data/gemfiles/dry_validation_v1.gemfile +1 -1
- data/glia-errors.gemspec +1 -1
- data/lib/glia/errors.rb +2 -1
- data/lib/glia/errors/client_errors.rb +212 -0
- data/lib/glia/errors/error.rb +9 -1
- data/lib/glia/errors/error_types.rb +13 -0
- data/lib/glia/errors/mapper.rb +25 -27
- data/lib/glia/errors/server_errors.rb +27 -0
- metadata +6 -12
- data/LICENSE +0 -21
- data/lib/glia/errors/validation_errors.rb +0 -115
- data/test_cases/invalid_format_error_case.json +0 -74
- data/test_cases/invalid_length_error_case.json +0 -106
- data/test_cases/invalid_nested_params_case.json +0 -77
- data/test_cases/invalid_number_error_case.json +0 -90
- data/test_cases/invalid_type_error_case.json +0 -98
- data/test_cases/invalid_value_error_case.json +0 -125
- data/test_cases/missing_value_error_case.json +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d9f3f981c61ebf8676c66ee15e0b326d052978374f8099574d8f84370247e834
|
4
|
+
data.tar.gz: a4d50d5aabad297607ef9a8c3f2fef743ba1620212cde790e95547ee9559216d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fcf46277d4d7f2368999acab163f89e192d76ca0db527e94c39fdbcefe6b4bdf83dd2e6a116eef2eb2568e0a4d005f86dc06729925aa50b1dc9e050564d62093
|
7
|
+
data.tar.gz: '04281d7946a62f546d3a2e4474336289a83142a2283a622a3abe13988f3d29bf24b66ff1e9aba02a5ce1043cd5232fc262138d1ee061a283e6d4b31a1f13ad98'
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.7.2
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -13,7 +13,7 @@ GEM
|
|
13
13
|
ast (~> 2.4.1)
|
14
14
|
patience_diff (1.1.0)
|
15
15
|
trollop (~> 1.16)
|
16
|
-
prettier (0.
|
16
|
+
prettier (0.22.0)
|
17
17
|
rainbow (3.0.0)
|
18
18
|
rake (13.0.1)
|
19
19
|
regexp_parser (2.0.0)
|
@@ -58,10 +58,11 @@ GEM
|
|
58
58
|
|
59
59
|
PLATFORMS
|
60
60
|
ruby
|
61
|
+
x86_64-darwin-19
|
61
62
|
|
62
63
|
DEPENDENCIES
|
63
64
|
appraisal
|
64
|
-
prettier (~> 0.
|
65
|
+
prettier (~> 0.22)
|
65
66
|
rake (~> 13.0)
|
66
67
|
rspec (~> 3.10)
|
67
68
|
rubocop (~> 1.5)
|
data/README.md
CHANGED
@@ -1,25 +1,24 @@
|
|
1
|
-
# glia-errors
|
2
|
-
Glia REST API errors (WIP)
|
1
|
+
# glia-errors
|
3
2
|
|
4
|
-
|
3
|
+
Implements Glia errors in Ruby and provides utilities to easily construct them.
|
5
4
|
|
6
|
-
|
5
|
+
# Installation
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
```
|
8
|
+
$ gem install glia-errors
|
9
|
+
```
|
11
10
|
|
12
|
-
|
11
|
+
## Usage
|
13
12
|
|
14
|
-
|
13
|
+
### Require library
|
15
14
|
|
16
15
|
```ruby
|
17
16
|
require 'glia/errors'
|
18
17
|
```
|
19
18
|
|
20
|
-
|
19
|
+
### For Glia developers
|
21
20
|
|
22
|
-
|
21
|
+
#### Map from `dry-validation` result
|
23
22
|
|
24
23
|
Currently 2 `dry-validation` versions are supported:
|
25
24
|
* `v0` up to `0.13`
|
@@ -51,7 +50,7 @@ error = Glia::Errors.from_dry_validation_result(result, ERROR_MAP)
|
|
51
50
|
response = error.to_h
|
52
51
|
```
|
53
52
|
|
54
|
-
|
53
|
+
### For REST API integrators
|
55
54
|
|
56
55
|
```ruby
|
57
56
|
require 'uri'
|
@@ -63,7 +62,6 @@ url = URI('https://api.glia.com/engagement_requests')
|
|
63
62
|
|
64
63
|
http = Net::HTTP.new(url.host, url.port)
|
65
64
|
http.use_ssl = true
|
66
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
67
65
|
request = Net::HTTP::Post.new(url)
|
68
66
|
request["Accept"] = 'application/json'
|
69
67
|
request["Content-Type"] = 'application/json'
|
@@ -91,9 +89,9 @@ else
|
|
91
89
|
end
|
92
90
|
```
|
93
91
|
|
94
|
-
|
92
|
+
## Contributing
|
95
93
|
|
96
|
-
|
94
|
+
### Testing
|
97
95
|
|
98
96
|
Glia errors support multiple versions of `dry-validation` and tests are run against each supported major version.
|
99
97
|
Under the hood we use `Appraisal` gem which generals additional gemfiles for each of the versions.
|
@@ -116,7 +114,7 @@ To run tests only for `dry-validation` v0:
|
|
116
114
|
bundle exec rake test_dry_validation_v0
|
117
115
|
```
|
118
116
|
|
119
|
-
|
117
|
+
### Formatting
|
120
118
|
|
121
119
|
```
|
122
120
|
bundle exec rake format
|
data/glia-errors.gemspec
CHANGED
data/lib/glia/errors.rb
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
require_relative './errors/error'
|
4
4
|
require_relative './errors/error_types'
|
5
|
-
require_relative './errors/
|
5
|
+
require_relative './errors/client_errors'
|
6
|
+
require_relative './errors/server_errors'
|
6
7
|
require_relative './errors/mapper'
|
7
8
|
|
8
9
|
module Glia
|
@@ -0,0 +1,212 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Glia
|
4
|
+
module Errors
|
5
|
+
# rubocop:disable Style/Documentation
|
6
|
+
class InputValidationError < Error
|
7
|
+
def initialize(error_details:, message: nil)
|
8
|
+
super(
|
9
|
+
type: INPUT_VALIDATION_ERROR,
|
10
|
+
ref: "https://example.com/errors/#{INPUT_VALIDATION_ERROR}.html",
|
11
|
+
message: message,
|
12
|
+
error_details: error_details
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class InvalidNumberError < Error
|
18
|
+
def initialize(field:, message: nil)
|
19
|
+
super(
|
20
|
+
type: INVALID_NUMBER_ERROR,
|
21
|
+
ref: "https://example.com/errors/#{INVALID_NUMBER_ERROR}.html",
|
22
|
+
message: message || "#{humanize(field)} value is invalid"
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class InvalidValueError < Error
|
28
|
+
def initialize(field:, message: nil)
|
29
|
+
super(
|
30
|
+
type: INVALID_VALUE_ERROR,
|
31
|
+
ref: "https://example.com/errors/#{INVALID_VALUE_ERROR}.html",
|
32
|
+
message: message || "#{humanize(field)} value is invalid"
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class InvalidLengthError < Error
|
38
|
+
def initialize(field:, message: nil)
|
39
|
+
super(
|
40
|
+
type: INVALID_LENGTH_ERROR,
|
41
|
+
ref: "https://example.com/errors/#{INVALID_LENGTH_ERROR}.html",
|
42
|
+
message: message || "#{humanize(field)} length is invalid"
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class InvalidFormatError < Error
|
48
|
+
class Formats
|
49
|
+
DATE_TIME = 'date_time'
|
50
|
+
DATE = 'date'
|
51
|
+
TIME = 'time'
|
52
|
+
UUID = 'uuid'
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize(field:, format: nil, message: nil)
|
56
|
+
default_message =
|
57
|
+
format ? "has invalid format, required format is #{format}" : 'has invalid format'
|
58
|
+
super(
|
59
|
+
type: INVALID_FORMAT_ERROR,
|
60
|
+
ref: "https://example.com/errors/#{INVALID_FORMAT_ERROR}.html",
|
61
|
+
message: message || "#{humanize(field)} #{default_message}"
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class InvalidTypeError < Error
|
67
|
+
class Types
|
68
|
+
STRING = 'string'
|
69
|
+
INTEGER = 'integer'
|
70
|
+
NUMBER = 'number'
|
71
|
+
BOOLEAN = 'boolean'
|
72
|
+
ARRAY = 'array'
|
73
|
+
OBJECT = 'object'
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize(field:, type:, message: nil)
|
77
|
+
super(
|
78
|
+
type: INVALID_TYPE_ERROR,
|
79
|
+
ref: "https://example.com/errors/#{INVALID_TYPE_ERROR}.html",
|
80
|
+
message: message || "#{humanize(field)} must be of type #{type}",
|
81
|
+
error_details: { type: type }
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class MissingValueError < Error
|
87
|
+
def initialize(field:, message: nil)
|
88
|
+
super(
|
89
|
+
type: MISSING_VALUE_ERROR,
|
90
|
+
ref: "https://example.com/errors/#{MISSING_VALUE_ERROR}.html",
|
91
|
+
message: message || "#{humanize(field)} is missing"
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class UnknownError < Error
|
97
|
+
def initialize(field:, message: nil)
|
98
|
+
super(
|
99
|
+
type: UNKNOWN_ERROR,
|
100
|
+
ref: "https://example.com/errors/#{UNKNOWN_ERROR}.html",
|
101
|
+
message: message || "#{humanize(field)} validation failed with unknown error"
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class ResourceNotFoundError < Error
|
107
|
+
def initialize(resource:, message: nil)
|
108
|
+
assert_snake_case(resource)
|
109
|
+
|
110
|
+
super(
|
111
|
+
type: RESOURCE_NOT_FOUND_ERROR,
|
112
|
+
ref: "https://example.com/errors/#{RESOURCE_NOT_FOUND_ERROR}.html",
|
113
|
+
message: message || "#{humanize(resource)} not found",
|
114
|
+
error_details: { resource: resource }
|
115
|
+
)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class NotVerifiedError < Error
|
120
|
+
def initialize(resource:, message: nil)
|
121
|
+
assert_snake_case(resource)
|
122
|
+
|
123
|
+
super(
|
124
|
+
type: NOT_VERIFIED_ERROR,
|
125
|
+
ref: "https://example.com/errors/#{NOT_VERIFIED_ERROR}.html",
|
126
|
+
message: message || "#{humanize(resource)} is not verified",
|
127
|
+
error_details: { resource: resource }
|
128
|
+
)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class RemainingAssociationError < Error
|
133
|
+
def initialize(resource:, associated_resource:, message: nil)
|
134
|
+
assert_snake_case(resource)
|
135
|
+
assert_snake_case(associated_resource)
|
136
|
+
|
137
|
+
default_message =
|
138
|
+
"cannot be modified/deleted because it is associated to one or more #{
|
139
|
+
humanize(associated_resource)
|
140
|
+
}(s)"
|
141
|
+
super(
|
142
|
+
type: REMAINING_ASSOCIATION_ERROR,
|
143
|
+
ref: "https://example.com/errors/#{REMAINING_ASSOCIATION_ERROR}.html",
|
144
|
+
message: message || "#{humanize(resource)} #{default_message}",
|
145
|
+
error_details: { resource: resource, associated_resource: associated_resource }
|
146
|
+
)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class LimitExceededError < Error
|
151
|
+
def initialize(resource:, max:, message: nil)
|
152
|
+
assert_snake_case(resource)
|
153
|
+
|
154
|
+
super(
|
155
|
+
type: LIMIT_EXCEEDED_ERROR,
|
156
|
+
ref: "https://example.com/errors/#{LIMIT_EXCEEDED_ERROR}.html",
|
157
|
+
message: message || "#{humanize(resource)} count must not exceed #{max}",
|
158
|
+
error_details: { resource: resource, max: max }
|
159
|
+
)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class ResourceAlreadyExistsError < Error
|
164
|
+
def initialize(resource:, message: nil)
|
165
|
+
assert_snake_case(resource)
|
166
|
+
|
167
|
+
super(
|
168
|
+
type: RESOURCE_ALREADY_EXISTS_ERROR,
|
169
|
+
ref: "https://example.com/errors/#{RESOURCE_ALREADY_EXISTS_ERROR}.html",
|
170
|
+
message: message || "#{humanize(resource)} already exists",
|
171
|
+
error_details: { resource: resource }
|
172
|
+
)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class InvalidResourceStateError < Error
|
177
|
+
def initialize(resource:, state:, message: nil)
|
178
|
+
assert_snake_case(resource)
|
179
|
+
assert_snake_case(state)
|
180
|
+
|
181
|
+
super(
|
182
|
+
type: INVALID_RESOURCE_STATE_ERROR,
|
183
|
+
ref: "https://example.com/errors/#{INVALID_RESOURCE_STATE_ERROR}.html",
|
184
|
+
message: message || "#{humanize(resource)} is in invalid state: #{state}",
|
185
|
+
error_details: { resource: resource, state: state }
|
186
|
+
)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class AuthorizationError < Error
|
191
|
+
def initialize(message: nil)
|
192
|
+
super(
|
193
|
+
type: AUTHORIZATION_ERROR,
|
194
|
+
ref: "https://example.com/errors/#{AUTHORIZATION_ERROR}.html",
|
195
|
+
message: message || 'You do not have permissions to perform the action'
|
196
|
+
)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
class RecipientOptedOutError < Error
|
201
|
+
def initialize(message: nil)
|
202
|
+
super(
|
203
|
+
type: RECIPIENT_OPTED_OUT_ERROR,
|
204
|
+
ref:
|
205
|
+
'https://docs.glia.com/glia-dev/reference/webhooks#example-engagementrequestfailure-event-with-fail_error',
|
206
|
+
message: message || 'Recipient has opted out'
|
207
|
+
)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
# rubocop:enable Style/Documentation
|
211
|
+
end
|
212
|
+
end
|
data/lib/glia/errors/error.rb
CHANGED
@@ -4,6 +4,7 @@ module Glia
|
|
4
4
|
module Errors
|
5
5
|
# Base error
|
6
6
|
class Error
|
7
|
+
SNAKE_CASE_REGEX = /^[a-z0-9]+(_[a-z0-9]+)*$/.freeze
|
7
8
|
attr_reader :type, :ref, :message, :error_details
|
8
9
|
|
9
10
|
def initialize(type:, ref:, message: nil, error_details: nil)
|
@@ -38,7 +39,8 @@ module Glia
|
|
38
39
|
end
|
39
40
|
|
40
41
|
def primitive?(details)
|
41
|
-
details.nil? ||
|
42
|
+
details.nil? ||
|
43
|
+
[TrueClass, FalseClass, String, Integer, Float, Symbol].include?(details.class)
|
42
44
|
end
|
43
45
|
|
44
46
|
# Converts from camel_case to capitalized more human readable value
|
@@ -46,6 +48,12 @@ module Glia
|
|
46
48
|
def humanize(value)
|
47
49
|
value.to_s.capitalize.gsub('_', ' ')
|
48
50
|
end
|
51
|
+
|
52
|
+
def assert_snake_case(value)
|
53
|
+
return if value.to_s.match(SNAKE_CASE_REGEX)
|
54
|
+
|
55
|
+
raise ArgumentError, "Expected '#{value}' to be in snake case"
|
56
|
+
end
|
49
57
|
end
|
50
58
|
end
|
51
59
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module Glia
|
4
4
|
module Errors
|
5
|
+
# Client errors
|
5
6
|
INPUT_VALIDATION_ERROR = 'input_validation_error'
|
6
7
|
INVALID_TYPE_ERROR = 'invalid_type_error'
|
7
8
|
INVALID_NUMBER_ERROR = 'invalid_number_error'
|
@@ -10,5 +11,17 @@ module Glia
|
|
10
11
|
INVALID_LENGTH_ERROR = 'invalid_length_error'
|
11
12
|
MISSING_VALUE_ERROR = 'missing_value_error'
|
12
13
|
UNKNOWN_ERROR = 'unknown_error'
|
14
|
+
RESOURCE_NOT_FOUND_ERROR = 'resource_not_found_error'
|
15
|
+
NOT_VERIFIED_ERROR = 'not_verified_error'
|
16
|
+
REMAINING_ASSOCIATION_ERROR = 'remaining_association_error'
|
17
|
+
LIMIT_EXCEEDED_ERROR = 'limit_exceeded_error'
|
18
|
+
RESOURCE_ALREADY_EXISTS_ERROR = 'resource_already_exists_error'
|
19
|
+
INVALID_RESOURCE_STATE_ERROR = 'invalid_resource_state_error'
|
20
|
+
AUTHORIZATION_ERROR = 'authorization_error'
|
21
|
+
RECIPIENT_OPTED_OUT_ERROR = 'recipient_opted_out_error'
|
22
|
+
|
23
|
+
# Server errors
|
24
|
+
INTERNAL_SERVER_ERROR = 'internal_server_error'
|
25
|
+
SERVICE_UNAVAILABLE_ERROR = 'service_unavailable_error'
|
13
26
|
end
|
14
27
|
end
|
data/lib/glia/errors/mapper.rb
CHANGED
@@ -46,24 +46,18 @@ module Glia
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
InvalidFormatError::Formats::TIME
|
62
|
-
end
|
63
|
-
InvalidFormatError.new(field: field, format: format)
|
64
|
-
else
|
65
|
-
InvalidTypeError.new(field: field, type: InvalidTypeError::Types::STRING)
|
66
|
-
end
|
49
|
+
INVALID_DATE_FORMAT =
|
50
|
+
lambda do |field, _value, message|
|
51
|
+
format =
|
52
|
+
case message
|
53
|
+
when 'must be a date', 'must be Date'
|
54
|
+
InvalidFormatError::Formats::DATE
|
55
|
+
when 'must be a date time', 'must be DateTime'
|
56
|
+
InvalidFormatError::Formats::DATE_TIME
|
57
|
+
when 'must be a time', 'must be Time'
|
58
|
+
InvalidFormatError::Formats::TIME
|
59
|
+
end
|
60
|
+
InvalidFormatError.new(field: field, format: format)
|
67
61
|
end
|
68
62
|
|
69
63
|
ERROR_MAP = {
|
@@ -83,13 +77,13 @@ module Glia
|
|
83
77
|
'must be Boolean' => INVALID_TYPE_ERROR,
|
84
78
|
'must be a string' => INVALID_TYPE_ERROR,
|
85
79
|
'must be String' => INVALID_TYPE_ERROR,
|
86
|
-
'must be a date time' => INVALID_DATE_FORMAT_OR_TYPE_ERROR,
|
87
|
-
'must be DateTime' => INVALID_DATE_FORMAT_OR_TYPE_ERROR,
|
88
|
-
'must be a date' => INVALID_DATE_FORMAT_OR_TYPE_ERROR,
|
89
|
-
'must be Date' => INVALID_DATE_FORMAT_OR_TYPE_ERROR,
|
90
|
-
'must be a time' => INVALID_DATE_FORMAT_OR_TYPE_ERROR,
|
91
|
-
'must be Time' => INVALID_DATE_FORMAT_OR_TYPE_ERROR,
|
92
80
|
# InvalidFormatError
|
81
|
+
'must be a date time' => INVALID_DATE_FORMAT,
|
82
|
+
'must be DateTime' => INVALID_DATE_FORMAT,
|
83
|
+
'must be a date' => INVALID_DATE_FORMAT,
|
84
|
+
'must be Date' => INVALID_DATE_FORMAT,
|
85
|
+
'must be a time' => INVALID_DATE_FORMAT,
|
86
|
+
'must be Time' => INVALID_DATE_FORMAT,
|
93
87
|
'is in invalid format' => INVALID_FORMAT_ERROR,
|
94
88
|
'is not a valid UUID' => INVALID_UUID_ERROR,
|
95
89
|
# InvalidNumberError
|
@@ -104,9 +98,11 @@ module Glia
|
|
104
98
|
'must be false' => INVALID_VALUE_ERROR,
|
105
99
|
'must include' => INVALID_VALUE_ERROR,
|
106
100
|
'must not include' => INVALID_VALUE_ERROR,
|
107
|
-
'must be
|
108
|
-
|
109
|
-
|
101
|
+
'must be filled' =>
|
102
|
+
lambda do |field, value, _message|
|
103
|
+
# Consider `nil` as invalid value error, but empty string or array as invalid length error
|
104
|
+
value.nil? ? InvalidValueError.new(field: field) : InvalidLengthError.new(field: field)
|
105
|
+
end,
|
110
106
|
'cannot be defined' => INVALID_VALUE_ERROR,
|
111
107
|
# InvalidNumberError or InvalidValueError
|
112
108
|
'must be equal to' => INVALID_NUMBER_OR_VALUE_ERROR,
|
@@ -123,6 +119,8 @@ module Glia
|
|
123
119
|
'bytes long' => INVALID_LENGTH_ERROR,
|
124
120
|
'length must be' => INVALID_LENGTH_ERROR,
|
125
121
|
'length must be within' => INVALID_LENGTH_ERROR,
|
122
|
+
'must be empty' => INVALID_LENGTH_ERROR,
|
123
|
+
'cannot be empty' => INVALID_LENGTH_ERROR,
|
126
124
|
# MissingValueError
|
127
125
|
'is missing' => MISSING_VALUE_ERROR,
|
128
126
|
# Custom format errors
|