maxmind-geoip2 0.1.0 → 0.2.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 (76) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +32 -0
  5. data/README.md +52 -0
  6. data/lib/maxmind/geoip2.rb +1 -0
  7. data/lib/maxmind/geoip2/client.rb +313 -0
  8. data/lib/maxmind/geoip2/errors.rb +44 -3
  9. data/lib/maxmind/geoip2/model/anonymous_ip.rb +2 -2
  10. data/lib/maxmind/geoip2/model/country.rb +7 -0
  11. data/lib/maxmind/geoip2/model/insights.rb +14 -0
  12. data/lib/maxmind/geoip2/record/maxmind.rb +17 -0
  13. data/maxmind-geoip2.gemspec +2 -1
  14. data/test/test_client.rb +424 -0
  15. metadata +21 -64
  16. data/test/data/LICENSE +0 -4
  17. data/test/data/MaxMind-DB-spec.md +0 -570
  18. data/test/data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
  19. data/test/data/README.md +0 -4
  20. data/test/data/bad-data/README.md +0 -7
  21. data/test/data/bad-data/libmaxminddb/libmaxminddb-offset-integer-overflow.mmdb +0 -0
  22. data/test/data/bad-data/maxminddb-golang/cyclic-data-structure.mmdb +0 -0
  23. data/test/data/bad-data/maxminddb-golang/invalid-bytes-length.mmdb +0 -1
  24. data/test/data/bad-data/maxminddb-golang/invalid-data-record-offset.mmdb +0 -0
  25. data/test/data/bad-data/maxminddb-golang/invalid-map-key-length.mmdb +0 -0
  26. data/test/data/bad-data/maxminddb-golang/invalid-string-length.mmdb +0 -1
  27. data/test/data/bad-data/maxminddb-golang/metadata-is-an-uint128.mmdb +0 -1
  28. data/test/data/bad-data/maxminddb-golang/unexpected-bytes.mmdb +0 -0
  29. data/test/data/perltidyrc +0 -12
  30. data/test/data/source-data/GeoIP2-Anonymous-IP-Test.json +0 -48
  31. data/test/data/source-data/GeoIP2-City-Test.json +0 -12852
  32. data/test/data/source-data/GeoIP2-Connection-Type-Test.json +0 -102
  33. data/test/data/source-data/GeoIP2-Country-Test.json +0 -15916
  34. data/test/data/source-data/GeoIP2-DensityIncome-Test.json +0 -14
  35. data/test/data/source-data/GeoIP2-Domain-Test.json +0 -452
  36. data/test/data/source-data/GeoIP2-Enterprise-Test.json +0 -687
  37. data/test/data/source-data/GeoIP2-ISP-Test.json +0 -12593
  38. data/test/data/source-data/GeoIP2-Precision-Enterprise-Test.json +0 -2061
  39. data/test/data/source-data/GeoIP2-Static-IP-Score-Test.json +0 -2132
  40. data/test/data/source-data/GeoIP2-User-Count-Test.json +0 -2837
  41. data/test/data/source-data/GeoLite2-ASN-Test.json +0 -37
  42. data/test/data/source-data/README +0 -15
  43. data/test/data/test-data/GeoIP2-Anonymous-IP-Test.mmdb +0 -0
  44. data/test/data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb +0 -0
  45. data/test/data/test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb +0 -0
  46. data/test/data/test-data/GeoIP2-City-Test.mmdb +0 -0
  47. data/test/data/test-data/GeoIP2-Connection-Type-Test.mmdb +0 -0
  48. data/test/data/test-data/GeoIP2-Country-Test.mmdb +0 -0
  49. data/test/data/test-data/GeoIP2-DensityIncome-Test.mmdb +0 -0
  50. data/test/data/test-data/GeoIP2-Domain-Test.mmdb +0 -0
  51. data/test/data/test-data/GeoIP2-Enterprise-Test.mmdb +0 -0
  52. data/test/data/test-data/GeoIP2-ISP-Test.mmdb +0 -0
  53. data/test/data/test-data/GeoIP2-Precision-Enterprise-Test.mmdb +0 -0
  54. data/test/data/test-data/GeoIP2-Static-IP-Score-Test.mmdb +0 -0
  55. data/test/data/test-data/GeoIP2-User-Count-Test.mmdb +0 -0
  56. data/test/data/test-data/GeoLite2-ASN-Test.mmdb +0 -0
  57. data/test/data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb +0 -0
  58. data/test/data/test-data/MaxMind-DB-string-value-entries.mmdb +0 -0
  59. data/test/data/test-data/MaxMind-DB-test-broken-pointers-24.mmdb +0 -0
  60. data/test/data/test-data/MaxMind-DB-test-broken-search-tree-24.mmdb +0 -0
  61. data/test/data/test-data/MaxMind-DB-test-decoder.mmdb +0 -0
  62. data/test/data/test-data/MaxMind-DB-test-ipv4-24.mmdb +0 -0
  63. data/test/data/test-data/MaxMind-DB-test-ipv4-28.mmdb +0 -0
  64. data/test/data/test-data/MaxMind-DB-test-ipv4-32.mmdb +0 -0
  65. data/test/data/test-data/MaxMind-DB-test-ipv6-24.mmdb +0 -0
  66. data/test/data/test-data/MaxMind-DB-test-ipv6-28.mmdb +0 -0
  67. data/test/data/test-data/MaxMind-DB-test-ipv6-32.mmdb +0 -0
  68. data/test/data/test-data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
  69. data/test/data/test-data/MaxMind-DB-test-mixed-24.mmdb +0 -0
  70. data/test/data/test-data/MaxMind-DB-test-mixed-28.mmdb +0 -0
  71. data/test/data/test-data/MaxMind-DB-test-mixed-32.mmdb +0 -0
  72. data/test/data/test-data/MaxMind-DB-test-nested.mmdb +0 -0
  73. data/test/data/test-data/README.md +0 -26
  74. data/test/data/test-data/maps-with-pointers.raw +0 -0
  75. data/test/data/test-data/write-test-data.pl +0 -641
  76. data/test/data/tidyall.ini +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 59a52bbdfb71c9b0ebe0104616db7425e90f166940aa14a04aef3baa60adce7e
4
- data.tar.gz: 51a5b6921967659ac3ee2a874b7625065b71102ce8fc5ee05b0ea7b94f34f233
2
+ SHA1:
3
+ metadata.gz: 107c7822a5a2de18a3339637547ca8fd8eef5163
4
+ data.tar.gz: 8d730256e5f036178cf13cbd69ed5be2b86afc10
5
5
  SHA512:
6
- metadata.gz: 3a75b0ac6a4da68a4c79b9a8b3a61d45c458279189edb5dc98a5366c4dbac567d6834528c6060ad3dbb759316614649b0d8d9bdf571516ca5d33d639dbf7f76c
7
- data.tar.gz: dfa9fb9b9d1f0a10194ee80b4860cd2b83398fbdc86f042a3e9bcf0c90bd4d7621323524167f19467d46faf94bd790283d5ffdb14d8ba66841615dea4d64c37e
6
+ metadata.gz: 1c9dc8f9b3a115494fa9f20c603f772721568a3759b9e2619fde20751ba71a50109d911effeb7d702d286a2011e56d71049ff6a2b854b177f012bbf6bd904291
7
+ data.tar.gz: a895b54353186ad7fdffec41d9fcf0efa03f093f2dd44a7bbe31e417e13e726a971bb4b88a8a7357c424b16d864061aa563aa150b87160a087d025e852be9a60
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 (2020-02-26)
4
+
5
+ * Added support for the GeoIP2 Precision web services: Country, City, and
6
+ Insights.
7
+
3
8
  ## 0.1.0 (2020-02-20)
4
9
 
5
10
  * Added support for the Anonymous IP, ASN, Connection Type, Domain, and ISP
data/Gemfile CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ gem 'http'
5
6
  gem 'maxmind-db'
6
7
  gem 'minitest', group: :development
7
8
  gem 'rake', group: :development
8
9
  gem 'rubocop', group: :development
9
10
  gem 'rubocop-performance', group: :development
11
+ gem 'webmock', group: :development
@@ -1,13 +1,35 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
+ addressable (2.7.0)
5
+ public_suffix (>= 2.0.2, < 5.0)
4
6
  ast (2.4.0)
7
+ crack (0.4.3)
8
+ safe_yaml (~> 1.0.0)
9
+ domain_name (0.5.20190701)
10
+ unf (>= 0.0.5, < 1.0.0)
11
+ ffi (1.12.2)
12
+ ffi-compiler (1.0.1)
13
+ ffi (>= 1.0.0)
14
+ rake
15
+ hashdiff (1.0.0)
16
+ http (4.3.0)
17
+ addressable (~> 2.3)
18
+ http-cookie (~> 1.0)
19
+ http-form_data (~> 2.2)
20
+ http-parser (~> 1.2.0)
21
+ http-cookie (1.0.3)
22
+ domain_name (~> 0.5)
23
+ http-form_data (2.2.0)
24
+ http-parser (1.2.1)
25
+ ffi-compiler (>= 1.0, < 2.0)
5
26
  jaro_winkler (1.5.4)
6
27
  maxmind-db (1.1.0)
7
28
  minitest (5.14.0)
8
29
  parallel (1.19.1)
9
30
  parser (2.7.0.2)
10
31
  ast (~> 2.4.0)
32
+ public_suffix (4.0.3)
11
33
  rainbow (3.0.0)
12
34
  rake (13.0.1)
13
35
  rexml (3.2.4)
@@ -22,17 +44,27 @@ GEM
22
44
  rubocop-performance (1.5.2)
23
45
  rubocop (>= 0.71.0)
24
46
  ruby-progressbar (1.10.1)
47
+ safe_yaml (1.0.5)
48
+ unf (0.1.4)
49
+ unf_ext
50
+ unf_ext (0.0.7.6)
25
51
  unicode-display_width (1.6.1)
52
+ webmock (3.8.2)
53
+ addressable (>= 2.3.6)
54
+ crack (>= 0.3.2)
55
+ hashdiff (>= 0.4.0, < 2.0.0)
26
56
 
27
57
  PLATFORMS
28
58
  ruby
29
59
 
30
60
  DEPENDENCIES
61
+ http
31
62
  maxmind-db
32
63
  minitest
33
64
  rake
34
65
  rubocop
35
66
  rubocop-performance
67
+ webmock
36
68
 
37
69
  BUNDLED WITH
38
70
  2.1.4
data/README.md CHANGED
@@ -183,6 +183,58 @@ puts record.isp # University of Minnesota
183
183
  puts record.organization # University of Minnesota
184
184
  ```
185
185
 
186
+ ## Web Service Client
187
+
188
+ ### Usage
189
+
190
+ To use this API, you must create a new `MaxMind::GeoIP2::Client` object
191
+ with your account ID and license key, then you call the method
192
+ corresponding to a specific end point, passing it the IP address you want
193
+ to look up.
194
+
195
+ If the request succeeds, the method call will return a model class for the end
196
+ point you called. This model in turn contains multiple record classes, each of
197
+ which represents part of the data returned by the web service.
198
+
199
+ If there is an error, a structured exception is thrown.
200
+
201
+ See the [API documentation](https://www.rubydoc.info/gems/maxmind-geoip2)
202
+ for more details.
203
+
204
+ ### Example
205
+
206
+ ```ruby
207
+ require 'maxmind/geoip2'
208
+
209
+ # This creates a Client object that can be reused across requests.
210
+ # Replace "42" with your account ID and "license_key" with your license
211
+ # key.
212
+ client = MaxMind::GeoIP2::Client.new(
213
+ account_id: 42,
214
+ license_key: 'license_key',
215
+ )
216
+
217
+ # Replace "city" with the method corresponding to the web service that
218
+ # you are using, e.g., "country", "insights".
219
+ record = client.city('128.101.101.101')
220
+
221
+ puts record.country.isoCode # US
222
+ puts record.country.name # United States
223
+ puts record.country.names['zh-CN'] # 美国
224
+
225
+ puts record.most_specific_subdivision.name # Minnesota
226
+ puts record.most_specific_subdivision.iso_code # MN
227
+
228
+ puts record.city.name # Minneapolis
229
+
230
+ puts record.postal.code # 55455
231
+
232
+ puts record.location.latitude # 44.9733
233
+ puts record.location.longitude # -93.2323
234
+
235
+ puts record.traits.network # 128.101.101.101/32
236
+ ```
237
+
186
238
  ## Values to use for Database or Array Keys
187
239
 
188
240
  **We strongly discourage you from using a value from any `names` property
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'maxmind/geoip2/client'
3
4
  require 'maxmind/geoip2/reader'
@@ -0,0 +1,313 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'http'
4
+ require 'json'
5
+ require 'maxmind/geoip2/errors'
6
+ require 'maxmind/geoip2/model/city'
7
+ require 'maxmind/geoip2/model/country'
8
+ require 'maxmind/geoip2/model/insights'
9
+
10
+ module MaxMind::GeoIP2
11
+ # This class provides a client API for all the
12
+ # {https://dev.maxmind.com/geoip/geoip2/web-services/ GeoIP2 Precision web
13
+ # services}. The services are Country, City, and Insights. Each service
14
+ # returns a different set of data about an IP address, with Country returning
15
+ # the least data and Insights the most.
16
+ #
17
+ # Each web service is represented by a different model class, and these model
18
+ # classes in turn contain multiple record classes. The record classes have
19
+ # attributes which contain data about the IP address.
20
+ #
21
+ # If the web service does not return a particular piece of data for an IP
22
+ # address, the associated attribute is not populated.
23
+ #
24
+ # The web service may not return any information for an entire record, in
25
+ # which case all of the attributes for that record class will be empty.
26
+ #
27
+ # == Usage
28
+ #
29
+ # The basic API for this class is the same for all of the web service end
30
+ # points. First you create a web service client object with your MaxMind
31
+ # account ID and license key, then you call the method corresponding to a
32
+ # specific end point, passing it the IP address you want to look up.
33
+ #
34
+ # If the request succeeds, the method call will return a model class for the
35
+ # service you called. This model in turn contains multiple record classes,
36
+ # each of which represents part of the data returned by the web service.
37
+ #
38
+ # If the request fails, the client class throws an exception.
39
+ #
40
+ # == Example
41
+ #
42
+ # require 'maxmind/geoip2'
43
+ #
44
+ # client = MaxMind::GeoIP2::Client.new(
45
+ # account_id: 42,
46
+ # license_key: 'abcdef123456',
47
+ # )
48
+ #
49
+ # # Replace 'city' with the method corresponding to the web service you
50
+ # # are using, e.g., 'country', 'insights'.
51
+ # record = client.city('128.101.101.101')
52
+ #
53
+ # puts record.country.iso_code
54
+ class Client
55
+ # rubocop:disable Metrics/ParameterLists
56
+
57
+ # Create a Client that may be used to query a GeoIP2 Precision web service.
58
+ #
59
+ # Once created, the Client is safe to use for lookups from multiple
60
+ # threads.
61
+ #
62
+ # @param account_id [Integer] your MaxMind account ID.
63
+ #
64
+ # @param license_key [String] your MaxMind license key.
65
+ #
66
+ # @param locales [Array<String>] a list of locale codes to use in the name
67
+ # property from most preferred to least preferred.
68
+ #
69
+ # @param host [String] the host to use when querying the web service.
70
+ #
71
+ # @param timeout [Integer] the number of seconds to wait for a request
72
+ # before timing out. If 0, no timeout is set.
73
+ #
74
+ # @param proxy_address [String] proxy address to use, if any.
75
+ #
76
+ # @param proxy_port [Integer] proxy port to use, if any.
77
+ #
78
+ # @param proxy_username [String] proxy username to use, if any.
79
+ #
80
+ # @param proxy_password [String] proxy password to use, if any.
81
+ def initialize(
82
+ account_id:,
83
+ license_key:,
84
+ locales: ['en'],
85
+ host: 'geoip.maxmind.com',
86
+ timeout: 0,
87
+ proxy_address: '',
88
+ proxy_port: 0,
89
+ proxy_username: '',
90
+ proxy_password: ''
91
+ )
92
+ @account_id = account_id
93
+ @license_key = license_key
94
+ @locales = locales
95
+ @host = host
96
+ @timeout = timeout
97
+ @proxy_address = proxy_address
98
+ @proxy_port = proxy_port
99
+ @proxy_username = proxy_username
100
+ @proxy_password = proxy_password
101
+ end
102
+ # rubocop:enable Metrics/ParameterLists
103
+
104
+ # This method calls the GeoIP2 Precision City web service.
105
+ #
106
+ # @param ip_address [String] IPv4 or IPv6 address as a string. If no
107
+ # address is provided, the address that the web service is called from is
108
+ # used.
109
+ #
110
+ # @raise [HTTP::Error] if there was an error performing the HTTP request,
111
+ # such as an error connecting.
112
+ #
113
+ # @raise [JSON::ParserError] if there was invalid JSON in the response.
114
+ #
115
+ # @raise [HTTPError] if there was a problem with the HTTP response, such as
116
+ # an unexpected HTTP status code.
117
+ #
118
+ # @raise [AddressInvalidError] if the web service believes the IP address
119
+ # to be invalid or missing.
120
+ #
121
+ # @raise [AddressNotFoundError] if the IP address was not found.
122
+ #
123
+ # @raise [AddressReservedError] if the IP address is reserved.
124
+ #
125
+ # @raise [AuthenticationError] if there was a problem authenticating to the
126
+ # web service, such as an invalid or missing license key.
127
+ #
128
+ # @raise [InsufficientFundsError] if your account is out of credit.
129
+ #
130
+ # @raise [PermissionRequiredError] if your account does not have permission
131
+ # to use the web service.
132
+ #
133
+ # @raise [InvalidRequestError] if the web service responded with an error
134
+ # and there is no more specific error to raise.
135
+ #
136
+ # @return [MaxMind::GeoIP2::Model::City]
137
+ def city(ip_address = 'me')
138
+ response_for('city', MaxMind::GeoIP2::Model::City, ip_address)
139
+ end
140
+
141
+ # This method calls the GeoIP2 Precision Country web service.
142
+ #
143
+ # @param ip_address [String] IPv4 or IPv6 address as a string. If no
144
+ # address is provided, the address that the web service is called from is
145
+ # used.
146
+ #
147
+ # @raise [HTTP::Error] if there was an error performing the HTTP request,
148
+ # such as an error connecting.
149
+ #
150
+ # @raise [JSON::ParserError] if there was invalid JSON in the response.
151
+ #
152
+ # @raise [HTTPError] if there was a problem with the HTTP response, such as
153
+ # an unexpected HTTP status code.
154
+ #
155
+ # @raise [AddressInvalidError] if the web service believes the IP address
156
+ # to be invalid or missing.
157
+ #
158
+ # @raise [AddressNotFoundError] if the IP address was not found.
159
+ #
160
+ # @raise [AddressReservedError] if the IP address is reserved.
161
+ #
162
+ # @raise [AuthenticationError] if there was a problem authenticating to the
163
+ # web service, such as an invalid or missing license key.
164
+ #
165
+ # @raise [InsufficientFundsError] if your account is out of credit.
166
+ #
167
+ # @raise [PermissionRequiredError] if your account does not have permission
168
+ # to use the web service.
169
+ #
170
+ # @raise [InvalidRequestError] if the web service responded with an error
171
+ # and there is no more specific error to raise.
172
+ #
173
+ # @return [MaxMind::GeoIP2::Model::Country]
174
+ def country(ip_address = 'me')
175
+ response_for('country', MaxMind::GeoIP2::Model::Country, ip_address)
176
+ end
177
+
178
+ # This method calls the GeoIP2 Precision Insights web service.
179
+ #
180
+ # @param ip_address [String] IPv4 or IPv6 address as a string. If no
181
+ # address is provided, the address that the web service is called from is
182
+ # used.
183
+ #
184
+ # @raise [HTTP::Error] if there was an error performing the HTTP request,
185
+ # such as an error connecting.
186
+ #
187
+ # @raise [JSON::ParserError] if there was invalid JSON in the response.
188
+ #
189
+ # @raise [HTTPError] if there was a problem with the HTTP response, such as
190
+ # an unexpected HTTP status code.
191
+ #
192
+ # @raise [AddressInvalidError] if the web service believes the IP address
193
+ # to be invalid or missing.
194
+ #
195
+ # @raise [AddressNotFoundError] if the IP address was not found.
196
+ #
197
+ # @raise [AddressReservedError] if the IP address is reserved.
198
+ #
199
+ # @raise [AuthenticationError] if there was a problem authenticating to the
200
+ # web service, such as an invalid or missing license key.
201
+ #
202
+ # @raise [InsufficientFundsError] if your account is out of credit.
203
+ #
204
+ # @raise [PermissionRequiredError] if your account does not have permission
205
+ # to use the web service.
206
+ #
207
+ # @raise [InvalidRequestError] if the web service responded with an error
208
+ # and there is no more specific error to raise.
209
+ #
210
+ # @return [MaxMind::GeoIP2::Model::Insights]
211
+ def insights(ip_address = 'me')
212
+ response_for('insights', MaxMind::GeoIP2::Model::Insights, ip_address)
213
+ end
214
+
215
+ private
216
+
217
+ def response_for(endpoint, model_class, ip_address)
218
+ record = get(endpoint, ip_address)
219
+
220
+ model_class.new(record, @locales)
221
+ end
222
+
223
+ # rubocop:disable Metrics/CyclomaticComplexity
224
+ # rubocop:disable Metrics/PerceivedComplexity
225
+ def get(endpoint, ip_address)
226
+ url = 'https://' + @host + '/geoip/v2.1/' + endpoint + '/' + ip_address
227
+
228
+ headers = HTTP.basic_auth(user: @account_id, pass: @license_key)
229
+ .headers(
230
+ accept: 'application/json',
231
+ user_agent: 'MaxMind-GeoIP2-ruby',
232
+ )
233
+ timeout = @timeout > 0 ? headers.timeout(@timeout) : headers
234
+
235
+ proxy = timeout
236
+ if @proxy_address != ''
237
+ opts = {}
238
+ opts[:proxy_port] = @proxy_port if @proxy_port != 0
239
+ opts[:proxy_username] = @proxy_username if @proxy_username != ''
240
+ opts[:proxy_password] = @proxy_password if @proxy_password != ''
241
+ proxy = timeout.via(@proxy_address, opts)
242
+ end
243
+
244
+ response = proxy.get(url)
245
+
246
+ body = response.to_s
247
+ is_json = response.headers[:content_type]&.include?('json')
248
+
249
+ if response.status.client_error?
250
+ return handle_client_error(endpoint, response.code, body, is_json)
251
+ end
252
+
253
+ if response.status.server_error?
254
+ raise HTTPError,
255
+ "Received server error response (#{response.code}) for #{endpoint} with body #{body}"
256
+ end
257
+
258
+ if response.code != 200
259
+ raise HTTPError,
260
+ "Received unexpected response (#{response.code}) for #{endpoint} with body #{body}"
261
+ end
262
+
263
+ handle_success(endpoint, body, is_json)
264
+ end
265
+ # rubocop:enable Metrics/CyclomaticComplexity
266
+ # rubocop:enable Metrics/PerceivedComplexity
267
+
268
+ # rubocop:disable Metrics/CyclomaticComplexity
269
+ def handle_client_error(endpoint, status, body, is_json)
270
+ if !is_json
271
+ raise HTTPError,
272
+ "Received client error response (#{status}) for #{endpoint} but it is not JSON: #{body}"
273
+ end
274
+
275
+ error = JSON.parse(body)
276
+
277
+ if !error.key?('code') || !error.key?('error')
278
+ raise HTTPError,
279
+ "Received client error response (#{status}) that is JSON but does not specify code or error keys: #{body}"
280
+ end
281
+
282
+ case error['code']
283
+ when 'IP_ADDRESS_INVALID', 'IP_ADDRESS_REQUIRED'
284
+ raise AddressInvalidError, error['error']
285
+ when 'IP_ADDRESS_NOT_FOUND'
286
+ raise AddressNotFoundError, error['error']
287
+ when 'IP_ADDRESS_RESERVED'
288
+ raise AddressReservedError, error['error']
289
+ when 'ACCOUNT_ID_REQUIRED',
290
+ 'ACCOUNT_ID_UNKNOWN',
291
+ 'AUTHORIZATION_INVALID',
292
+ 'LICENSE_KEY_REQUIRED'
293
+ raise AuthenticationError, error['error']
294
+ when 'INSUFFICIENT_FUNDS'
295
+ raise InsufficientFundsError, error['error']
296
+ when 'PERMISSION_REQUIRED'
297
+ raise PermissionRequiredError, error['error']
298
+ else
299
+ raise InvalidRequestError, error['error']
300
+ end
301
+ end
302
+ # rubocop:enable Metrics/CyclomaticComplexity
303
+
304
+ def handle_success(endpoint, body, is_json)
305
+ if !is_json
306
+ raise HTTPError,
307
+ "Received a success response for #{endpoint} but it is not JSON: #{body}"
308
+ end
309
+
310
+ JSON.parse(body)
311
+ end
312
+ end
313
+ end