coinbase_commerce_client 0.1.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.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/.idea/.gitignore +8 -0
  3. data/.idea/coinbase_commerce_client.iml +85 -0
  4. data/.idea/misc.xml +4 -0
  5. data/.idea/modules.xml +8 -0
  6. data/.idea/vcs.xml +6 -0
  7. data/.rspec +3 -0
  8. data/.rubocop.yml +11 -0
  9. data/CODE_OF_CONDUCT.md +84 -0
  10. data/Gemfile +22 -0
  11. data/Gemfile.lock +99 -0
  12. data/LICENSE +21 -0
  13. data/LICENSE.txt +21 -0
  14. data/README.md +267 -0
  15. data/Rakefile +8 -0
  16. data/assets/banner.png +0 -0
  17. data/badge.svg +1 -0
  18. data/lib/coinbase_commerce_client/api_errors.rb +150 -0
  19. data/lib/coinbase_commerce_client/api_resources/base/api_object.rb +203 -0
  20. data/lib/coinbase_commerce_client/api_resources/base/api_resource.rb +23 -0
  21. data/lib/coinbase_commerce_client/api_resources/base/create.rb +12 -0
  22. data/lib/coinbase_commerce_client/api_resources/base/delete.rb +13 -0
  23. data/lib/coinbase_commerce_client/api_resources/base/list.rb +23 -0
  24. data/lib/coinbase_commerce_client/api_resources/base/save.rb +15 -0
  25. data/lib/coinbase_commerce_client/api_resources/base/update.rb +12 -0
  26. data/lib/coinbase_commerce_client/api_resources/charge.rb +11 -0
  27. data/lib/coinbase_commerce_client/api_resources/checkout.rb +15 -0
  28. data/lib/coinbase_commerce_client/api_resources/event.rb +10 -0
  29. data/lib/coinbase_commerce_client/api_response.rb +36 -0
  30. data/lib/coinbase_commerce_client/client.rb +115 -0
  31. data/lib/coinbase_commerce_client/util.rb +57 -0
  32. data/lib/coinbase_commerce_client/version.rb +5 -0
  33. data/lib/coinbase_commerce_client/webhooks.rb +48 -0
  34. data/lib/coinbase_commerce_client.rb +45 -0
  35. data/sig/coinbase_commerce_client.rbs +4 -0
  36. metadata +150 -0
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/assets/banner.png ADDED
Binary file
data/badge.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="114" height="20" role="img" aria-label="coverage: 88.92%"><title>coverage: 88.92%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="114" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="53" height="20" fill="#97ca00"/><rect width="114" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="865" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">88.92%</text><text x="865" y="140" transform="scale(.1)" fill="#fff" textLength="430">88.92%</text></g></svg>
@@ -0,0 +1,150 @@
1
+ module CoinbaseCommerceClient
2
+ module Errors
3
+ class APIError < StandardError
4
+ attr_reader :message, :http_body, :http_headers, :http_status, :json_body, :request_id
5
+
6
+ # Response contains a CoinbaseCommerceResponse object
7
+ attr_accessor :response
8
+
9
+ # Initializes a API error.
10
+ def initialize(message = nil, http_status = nil, http_body = nil, json_body = nil, http_headers = nil)
11
+ @message = message
12
+ @http_status = http_status
13
+ @http_body = http_body
14
+ @http_headers = http_headers || {}
15
+ @json_body = json_body
16
+ @request_id = @http_headers['x-request-id']
17
+ end
18
+
19
+ def to_s
20
+ status_string = @http_status.nil? ? '' : "(Status #{@http_status}) "
21
+ id_string = @request_id.nil? ? '' : "(Request #{@request_id}) "
22
+ "#{status_string}#{id_string}#{@message}"
23
+ end
24
+ end
25
+
26
+ # in case error connecting to coinbase commerce server
27
+ class APIConnectionError < APIError
28
+ end
29
+
30
+ # Status 400
31
+ class BadRequestError < APIError
32
+ end
33
+
34
+ class ParamRequiredError < APIError
35
+ end
36
+
37
+ class InvalidRequestError < APIError
38
+ end
39
+
40
+ # Status 401
41
+ class AuthenticationError < APIError
42
+ end
43
+
44
+ # Status 404
45
+ class ResourceNotFoundError < APIError
46
+ end
47
+
48
+ # Status 422
49
+ class ValidationError < APIError
50
+ end
51
+
52
+ # Status 429
53
+ class RateLimitExceededError < APIError
54
+ end
55
+
56
+ # Status 500
57
+ class InternalServerError < APIError
58
+ end
59
+
60
+ # Status 503
61
+ class ServiceUnavailableError < APIError
62
+ end
63
+
64
+ # Webhook errors
65
+ class WebhookError < APIError
66
+ attr_accessor :sig_header
67
+
68
+ def initialize(message, sig_header, http_body: nil)
69
+ super(message, http_body: http_body)
70
+ @sig_header = sig_header
71
+ end
72
+ end
73
+
74
+ class SignatureVerificationError < WebhookError
75
+ end
76
+
77
+ class WebhookInvalidPayload < WebhookError
78
+ end
79
+
80
+ # Errors handling
81
+ def self.handle_error_response(http_resp)
82
+ begin
83
+ resp = CoinbaseCommerceClientResponse.from_faraday_hash(http_resp)
84
+ error_data = resp.data[:error]
85
+
86
+ raise APIError, 'Unknown error' unless error_data
87
+ rescue JSON::ParserError, APIError
88
+ raise general_api_error(http_resp[:status], http_resp[:body])
89
+ end
90
+ error = specific_api_error(resp, error_data)
91
+ error.response = resp
92
+ raise(error)
93
+ end
94
+
95
+ def self.general_api_error(status, body)
96
+ APIError.new("Invalid response object from API: #{body.inspect} " \
97
+ "(HTTP response code: #{status} http_body: #{body}")
98
+ end
99
+
100
+ def self.specific_api_error(resp, error_data)
101
+ opts = {
102
+ http_body: resp.http_body,
103
+ http_headers: resp.http_headers,
104
+ http_status: resp.http_status,
105
+ json_body: resp.data
106
+ }
107
+ case resp.http_status
108
+ when 400
109
+ # in case of known error code
110
+ case error_data[:type]
111
+ when 'param_required'
112
+ ParamRequiredError.new(error_data[:message], opts)
113
+ when 'validation_error'
114
+ ValidationError.new(error_data[:message], opts)
115
+ when 'invalid_request'
116
+ InvalidRequestError.new(error_data[:message], opts)
117
+ else
118
+ InvalidRequestError.new(error_data[:message], opts)
119
+ end
120
+ when 401
121
+ AuthenticationError.new(error_data[:message], opts)
122
+ when 404
123
+ ResourceNotFoundError.new(error_data[:message], opts)
124
+ when 429
125
+ RateLimitExceededError.new(error_data[:message], opts)
126
+ when 500
127
+ InternalServerError.new(error_data[:message], opts)
128
+ when 503
129
+ ServiceUnavailableError.new(error_data[:message], opts)
130
+ else
131
+ APIError.new(error_data[:message], opts)
132
+ end
133
+ end
134
+
135
+ def self.handle_network_error(e, api_base = nil)
136
+ api_base ||= @api_uri
137
+ message = case e
138
+ when Faraday::ConnectionFailed
139
+ 'Unexpected error communicating when trying to connect to Coinbase Commerce.'
140
+ when Faraday::SSLError
141
+ 'Could not establish a secure connection to Coinbase Commerce.'
142
+ when Faraday::TimeoutError
143
+ "Could not connect to Coinbase Commerce (#{api_base})."
144
+ else
145
+ 'Unexpected error communicating with Coinbase Commerce.'
146
+ end
147
+ raise APIConnectionError, message + "\n\n(Network error: #{e.message})"
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,203 @@
1
+ module CoinbaseCommerceClient
2
+ module APIResources
3
+ module Base
4
+ class APIObject
5
+ include Enumerable
6
+
7
+ def initialize(id = nil, client = nil)
8
+ @data = {}
9
+ @data[:id] = id if id
10
+ @client = client
11
+ @unsaved_values = Set.new
12
+ @transient_values = Set.new
13
+ end
14
+
15
+ # Base object options section
16
+ def [](k)
17
+ @data[k.to_sym]
18
+ end
19
+
20
+ def []=(k, v)
21
+ send(:"#{k}=", v)
22
+ end
23
+
24
+ def keys
25
+ @data.keys
26
+ end
27
+
28
+ def values
29
+ @data.values
30
+ end
31
+
32
+ def each(&blk)
33
+ @data.each(&blk)
34
+ end
35
+
36
+ def to_s(*_args)
37
+ JSON.pretty_generate(to_hash)
38
+ end
39
+
40
+ def to_hash
41
+ @data.each_with_object({}) do |(key, value), output|
42
+ case value
43
+ when Array
44
+ output[key] = value.map {|v| v.respond_to?(:to_hash) ? v.to_hash : v}
45
+ else
46
+ output[key] = value.respond_to?(:to_hash) ? value.to_hash : value
47
+ end
48
+ end
49
+ end
50
+
51
+ def to_json(*_a)
52
+ JSON.generate(@data)
53
+ end
54
+
55
+ def inspect
56
+ item_id = respond_to?(:id) && !id.nil? ? "id=#{id}" : "No ID"
57
+ "#{self.class}: #{item_id}> Serialized: " + JSON.pretty_generate(@data)
58
+ end
59
+
60
+ def respond_to_missing?(symbol, include_private = false)
61
+ @data && @data.key?(symbol) || super
62
+ end
63
+
64
+ def method_missing(name, *args)
65
+ if name.to_s.end_with?("=")
66
+
67
+ attr = name.to_s[0...-1].to_sym
68
+ val = args.first
69
+ add_accessors([attr], attr => val)
70
+
71
+ begin
72
+ mth = method(name)
73
+ rescue NameError
74
+ raise NoMethodError, "Cannot set #{attr} on this object."
75
+ end
76
+
77
+ return mth.call(args[0])
78
+
79
+ elsif @data.key?(name)
80
+ return @data[name]
81
+ end
82
+
83
+ begin
84
+ super
85
+ rescue NoMethodError => e
86
+ raise unless @transient_values.include?(name)
87
+ raise NoMethodError, e.message + " Available attributes: #{@data.keys.join(', ')}"
88
+ end
89
+ end
90
+
91
+ # Object serialize section
92
+ def serialize_params(options = {})
93
+ update_hash = {}
94
+
95
+ @data.each do |k, v|
96
+ if options[:push] || @unsaved_values.include?(k) || v.is_a?(APIObject)
97
+ push = options[:push] || @unsaved_values.include?(k)
98
+ update_hash[k.to_sym] = serialize_params_value(@data[k], push)
99
+ end
100
+ end
101
+
102
+ update_hash.reject! {|_, v| v.nil? || v.empty?}
103
+ update_hash
104
+ end
105
+
106
+ def serialize_params_value(value, push)
107
+ if value.nil?
108
+ ""
109
+ elsif value.is_a?(Array)
110
+ value.map {|v| serialize_params_value(v, push)}
111
+
112
+ elsif value.is_a?(Hash)
113
+ Util.convert_to_api_object(value, @opts).serialize_params
114
+
115
+ elsif value.is_a?(APIObject)
116
+ value.serialize_params(push: push)
117
+ else
118
+ value
119
+ end
120
+ end
121
+
122
+ # Object initialize/update section
123
+ def self.create_from(values, client = nil)
124
+ values = Util.symbolize_names(values)
125
+ new(values[:id], client).send(:initialize_from, values)
126
+ end
127
+
128
+ def initialize_from(values, partial = false)
129
+ removed = partial ? Set.new : Set.new(@data.keys - values.keys)
130
+ added = Set.new(values.keys - @data.keys)
131
+
132
+ remove_accessors(removed)
133
+ add_accessors(added, values)
134
+
135
+ removed.each do |k|
136
+ @data.delete(k)
137
+ @transient_values.add(k)
138
+ @unsaved_values.delete(k)
139
+ end
140
+
141
+ update_attributes(values)
142
+ values.each_key do |k|
143
+ @transient_values.delete(k)
144
+ @unsaved_values.delete(k)
145
+ end
146
+
147
+ self
148
+ end
149
+
150
+ def update_attributes(values)
151
+ values.each do |k, v|
152
+ add_accessors([k], values) unless metaclass.method_defined?(k.to_sym)
153
+ @data[k] = Util.convert_to_api_object(v, @client)
154
+ @unsaved_values.add(k)
155
+ end
156
+ end
157
+
158
+
159
+ protected
160
+
161
+ def metaclass
162
+ class << self
163
+ self
164
+ end
165
+ end
166
+
167
+ def remove_accessors(keys)
168
+ metaclass.instance_eval do
169
+ keys.each do |k|
170
+ # Remove methods for the accessor's reader and writer.
171
+ [k, :"#{k}=", :"#{k}?"].each do |method_name|
172
+ remove_method(method_name) if method_defined?(method_name)
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ def add_accessors(keys, values)
179
+ metaclass.instance_eval do
180
+ keys.each do |k|
181
+ if k == :method
182
+ define_method(k) {|*args| args.empty? ? @data[k] : super(*args)}
183
+ else
184
+ define_method(k) {@data[k]}
185
+ end
186
+
187
+ define_method(:"#{k}=") do |v|
188
+ if v != ""
189
+ @data[k] = Util.convert_to_api_object(v, @opts)
190
+ @unsaved_values.add(k)
191
+ end
192
+ end
193
+
194
+ if [FalseClass, TrueClass].include?(values[k].class)
195
+ define_method(:"#{k}?") {@data[k]}
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,23 @@
1
+ module CoinbaseCommerceClient
2
+ module APIResources
3
+ module Base
4
+ class APIResource < APIObject
5
+ class << self
6
+ attr_accessor :client
7
+ end
8
+
9
+ @client = nil
10
+
11
+ def self.retrieve(id, params = {})
12
+ resp = @client.request(:get, "#{self::RESOURCE_PATH}/#{id}", params)
13
+ Util.convert_to_api_object(resp.data, @client, self)
14
+ end
15
+
16
+ def refresh(params = {})
17
+ resp = @client.request(:get, "#{self.class::RESOURCE_PATH}/#{self[:id]}", params)
18
+ initialize_from(resp.data)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ module CoinbaseCommerceClient
2
+ module APIResources
3
+ module Base
4
+ module Create
5
+ def create(params = {})
6
+ response = @client.request(:post, "#{self::RESOURCE_PATH}", params)
7
+ Util.convert_to_api_object(response.data, @client, self)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module CoinbaseCommerceClient
2
+ module APIResources
3
+ module Base
4
+ module Delete
5
+ def delete
6
+ response = @client.request(:delete, "#{self.class::RESOURCE_PATH}/#{self[:id]}")
7
+ initialize_from(response.data)
8
+ self
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ module CoinbaseCommerceClient
2
+ module APIResources
3
+ module Base
4
+ module List
5
+ def list(params = {})
6
+ resp = @client.request(:get, self::RESOURCE_PATH.to_s, params)
7
+ Util.convert_to_api_object(resp.data, @client, self)
8
+ end
9
+
10
+ def auto_paging(params = {}, &blk)
11
+ loop do
12
+ page = list(params)
13
+ last_id = page.data.empty? ? nil : page.data.last.id
14
+ break if last_id.nil?
15
+
16
+ params[:starting_after] = last_id
17
+ page.data.each(&blk)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module CoinbaseCommerceClient
2
+ module APIResources
3
+ module Base
4
+ module Save
5
+ def save
6
+ values = serialize_params(self)
7
+ values.delete(:id)
8
+ resp = @client.request(:put, "#{self.class::RESOURCE_PATH}/#{self[:id]}", self)
9
+ initialize_from(resp.data)
10
+ self
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ module CoinbaseCommerceClient
2
+ module APIResources
3
+ module Base
4
+ module Update
5
+ def modify(id, params={})
6
+ resp = @client.request(:put, "#{self::RESOURCE_PATH}/#{id}", params)
7
+ Util.convert_to_api_object(resp.data, @client, self)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ module CoinbaseCommerceClient
2
+ module APIResources
3
+ class Charge < Base::APIResource
4
+ extend Base::List
5
+ extend Base::Create
6
+
7
+ OBJECT_NAME = 'charge'.freeze
8
+ RESOURCE_PATH = 'charges'.freeze
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module CoinbaseCommerceClient
2
+ module APIResources
3
+ class Checkout < Base::APIResource
4
+ extend Base::List
5
+ extend Base::Create
6
+ extend Base::Update
7
+
8
+ include Base::Save
9
+ include Base::Delete
10
+
11
+ OBJECT_NAME = 'checkout'.freeze
12
+ RESOURCE_PATH = 'checkouts'.freeze
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ module CoinbaseCommerceClient
2
+ module APIResources
3
+ class Event < Base::APIResource
4
+ extend Base::List
5
+
6
+ OBJECT_NAME = 'event'.freeze
7
+ RESOURCE_PATH = 'events'.freeze
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,36 @@
1
+ module CoinbaseCommerceClient
2
+ class CoinbaseCommerceClientResponse
3
+ attr_accessor :data, :http_body, :http_headers, :http_status, :request_id
4
+
5
+ # Initializes a CoinbaseCommerceResponse object
6
+ # from a Hash like the kind returned as part of a Faraday exception.
7
+ def self.from_faraday_hash(http_resp)
8
+ resp = CoinbaseCommerceClientResponse.new
9
+ resp.data = JSON.parse(http_resp[:body], symbolize_names: true)
10
+ resp.http_body = http_resp[:body]
11
+ resp.http_headers = http_resp[:headers]
12
+ resp.http_status = http_resp[:status]
13
+ resp.request_id = http_resp[:headers]['x-request-id']
14
+ resp
15
+ end
16
+
17
+ # Initializes a CoinbaseCommerceResponse object
18
+ # from a Faraday HTTP response object.
19
+ def self.from_faraday_response(http_resp)
20
+ resp = CoinbaseCommerceClientResponse.new
21
+ resp.data = JSON.parse(http_resp.body, symbolize_names: true)
22
+ resp.http_body = http_resp.body
23
+ resp.http_headers = http_resp.headers
24
+ resp.http_status = http_resp.status
25
+ resp.request_id = http_resp.headers['x-request-id']
26
+
27
+ # unpack nested data field if it exist
28
+ resp.data.update(resp.data.delete(:data)) if resp.data.is_a?(Hash) && resp.data.fetch(:data, nil).is_a?(Hash)
29
+
30
+ # warn in there warnings in response
31
+ warn(resp.data[:warnings].first.to_s) if resp.data.is_a?(Hash) && resp.data.fetch(:warnings, nil).is_a?(Array)
32
+
33
+ resp
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,115 @@
1
+ module CoinbaseCommerceClient
2
+ BASE_API_URL = 'https://api.commerce.coinbase.com/'.freeze
3
+ API_VERSION = '2018-03-22'.freeze
4
+
5
+ class Client
6
+ def initialize(options = {})
7
+ check_api_key!(options[:api_key])
8
+ @api_key = options[:api_key]
9
+ @api_uri = URI.parse(options[:api_url] || BASE_API_URL)
10
+ @api_ver = options[:api_ver] || API_VERSION
11
+
12
+ @conn = Faraday.new do |c|
13
+ c.use Faraday::Request::UrlEncoded
14
+ c.use Faraday::Response::RaiseError
15
+ c.adapter Faraday.default_adapter
16
+ end
17
+ end
18
+
19
+ def charge
20
+ APIResources::Charge.client = self
21
+ APIResources::Charge
22
+ end
23
+
24
+ def checkout
25
+ APIResources::Checkout.client = self
26
+ APIResources::Checkout
27
+ end
28
+
29
+ def event
30
+ APIResources::Event.client = self
31
+ APIResources::Event
32
+ end
33
+
34
+ def api_url(url = "", api_base = nil)
35
+ (api_base || CoinbaseCommerceClient::BASE_API_URL) + url
36
+ end
37
+
38
+ def request_headers(api_key)
39
+ {
40
+ "User-Agent" => "CoinbaseCommerce/#{CoinbaseCommerceClient::VERSION}",
41
+ "Accept" => "application/json",
42
+ "X-CC-Api-Key" => api_key,
43
+ "X-CC-Version" => @api_ver,
44
+ "Content-Type" => "application/json",
45
+ }
46
+ end
47
+
48
+ def check_api_key!(api_key)
49
+ raise AuthenticationError, 'No API key provided' unless api_key
50
+ end
51
+
52
+ def request(method, path, params = {})
53
+ @last_response = nil
54
+ url = api_url(path, @api_uri)
55
+ headers = request_headers(@api_key)
56
+
57
+ body = nil
58
+ query_params = nil
59
+
60
+ case method.to_s.downcase.to_sym
61
+ when :get, :head, :delete
62
+ query_params = params
63
+ else
64
+ body = params.to_json
65
+ end
66
+
67
+ u = URI.parse(path)
68
+ unless u.query.nil?
69
+ query_params ||= {}
70
+ query_params = Hash[URI.decode_www_form(u.query)].merge(query_params)
71
+ end
72
+
73
+
74
+ http_resp = execute_request_with_rescues(@api_uri) do
75
+ @conn.run_request(method, url, body, headers) do |req|
76
+ req.params = query_params unless query_params.nil?
77
+ end
78
+ end
79
+
80
+ begin
81
+ resp = CoinbaseCommerceClientResponse.from_faraday_response(http_resp)
82
+ rescue JSON::ParserError
83
+ raise Errors.general_api_error(http_resp.status, http_resp.body)
84
+ end
85
+
86
+ @last_response = resp
87
+ resp
88
+ end
89
+
90
+ def execute_request_with_rescues(api_base)
91
+ begin
92
+ resp = yield
93
+ rescue StandardError => e
94
+ case e
95
+ when Faraday::ClientError
96
+ if e.response
97
+ Errors.handle_error_response(e.response)
98
+ else
99
+ Errors.handle_network_error(e, api_base)
100
+ end
101
+ when Faraday::ServerError
102
+ if e.response
103
+ Errors.handle_error_response(e.response)
104
+ else
105
+ Errors.handle_network_error(e, api_base)
106
+ end
107
+ else
108
+ raise
109
+ end
110
+ end
111
+ resp
112
+ end
113
+
114
+ end
115
+ end