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 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