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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8336fd0b3da25f1ce3dd3e6b2f61ad4b9e736f391088e1824f6427b5619498b2
4
- data.tar.gz: 79481e2446fb498d3f606c118fcbfb2e50df1e16e559e8136bd8a30c5cc84109
3
+ metadata.gz: d9f3f981c61ebf8676c66ee15e0b326d052978374f8099574d8f84370247e834
4
+ data.tar.gz: a4d50d5aabad297607ef9a8c3f2fef743ba1620212cde790e95547ee9559216d
5
5
  SHA512:
6
- metadata.gz: 4ea9ab4f9b4e101ea50eb9813a28cc0ff0d221056447e91660e927d3579b531aafb8a1718706ca51ffb09c9bd2556c90c259670590d416b6e602cbbdbcc89389
7
- data.tar.gz: e83dede85cf3a57e01066970fbfc653fd28414624fda32173b9397691338c8d5e32a200a4e3f4e36eb0d8f480e97bbab7d0588dc7cc50978c0a1cf9cc8a4e93e
6
+ metadata.gz: fcf46277d4d7f2368999acab163f89e192d76ca0db527e94c39fdbcefe6b4bdf83dd2e6a116eef2eb2568e0a4d005f86dc06729925aa50b1dc9e050564d62093
7
+ data.tar.gz: '04281d7946a62f546d3a2e4474336289a83142a2283a622a3abe13988f3d29bf24b66ff1e9aba02a5ce1043cd5232fc262138d1ee061a283e6d4b31a1f13ad98'
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.7.2
data/Gemfile CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- gem 'prettier', '~> 0.21'
5
+ gem 'prettier', '~> 0.22'
6
6
  gem 'rake', '~> 13.0'
7
7
  gem 'rspec', '~> 3.10'
8
8
  gem 'rubocop', '~> 1.5'
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.21.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.21)
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-ruby
2
- Glia REST API errors (WIP)
1
+ # glia-errors
3
2
 
4
- [![Build Status](https://travis-ci.org/salemove/glia-errors-ruby.svg?branch=master)](https://travis-ci.org/salemove/glia-errors-ruby)
3
+ Implements Glia errors in Ruby and provides utilities to easily construct them.
5
4
 
6
- The library provides a list of errors that are used in Glia REST API and utilities to easily construct them.
5
+ # Installation
7
6
 
8
- It is used by Glia developers to map application specific errors to one of standardized Glia errors.
9
- Glia REST API integrators can use the library as a collection of constants to match Glia error response with the error
10
- type, see the example below.
7
+ ```
8
+ $ gem install glia-errors
9
+ ```
11
10
 
12
- # Usage
11
+ ## Usage
13
12
 
14
- ## Require library
13
+ ### Require library
15
14
 
16
15
  ```ruby
17
16
  require 'glia/errors'
18
17
  ```
19
18
 
20
- ## For Glia developers
19
+ ### For Glia developers
21
20
 
22
- ### Map from `dry-validation` result
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
- ## For REST API integrators
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
- # Contributing
92
+ ## Contributing
95
93
 
96
- ## Testing
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
- ## Formatting
117
+ ### Formatting
120
118
 
121
119
  ```
122
120
  bundle exec rake format
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "prettier", "~> 0.21"
5
+ gem "prettier", "~> 0.22"
6
6
  gem "rake", "~> 13.0"
7
7
  gem "rspec", "~> 3.10"
8
8
  gem "rubocop", "~> 1.5"
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "prettier", "~> 0.21"
5
+ gem "prettier", "~> 0.22"
6
6
  gem "rake", "~> 13.0"
7
7
  gem "rspec", "~> 3.10"
8
8
  gem "rubocop", "~> 1.5"
data/glia-errors.gemspec CHANGED
@@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'glia-errors'
8
- spec.version = '0.0.1'
8
+ spec.version = '0.6.1'
9
9
  spec.authors = ['Glia TechMovers']
10
10
  spec.email = ['techmovers@glia.com']
11
11
 
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/validation_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
@@ -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? || [TrueClass, FalseClass, String, Integer, Float].include?(details.class)
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
@@ -46,24 +46,18 @@ module Glia
46
46
  end
47
47
  end
48
48
 
49
- # dry-validation will return `must be a date/time` even if we provide a number or boolean,
50
- # in which case `invalid_format_error` does not make sense, so instead we will return `invalid_type_error`
51
- INVALID_DATE_FORMAT_OR_TYPE_ERROR =
52
- lambda do |field, value, message|
53
- if value.is_a?(String)
54
- format =
55
- case message
56
- when 'must be a date', 'must be Date'
57
- InvalidFormatError::Formats::DATE
58
- when 'must be a date time', 'must be DateTime'
59
- InvalidFormatError::Formats::DATE_TIME
60
- when 'must be a time', 'must be Time'
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 empty' => INVALID_VALUE_ERROR,
108
- 'must be filled' => INVALID_VALUE_ERROR,
109
- 'cannot be empty' => INVALID_VALUE_ERROR,
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