maxmind-geoip2 0.0.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +30 -0
  3. data/Gemfile +1 -5
  4. data/README.md +121 -0
  5. data/lib/maxmind/geoip2.rb +1 -13
  6. data/lib/maxmind/geoip2/client.rb +328 -0
  7. data/lib/maxmind/geoip2/errors.rb +37 -3
  8. data/lib/maxmind/geoip2/model/abstract.rb +31 -0
  9. data/lib/maxmind/geoip2/model/anonymous_ip.rb +75 -0
  10. data/lib/maxmind/geoip2/model/asn.rb +43 -0
  11. data/lib/maxmind/geoip2/model/city.rb +63 -44
  12. data/lib/maxmind/geoip2/model/connection_type.rb +36 -0
  13. data/lib/maxmind/geoip2/model/country.rb +64 -45
  14. data/lib/maxmind/geoip2/model/domain.rb +36 -0
  15. data/lib/maxmind/geoip2/model/enterprise.rb +13 -9
  16. data/lib/maxmind/geoip2/model/insights.rb +18 -0
  17. data/lib/maxmind/geoip2/model/isp.rb +57 -0
  18. data/lib/maxmind/geoip2/reader.rb +250 -131
  19. data/lib/maxmind/geoip2/record/abstract.rb +17 -12
  20. data/lib/maxmind/geoip2/record/city.rb +33 -24
  21. data/lib/maxmind/geoip2/record/continent.rb +32 -23
  22. data/lib/maxmind/geoip2/record/country.rb +47 -34
  23. data/lib/maxmind/geoip2/record/location.rb +64 -50
  24. data/lib/maxmind/geoip2/record/maxmind.rb +21 -0
  25. data/lib/maxmind/geoip2/record/place.rb +22 -16
  26. data/lib/maxmind/geoip2/record/postal.rb +26 -18
  27. data/lib/maxmind/geoip2/record/represented_country.rb +20 -14
  28. data/lib/maxmind/geoip2/record/subdivision.rb +42 -34
  29. data/lib/maxmind/geoip2/record/traits.rb +204 -160
  30. data/maxmind-geoip2.gemspec +12 -2
  31. data/test/data/LICENSE +4 -0
  32. data/test/data/MaxMind-DB-spec.md +570 -0
  33. data/test/data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
  34. data/test/data/README.md +4 -0
  35. data/test/data/bad-data/README.md +7 -0
  36. data/test/data/bad-data/libmaxminddb/libmaxminddb-offset-integer-overflow.mmdb +0 -0
  37. data/test/data/bad-data/maxminddb-golang/cyclic-data-structure.mmdb +0 -0
  38. data/test/data/bad-data/maxminddb-golang/invalid-bytes-length.mmdb +1 -0
  39. data/test/data/bad-data/maxminddb-golang/invalid-data-record-offset.mmdb +0 -0
  40. data/test/data/bad-data/maxminddb-golang/invalid-map-key-length.mmdb +0 -0
  41. data/test/data/bad-data/maxminddb-golang/invalid-string-length.mmdb +1 -0
  42. data/test/data/bad-data/maxminddb-golang/metadata-is-an-uint128.mmdb +1 -0
  43. data/test/data/bad-data/maxminddb-golang/unexpected-bytes.mmdb +0 -0
  44. data/test/data/bad-data/maxminddb-python/bad-unicode-in-map-key.mmdb +0 -0
  45. data/test/data/perltidyrc +12 -0
  46. data/test/data/source-data/GeoIP2-Anonymous-IP-Test.json +49 -0
  47. data/test/data/source-data/GeoIP2-City-Test.json +12852 -0
  48. data/test/data/source-data/GeoIP2-Connection-Type-Test.json +102 -0
  49. data/test/data/source-data/GeoIP2-Country-Test.json +15916 -0
  50. data/test/data/source-data/GeoIP2-DensityIncome-Test.json +14 -0
  51. data/test/data/source-data/GeoIP2-Domain-Test.json +452 -0
  52. data/test/data/source-data/GeoIP2-Enterprise-Test.json +687 -0
  53. data/test/data/source-data/GeoIP2-ISP-Test.json +12595 -0
  54. data/test/data/source-data/GeoIP2-Precision-Enterprise-Test.json +2148 -0
  55. data/test/data/source-data/GeoIP2-Static-IP-Score-Test.json +2132 -0
  56. data/test/data/source-data/GeoIP2-User-Count-Test.json +2837 -0
  57. data/test/data/source-data/GeoLite2-ASN-Test.json +37 -0
  58. data/test/data/source-data/README +15 -0
  59. data/test/data/test-data/GeoIP2-Anonymous-IP-Test.mmdb +0 -0
  60. data/test/data/test-data/GeoIP2-City-Test-Broken-Double-Format.mmdb +0 -0
  61. data/test/data/test-data/GeoIP2-City-Test-Invalid-Node-Count.mmdb +0 -0
  62. data/test/data/test-data/GeoIP2-City-Test.mmdb +0 -0
  63. data/test/data/test-data/GeoIP2-Connection-Type-Test.mmdb +0 -0
  64. data/test/data/test-data/GeoIP2-Country-Test.mmdb +0 -0
  65. data/test/data/test-data/GeoIP2-DensityIncome-Test.mmdb +0 -0
  66. data/test/data/test-data/GeoIP2-Domain-Test.mmdb +0 -0
  67. data/test/data/test-data/GeoIP2-Enterprise-Test.mmdb +0 -0
  68. data/test/data/test-data/GeoIP2-ISP-Test.mmdb +0 -0
  69. data/test/data/test-data/GeoIP2-Precision-Enterprise-Test.mmdb +0 -0
  70. data/test/data/test-data/GeoIP2-Static-IP-Score-Test.mmdb +0 -0
  71. data/test/data/test-data/GeoIP2-User-Count-Test.mmdb +0 -0
  72. data/test/data/test-data/GeoLite2-ASN-Test.mmdb +0 -0
  73. data/test/data/test-data/MaxMind-DB-no-ipv4-search-tree.mmdb +0 -0
  74. data/test/data/test-data/MaxMind-DB-string-value-entries.mmdb +0 -0
  75. data/test/data/test-data/MaxMind-DB-test-broken-pointers-24.mmdb +0 -0
  76. data/test/data/test-data/MaxMind-DB-test-broken-search-tree-24.mmdb +0 -0
  77. data/test/data/test-data/MaxMind-DB-test-decoder.mmdb +0 -0
  78. data/test/data/test-data/MaxMind-DB-test-ipv4-24.mmdb +0 -0
  79. data/test/data/test-data/MaxMind-DB-test-ipv4-28.mmdb +0 -0
  80. data/test/data/test-data/MaxMind-DB-test-ipv4-32.mmdb +0 -0
  81. data/test/data/test-data/MaxMind-DB-test-ipv6-24.mmdb +0 -0
  82. data/test/data/test-data/MaxMind-DB-test-ipv6-28.mmdb +0 -0
  83. data/test/data/test-data/MaxMind-DB-test-ipv6-32.mmdb +0 -0
  84. data/test/data/test-data/MaxMind-DB-test-metadata-pointers.mmdb +0 -0
  85. data/test/data/test-data/MaxMind-DB-test-mixed-24.mmdb +0 -0
  86. data/test/data/test-data/MaxMind-DB-test-mixed-28.mmdb +0 -0
  87. data/test/data/test-data/MaxMind-DB-test-mixed-32.mmdb +0 -0
  88. data/test/data/test-data/MaxMind-DB-test-nested.mmdb +0 -0
  89. data/test/data/test-data/MaxMind-DB-test-pointer-decoder.mmdb +0 -0
  90. data/test/data/test-data/README.md +26 -0
  91. data/test/data/test-data/maps-with-pointers.raw +0 -0
  92. data/test/data/test-data/write-test-data.pl +691 -0
  93. data/test/data/tidyall.ini +5 -0
  94. data/test/test_client.rb +426 -0
  95. data/test/test_model_country.rb +1 -1
  96. data/test/test_model_names.rb +1 -1
  97. data/test/test_reader.rb +92 -1
  98. metadata +194 -10
  99. data/Gemfile.lock +0 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1a002f3e88bfdcee342f01131f73efbb7d22263e
4
- data.tar.gz: c84ca6ebd6460ff4e9e5a449198be9bd4a6cfe1e
2
+ SHA256:
3
+ metadata.gz: f724b0db8ed7f0700617ba0d37cb08e0b1b10a4ed04d2a59bd8538451d503ffb
4
+ data.tar.gz: 172ce815ec47fc1ea64350c9ae5a14880c77a11f429e5a3a7e2bda649903b634
5
5
  SHA512:
6
- metadata.gz: 67ef5b0bbef11189d028619f8f9543eefd270e0a4d09ce9b5c7447ccd1987438e034bf0b0394cef815dbc3ae919cfa7fa54829bbba45351a897c6828df9547cb
7
- data.tar.gz: 4f4fef06fa1d74c670e562aeca1a01eb41786af8130519270d500c72ebd93afb84038ed54e34ead1ea08e6b98828b62f3aeb1a5cffa40170db3a523d651647b5
6
+ metadata.gz: ac670d5d555b89fd01fbc638ba32f25cb884c6d138ff60154661bcd75197a2f583f243a5431ae19d137752a4077d4ff160c0132c293428d9764e55c3aeee2b4b
7
+ data.tar.gz: add54d450ec8f3d5440fb329bc6cd0299b70eb204fdf8b9d2fdec5338a487efe11f917a5630e5a7b02de498078015746deaa13a835823621c70699d44d5b0321
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 (2020-09-25)
4
+
5
+ * Added the `residential_proxy?` method to
6
+ `MaxMind::GeoIP2::Model::AnonymousIP` and
7
+ `MaxMind::GeoIP2::Record::Traits` for use with the Anonymous IP database
8
+ and GeoIP2 Precision Insights.
9
+
10
+ ## 0.4.0 (2020-03-06)
11
+
12
+ * HTTP connections are now persistent. There is a new parameter that
13
+ controls the maximum number of connections the client will use.
14
+
15
+ ## 0.3.0 (2020-03-04)
16
+
17
+ * Modules are now always be defined. Previously we used a shorthand syntax
18
+ which meant including individual classes could leave module constants
19
+ undefined.
20
+
21
+ ## 0.2.0 (2020-02-26)
22
+
23
+ * Added support for the GeoIP2 Precision web services: Country, City, and
24
+ Insights.
25
+
26
+ ## 0.1.0 (2020-02-20)
27
+
28
+ * Added support for the Anonymous IP, ASN, Connection Type, Domain, and ISP
29
+ databases.
30
+ * Added missing dependency on maxmind-db to the gemspec. Reported by Sean
31
+ Dilda. GitHub #4.
32
+
3
33
  ## 0.0.1 (2020-01-09)
4
34
 
5
35
  * Initial implementation with support for location databases.
data/Gemfile CHANGED
@@ -2,8 +2,4 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- gem 'maxmind-db'
6
- gem 'minitest', group: :development
7
- gem 'rake', group: :development
8
- gem 'rubocop', group: :development
9
- gem 'rubocop-performance', group: :development
5
+ gemspec
data/README.md CHANGED
@@ -114,6 +114,127 @@ puts record.location.longitude # -93.2323
114
114
  puts record.traits.network # 128.101.101.101/32
115
115
  ```
116
116
 
117
+ ### Anonymous IP Example
118
+
119
+ ```ruby
120
+ require 'maxmind/geoip2'
121
+
122
+ # This creates the Reader object which should be reused across lookups.
123
+ reader = MaxMind::GeoIP2::Reader.new('/usr/share/GeoIP/GeoIP2-Anonymous-IP.mmdb')
124
+
125
+ record = reader.anonymous_ip('128.101.101.101')
126
+
127
+ puts "Anonymous" if record.is_anonymous
128
+ ```
129
+
130
+ ### ASN Example
131
+
132
+ ```ruby
133
+ require 'maxmind/geoip2'
134
+
135
+ # This creates the Reader object which should be reused across lookups.
136
+ reader = MaxMind::GeoIP2::Reader.new('/usr/share/GeoIP/GeoLite2-ASN.mmdb')
137
+
138
+ record = reader.asn('128.101.101.101')
139
+
140
+ puts record.autonomous_system_number # 1234
141
+ puts record.autonomous_system_organization # Example Ltd
142
+ ```
143
+
144
+ ### Connection Type Example
145
+
146
+ ```ruby
147
+ require 'maxmind/geoip2'
148
+
149
+ # This creates the Reader object which should be reused across lookups.
150
+ reader = MaxMind::GeoIP2::Reader.new('/usr/share/GeoIP/GeoIP2-Connection-Type.mmdb')
151
+
152
+ record = reader.connection_type('128.101.101.101')
153
+
154
+ puts record.connection_type # Cable/DSL
155
+ ```
156
+
157
+ ### Domain Example
158
+
159
+ ```ruby
160
+ require 'maxmind/geoip2'
161
+
162
+ # This creates the Reader object which should be reused across lookups.
163
+ reader = MaxMind::GeoIP2::Reader.new('/usr/share/GeoIP/GeoIP2-Domain.mmdb')
164
+
165
+ record = reader.domain('128.101.101.101')
166
+
167
+ puts record.domain # example.com
168
+ ```
169
+
170
+ ### ISP Example
171
+
172
+ ```ruby
173
+ require 'maxmind/geoip2'
174
+
175
+ # This creates the Reader object which should be reused across lookups.
176
+ reader = MaxMind::GeoIP2::Reader.new('/usr/share/GeoIP/GeoIP2-ISP.mmdb')
177
+
178
+ record = reader.isp('128.101.101.101')
179
+
180
+ puts record.autonomous_system_number # 217
181
+ puts record.autonomous_system_organization # University of Minnesota
182
+ puts record.isp # University of Minnesota
183
+ puts record.organization # University of Minnesota
184
+ ```
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
+
117
238
  ## Values to use for Database or Array Keys
118
239
 
119
240
  **We strongly discourage you from using a value from any `names` property
@@ -1,16 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'maxmind/geoip2/client'
3
4
  require 'maxmind/geoip2/reader'
4
-
5
- # rubocop:disable Style/ClassAndModuleChildren
6
- # :nodoc: all
7
- module MaxMind
8
- module GeoIP2
9
- module Record # :nodoc:
10
- end
11
-
12
- module Model # :nodoc:
13
- end
14
- end
15
- end
16
- # rubocop:enable Style/ClassAndModuleChildren
@@ -0,0 +1,328 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'connection_pool'
4
+ require 'http'
5
+ require 'json'
6
+ require 'maxmind/geoip2/errors'
7
+ require 'maxmind/geoip2/model/city'
8
+ require 'maxmind/geoip2/model/country'
9
+ require 'maxmind/geoip2/model/insights'
10
+
11
+ module MaxMind
12
+ module GeoIP2
13
+ # This class provides a client API for all the
14
+ # {https://dev.maxmind.com/geoip/geoip2/web-services/ GeoIP2 Precision web
15
+ # services}. The services are Country, City, and Insights. Each service
16
+ # returns a different set of data about an IP address, with Country returning
17
+ # the least data and Insights the most.
18
+ #
19
+ # Each web service is represented by a different model class, and these model
20
+ # classes in turn contain multiple record classes. The record classes have
21
+ # attributes which contain data about the IP address.
22
+ #
23
+ # If the web service does not return a particular piece of data for an IP
24
+ # address, the associated attribute is not populated.
25
+ #
26
+ # The web service may not return any information for an entire record, in
27
+ # which case all of the attributes for that record class will be empty.
28
+ #
29
+ # == Usage
30
+ #
31
+ # The basic API for this class is the same for all of the web service end
32
+ # points. First you create a web service client object with your MaxMind
33
+ # account ID and license key, then you call the method corresponding to a
34
+ # specific end point, passing it the IP address you want to look up.
35
+ #
36
+ # If the request succeeds, the method call will return a model class for the
37
+ # service you called. This model in turn contains multiple record classes,
38
+ # each of which represents part of the data returned by the web service.
39
+ #
40
+ # If the request fails, the client class throws an exception.
41
+ #
42
+ # == Example
43
+ #
44
+ # require 'maxmind/geoip2'
45
+ #
46
+ # client = MaxMind::GeoIP2::Client.new(
47
+ # account_id: 42,
48
+ # license_key: 'abcdef123456',
49
+ # )
50
+ #
51
+ # # Replace 'city' with the method corresponding to the web service you
52
+ # # are using, e.g., 'country', 'insights'.
53
+ # record = client.city('128.101.101.101')
54
+ #
55
+ # puts record.country.iso_code
56
+ class Client
57
+ # rubocop:disable Metrics/ParameterLists
58
+
59
+ # Create a Client that may be used to query a GeoIP2 Precision web service.
60
+ #
61
+ # Once created, the Client is safe to use for lookups from multiple
62
+ # threads.
63
+ #
64
+ # @param account_id [Integer] your MaxMind account ID.
65
+ #
66
+ # @param license_key [String] your MaxMind license key.
67
+ #
68
+ # @param locales [Array<String>] a list of locale codes to use in the name
69
+ # property from most preferred to least preferred.
70
+ #
71
+ # @param host [String] the host to use when querying the web service.
72
+ #
73
+ # @param timeout [Integer] the number of seconds to wait for a request
74
+ # before timing out. If 0, no timeout is set.
75
+ #
76
+ # @param proxy_address [String] proxy address to use, if any.
77
+ #
78
+ # @param proxy_port [Integer] proxy port to use, if any.
79
+ #
80
+ # @param proxy_username [String] proxy username to use, if any.
81
+ #
82
+ # @param proxy_password [String] proxy password to use, if any.
83
+ #
84
+ # @param pool_size [Integer] HTTP connection pool size
85
+ def initialize(
86
+ account_id:,
87
+ license_key:,
88
+ locales: ['en'],
89
+ host: 'geoip.maxmind.com',
90
+ timeout: 0,
91
+ proxy_address: '',
92
+ proxy_port: 0,
93
+ proxy_username: '',
94
+ proxy_password: '',
95
+ pool_size: 5
96
+ )
97
+ @account_id = account_id
98
+ @license_key = license_key
99
+ @locales = locales
100
+ @host = host
101
+ @timeout = timeout
102
+ @proxy_address = proxy_address
103
+ @proxy_port = proxy_port
104
+ @proxy_username = proxy_username
105
+ @proxy_password = proxy_password
106
+ @pool_size = pool_size
107
+
108
+ @connection_pool = ConnectionPool.new(size: @pool_size) do
109
+ make_http_client.persistent("https://#{@host}")
110
+ end
111
+ end
112
+ # rubocop:enable Metrics/ParameterLists
113
+
114
+ # This method calls the GeoIP2 Precision City web service.
115
+ #
116
+ # @param ip_address [String] IPv4 or IPv6 address as a string. If no
117
+ # address is provided, the address that the web service is called from is
118
+ # used.
119
+ #
120
+ # @raise [HTTP::Error] if there was an error performing the HTTP request,
121
+ # such as an error connecting.
122
+ #
123
+ # @raise [JSON::ParserError] if there was invalid JSON in the response.
124
+ #
125
+ # @raise [HTTPError] if there was a problem with the HTTP response, such as
126
+ # an unexpected HTTP status code.
127
+ #
128
+ # @raise [AddressInvalidError] if the web service believes the IP address
129
+ # to be invalid or missing.
130
+ #
131
+ # @raise [AddressNotFoundError] if the IP address was not found.
132
+ #
133
+ # @raise [AddressReservedError] if the IP address is reserved.
134
+ #
135
+ # @raise [AuthenticationError] if there was a problem authenticating to the
136
+ # web service, such as an invalid or missing license key.
137
+ #
138
+ # @raise [InsufficientFundsError] if your account is out of credit.
139
+ #
140
+ # @raise [PermissionRequiredError] if your account does not have permission
141
+ # to use the web service.
142
+ #
143
+ # @raise [InvalidRequestError] if the web service responded with an error
144
+ # and there is no more specific error to raise.
145
+ #
146
+ # @return [MaxMind::GeoIP2::Model::City]
147
+ def city(ip_address = 'me')
148
+ response_for('city', MaxMind::GeoIP2::Model::City, ip_address)
149
+ end
150
+
151
+ # This method calls the GeoIP2 Precision Country web service.
152
+ #
153
+ # @param ip_address [String] IPv4 or IPv6 address as a string. If no
154
+ # address is provided, the address that the web service is called from is
155
+ # used.
156
+ #
157
+ # @raise [HTTP::Error] if there was an error performing the HTTP request,
158
+ # such as an error connecting.
159
+ #
160
+ # @raise [JSON::ParserError] if there was invalid JSON in the response.
161
+ #
162
+ # @raise [HTTPError] if there was a problem with the HTTP response, such as
163
+ # an unexpected HTTP status code.
164
+ #
165
+ # @raise [AddressInvalidError] if the web service believes the IP address
166
+ # to be invalid or missing.
167
+ #
168
+ # @raise [AddressNotFoundError] if the IP address was not found.
169
+ #
170
+ # @raise [AddressReservedError] if the IP address is reserved.
171
+ #
172
+ # @raise [AuthenticationError] if there was a problem authenticating to the
173
+ # web service, such as an invalid or missing license key.
174
+ #
175
+ # @raise [InsufficientFundsError] if your account is out of credit.
176
+ #
177
+ # @raise [PermissionRequiredError] if your account does not have permission
178
+ # to use the web service.
179
+ #
180
+ # @raise [InvalidRequestError] if the web service responded with an error
181
+ # and there is no more specific error to raise.
182
+ #
183
+ # @return [MaxMind::GeoIP2::Model::Country]
184
+ def country(ip_address = 'me')
185
+ response_for('country', MaxMind::GeoIP2::Model::Country, ip_address)
186
+ end
187
+
188
+ # This method calls the GeoIP2 Precision Insights web service.
189
+ #
190
+ # @param ip_address [String] IPv4 or IPv6 address as a string. If no
191
+ # address is provided, the address that the web service is called from is
192
+ # used.
193
+ #
194
+ # @raise [HTTP::Error] if there was an error performing the HTTP request,
195
+ # such as an error connecting.
196
+ #
197
+ # @raise [JSON::ParserError] if there was invalid JSON in the response.
198
+ #
199
+ # @raise [HTTPError] if there was a problem with the HTTP response, such as
200
+ # an unexpected HTTP status code.
201
+ #
202
+ # @raise [AddressInvalidError] if the web service believes the IP address
203
+ # to be invalid or missing.
204
+ #
205
+ # @raise [AddressNotFoundError] if the IP address was not found.
206
+ #
207
+ # @raise [AddressReservedError] if the IP address is reserved.
208
+ #
209
+ # @raise [AuthenticationError] if there was a problem authenticating to the
210
+ # web service, such as an invalid or missing license key.
211
+ #
212
+ # @raise [InsufficientFundsError] if your account is out of credit.
213
+ #
214
+ # @raise [PermissionRequiredError] if your account does not have permission
215
+ # to use the web service.
216
+ #
217
+ # @raise [InvalidRequestError] if the web service responded with an error
218
+ # and there is no more specific error to raise.
219
+ #
220
+ # @return [MaxMind::GeoIP2::Model::Insights]
221
+ def insights(ip_address = 'me')
222
+ response_for('insights', MaxMind::GeoIP2::Model::Insights, ip_address)
223
+ end
224
+
225
+ private
226
+
227
+ def response_for(endpoint, model_class, ip_address)
228
+ record = get(endpoint, ip_address)
229
+
230
+ model_class.new(record, @locales)
231
+ end
232
+
233
+ def make_http_client
234
+ headers = HTTP.basic_auth(user: @account_id, pass: @license_key)
235
+ .headers(
236
+ accept: 'application/json',
237
+ user_agent: 'MaxMind-GeoIP2-ruby',
238
+ )
239
+ timeout = @timeout > 0 ? headers.timeout(@timeout) : headers
240
+
241
+ proxy = timeout
242
+ if @proxy_address != ''
243
+ opts = {}
244
+ opts[:proxy_port] = @proxy_port if @proxy_port != 0
245
+ opts[:proxy_username] = @proxy_username if @proxy_username != ''
246
+ opts[:proxy_password] = @proxy_password if @proxy_password != ''
247
+ proxy = timeout.via(@proxy_address, opts)
248
+ end
249
+
250
+ proxy
251
+ end
252
+
253
+ def get(endpoint, ip_address)
254
+ url = "/geoip/v2.1/#{endpoint}/#{ip_address}"
255
+
256
+ response = nil
257
+ body = nil
258
+ @connection_pool.with do |client|
259
+ response = client.get(url)
260
+ body = response.to_s
261
+ end
262
+
263
+ is_json = response.headers[:content_type]&.include?('json')
264
+
265
+ if response.status.client_error?
266
+ return handle_client_error(endpoint, response.code, body, is_json)
267
+ end
268
+
269
+ if response.status.server_error?
270
+ raise HTTPError,
271
+ "Received server error response (#{response.code}) for #{endpoint} with body #{body}"
272
+ end
273
+
274
+ if response.code != 200
275
+ raise HTTPError,
276
+ "Received unexpected response (#{response.code}) for #{endpoint} with body #{body}"
277
+ end
278
+
279
+ handle_success(endpoint, body, is_json)
280
+ end
281
+
282
+ # rubocop:disable Metrics/CyclomaticComplexity
283
+ def handle_client_error(endpoint, status, body, is_json)
284
+ if !is_json
285
+ raise HTTPError,
286
+ "Received client error response (#{status}) for #{endpoint} but it is not JSON: #{body}"
287
+ end
288
+
289
+ error = JSON.parse(body)
290
+
291
+ if !error.key?('code') || !error.key?('error')
292
+ raise HTTPError,
293
+ "Received client error response (#{status}) that is JSON but does not specify code or error keys: #{body}"
294
+ end
295
+
296
+ case error['code']
297
+ when 'IP_ADDRESS_INVALID', 'IP_ADDRESS_REQUIRED'
298
+ raise AddressInvalidError, error['error']
299
+ when 'IP_ADDRESS_NOT_FOUND'
300
+ raise AddressNotFoundError, error['error']
301
+ when 'IP_ADDRESS_RESERVED'
302
+ raise AddressReservedError, error['error']
303
+ when 'ACCOUNT_ID_REQUIRED',
304
+ 'ACCOUNT_ID_UNKNOWN',
305
+ 'AUTHORIZATION_INVALID',
306
+ 'LICENSE_KEY_REQUIRED'
307
+ raise AuthenticationError, error['error']
308
+ when 'INSUFFICIENT_FUNDS'
309
+ raise InsufficientFundsError, error['error']
310
+ when 'PERMISSION_REQUIRED'
311
+ raise PermissionRequiredError, error['error']
312
+ else
313
+ raise InvalidRequestError, error['error']
314
+ end
315
+ end
316
+ # rubocop:enable Metrics/CyclomaticComplexity
317
+
318
+ def handle_success(endpoint, body, is_json)
319
+ if !is_json
320
+ raise HTTPError,
321
+ "Received a success response for #{endpoint} but it is not JSON: #{body}"
322
+ end
323
+
324
+ JSON.parse(body)
325
+ end
326
+ end
327
+ end
328
+ end