rdstation-ruby-client 1.2.0 → 2.3.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.
- checksums.yaml +5 -5
- data/.github/ISSUE_TEMPLATE/rdsm-ruby-client-issue-template.md +49 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +163 -0
- data/README.md +243 -43
- data/lib/rdstation-ruby-client.rb +6 -0
- data/lib/rdstation.rb +19 -0
- data/lib/rdstation/api_response.rb +3 -3
- data/lib/rdstation/authentication.rb +32 -3
- data/lib/rdstation/authorization.rb +24 -0
- data/lib/rdstation/builder/field.rb +70 -0
- data/lib/rdstation/client.rb +17 -70
- data/lib/rdstation/contacts.rb +21 -16
- data/lib/rdstation/error.rb +22 -15
- data/lib/rdstation/error/format.rb +21 -3
- data/lib/rdstation/error/formatter.rb +53 -7
- data/lib/rdstation/error_handler.rb +29 -26
- data/lib/rdstation/error_handler/bad_request.rb +30 -0
- data/lib/rdstation/error_handler/unauthorized.rb +17 -9
- data/lib/rdstation/events.rb +7 -19
- data/lib/rdstation/fields.rb +31 -7
- data/lib/rdstation/retryable_request.rb +35 -0
- data/lib/rdstation/version.rb +1 -1
- data/lib/rdstation/webhooks.rb +25 -17
- data/rdstation-ruby-client.gemspec +4 -1
- data/spec/lib/rdstation-ruby-client_spec.rb +1 -1
- data/spec/lib/rdstation/api_response_spec.rb +34 -0
- data/spec/lib/rdstation/authentication_spec.rb +164 -0
- data/spec/lib/rdstation/authorization_spec.rb +24 -0
- data/spec/lib/rdstation/builder/field_spec.rb +69 -0
- data/spec/lib/rdstation/client_spec.rb +37 -0
- data/spec/lib/rdstation/contacts_spec.rb +54 -41
- data/spec/lib/rdstation/error/format_spec.rb +46 -0
- data/spec/lib/rdstation/error/formatter_spec.rb +83 -0
- data/spec/lib/rdstation/error_handler/unauthorized_spec.rb +0 -29
- data/spec/lib/rdstation/error_handler_spec.rb +153 -26
- data/spec/lib/rdstation/events_spec.rb +20 -9
- data/spec/lib/rdstation/fields_spec.rb +10 -3
- data/spec/lib/rdstation/retryable_request_spec.rb +142 -0
- data/spec/lib/rdstation/webhooks_spec.rb +41 -13
- data/spec/lib/rdstation_spec.rb +18 -0
- metadata +40 -13
- data/Gemfile.lock +0 -59
- data/lib/rdstation/error_handler/default.rb +0 -15
- data/lib/rdstation/error_handler/resource_not_found.rb +0 -24
- data/spec/lib/rdstation/error_handler/default_spec.rb +0 -14
- data/spec/lib/rdstation/error_handler/resource_not_found_spec.rb +0 -54
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative './format'
|
2
4
|
|
3
5
|
module RDStation
|
@@ -15,6 +17,10 @@ module RDStation
|
|
15
17
|
return from_flat_hash
|
16
18
|
when RDStation::Error::Format::HASH_OF_ARRAYS
|
17
19
|
return from_hash_of_arrays
|
20
|
+
when RDStation::Error::Format::HASH_OF_HASHES
|
21
|
+
return from_hash_of_hashes
|
22
|
+
when RDStation::Error::Format::HASH_OF_MULTIPLE_TYPES
|
23
|
+
return from_hash_of_multiple_types
|
18
24
|
end
|
19
25
|
|
20
26
|
errors
|
@@ -24,14 +30,29 @@ module RDStation
|
|
24
30
|
[errors]
|
25
31
|
end
|
26
32
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
33
|
+
def from_hash_of_multiple_types
|
34
|
+
array_of_errors = []
|
35
|
+
errors.each do |attribute_name, errors|
|
36
|
+
if errors.is_a? Array
|
37
|
+
result = build_error_from_array(attribute_name, errors)
|
38
|
+
end
|
39
|
+
if errors.is_a? Hash
|
40
|
+
result = build_error_from_multilingual_hash(attribute_name, errors)
|
41
|
+
end
|
42
|
+
array_of_errors.push(*result)
|
34
43
|
end
|
44
|
+
|
45
|
+
array_of_errors
|
46
|
+
end
|
47
|
+
|
48
|
+
def from_hash_of_hashes
|
49
|
+
array_of_errors = []
|
50
|
+
errors.each do |attribute_name, errors|
|
51
|
+
result = build_error_from_multilingual_hash(attribute_name, errors)
|
52
|
+
array_of_errors.push(*result)
|
53
|
+
end
|
54
|
+
|
55
|
+
array_of_errors
|
35
56
|
end
|
36
57
|
|
37
58
|
def error_format
|
@@ -41,6 +62,31 @@ module RDStation
|
|
41
62
|
def errors
|
42
63
|
@errors ||= @error_response['errors']
|
43
64
|
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def build_error_from_array(attribute_name, attribute_errors)
|
69
|
+
path = { 'path' => "body.#{attribute_name}" }
|
70
|
+
attribute_errors.map { |error| error.merge(path) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def build_error_from_multilingual_hash(attribute_name, errors_by_language)
|
74
|
+
array_of_errors = []
|
75
|
+
errors_by_language.each do |language, errors|
|
76
|
+
result = build_error_from_array("#{attribute_name}.#{language}", errors)
|
77
|
+
array_of_errors.push(*result)
|
78
|
+
end
|
79
|
+
array_of_errors
|
80
|
+
end
|
81
|
+
|
82
|
+
def from_hash_of_arrays
|
83
|
+
errors.each_with_object([]) do |errors, array_of_errors|
|
84
|
+
attribute_name = errors.first
|
85
|
+
attribute_errors = errors.last
|
86
|
+
errors = build_error_from_array(attribute_name, attribute_errors)
|
87
|
+
array_of_errors.push(*errors)
|
88
|
+
end
|
89
|
+
end
|
44
90
|
end
|
45
91
|
end
|
46
92
|
end
|
@@ -1,37 +1,44 @@
|
|
1
1
|
require_relative 'error/formatter'
|
2
|
-
require_relative 'error_handler/
|
3
|
-
require_relative 'error_handler/default'
|
4
|
-
require_relative 'error_handler/expired_access_token'
|
5
|
-
require_relative 'error_handler/expired_code_grant'
|
6
|
-
require_relative 'error_handler/invalid_credentials'
|
7
|
-
require_relative 'error_handler/invalid_event_type'
|
8
|
-
require_relative 'error_handler/resource_not_found'
|
2
|
+
require_relative 'error_handler/bad_request'
|
9
3
|
require_relative 'error_handler/unauthorized'
|
10
4
|
|
11
5
|
module RDStation
|
12
6
|
class ErrorHandler
|
13
|
-
ERROR_TYPES = [
|
14
|
-
ErrorHandler::ConflictingField,
|
15
|
-
ErrorHandler::ExpiredAccessToken,
|
16
|
-
ErrorHandler::ExpiredCodeGrant,
|
17
|
-
ErrorHandler::InvalidCredentials,
|
18
|
-
ErrorHandler::InvalidEventType,
|
19
|
-
ErrorHandler::ResourceNotFound,
|
20
|
-
ErrorHandler::Unauthorized,
|
21
|
-
ErrorHandler::Default
|
22
|
-
].freeze
|
23
|
-
|
24
7
|
def initialize(response)
|
25
8
|
@response = response
|
9
|
+
@code = response.code
|
26
10
|
end
|
27
11
|
|
28
|
-
def
|
29
|
-
|
12
|
+
def raise_error
|
13
|
+
raise error_class, array_of_errors.first if error_class < RDStation::Error
|
14
|
+
|
15
|
+
error_class.new(array_of_errors).raise_error
|
16
|
+
rescue JSON::ParserError => error
|
17
|
+
raise error_class, { 'error_message' => response.body }
|
30
18
|
end
|
31
19
|
|
32
20
|
private
|
33
21
|
|
34
|
-
attr_reader :response
|
22
|
+
attr_reader :response, :code
|
23
|
+
|
24
|
+
def error_class
|
25
|
+
case code
|
26
|
+
when 400 then RDStation::ErrorHandler::BadRequest
|
27
|
+
when 401 then RDStation::ErrorHandler::Unauthorized
|
28
|
+
when 403 then RDStation::Error::Forbidden
|
29
|
+
when 404 then RDStation::Error::NotFound
|
30
|
+
when 405 then RDStation::Error::MethodNotAllowed
|
31
|
+
when 406 then RDStation::Error::NotAcceptable
|
32
|
+
when 409 then RDStation::Error::Conflict
|
33
|
+
when 415 then RDStation::Error::UnsupportedMediaType
|
34
|
+
when 422 then RDStation::Error::UnprocessableEntity
|
35
|
+
when 500 then RDStation::Error::InternalServerError
|
36
|
+
when 501 then RDStation::Error::NotImplemented
|
37
|
+
when 502 then RDStation::Error::BadGateway
|
38
|
+
when 503 then RDStation::Error::ServiceUnavailable
|
39
|
+
when 500..599 then RDStation::Error::ServerError
|
40
|
+
end
|
41
|
+
end
|
35
42
|
|
36
43
|
def array_of_errors
|
37
44
|
error_formatter.to_array.map do |error|
|
@@ -39,10 +46,6 @@ module RDStation
|
|
39
46
|
end
|
40
47
|
end
|
41
48
|
|
42
|
-
def error_types
|
43
|
-
ERROR_TYPES.map { |error_type| error_type.new(array_of_errors) }
|
44
|
-
end
|
45
|
-
|
46
49
|
def response_errors
|
47
50
|
JSON.parse(response.body)
|
48
51
|
end
|
@@ -55,7 +58,7 @@ module RDStation
|
|
55
58
|
{
|
56
59
|
'headers' => response.headers,
|
57
60
|
'body' => JSON.parse(response.body),
|
58
|
-
'http_status' => response.code
|
61
|
+
'http_status' => response.code,
|
59
62
|
}
|
60
63
|
end
|
61
64
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'conflicting_field'
|
2
|
+
require_relative 'invalid_event_type'
|
3
|
+
|
4
|
+
module RDStation
|
5
|
+
class ErrorHandler
|
6
|
+
class BadRequest
|
7
|
+
BAD_REQUEST_ERRORS = [
|
8
|
+
ErrorHandler::ConflictingField,
|
9
|
+
ErrorHandler::InvalidEventType,
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
def initialize(array_of_errors)
|
13
|
+
@array_of_errors = array_of_errors
|
14
|
+
end
|
15
|
+
|
16
|
+
def raise_error
|
17
|
+
error_classes.each(&:raise_error)
|
18
|
+
raise RDStation::Error::BadRequest, @array_of_errors.first
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def error_classes
|
24
|
+
BAD_REQUEST_ERRORS.map do |error|
|
25
|
+
error.new(@array_of_errors)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,23 +1,31 @@
|
|
1
|
+
require_relative 'expired_access_token'
|
2
|
+
require_relative 'expired_code_grant'
|
3
|
+
require_relative 'invalid_credentials'
|
4
|
+
|
1
5
|
module RDStation
|
2
6
|
class ErrorHandler
|
3
7
|
class Unauthorized
|
4
|
-
|
5
|
-
|
6
|
-
|
8
|
+
UNAUTHORIZED_ERRORS = [
|
9
|
+
ErrorHandler::ExpiredAccessToken,
|
10
|
+
ErrorHandler::ExpiredCodeGrant,
|
11
|
+
ErrorHandler::InvalidCredentials,
|
12
|
+
].freeze
|
7
13
|
|
8
|
-
def initialize(
|
9
|
-
@
|
14
|
+
def initialize(array_of_errors)
|
15
|
+
@array_of_errors = array_of_errors
|
10
16
|
end
|
11
17
|
|
12
18
|
def raise_error
|
13
|
-
|
14
|
-
raise RDStation::Error::Unauthorized,
|
19
|
+
error_classes.each(&:raise_error)
|
20
|
+
raise RDStation::Error::Unauthorized, @array_of_errors.first
|
15
21
|
end
|
16
22
|
|
17
23
|
private
|
18
24
|
|
19
|
-
def
|
20
|
-
|
25
|
+
def error_classes
|
26
|
+
UNAUTHORIZED_ERRORS.map do |error|
|
27
|
+
error.new(@array_of_errors)
|
28
|
+
end
|
21
29
|
end
|
22
30
|
end
|
23
31
|
end
|
data/lib/rdstation/events.rb
CHANGED
@@ -1,31 +1,19 @@
|
|
1
1
|
module RDStation
|
2
2
|
class Events
|
3
3
|
include HTTParty
|
4
|
+
include ::RDStation::RetryableRequest
|
4
5
|
|
5
6
|
EVENTS_ENDPOINT = 'https://api.rd.services/platform/events'.freeze
|
6
7
|
|
7
|
-
def initialize(
|
8
|
-
@
|
8
|
+
def initialize(authorization:)
|
9
|
+
@authorization = authorization
|
9
10
|
end
|
10
11
|
|
11
12
|
def create(payload)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def errors?(response_body)
|
21
|
-
response_body.is_a?(Array) || response_body['errors']
|
22
|
-
end
|
23
|
-
|
24
|
-
def required_headers
|
25
|
-
{
|
26
|
-
'Authorization' => "Bearer #{@auth_token}",
|
27
|
-
'Content-Type' => 'application/json'
|
28
|
-
}
|
13
|
+
retryable_request(@authorization) do |authorization|
|
14
|
+
response = self.class.post(EVENTS_ENDPOINT, headers: authorization.headers, body: payload.to_json)
|
15
|
+
ApiResponse.build(response)
|
16
|
+
end
|
29
17
|
end
|
30
18
|
end
|
31
19
|
end
|
data/lib/rdstation/fields.rb
CHANGED
@@ -1,24 +1,48 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
module RDStation
|
3
|
-
# More info: https://developers.rdstation.com/pt-BR/reference/
|
3
|
+
# More info: https://developers.rdstation.com/pt-BR/reference/fields
|
4
4
|
class Fields
|
5
5
|
include HTTParty
|
6
|
+
include ::RDStation::RetryableRequest
|
6
7
|
|
7
8
|
BASE_URL = 'https://api.rd.services/platform/contacts/fields'.freeze
|
8
9
|
|
9
|
-
def initialize(
|
10
|
-
@
|
10
|
+
def initialize(authorization:)
|
11
|
+
@authorization = authorization
|
11
12
|
end
|
12
13
|
|
13
14
|
def all
|
14
|
-
|
15
|
-
|
15
|
+
retryable_request(@authorization) do |authorization|
|
16
|
+
response = self.class.get(BASE_URL, headers: authorization.headers)
|
17
|
+
ApiResponse.build(response)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def create(payload)
|
22
|
+
retryable_request(@authorization) do |authorization|
|
23
|
+
response = self.class.post(BASE_URL, headers: authorization.headers, body: payload.to_json)
|
24
|
+
ApiResponse.build(response)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def update(uuid, payload)
|
29
|
+
retryable_request(@authorization) do |authorization|
|
30
|
+
response = self.class.patch(base_url(uuid), headers: authorization.headers, body: payload.to_json)
|
31
|
+
ApiResponse.build(response)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete(uuid)
|
36
|
+
retryable_request(@authorization) do |authorization|
|
37
|
+
response = self.class.delete(base_url(uuid), headers: authorization.headers)
|
38
|
+
ApiResponse.build(response)
|
39
|
+
end
|
16
40
|
end
|
17
41
|
|
18
42
|
private
|
19
43
|
|
20
|
-
def
|
21
|
-
|
44
|
+
def base_url(path = '')
|
45
|
+
"#{BASE_URL}/#{path}"
|
22
46
|
end
|
23
47
|
end
|
24
48
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RDStation
|
4
|
+
module RetryableRequest
|
5
|
+
MAX_RETRIES = 1
|
6
|
+
def retryable_request(authorization)
|
7
|
+
retries = 0
|
8
|
+
begin
|
9
|
+
yield authorization
|
10
|
+
rescue ::RDStation::Error::ExpiredAccessToken => e
|
11
|
+
raise if !retry_possible?(authorization) || retries >= MAX_RETRIES
|
12
|
+
|
13
|
+
retries += 1
|
14
|
+
refresh_access_token(authorization)
|
15
|
+
retry
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def retry_possible?(authorization)
|
20
|
+
[
|
21
|
+
RDStation.configuration&.client_id,
|
22
|
+
RDStation.configuration&.client_secret,
|
23
|
+
authorization.refresh_token
|
24
|
+
].all?
|
25
|
+
end
|
26
|
+
|
27
|
+
def refresh_access_token(authorization)
|
28
|
+
client = RDStation::Authentication.new
|
29
|
+
response = client.update_access_token(authorization.refresh_token)
|
30
|
+
authorization.access_token = response['access_token']
|
31
|
+
authorization.access_token_expires_in = response['expires_in']
|
32
|
+
RDStation.configuration&.access_token_refresh_callback&.call(authorization)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/rdstation/version.rb
CHANGED
data/lib/rdstation/webhooks.rb
CHANGED
@@ -1,35 +1,47 @@
|
|
1
1
|
module RDStation
|
2
2
|
class Webhooks
|
3
3
|
include HTTParty
|
4
|
+
include ::RDStation::RetryableRequest
|
4
5
|
|
5
|
-
def initialize(
|
6
|
-
@
|
6
|
+
def initialize(authorization:)
|
7
|
+
@authorization = authorization
|
7
8
|
end
|
8
9
|
|
9
10
|
def all
|
10
|
-
|
11
|
-
|
11
|
+
retryable_request(@authorization) do |authorization|
|
12
|
+
response = self.class.get(base_url, headers: authorization.headers)
|
13
|
+
ApiResponse.build(response)
|
14
|
+
end
|
12
15
|
end
|
13
16
|
|
14
17
|
def by_uuid(uuid)
|
15
|
-
|
16
|
-
|
18
|
+
retryable_request(@authorization) do |authorization|
|
19
|
+
response = self.class.get(base_url(uuid), headers: authorization.headers)
|
20
|
+
ApiResponse.build(response)
|
21
|
+
end
|
17
22
|
end
|
18
23
|
|
19
24
|
def create(payload)
|
20
|
-
|
21
|
-
|
25
|
+
retryable_request(@authorization) do |authorization|
|
26
|
+
response = self.class.post(base_url, headers: authorization.headers, body: payload.to_json)
|
27
|
+
ApiResponse.build(response)
|
28
|
+
end
|
22
29
|
end
|
23
30
|
|
24
31
|
def update(uuid, payload)
|
25
|
-
|
26
|
-
|
32
|
+
retryable_request(@authorization) do |authorization|
|
33
|
+
response = self.class.put(base_url(uuid), headers: authorization.headers, body: payload.to_json)
|
34
|
+
ApiResponse.build(response)
|
35
|
+
end
|
27
36
|
end
|
28
37
|
|
29
38
|
def delete(uuid)
|
30
|
-
|
31
|
-
|
32
|
-
|
39
|
+
retryable_request(@authorization) do |authorization|
|
40
|
+
response = self.class.delete(base_url(uuid), headers: authorization.headers)
|
41
|
+
return webhook_deleted_message unless response.body
|
42
|
+
|
43
|
+
RDStation::ErrorHandler.new(response).raise_error
|
44
|
+
end
|
33
45
|
end
|
34
46
|
|
35
47
|
private
|
@@ -41,9 +53,5 @@ module RDStation
|
|
41
53
|
def base_url(path = '')
|
42
54
|
"https://api.rd.services/integrations/webhooks/#{path}"
|
43
55
|
end
|
44
|
-
|
45
|
-
def required_headers
|
46
|
-
{ 'Authorization' => "Bearer #{@auth_token}", 'Content-Type' => 'application/json' }
|
47
|
-
end
|
48
56
|
end
|
49
57
|
end
|