synapse_api 1.0.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 +7 -0
- data/.env.sample +9 -0
- data/.gitignore +3 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +53 -0
- data/README.md +53 -0
- data/Rakefile +10 -0
- data/lib/synapse_api.rb +28 -0
- data/lib/synapse_api/client.rb +405 -0
- data/lib/synapse_api/error.rb +114 -0
- data/lib/synapse_api/http_request.rb +237 -0
- data/lib/synapse_api/node.rb +19 -0
- data/lib/synapse_api/nodes.rb +19 -0
- data/lib/synapse_api/subnet.rb +15 -0
- data/lib/synapse_api/subnets.rb +16 -0
- data/lib/synapse_api/subscription.rb +15 -0
- data/lib/synapse_api/subscriptions.rb +17 -0
- data/lib/synapse_api/transaction.rb +21 -0
- data/lib/synapse_api/transactions.rb +19 -0
- data/lib/synapse_api/user.rb +823 -0
- data/lib/synapse_api/users.rb +20 -0
- data/lib/synapse_api/version.rb +8 -0
- data/samples.md +490 -0
- data/synapse_api.gemspec +30 -0
- data/synapseruby-1.0.10.gem +0 -0
- data/synapseruby-1.0.9.gem +0 -0
- data/yarn.lock +8 -0
- metadata +153 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
module Synapse
|
2
|
+
# Custom class for handling HTTP and API errors.
|
3
|
+
class Error < StandardError
|
4
|
+
# Raised on a 4xx HTTP status code
|
5
|
+
ClientError = Class.new(self)
|
6
|
+
|
7
|
+
# Raised on the HTTP status code 202
|
8
|
+
Accepted = Class.new(ClientError)
|
9
|
+
|
10
|
+
# Raised on the HTTP status code 400
|
11
|
+
BadRequest = Class.new(ClientError)
|
12
|
+
|
13
|
+
# Raised on the HTTP status code 401
|
14
|
+
Unauthorized = Class.new(ClientError)
|
15
|
+
|
16
|
+
# Raised on the HTTP status code 402
|
17
|
+
RequestDeclined = Class.new(ClientError)
|
18
|
+
|
19
|
+
# Raised on the HTTP status code 404
|
20
|
+
NotFound = Class.new(ClientError)
|
21
|
+
|
22
|
+
# Raised on the HTTP status code 409
|
23
|
+
Conflict = Class.new(ClientError)
|
24
|
+
|
25
|
+
# Raised on the HTTP status code 429
|
26
|
+
TooManyRequests = Class.new(ClientError)
|
27
|
+
|
28
|
+
# Raised on a 5xx HTTP status code
|
29
|
+
ServerError = Class.new(self)
|
30
|
+
|
31
|
+
# Raised on the HTTP status code 500
|
32
|
+
InternalServerError = Class.new(ServerError)
|
33
|
+
|
34
|
+
# Raised on the HTTP status code 503
|
35
|
+
ServiceUnavailable = Class.new(ServerError)
|
36
|
+
|
37
|
+
# Raised on unexpected HTTP status codes
|
38
|
+
Unknown = Class.new(self)
|
39
|
+
|
40
|
+
# HTTP status code to Error subclass mapping
|
41
|
+
ERRORS = {
|
42
|
+
'202' => Synapse::Error::Accepted,
|
43
|
+
'400' => Synapse::Error::BadRequest,
|
44
|
+
'401' => Synapse::Error::Unauthorized,
|
45
|
+
'402' => Synapse::Error::RequestDeclined,
|
46
|
+
'404' => Synapse::Error::NotFound,
|
47
|
+
'409' => Synapse::Error::Conflict,
|
48
|
+
'429' => Synapse::Error::TooManyRequests,
|
49
|
+
'500' => Synapse::Error::InternalServerError,
|
50
|
+
'503' => Synapse::Error::ServiceUnavailable,
|
51
|
+
}.freeze
|
52
|
+
|
53
|
+
# The SynapsePay API Error Code
|
54
|
+
#
|
55
|
+
# @return [Integer]
|
56
|
+
attr_reader :code, :http_code
|
57
|
+
|
58
|
+
# The JSON HTTP response in Hash form
|
59
|
+
# @return [Hash]
|
60
|
+
attr_reader :response, :message
|
61
|
+
|
62
|
+
class << self
|
63
|
+
# Create a new error from an HTTP response
|
64
|
+
# @param body [String]
|
65
|
+
# @param code [Integer]
|
66
|
+
# @param http_code [Integer]
|
67
|
+
# @return [Synapse::Error]
|
68
|
+
def from_response(body)
|
69
|
+
message, error_code, http_code = parse_error(body)
|
70
|
+
http_code = http_code.to_s
|
71
|
+
klass = ERRORS[http_code] || Synapse::Error::Unknown
|
72
|
+
klass.new(message: message, code: error_code, response: body, http_code: http_code)
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def parse_error(body)
|
78
|
+
|
79
|
+
if body.nil? || body.empty?
|
80
|
+
['', nil, nil]
|
81
|
+
|
82
|
+
elsif body['mfa'] && body.is_a?(Hash)
|
83
|
+
["#{body['mfa']["message"] } acces_token: #{body['mfa']["access_token"]}", body['error_code'], body['http_code']]
|
84
|
+
elsif body[:mfa] && body.is_a?(Hash)
|
85
|
+
["#{body[:mfa][:message] } acces_token: #{body[:mfa][:access_token]}", body[:error_code], body[:http_code]]
|
86
|
+
|
87
|
+
elsif body['message'] && body.is_a?(Hash)
|
88
|
+
[body["message"]["en"], body['error_code'], body['http_code']]
|
89
|
+
elsif body[:message] && body.is_a?(Hash)
|
90
|
+
[body[:message][:en], body[:error_code], body[:http_code]]
|
91
|
+
|
92
|
+
elsif body.is_a?(Hash) && body['error'].is_a?(Hash)
|
93
|
+
[body['error']['en'], body['error_code'], body['http_code']]
|
94
|
+
elsif body.is_a?(Hash) && body[:error].is_a?(Hash)
|
95
|
+
[body[:error][:en], body[:error_code], body[:http_code]]
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Initializes a new Error object
|
102
|
+
# @param message [Exception, String]
|
103
|
+
# @param code [Integer]
|
104
|
+
# @param response [Hash]
|
105
|
+
# @return [Synapse::Error]
|
106
|
+
def initialize(message: '', code: nil, response: {}, http_code:)
|
107
|
+
super(message)
|
108
|
+
@code = code
|
109
|
+
@response = response
|
110
|
+
@message = message
|
111
|
+
@http_code = http_code
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Synapse
|
6
|
+
# Wrapper for HTTP requests using RestClient.
|
7
|
+
class HTTPClient
|
8
|
+
|
9
|
+
# @!attribute [rw] base_url
|
10
|
+
# @return [String] the base url of the API (production or sandbox)
|
11
|
+
# @!attribute [rw] config
|
12
|
+
# @return [Hash] various settings related to request headers
|
13
|
+
# @!attribute [rw] raise_for_202
|
14
|
+
# @return [Boolean] relating to how to handle 202 exception
|
15
|
+
attr_accessor :base_url, :config, :raise_for_202
|
16
|
+
|
17
|
+
# @param base_url [String] the base url of the API (production or sandbox)
|
18
|
+
# @param client_id [String]
|
19
|
+
# @param client_secret [String]
|
20
|
+
# @param fingerprint [String]
|
21
|
+
# @param ip_address [String]
|
22
|
+
# @param raise_for_202 [String]
|
23
|
+
# @param logging [Boolean] (optional) logs to stdout when true
|
24
|
+
# @param log_to [String] (optional) file path to log to file (logging must be true)
|
25
|
+
def initialize(base_url:, client_id:, client_secret:, fingerprint:, ip_address:, raise_for_202:false, **options)
|
26
|
+
@raise_for_202 = raise_for_202
|
27
|
+
log_to = options[:log_to] || 'stdout'
|
28
|
+
RestClient.log = log_to if options[:logging]
|
29
|
+
@logging = options[:logging]
|
30
|
+
|
31
|
+
@config = {
|
32
|
+
client_id: client_id,
|
33
|
+
client_secret: client_secret,
|
34
|
+
fingerprint: fingerprint,
|
35
|
+
ip_address: ip_address,
|
36
|
+
oauth_key: '',
|
37
|
+
}
|
38
|
+
@base_url = base_url
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns headers for HTTP requests.
|
42
|
+
# @return [Hash]
|
43
|
+
def headers
|
44
|
+
user = "#{config[:oauth_key]}|#{config[:fingerprint]}"
|
45
|
+
gateway = "#{config[:client_id]}|#{config[:client_secret]}"
|
46
|
+
headers = {
|
47
|
+
content_type: :json,
|
48
|
+
accept: :json,
|
49
|
+
'X-SP-GATEWAY' => gateway,
|
50
|
+
'X-SP-USER' => user,
|
51
|
+
'X-SP-USER-IP' => config[:ip_address],
|
52
|
+
}
|
53
|
+
if config[:idemopotency_key]
|
54
|
+
headers['X-SP-IDEMPOTENCY-KEY'] = config[:idemopotency_key]
|
55
|
+
end
|
56
|
+
headers
|
57
|
+
end
|
58
|
+
|
59
|
+
# Alias for headers (copy of current headers)
|
60
|
+
alias_method :get_headers, :headers
|
61
|
+
|
62
|
+
# Updates current HTPP headers
|
63
|
+
# @param fingerprint [String]
|
64
|
+
# @param oauth_key [String]
|
65
|
+
# @param fingerprint [String]
|
66
|
+
# @param client_id [String]
|
67
|
+
# @param client_secret [String]
|
68
|
+
# @param ip_address [String]
|
69
|
+
# @param idemopotency_key [String]
|
70
|
+
def update_headers(oauth_key: nil, fingerprint: nil, client_id: nil, client_secret: nil, ip_address: nil, idemopotency_key: nil)
|
71
|
+
config[:fingerprint] = fingerprint if fingerprint
|
72
|
+
config[:oauth_key] = oauth_key if oauth_key
|
73
|
+
config[:client_id] = client_id if client_id
|
74
|
+
config[:client_secret] = client_secret if client_secret
|
75
|
+
config[:ip_address] = ip_address if ip_address
|
76
|
+
config[:idemopotency_key] = idemopotency_key if idemopotency_key
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
# Send a POST request to the given path with the given payload
|
81
|
+
# @param path [String]
|
82
|
+
# @param payload [HASH]
|
83
|
+
# @param **options payload = idempotency_key [String] (optional) avoid accidentally performing the same operation twice
|
84
|
+
# @return [Hash] API response
|
85
|
+
# @raise [Synapse::Error] subclass depends on HTTP response
|
86
|
+
def post(path, payload, **options)
|
87
|
+
#copy of current headers
|
88
|
+
headers = get_headers
|
89
|
+
|
90
|
+
# update the headers with idempotency_key
|
91
|
+
if options[:idempotency_key]
|
92
|
+
headers = headers.merge({'X-SP-IDEMPOTENCY-KEY' => options[:idempotency_key]})
|
93
|
+
end
|
94
|
+
|
95
|
+
response = with_error_handling { RestClient::Request.execute(:method => :post,
|
96
|
+
:url => full_url(path),
|
97
|
+
:payload => payload.to_json,
|
98
|
+
:headers => headers,
|
99
|
+
:timeout => 300
|
100
|
+
) }
|
101
|
+
puts 'RESPONSE:', JSON.parse(response) if @logging
|
102
|
+
response = JSON.parse(response)
|
103
|
+
|
104
|
+
if raise_for_202 && response["http_code"] == "202"
|
105
|
+
raise Error.from_response(response)
|
106
|
+
elsif response["error"]
|
107
|
+
raise Error.from_response(response)
|
108
|
+
else
|
109
|
+
response
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Sends a GET request to the given path with the given payload.
|
114
|
+
# @param path [String]
|
115
|
+
# @return [Hash] API response
|
116
|
+
# @raise [Synapse::Error] subclass depends on HTTP response
|
117
|
+
def get(path)
|
118
|
+
response = with_error_handling {RestClient.get(full_url(path), headers)}
|
119
|
+
puts 'RESPONSE:', JSON.parse(response) if @logging
|
120
|
+
response = JSON.parse(response)
|
121
|
+
|
122
|
+
if raise_for_202 && response["http_code"] == "202"
|
123
|
+
raise Error.from_response(response)
|
124
|
+
elsif response["error"]
|
125
|
+
raise Error.from_response(response)
|
126
|
+
else
|
127
|
+
response
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Sends a DELETE request to the given path
|
132
|
+
# @param path [String]
|
133
|
+
# @return [Hash] API response
|
134
|
+
# @raise [Synapse::Error] subclass depends on HTTP response
|
135
|
+
def delete(path)
|
136
|
+
response = with_error_handling {RestClient.delete(full_url(path), headers)}
|
137
|
+
puts 'RESPONSE:', JSON.parse(response) if @logging
|
138
|
+
response = JSON.parse(response)
|
139
|
+
|
140
|
+
if raise_for_202 && response["http_code"] == "202"
|
141
|
+
raise Error.from_response(response)
|
142
|
+
elsif response["error"]
|
143
|
+
raise Error.from_response(response)
|
144
|
+
else
|
145
|
+
response
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Sends a PATCH request to the given path with the given payload.
|
150
|
+
# @param path [String]
|
151
|
+
# @param payload [Hash]
|
152
|
+
# @return [Hash] API response
|
153
|
+
# @raise [Synapse::Error] subclass depends on HTTP response
|
154
|
+
def patch(path, payload)
|
155
|
+
response = with_error_handling {RestClient::Request.execute(:method => :patch,
|
156
|
+
:url => full_url(path),
|
157
|
+
:payload => payload.to_json,
|
158
|
+
:headers => headers,
|
159
|
+
:timeout => 300
|
160
|
+
)}
|
161
|
+
p 'RESPONSE:', JSON.parse(response) if @logging
|
162
|
+
response = JSON.parse(response)
|
163
|
+
|
164
|
+
if raise_for_202 && response["http_code"] == "202"
|
165
|
+
raise Error.from_response(response)
|
166
|
+
elsif response["error"]
|
167
|
+
raise Error.from_response(response)
|
168
|
+
else
|
169
|
+
response
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def oauthenticate(user_id:)
|
174
|
+
refresh_token = refresh_token(user_id: user_id)
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
# get user
|
180
|
+
# get refresh_token
|
181
|
+
# send refresh_token to oauth path
|
182
|
+
# grabs the refresh token and formats a refresh token payload
|
183
|
+
def refresh_token(user_id:)
|
184
|
+
path = "/users/#{user_id}"
|
185
|
+
response = get(path)
|
186
|
+
refresh_token = response["refresh_token"]
|
187
|
+
|
188
|
+
refresh_token = {"refresh_token" => refresh_token}
|
189
|
+
oauth_path = oauth_path(user_id)
|
190
|
+
authenticate(refresh_token, oauth_path)
|
191
|
+
end
|
192
|
+
|
193
|
+
# options payload to change scope of oauth
|
194
|
+
def authenticate(refresh_token, oauth_path)
|
195
|
+
oauth_key = post(oauth_path, refresh_token)
|
196
|
+
oauth_key = oauth_key['oauth_key']
|
197
|
+
update_headers(oauth_key: oauth_key)
|
198
|
+
nil
|
199
|
+
end
|
200
|
+
|
201
|
+
def oauth_path(user_id)
|
202
|
+
"/oauth/#{user_id}"
|
203
|
+
end
|
204
|
+
|
205
|
+
def full_url(path)
|
206
|
+
"#{base_url}#{path}"
|
207
|
+
end
|
208
|
+
|
209
|
+
# raising an exception based on http_request
|
210
|
+
# yeilds if http_request raises an exception
|
211
|
+
def with_error_handling
|
212
|
+
yield
|
213
|
+
rescue RestClient::Exceptions::Timeout
|
214
|
+
body = {
|
215
|
+
error: {
|
216
|
+
en: "Request Timeout"
|
217
|
+
},
|
218
|
+
http_code: 504
|
219
|
+
}
|
220
|
+
raise Error.from_response(body)
|
221
|
+
rescue RestClient::Exception => e
|
222
|
+
if e.response.headers[:content_type] == 'application/json'
|
223
|
+
body = JSON.parse(e.response.body)
|
224
|
+
else
|
225
|
+
body = {
|
226
|
+
error: {
|
227
|
+
en: e.response.body
|
228
|
+
},
|
229
|
+
http_code: e.response.code
|
230
|
+
}
|
231
|
+
end
|
232
|
+
raise Error.from_response(body)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Synapse
|
2
|
+
|
3
|
+
class Node
|
4
|
+
|
5
|
+
attr_reader :node_id, :user_id, :payload, :full_dehydrate, :type
|
6
|
+
|
7
|
+
attr_accessor
|
8
|
+
|
9
|
+
def initialize(node_id:, user_id:,payload:, full_dehydrate:, type:nil)
|
10
|
+
@node_id = node_id
|
11
|
+
@full_dehydrate = full_dehydrate
|
12
|
+
@user_id = user_id
|
13
|
+
@payload = payload
|
14
|
+
@type = type
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Synapse
|
2
|
+
|
3
|
+
class Nodes
|
4
|
+
|
5
|
+
attr_reader :page, :page_count, :limit, :payload, :nodes_count
|
6
|
+
|
7
|
+
attr_accessor
|
8
|
+
|
9
|
+
def initialize(page:,limit:, page_count:, nodes_count:, payload:)
|
10
|
+
@page = page
|
11
|
+
@limit = limit
|
12
|
+
@nodes_count = nodes_count
|
13
|
+
@page_count = page_count
|
14
|
+
@payload = payload
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Synapse
|
2
|
+
|
3
|
+
class Subnets
|
4
|
+
|
5
|
+
attr_accessor :page, :page_count, :limit, :payload, :subnets_count, :node_id
|
6
|
+
|
7
|
+
def initialize(limit:, page:, page_count:, subnets_count:, payload:, node_id:)
|
8
|
+
@page = page
|
9
|
+
@limit = limit
|
10
|
+
@subnets_count = subnets_count
|
11
|
+
@payload = payload
|
12
|
+
@page_count = page_count
|
13
|
+
@node_id = node_id
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Synapse
|
2
|
+
|
3
|
+
class Subscription
|
4
|
+
|
5
|
+
attr_reader :subscription_id, :url, :payload
|
6
|
+
|
7
|
+
attr_accessor
|
8
|
+
|
9
|
+
def initialize(subscription_id:, url:, payload:)
|
10
|
+
@subscription_id = subscription_id
|
11
|
+
@url = url
|
12
|
+
@payload = payload
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Synapse
|
2
|
+
|
3
|
+
class Subscriptions
|
4
|
+
|
5
|
+
attr_reader :subscriptions_count, :page, :limit, :payload, :page_count
|
6
|
+
|
7
|
+
attr_accessor
|
8
|
+
|
9
|
+
def initialize(page:,limit:, subscriptions_count:, payload:, page_count:)
|
10
|
+
@subscriptions_count = subscriptions_count
|
11
|
+
@page = page
|
12
|
+
@limit = limit
|
13
|
+
@payload = payload
|
14
|
+
@page_count = page_count
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|