glia-errors 0.0.1 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|