maxmind-geoip2 0.0.1 → 0.5.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 (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