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.
@@ -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,15 @@
1
+ module Synapse
2
+
3
+ class Subnet
4
+
5
+ attr_accessor :subnet_id, :payload, :node_id
6
+
7
+ def initialize(subnet_id:, payload:, node_id:)
8
+ @subnet_id = subnet_id
9
+ @payload = payload
10
+ @node_id = node_id
11
+ end
12
+ end
13
+ end
14
+
15
+
@@ -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