rdstation-ruby-client 2.0.0 → 2.4.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 +4 -4
- data/CHANGELOG.md +116 -4
- data/README.md +114 -22
- data/lib/rdstation-ruby-client.rb +6 -1
- data/lib/rdstation.rb +19 -0
- data/lib/rdstation/api_response.rb +1 -2
- data/lib/rdstation/authentication.rb +32 -3
- data/lib/rdstation/{authorization_header.rb → authorization.rb} +11 -8
- data/lib/rdstation/builder/field.rb +70 -0
- data/lib/rdstation/client.rb +17 -7
- data/lib/rdstation/contacts.rb +22 -13
- data/lib/rdstation/error.rb +2 -0
- data/lib/rdstation/error/format.rb +29 -3
- data/lib/rdstation/error/formatter.rb +69 -8
- data/lib/rdstation/error_handler.rb +6 -1
- data/lib/rdstation/events.rb +7 -12
- data/lib/rdstation/fields.rb +35 -6
- data/lib/rdstation/retryable_request.rb +35 -0
- data/lib/rdstation/version.rb +1 -1
- data/lib/rdstation/webhooks.rb +25 -13
- data/rdstation-ruby-client.gemspec +2 -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_header_spec.rb → authorization_spec.rb} +3 -3
- data/spec/lib/rdstation/builder/field_spec.rb +69 -0
- data/spec/lib/rdstation/client_spec.rb +6 -6
- data/spec/lib/rdstation/contacts_spec.rb +23 -3
- data/spec/lib/rdstation/error/format_spec.rb +63 -0
- data/spec/lib/rdstation/error/formatter_spec.rb +113 -0
- data/spec/lib/rdstation/error_handler_spec.rb +23 -0
- data/spec/lib/rdstation/events_spec.rb +8 -3
- data/spec/lib/rdstation/fields_spec.rb +6 -1
- data/spec/lib/rdstation/retryable_request_spec.rb +142 -0
- data/spec/lib/rdstation/webhooks_spec.rb +26 -1
- data/spec/lib/rdstation_spec.rb +18 -0
- metadata +36 -11
@@ -1,8 +1,7 @@
|
|
1
1
|
module RDStation
|
2
2
|
module ApiResponse
|
3
3
|
def self.build(response)
|
4
|
-
|
5
|
-
return response_body if response.code.between?(200, 299)
|
4
|
+
return JSON.parse(response.body) if response.code.between?(200, 299)
|
6
5
|
|
7
6
|
RDStation::ErrorHandler.new(response).raise_error
|
8
7
|
end
|
@@ -5,10 +5,12 @@ module RDStation
|
|
5
5
|
|
6
6
|
AUTH_TOKEN_URL = 'https://api.rd.services/auth/token'.freeze
|
7
7
|
DEFAULT_HEADERS = { 'Content-Type' => 'application/json' }.freeze
|
8
|
+
REVOKE_URL = 'https://api.rd.services/auth/revoke'.freeze
|
8
9
|
|
9
|
-
def initialize(client_id, client_secret)
|
10
|
-
|
11
|
-
@
|
10
|
+
def initialize(client_id = nil, client_secret = nil)
|
11
|
+
warn_deprecation if client_id || client_secret
|
12
|
+
@client_id = client_id || RDStation.configuration&.client_id
|
13
|
+
@client_secret = client_secret || RDStation.configuration&.client_secret
|
12
14
|
end
|
13
15
|
|
14
16
|
#
|
@@ -47,8 +49,31 @@ module RDStation
|
|
47
49
|
ApiResponse.build(response)
|
48
50
|
end
|
49
51
|
|
52
|
+
def self.revoke(access_token:)
|
53
|
+
response = self.post(
|
54
|
+
REVOKE_URL,
|
55
|
+
body: revoke_body(access_token),
|
56
|
+
headers: revoke_headers(access_token)
|
57
|
+
)
|
58
|
+
ApiResponse.build(response)
|
59
|
+
end
|
60
|
+
|
50
61
|
private
|
51
62
|
|
63
|
+
def self.revoke_body(access_token)
|
64
|
+
URI.encode_www_form({
|
65
|
+
token: access_token,
|
66
|
+
token_type_hint: 'access_token'
|
67
|
+
})
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.revoke_headers(access_token)
|
71
|
+
{
|
72
|
+
"Authorization" => "Bearer #{access_token}",
|
73
|
+
"Content-Type" => "application/x-www-form-urlencoded"
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
52
77
|
def post_to_auth_endpoint(params)
|
53
78
|
default_body = { client_id: @client_id, client_secret: @client_secret }
|
54
79
|
body = default_body.merge(params)
|
@@ -59,5 +84,9 @@ module RDStation
|
|
59
84
|
headers: DEFAULT_HEADERS
|
60
85
|
)
|
61
86
|
end
|
87
|
+
|
88
|
+
def warn_deprecation
|
89
|
+
warn "DEPRECATION WARNING: Providing client_id and client_secret directly to RDStation::Authentication.new is deprecated and will be removed in future versions. Use RDStation.configure instead."
|
90
|
+
end
|
62
91
|
end
|
63
92
|
end
|
@@ -1,21 +1,24 @@
|
|
1
1
|
module RDStation
|
2
|
-
class
|
3
|
-
|
4
|
-
|
2
|
+
class Authorization
|
3
|
+
attr_reader :refresh_token
|
4
|
+
attr_accessor :access_token, :access_token_expires_in
|
5
|
+
def initialize(access_token:, refresh_token: nil, access_token_expires_in: nil)
|
5
6
|
@access_token = access_token
|
7
|
+
@refresh_token = refresh_token
|
8
|
+
@access_token_expires_in = access_token_expires_in
|
6
9
|
validate_access_token access_token
|
7
10
|
end
|
8
|
-
|
9
|
-
def
|
11
|
+
|
12
|
+
def headers
|
10
13
|
{ "Authorization" => "Bearer #{@access_token}", "Content-Type" => "application/json" }
|
11
14
|
end
|
12
|
-
|
15
|
+
|
13
16
|
private
|
14
|
-
|
17
|
+
|
15
18
|
def validate_access_token(access_token)
|
16
19
|
access_token_msg = ':access_token is required'
|
17
20
|
raise ArgumentError, access_token_msg unless access_token
|
18
21
|
end
|
19
|
-
|
22
|
+
|
20
23
|
end
|
21
24
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RDStation
|
4
|
+
class Builder
|
5
|
+
# More info: https://developers.rdstation.com/pt-BR/reference/fields#methodPostDetails
|
6
|
+
class Field
|
7
|
+
DATA_TYPES = %w(STRING INTEGER BOOLEAN STRING[]).freeze
|
8
|
+
PRESENTATION_TYPES = %w[TEXT_INPUT TEXT_AREA URL_INPUT PHONE_INPUT
|
9
|
+
EMAIL_INPUT CHECK_BOX NUMBER_INPUT COMBO_BOX
|
10
|
+
RADIO_BUTTON MULTIPLE_CHOICE].freeze
|
11
|
+
|
12
|
+
REQUIRED_FIELDS = %w[api_identifier data_type presentation_type label name].freeze
|
13
|
+
|
14
|
+
def initialize(api_identifier)
|
15
|
+
raise 'api_identifier required' unless api_identifier
|
16
|
+
unless valid_identifier(api_identifier)
|
17
|
+
raise 'api_identifier is not in a valid format, need start with "cf_"'
|
18
|
+
end
|
19
|
+
|
20
|
+
@values = {}
|
21
|
+
@values['api_identifier'] = api_identifier
|
22
|
+
end
|
23
|
+
|
24
|
+
def data_type(data_type)
|
25
|
+
raise "Not valid data_type - #{DATA_TYPES}" unless DATA_TYPES.include? data_type
|
26
|
+
|
27
|
+
@values['data_type'] = data_type
|
28
|
+
end
|
29
|
+
|
30
|
+
def presentation_type(presentation_type)
|
31
|
+
unless PRESENTATION_TYPES.include? presentation_type
|
32
|
+
raise "Not valid presentation_type - #{PRESENTATION_TYPES}"
|
33
|
+
end
|
34
|
+
|
35
|
+
@values['presentation_type'] = presentation_type
|
36
|
+
end
|
37
|
+
|
38
|
+
def label(language, label)
|
39
|
+
@values['label'] = { language.to_s => label }
|
40
|
+
end
|
41
|
+
|
42
|
+
def name(language, name)
|
43
|
+
@values['name'] = { language.to_s => name }
|
44
|
+
end
|
45
|
+
|
46
|
+
def validation_rules(validation_rules)
|
47
|
+
@values['validation_rules'] = validation_rules
|
48
|
+
end
|
49
|
+
|
50
|
+
def valid_options(valid_options)
|
51
|
+
@values['valid_options'] = valid_options
|
52
|
+
end
|
53
|
+
|
54
|
+
def build
|
55
|
+
empty_fields = REQUIRED_FIELDS.select { |field| @values[field].nil? }
|
56
|
+
unless empty_fields.empty?
|
57
|
+
raise "Required fields are missing - #{empty_fields}"
|
58
|
+
end
|
59
|
+
|
60
|
+
@values
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def valid_identifier(api_identifier)
|
66
|
+
api_identifier.start_with? 'cf_'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/rdstation/client.rb
CHANGED
@@ -1,23 +1,33 @@
|
|
1
1
|
module RDStation
|
2
2
|
class Client
|
3
|
-
def initialize(access_token:)
|
4
|
-
|
3
|
+
def initialize(access_token:, refresh_token: nil)
|
4
|
+
warn_deprecation unless refresh_token
|
5
|
+
@authorization = Authorization.new(
|
6
|
+
access_token: access_token,
|
7
|
+
refresh_token: refresh_token
|
8
|
+
)
|
5
9
|
end
|
6
|
-
|
10
|
+
|
7
11
|
def contacts
|
8
|
-
@contacts ||= RDStation::Contacts.new(
|
12
|
+
@contacts ||= RDStation::Contacts.new(authorization: @authorization)
|
9
13
|
end
|
10
14
|
|
11
15
|
def events
|
12
|
-
@events ||= RDStation::Events.new(
|
16
|
+
@events ||= RDStation::Events.new(authorization: @authorization)
|
13
17
|
end
|
14
18
|
|
15
19
|
def fields
|
16
|
-
@fields ||= RDStation::Fields.new(
|
20
|
+
@fields ||= RDStation::Fields.new(authorization: @authorization)
|
17
21
|
end
|
18
22
|
|
19
23
|
def webhooks
|
20
|
-
@webhooks ||= RDStation::Webhooks.new(
|
24
|
+
@webhooks ||= RDStation::Webhooks.new(authorization: @authorization)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def warn_deprecation
|
30
|
+
warn "DEPRECATION WARNING: Specifying refresh_token in RDStation::Client.new(access_token: 'at', refresh_token: 'rt') is optional right now, but will be mandatory in future versions. "
|
21
31
|
end
|
22
32
|
end
|
23
33
|
end
|
data/lib/rdstation/contacts.rb
CHANGED
@@ -3,9 +3,10 @@ module RDStation
|
|
3
3
|
# More info: https://developers.rdstation.com/pt-BR/reference/contacts
|
4
4
|
class Contacts
|
5
5
|
include HTTParty
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
include ::RDStation::RetryableRequest
|
7
|
+
|
8
|
+
def initialize(authorization:)
|
9
|
+
@authorization = authorization
|
9
10
|
end
|
10
11
|
|
11
12
|
#
|
@@ -13,13 +14,17 @@ module RDStation
|
|
13
14
|
# The unique uuid associated to each RD Station Contact.
|
14
15
|
#
|
15
16
|
def by_uuid(uuid)
|
16
|
-
|
17
|
-
|
17
|
+
retryable_request(@authorization) do |authorization|
|
18
|
+
response = self.class.get(base_url(uuid), headers: authorization.headers)
|
19
|
+
ApiResponse.build(response)
|
20
|
+
end
|
18
21
|
end
|
19
22
|
|
20
23
|
def by_email(email)
|
21
|
-
|
22
|
-
|
24
|
+
retryable_request(@authorization) do |authorization|
|
25
|
+
response = self.class.get(base_url("email:#{email}"), headers: authorization.headers)
|
26
|
+
ApiResponse.build(response)
|
27
|
+
end
|
23
28
|
end
|
24
29
|
|
25
30
|
# The Contact hash may contain the following parameters:
|
@@ -34,8 +39,10 @@ module RDStation
|
|
34
39
|
# :website
|
35
40
|
# :tags
|
36
41
|
def update(uuid, contact_hash)
|
37
|
-
|
38
|
-
|
42
|
+
retryable_request(@authorization) do |authorization|
|
43
|
+
response = self.class.patch(base_url(uuid), :body => contact_hash.to_json, :headers => authorization.headers)
|
44
|
+
ApiResponse.build(response)
|
45
|
+
end
|
39
46
|
end
|
40
47
|
|
41
48
|
#
|
@@ -47,14 +54,16 @@ module RDStation
|
|
47
54
|
# Contact data
|
48
55
|
#
|
49
56
|
def upsert(identifier, identifier_value, contact_hash)
|
50
|
-
|
51
|
-
|
52
|
-
|
57
|
+
retryable_request(@authorization) do |authorization|
|
58
|
+
path = "#{identifier}:#{identifier_value}"
|
59
|
+
response = self.class.patch(base_url(path), body: contact_hash.to_json, headers: authorization.headers)
|
60
|
+
ApiResponse.build(response)
|
61
|
+
end
|
53
62
|
end
|
54
63
|
|
55
64
|
private
|
56
65
|
|
57
|
-
def base_url(path =
|
66
|
+
def base_url(path = '')
|
58
67
|
"https://api.rd.services/platform/contacts/#{path}"
|
59
68
|
end
|
60
69
|
end
|
data/lib/rdstation/error.rb
CHANGED
@@ -19,11 +19,13 @@ module RDStation
|
|
19
19
|
class Conflict < Error; end
|
20
20
|
class UnsupportedMediaType < Error; end
|
21
21
|
class UnprocessableEntity < Error; end
|
22
|
+
class TooManyRequests < Error; end
|
22
23
|
class InternalServerError < Error; end
|
23
24
|
class NotImplemented < Error; end
|
24
25
|
class BadGateway < Error; end
|
25
26
|
class ServiceUnavailable < Error; end
|
26
27
|
class ServerError < Error; end
|
28
|
+
class UnknownError < Error; end
|
27
29
|
|
28
30
|
# 400 - Bad Request
|
29
31
|
class ConflictingField < BadRequest; end
|
@@ -1,9 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RDStation
|
2
4
|
class Error
|
3
5
|
class Format
|
4
|
-
FLAT_HASH = 'FLAT_HASH'
|
5
|
-
HASH_OF_ARRAYS = 'HASH_OF_ARRAYS'
|
6
|
-
ARRAY_OF_HASHES = 'ARRAY_OF_HASHES'
|
6
|
+
FLAT_HASH = 'FLAT_HASH'
|
7
|
+
HASH_OF_ARRAYS = 'HASH_OF_ARRAYS'
|
8
|
+
ARRAY_OF_HASHES = 'ARRAY_OF_HASHES'
|
9
|
+
HASH_OF_MULTIPLE_TYPES = 'HASH_OF_MULTIPLE_TYPES'
|
10
|
+
HASH_OF_HASHES = 'HASH_OF_HASHES'
|
11
|
+
SINGLE_HASH = 'SINGLE_HASH'
|
7
12
|
|
8
13
|
def initialize(errors)
|
9
14
|
@errors = errors
|
@@ -11,20 +16,41 @@ module RDStation
|
|
11
16
|
|
12
17
|
def format
|
13
18
|
return FLAT_HASH if flat_hash?
|
19
|
+
return SINGLE_HASH if single_hash?
|
14
20
|
return HASH_OF_ARRAYS if hash_of_arrays?
|
21
|
+
return HASH_OF_HASHES if hash_of_hashes?
|
22
|
+
return HASH_OF_MULTIPLE_TYPES if hash_of_multiple_types?
|
23
|
+
|
15
24
|
ARRAY_OF_HASHES
|
16
25
|
end
|
17
26
|
|
18
27
|
private
|
19
28
|
|
29
|
+
def single_hash?
|
30
|
+
return unless @errors.is_a?(Hash)
|
31
|
+
|
32
|
+
@errors.key?('error')
|
33
|
+
end
|
34
|
+
|
20
35
|
def flat_hash?
|
21
36
|
return unless @errors.is_a?(Hash)
|
37
|
+
|
22
38
|
@errors.key?('error_type')
|
23
39
|
end
|
24
40
|
|
25
41
|
def hash_of_arrays?
|
26
42
|
@errors.is_a?(Hash) && @errors.values.all? { |error| error.is_a? Array }
|
27
43
|
end
|
44
|
+
|
45
|
+
def hash_of_hashes?
|
46
|
+
@errors.is_a?(Hash) && @errors.values.all? { |error| error.is_a? Hash }
|
47
|
+
end
|
48
|
+
|
49
|
+
def hash_of_multiple_types?
|
50
|
+
@errors.is_a?(Hash) &&
|
51
|
+
@errors.values.any? { |error| error.is_a? Hash } &&
|
52
|
+
@errors.values.any? { |error| error.is_a? Array }
|
53
|
+
end
|
28
54
|
end
|
29
55
|
end
|
30
56
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative './format'
|
2
4
|
|
3
5
|
module RDStation
|
@@ -11,27 +13,61 @@ module RDStation
|
|
11
13
|
return @error_response unless @error_response.is_a?(Hash)
|
12
14
|
|
13
15
|
case error_format.format
|
16
|
+
when RDStation::Error::Format::SINGLE_HASH
|
17
|
+
return from_single_hash
|
14
18
|
when RDStation::Error::Format::FLAT_HASH
|
15
19
|
return from_flat_hash
|
16
20
|
when RDStation::Error::Format::HASH_OF_ARRAYS
|
17
21
|
return from_hash_of_arrays
|
22
|
+
when RDStation::Error::Format::HASH_OF_HASHES
|
23
|
+
return from_hash_of_hashes
|
24
|
+
when RDStation::Error::Format::HASH_OF_MULTIPLE_TYPES
|
25
|
+
return from_hash_of_multiple_types
|
18
26
|
end
|
19
27
|
|
20
28
|
errors
|
21
29
|
end
|
22
30
|
|
31
|
+
def from_single_hash
|
32
|
+
error_hash = @error_response.dup
|
33
|
+
error_message = error_hash.delete('error')
|
34
|
+
|
35
|
+
[
|
36
|
+
{
|
37
|
+
'error_type' => 'TOO_MANY_REQUESTS',
|
38
|
+
'error_message' => error_message,
|
39
|
+
'details' => error_hash
|
40
|
+
}
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
23
44
|
def from_flat_hash
|
24
45
|
[errors]
|
25
46
|
end
|
26
47
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
48
|
+
def from_hash_of_multiple_types
|
49
|
+
array_of_errors = []
|
50
|
+
errors.each do |attribute_name, errors|
|
51
|
+
if errors.is_a? Array
|
52
|
+
result = build_error_from_array(attribute_name, errors)
|
53
|
+
end
|
54
|
+
if errors.is_a? Hash
|
55
|
+
result = build_error_from_multilingual_hash(attribute_name, errors)
|
56
|
+
end
|
57
|
+
array_of_errors.push(*result)
|
34
58
|
end
|
59
|
+
|
60
|
+
array_of_errors
|
61
|
+
end
|
62
|
+
|
63
|
+
def from_hash_of_hashes
|
64
|
+
array_of_errors = []
|
65
|
+
errors.each do |attribute_name, errors|
|
66
|
+
result = build_error_from_multilingual_hash(attribute_name, errors)
|
67
|
+
array_of_errors.push(*result)
|
68
|
+
end
|
69
|
+
|
70
|
+
array_of_errors
|
35
71
|
end
|
36
72
|
|
37
73
|
def error_format
|
@@ -39,7 +75,32 @@ module RDStation
|
|
39
75
|
end
|
40
76
|
|
41
77
|
def errors
|
42
|
-
@errors ||= @error_response
|
78
|
+
@errors ||= @error_response.fetch('errors', @error_response)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def build_error_from_array(attribute_name, attribute_errors)
|
84
|
+
path = { 'path' => "body.#{attribute_name}" }
|
85
|
+
attribute_errors.map { |error| error.merge(path) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def build_error_from_multilingual_hash(attribute_name, errors_by_language)
|
89
|
+
array_of_errors = []
|
90
|
+
errors_by_language.each do |language, errors|
|
91
|
+
result = build_error_from_array("#{attribute_name}.#{language}", errors)
|
92
|
+
array_of_errors.push(*result)
|
93
|
+
end
|
94
|
+
array_of_errors
|
95
|
+
end
|
96
|
+
|
97
|
+
def from_hash_of_arrays
|
98
|
+
errors.each_with_object([]) do |errors, array_of_errors|
|
99
|
+
attribute_name = errors.first
|
100
|
+
attribute_errors = errors.last
|
101
|
+
errors = build_error_from_array(attribute_name, attribute_errors)
|
102
|
+
array_of_errors.push(*errors)
|
103
|
+
end
|
43
104
|
end
|
44
105
|
end
|
45
106
|
end
|