ipgeolocation_sdk 1.0.0 → 2.0.1

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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/README.md +288 -2003
  4. data/lib/ipgeolocation_sdk/client.rb +227 -0
  5. data/lib/ipgeolocation_sdk/client_config.rb +106 -0
  6. data/lib/ipgeolocation_sdk/enums.rb +110 -0
  7. data/lib/ipgeolocation_sdk/errors.rb +38 -0
  8. data/lib/ipgeolocation_sdk/json_output.rb +30 -0
  9. data/lib/ipgeolocation_sdk/models.rb +186 -0
  10. data/lib/ipgeolocation_sdk/request_models.rb +175 -0
  11. data/lib/ipgeolocation_sdk/serde.rb +434 -0
  12. data/lib/ipgeolocation_sdk/transport.rb +124 -0
  13. data/lib/ipgeolocation_sdk/value_object.rb +60 -0
  14. data/lib/ipgeolocation_sdk/version.rb +1 -13
  15. data/lib/ipgeolocation_sdk.rb +19 -101
  16. metadata +38 -308
  17. data/Gemfile +0 -9
  18. data/Rakefile +0 -10
  19. data/docs/ASNConnection.md +0 -22
  20. data/docs/ASNDetails.md +0 -48
  21. data/docs/ASNLookupApi.md +0 -89
  22. data/docs/ASNResponse.md +0 -20
  23. data/docs/ASNResponseXML.md +0 -20
  24. data/docs/ASNResponseXMLAsn.md +0 -48
  25. data/docs/Abuse.md +0 -40
  26. data/docs/AbuseContactApi.md +0 -85
  27. data/docs/AbuseResponse.md +0 -20
  28. data/docs/AbuseResponseXML.md +0 -20
  29. data/docs/Astronomy.md +0 -68
  30. data/docs/AstronomyApi.md +0 -97
  31. data/docs/AstronomyEvening.md +0 -36
  32. data/docs/AstronomyLocation.md +0 -50
  33. data/docs/AstronomyMorning.md +0 -36
  34. data/docs/AstronomyResponse.md +0 -22
  35. data/docs/AstronomyXMLResponse.md +0 -22
  36. data/docs/BulkIPGeolocation.md +0 -42
  37. data/docs/BulkIPSecurity.md +0 -42
  38. data/docs/CountryMetadata.md +0 -22
  39. data/docs/Currency.md +0 -22
  40. data/docs/ErrorResponse.md +0 -18
  41. data/docs/ErrorXMLResponse.md +0 -18
  42. data/docs/ErrorXMLResponseArray.md +0 -18
  43. data/docs/GeolocationResponse.md +0 -38
  44. data/docs/GeolocationXMLResponse.md +0 -38
  45. data/docs/GeolocationXMLResponseArray.md +0 -38
  46. data/docs/GetBulkIpGeolocation200ResponseInner1.md +0 -49
  47. data/docs/GetBulkIpGeolocationRequest.md +0 -18
  48. data/docs/GetBulkIpSecurityInfo200ResponseInner1.md +0 -49
  49. data/docs/IPLocationApi.md +0 -175
  50. data/docs/Location.md +0 -58
  51. data/docs/LocationMinimal.md +0 -52
  52. data/docs/Network.md +0 -22
  53. data/docs/NetworkAsn.md +0 -38
  54. data/docs/NetworkCompany.md +0 -22
  55. data/docs/NetworkMinimal.md +0 -20
  56. data/docs/NetworkMinimalAsn.md +0 -22
  57. data/docs/NetworkMinimalCompany.md +0 -18
  58. data/docs/ParseBulkUserAgentStringsRequest.md +0 -18
  59. data/docs/ParseUserAgentStringRequest.md +0 -18
  60. data/docs/Security.md +0 -38
  61. data/docs/SecurityAPIResponse.md +0 -34
  62. data/docs/SecurityAPIXMLResponse.md +0 -34
  63. data/docs/SecurityAPIXMLResponseArray.md +0 -34
  64. data/docs/SecurityApi.md +0 -175
  65. data/docs/TimeConversionApi.md +0 -109
  66. data/docs/TimeConversionResponse.md +0 -24
  67. data/docs/TimeConversionXMLResponse.md +0 -24
  68. data/docs/TimeZone.md +0 -36
  69. data/docs/TimeZoneDetailedResponse.md +0 -26
  70. data/docs/TimeZoneDetailedXMLResponse.md +0 -26
  71. data/docs/TimeZoneDstEnd.md +0 -28
  72. data/docs/TimeZoneDstStart.md +0 -28
  73. data/docs/TimezoneAirport.md +0 -40
  74. data/docs/TimezoneApi.md +0 -99
  75. data/docs/TimezoneDetail.md +0 -56
  76. data/docs/TimezoneDetailDstEnd.md +0 -28
  77. data/docs/TimezoneDetailDstStart.md +0 -28
  78. data/docs/TimezoneLocation.md +0 -48
  79. data/docs/TimezoneLocode.md +0 -32
  80. data/docs/UserAgentApi.md +0 -235
  81. data/docs/UserAgentData.md +0 -32
  82. data/docs/UserAgentDataDevice.md +0 -24
  83. data/docs/UserAgentDataEngine.md +0 -24
  84. data/docs/UserAgentDataOperatingSystem.md +0 -26
  85. data/docs/UserAgentXMLData.md +0 -32
  86. data/docs/UserAgentXMLDataArray.md +0 -32
  87. data/git_push.sh +0 -57
  88. data/ipgeolocation_sdk.gemspec +0 -29
  89. data/lib/ipgeolocation_sdk/api/abuse_contact_api.rb +0 -86
  90. data/lib/ipgeolocation_sdk/api/asn_lookup_api.rb +0 -92
  91. data/lib/ipgeolocation_sdk/api/astronomy_api.rb +0 -116
  92. data/lib/ipgeolocation_sdk/api/ip_geolocation_api.rb +0 -186
  93. data/lib/ipgeolocation_sdk/api/ip_security_api.rb +0 -184
  94. data/lib/ipgeolocation_sdk/api/time_conversion_api.rb +0 -122
  95. data/lib/ipgeolocation_sdk/api/timezone_api.rb +0 -113
  96. data/lib/ipgeolocation_sdk/api/user_agent_api.rb +0 -158
  97. data/lib/ipgeolocation_sdk/api_client.rb +0 -393
  98. data/lib/ipgeolocation_sdk/api_error.rb +0 -58
  99. data/lib/ipgeolocation_sdk/configuration.rb +0 -308
  100. data/lib/ipgeolocation_sdk/models/abuse.rb +0 -305
  101. data/lib/ipgeolocation_sdk/models/abuse_response.rb +0 -229
  102. data/lib/ipgeolocation_sdk/models/abuse_response_xml.rb +0 -229
  103. data/lib/ipgeolocation_sdk/models/asn_connection.rb +0 -238
  104. data/lib/ipgeolocation_sdk/models/asn_response.rb +0 -230
  105. data/lib/ipgeolocation_sdk/models/asn_response_asn.rb +0 -368
  106. data/lib/ipgeolocation_sdk/models/asn_response_xml.rb +0 -229
  107. data/lib/ipgeolocation_sdk/models/asn_response_xml_asn.rb +0 -364
  108. data/lib/ipgeolocation_sdk/models/astronomy.rb +0 -445
  109. data/lib/ipgeolocation_sdk/models/astronomy_evening.rb +0 -301
  110. data/lib/ipgeolocation_sdk/models/astronomy_location.rb +0 -364
  111. data/lib/ipgeolocation_sdk/models/astronomy_morning.rb +0 -301
  112. data/lib/ipgeolocation_sdk/models/astronomy_response.rb +0 -238
  113. data/lib/ipgeolocation_sdk/models/astronomy_xml_response.rb +0 -238
  114. data/lib/ipgeolocation_sdk/models/bulk_ip_geolocation.rb +0 -113
  115. data/lib/ipgeolocation_sdk/models/bulk_ip_security.rb +0 -113
  116. data/lib/ipgeolocation_sdk/models/country_metadata.rb +0 -240
  117. data/lib/ipgeolocation_sdk/models/currency.rb +0 -238
  118. data/lib/ipgeolocation_sdk/models/error_response.rb +0 -220
  119. data/lib/ipgeolocation_sdk/models/error_xml_response.rb +0 -220
  120. data/lib/ipgeolocation_sdk/models/error_xml_response_array.rb +0 -220
  121. data/lib/ipgeolocation_sdk/models/geolocation_response.rb +0 -310
  122. data/lib/ipgeolocation_sdk/models/geolocation_xml_response.rb +0 -310
  123. data/lib/ipgeolocation_sdk/models/geolocation_xml_response_array.rb +0 -310
  124. data/lib/ipgeolocation_sdk/models/get_bulk_ip_geolocation200_response_inner1.rb +0 -105
  125. data/lib/ipgeolocation_sdk/models/get_bulk_ip_geolocation_request.rb +0 -241
  126. data/lib/ipgeolocation_sdk/models/get_bulk_ip_security_info200_response_inner1.rb +0 -105
  127. data/lib/ipgeolocation_sdk/models/location.rb +0 -400
  128. data/lib/ipgeolocation_sdk/models/location_minimal.rb +0 -373
  129. data/lib/ipgeolocation_sdk/models/network.rb +0 -238
  130. data/lib/ipgeolocation_sdk/models/network_asn.rb +0 -310
  131. data/lib/ipgeolocation_sdk/models/network_company.rb +0 -238
  132. data/lib/ipgeolocation_sdk/models/network_minimal.rb +0 -229
  133. data/lib/ipgeolocation_sdk/models/network_minimal_asn.rb +0 -238
  134. data/lib/ipgeolocation_sdk/models/network_minimal_company.rb +0 -220
  135. data/lib/ipgeolocation_sdk/models/parse_bulk_user_agent_strings_request.rb +0 -222
  136. data/lib/ipgeolocation_sdk/models/parse_user_agent_string_request.rb +0 -220
  137. data/lib/ipgeolocation_sdk/models/security.rb +0 -310
  138. data/lib/ipgeolocation_sdk/models/security_api_response.rb +0 -292
  139. data/lib/ipgeolocation_sdk/models/security_apixml_response.rb +0 -292
  140. data/lib/ipgeolocation_sdk/models/security_apixml_response_array.rb +0 -292
  141. data/lib/ipgeolocation_sdk/models/time_conversion_response.rb +0 -247
  142. data/lib/ipgeolocation_sdk/models/time_conversion_xml_response.rb +0 -247
  143. data/lib/ipgeolocation_sdk/models/time_zone.rb +0 -301
  144. data/lib/ipgeolocation_sdk/models/time_zone_detailed_response.rb +0 -256
  145. data/lib/ipgeolocation_sdk/models/time_zone_detailed_xml_response.rb +0 -256
  146. data/lib/ipgeolocation_sdk/models/time_zone_dst_end.rb +0 -265
  147. data/lib/ipgeolocation_sdk/models/time_zone_dst_start.rb +0 -265
  148. data/lib/ipgeolocation_sdk/models/timezone_airport.rb +0 -319
  149. data/lib/ipgeolocation_sdk/models/timezone_detail.rb +0 -391
  150. data/lib/ipgeolocation_sdk/models/timezone_detail_dst_end.rb +0 -265
  151. data/lib/ipgeolocation_sdk/models/timezone_detail_dst_start.rb +0 -265
  152. data/lib/ipgeolocation_sdk/models/timezone_location.rb +0 -355
  153. data/lib/ipgeolocation_sdk/models/timezone_locode.rb +0 -283
  154. data/lib/ipgeolocation_sdk/models/user_agent_data.rb +0 -283
  155. data/lib/ipgeolocation_sdk/models/user_agent_data_device.rb +0 -247
  156. data/lib/ipgeolocation_sdk/models/user_agent_data_engine.rb +0 -247
  157. data/lib/ipgeolocation_sdk/models/user_agent_data_operating_system.rb +0 -256
  158. data/lib/ipgeolocation_sdk/models/user_agent_xml_data.rb +0 -283
  159. data/lib/ipgeolocation_sdk/models/user_agent_xml_data_array.rb +0 -283
  160. data/spec/api/abuse_contact_api_spec.rb +0 -48
  161. data/spec/api/asn_lookup_api_spec.rb +0 -50
  162. data/spec/api/astronomy_api_spec.rb +0 -54
  163. data/spec/api/ip_location_api_spec.rb +0 -67
  164. data/spec/api/security_api_spec.rb +0 -67
  165. data/spec/api/time_conversion_api_spec.rb +0 -60
  166. data/spec/api/timezone_api_spec.rb +0 -56
  167. data/spec/api/user_agent_api_spec.rb +0 -74
  168. data/spec/models/abuse_response_spec.rb +0 -42
  169. data/spec/models/abuse_response_xml_spec.rb +0 -42
  170. data/spec/models/abuse_spec.rb +0 -90
  171. data/spec/models/asn_connection_spec.rb +0 -48
  172. data/spec/models/asn_response_asn_spec.rb +0 -126
  173. data/spec/models/asn_response_spec.rb +0 -42
  174. data/spec/models/asn_response_xml_asn_spec.rb +0 -126
  175. data/spec/models/asn_response_xml_spec.rb +0 -42
  176. data/spec/models/astronomy_evening_spec.rb +0 -90
  177. data/spec/models/astronomy_location_spec.rb +0 -132
  178. data/spec/models/astronomy_morning_spec.rb +0 -90
  179. data/spec/models/astronomy_response_spec.rb +0 -48
  180. data/spec/models/astronomy_spec.rb +0 -186
  181. data/spec/models/astronomy_xml_response_spec.rb +0 -48
  182. data/spec/models/country_metadata_spec.rb +0 -48
  183. data/spec/models/currency_spec.rb +0 -48
  184. data/spec/models/error_response_spec.rb +0 -36
  185. data/spec/models/error_xml_response_array_spec.rb +0 -36
  186. data/spec/models/error_xml_response_spec.rb +0 -36
  187. data/spec/models/geolocation_response_spec.rb +0 -96
  188. data/spec/models/geolocation_xml_response_array_spec.rb +0 -96
  189. data/spec/models/geolocation_xml_response_spec.rb +0 -96
  190. data/spec/models/get_bulk_ip_geolocation200_response_inner1_spec.rb +0 -32
  191. data/spec/models/get_bulk_ip_geolocation200_response_inner_spec.rb +0 -32
  192. data/spec/models/get_bulk_ip_geolocation_request_spec.rb +0 -36
  193. data/spec/models/get_bulk_ip_security_info200_response_inner1_spec.rb +0 -32
  194. data/spec/models/get_bulk_ip_security_info200_response_inner_spec.rb +0 -32
  195. data/spec/models/location_minimal_spec.rb +0 -138
  196. data/spec/models/location_spec.rb +0 -156
  197. data/spec/models/network_asn_spec.rb +0 -96
  198. data/spec/models/network_company_spec.rb +0 -48
  199. data/spec/models/network_minimal_asn_spec.rb +0 -48
  200. data/spec/models/network_minimal_company_spec.rb +0 -36
  201. data/spec/models/network_minimal_spec.rb +0 -42
  202. data/spec/models/network_spec.rb +0 -48
  203. data/spec/models/parse_bulk_user_agent_strings_request_spec.rb +0 -36
  204. data/spec/models/parse_user_agent_string_request_spec.rb +0 -36
  205. data/spec/models/security_api_response_spec.rb +0 -84
  206. data/spec/models/security_apixml_response_array_spec.rb +0 -84
  207. data/spec/models/security_apixml_response_spec.rb +0 -84
  208. data/spec/models/security_spec.rb +0 -96
  209. data/spec/models/time_conversion_response_spec.rb +0 -54
  210. data/spec/models/time_conversion_xml_response_spec.rb +0 -54
  211. data/spec/models/time_zone_detailed_response_spec.rb +0 -60
  212. data/spec/models/time_zone_detailed_xml_response_spec.rb +0 -60
  213. data/spec/models/time_zone_dst_end_spec.rb +0 -66
  214. data/spec/models/time_zone_dst_start_spec.rb +0 -66
  215. data/spec/models/time_zone_spec.rb +0 -90
  216. data/spec/models/timezone_airport_spec.rb +0 -102
  217. data/spec/models/timezone_detail_dst_end_spec.rb +0 -66
  218. data/spec/models/timezone_detail_dst_start_spec.rb +0 -66
  219. data/spec/models/timezone_detail_spec.rb +0 -150
  220. data/spec/models/timezone_location_spec.rb +0 -126
  221. data/spec/models/timezone_locode_spec.rb +0 -78
  222. data/spec/models/user_agent_data_device_spec.rb +0 -54
  223. data/spec/models/user_agent_data_engine_spec.rb +0 -54
  224. data/spec/models/user_agent_data_operating_system_spec.rb +0 -60
  225. data/spec/models/user_agent_data_spec.rb +0 -78
  226. data/spec/models/user_agent_xml_data_array_spec.rb +0 -78
  227. data/spec/models/user_agent_xml_data_spec.rb +0 -78
  228. data/spec/spec_helper.rb +0 -111
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module IpgeolocationSdk
6
+ class IpGeolocationClient
7
+ def self.default_user_agent
8
+ "ipgeolocation-ruby-sdk/#{VERSION}"
9
+ end
10
+
11
+ def initialize(config, transport = nil)
12
+ raise TypeError, "config must not be nil" if config.nil?
13
+
14
+ @config =
15
+ case config
16
+ when IpGeolocationClientConfig
17
+ config
18
+ when Hash
19
+ IpGeolocationClientConfig.new(**config)
20
+ else
21
+ raise TypeError, "config must be an IpGeolocationClientConfig or hash"
22
+ end
23
+
24
+ @transport = transport || NetHttpTransport.new
25
+ @owns_transport = transport.nil?
26
+ @closed = false
27
+ end
28
+
29
+ attr_reader :config
30
+
31
+ def inspect
32
+ "#<#{self.class} config=#{@config.inspect} closed=#{@closed}>"
33
+ end
34
+
35
+ def lookup_ip_geolocation(request = nil)
36
+ ensure_open
37
+ typed_request = to_lookup_request(request)
38
+ validate_lookup_request_base
39
+ Serde.validate_json_output(typed_request.output)
40
+
41
+ response, duration_ms = execute_with_metrics(build_lookup_http_request(typed_request))
42
+ raise Serde.to_api_error(response.status_code, response.body) unless success_status?(response.status_code)
43
+
44
+ ApiResponse.new(
45
+ data: Serde.parse_single_lookup(response.body),
46
+ metadata: Serde.to_metadata(response.status_code, duration_ms, response.headers)
47
+ )
48
+ end
49
+
50
+ def lookup_ip_geolocation_raw(request = nil)
51
+ ensure_open
52
+ typed_request = to_lookup_request(request)
53
+ validate_lookup_request_base
54
+
55
+ response, duration_ms = execute_with_metrics(build_lookup_http_request(typed_request))
56
+ raise Serde.to_api_error(response.status_code, response.body) unless success_status?(response.status_code)
57
+
58
+ ApiResponse.new(
59
+ data: response.body,
60
+ metadata: Serde.to_metadata(response.status_code, duration_ms, response.headers)
61
+ )
62
+ end
63
+
64
+ def bulk_lookup_ip_geolocation(request)
65
+ ensure_open
66
+ typed_request = to_bulk_request(request)
67
+ validate_bulk_request_base
68
+ Serde.validate_json_output(typed_request.output)
69
+
70
+ response, duration_ms = execute_with_metrics(build_bulk_http_request(typed_request))
71
+ raise Serde.to_api_error(response.status_code, response.body) unless success_status?(response.status_code)
72
+
73
+ ApiResponse.new(
74
+ data: Serde.parse_bulk_lookup(response.body),
75
+ metadata: Serde.to_metadata(response.status_code, duration_ms, response.headers)
76
+ )
77
+ end
78
+
79
+ def bulk_lookup_ip_geolocation_raw(request)
80
+ ensure_open
81
+ typed_request = to_bulk_request(request)
82
+ validate_bulk_request_base
83
+
84
+ response, duration_ms = execute_with_metrics(build_bulk_http_request(typed_request))
85
+ raise Serde.to_api_error(response.status_code, response.body) unless success_status?(response.status_code)
86
+
87
+ ApiResponse.new(
88
+ data: response.body,
89
+ metadata: Serde.to_metadata(response.status_code, duration_ms, response.headers)
90
+ )
91
+ end
92
+
93
+ def close
94
+ return if @closed
95
+
96
+ @closed = true
97
+ @transport.close if @owns_transport && @transport.respond_to?(:close)
98
+ nil
99
+ end
100
+
101
+ private
102
+
103
+ def ensure_open
104
+ raise ValidationError, "client is closed" if @closed
105
+ end
106
+
107
+ def validate_lookup_request_base
108
+ return unless @config.api_key.nil? && @config.request_origin.nil?
109
+
110
+ raise ValidationError, "single lookup requires api_key or request_origin in client config"
111
+ end
112
+
113
+ def validate_bulk_request_base
114
+ raise ValidationError, "bulk lookup requires api_key in client config" if @config.api_key.nil?
115
+ end
116
+
117
+ def to_lookup_request(request)
118
+ case request
119
+ when nil
120
+ LookupIpGeolocationRequest.new
121
+ when LookupIpGeolocationRequest
122
+ request
123
+ when Hash
124
+ LookupIpGeolocationRequest.new(**request)
125
+ else
126
+ raise TypeError, "request must be a LookupIpGeolocationRequest, hash, or nil"
127
+ end
128
+ end
129
+
130
+ def to_bulk_request(request)
131
+ case request
132
+ when BulkLookupIpGeolocationRequest
133
+ request
134
+ when Hash
135
+ BulkLookupIpGeolocationRequest.new(**request)
136
+ when nil
137
+ raise ValidationError, "request must not be nil"
138
+ else
139
+ raise TypeError, "request must be a BulkLookupIpGeolocationRequest or hash"
140
+ end
141
+ end
142
+
143
+ def build_lookup_http_request(request)
144
+ output = request.output || ResponseFormat::JSON
145
+ query = Serde.build_query(
146
+ "apiKey" => @config.api_key,
147
+ "ip" => request.ip,
148
+ "lang" => request.lang,
149
+ "include" => request.include.empty? ? nil : request.include.join(","),
150
+ "fields" => request.fields.empty? ? nil : request.fields.join(","),
151
+ "excludes" => request.excludes.empty? ? nil : request.excludes.join(","),
152
+ "output" => output
153
+ )
154
+
155
+ headers = Serde.merge_headers(
156
+ request.headers,
157
+ @config.request_origin.nil? ? nil : { "Origin" => [@config.request_origin].freeze },
158
+ {
159
+ "User-Agent" => Serde.resolve_user_agent_header(
160
+ request.user_agent,
161
+ request.headers,
162
+ self.class.default_user_agent
163
+ ),
164
+ "Accept" => [output == ResponseFormat::XML ? "application/xml" : "application/json"].freeze
165
+ }.freeze
166
+ )
167
+
168
+ HttpRequestData.new(
169
+ url: "#{@config.base_url}/v3/ipgeo#{query}",
170
+ method: "GET",
171
+ headers: headers,
172
+ body: nil,
173
+ connect_timeout: @config.connect_timeout,
174
+ read_timeout: @config.read_timeout
175
+ )
176
+ end
177
+
178
+ def build_bulk_http_request(request)
179
+ output = request.output || ResponseFormat::JSON
180
+ query = Serde.build_query(
181
+ "apiKey" => @config.api_key,
182
+ "lang" => request.lang,
183
+ "include" => request.include.empty? ? nil : request.include.join(","),
184
+ "fields" => request.fields.empty? ? nil : request.fields.join(","),
185
+ "excludes" => request.excludes.empty? ? nil : request.excludes.join(","),
186
+ "output" => output
187
+ )
188
+
189
+ payload = JSON.generate({ ips: request.ips })
190
+ headers = Serde.merge_headers(
191
+ request.headers,
192
+ @config.request_origin.nil? ? nil : { "Origin" => [@config.request_origin].freeze },
193
+ {
194
+ "User-Agent" => Serde.resolve_user_agent_header(
195
+ request.user_agent,
196
+ request.headers,
197
+ self.class.default_user_agent
198
+ ),
199
+ "Accept" => [output == ResponseFormat::XML ? "application/xml" : "application/json"].freeze,
200
+ "Content-Type" => ["application/json"].freeze
201
+ }.freeze
202
+ )
203
+
204
+ HttpRequestData.new(
205
+ url: "#{@config.base_url}/v3/ipgeo-bulk#{query}",
206
+ method: "POST",
207
+ headers: headers,
208
+ body: payload,
209
+ connect_timeout: @config.connect_timeout,
210
+ read_timeout: @config.read_timeout
211
+ )
212
+ rescue JSON::GeneratorError => error
213
+ raise SerializationError.new("Failed to serialize bulk lookup request body", cause: error)
214
+ end
215
+
216
+ def execute_with_metrics(request)
217
+ started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
218
+ response = @transport.send_request(request)
219
+ duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).floor
220
+ [response, [duration_ms, 0].max]
221
+ end
222
+
223
+ def success_status?(status_code)
224
+ status_code / 100 == 2
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module IpgeolocationSdk
6
+ class IpGeolocationClientConfig < ValueObject
7
+ DEFAULT_BASE_URL = "https://api.ipgeolocation.io"
8
+ DEFAULT_CONNECT_TIMEOUT = 10.0
9
+ DEFAULT_READ_TIMEOUT = 30.0
10
+
11
+ attributes :api_key, :request_origin, :base_url, :connect_timeout, :read_timeout
12
+
13
+ def initialize(
14
+ api_key: nil,
15
+ request_origin: nil,
16
+ base_url: DEFAULT_BASE_URL,
17
+ connect_timeout: DEFAULT_CONNECT_TIMEOUT,
18
+ read_timeout: DEFAULT_READ_TIMEOUT
19
+ )
20
+ super(
21
+ api_key: normalize_api_key(api_key),
22
+ request_origin: normalize_request_origin(request_origin),
23
+ base_url: normalize_base_url(base_url),
24
+ connect_timeout: normalize_timeout(connect_timeout, "connect_timeout"),
25
+ read_timeout: normalize_timeout(read_timeout, "read_timeout")
26
+ )
27
+ end
28
+
29
+ def inspect
30
+ "#<#{self.class} #{to_h(mode: JsonOutputMode::FULL).inspect}>"
31
+ end
32
+
33
+ private
34
+
35
+ def normalize_api_key(value)
36
+ return nil if value.nil?
37
+ raise TypeError, "api_key must be a string" unless value.is_a?(String)
38
+
39
+ normalized = value.strip
40
+ raise ValidationError, "api_key must not be blank" if normalized.empty?
41
+
42
+ normalized
43
+ end
44
+
45
+ def normalize_request_origin(value)
46
+ return nil if value.nil?
47
+ raise TypeError, "request_origin must be a string" unless value.is_a?(String)
48
+
49
+ normalized = value.strip
50
+ raise ValidationError, "request_origin must not be blank" if normalized.empty?
51
+ raise ValidationError, "request_origin must not contain CR or LF" if normalized.include?("\r") || normalized.include?("\n")
52
+
53
+ uri = parse_uri(normalized, "request_origin must be an absolute http or https origin")
54
+
55
+ unless %w[http https].include?(uri.scheme) && uri.host
56
+ raise ValidationError, "request_origin must be an absolute http or https origin"
57
+ end
58
+ raise ValidationError, "request_origin must not include a path" unless ["", "/"].include?(uri.path.to_s)
59
+ raise ValidationError, "request_origin must not include params, query, or fragment" unless [nil, ""].include?(uri.query) && [nil, ""].include?(uri.fragment)
60
+ raise ValidationError, "request_origin must not include userinfo" unless uri.userinfo.nil?
61
+
62
+ "#{uri.scheme}://#{uri.host}#{port_suffix(uri)}"
63
+ end
64
+
65
+ def normalize_base_url(value)
66
+ raise TypeError, "base_url must be a string" unless value.is_a?(String)
67
+
68
+ normalized = value.strip.sub(%r{/+\z}, "")
69
+ raise ValidationError, "base_url must not be blank" if normalized.empty?
70
+
71
+ uri = parse_uri(normalized, "base_url must be an absolute http or https URL")
72
+
73
+ unless %w[http https].include?(uri.scheme) && uri.host
74
+ raise ValidationError, "base_url must be an absolute http or https URL"
75
+ end
76
+ raise ValidationError, "base_url must not include params, query, or fragment" unless [nil, ""].include?(uri.query) && [nil, ""].include?(uri.fragment)
77
+ raise ValidationError, "base_url must not include userinfo" unless uri.userinfo.nil?
78
+
79
+ normalized
80
+ end
81
+
82
+ def normalize_timeout(value, field)
83
+ raise TypeError, "#{field} must be a number" unless value.is_a?(Numeric) && !value.is_a?(TrueClass) && !value.is_a?(FalseClass)
84
+
85
+ normalized = value.to_f
86
+ raise ValidationError, "#{field} must be finite" unless normalized.finite?
87
+ raise ValidationError, "#{field} must be greater than zero" unless normalized.positive?
88
+
89
+ normalized
90
+ end
91
+
92
+ def parse_uri(value, message)
93
+ URI.parse(value)
94
+ rescue URI::InvalidURIError
95
+ raise ValidationError, message
96
+ end
97
+
98
+ def port_suffix(uri)
99
+ return "" if uri.port.nil?
100
+ return "" if uri.scheme == "http" && uri.port == 80
101
+ return "" if uri.scheme == "https" && uri.port == 443
102
+
103
+ ":#{uri.port}"
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IpgeolocationSdk
4
+ module Language
5
+ EN = "en"
6
+ DE = "de"
7
+ RU = "ru"
8
+ JA = "ja"
9
+ FR = "fr"
10
+ CN = "cn"
11
+ ES = "es"
12
+ CS = "cs"
13
+ IT = "it"
14
+ KO = "ko"
15
+ FA = "fa"
16
+ PT = "pt"
17
+
18
+ VALUES = [
19
+ EN, DE, RU, JA, FR, CN, ES, CS, IT, KO, FA, PT
20
+ ].freeze
21
+
22
+ module_function
23
+
24
+ def from_code(code)
25
+ normalized = normalize_token(code, "code")
26
+ value = VALUES.find { |item| item.casecmp?(normalized) }
27
+ raise ValidationError, "Unsupported language code: #{code}" if value.nil?
28
+
29
+ value
30
+ end
31
+
32
+ def valid?(code)
33
+ VALUES.any? { |item| item.casecmp?(code.to_s.strip) }
34
+ end
35
+
36
+ def normalize(value)
37
+ return nil if value.nil?
38
+
39
+ from_code(value)
40
+ end
41
+
42
+ def normalize_token(value, field)
43
+ raise TypeError, "#{field} must be a string or symbol" unless value.is_a?(String) || value.is_a?(Symbol)
44
+
45
+ normalized = value.to_s.strip
46
+ raise ValidationError, "#{field} must not be blank" if normalized.empty?
47
+
48
+ normalized
49
+ end
50
+ private_class_method :normalize_token
51
+ end
52
+
53
+ module ResponseFormat
54
+ JSON = "json"
55
+ XML = "xml"
56
+
57
+ VALUES = [JSON, XML].freeze
58
+
59
+ module_function
60
+
61
+ def normalize(value)
62
+ return JSON if value.nil?
63
+ raise TypeError, "output must be a string or symbol" unless value.is_a?(String) || value.is_a?(Symbol)
64
+
65
+ normalized = value.to_s.strip.downcase
66
+ return normalized if VALUES.include?(normalized)
67
+
68
+ raise ValidationError, "output must be either 'json' or 'xml'"
69
+ end
70
+ end
71
+
72
+ module JsonOutputMode
73
+ COMPACT = "compact"
74
+ FULL = "full"
75
+
76
+ VALUES = [COMPACT, FULL].freeze
77
+
78
+ module_function
79
+
80
+ def normalize(value)
81
+ return COMPACT if value.nil?
82
+ raise TypeError, "mode must be a string or symbol" unless value.is_a?(String) || value.is_a?(Symbol)
83
+
84
+ normalized = value.to_s.strip.downcase
85
+ return normalized if VALUES.include?(normalized)
86
+
87
+ raise ValidationError, "mode must be either 'compact' or 'full'"
88
+ end
89
+ end
90
+
91
+ module LocationConfidence
92
+ HIGH = "high"
93
+ MEDIUM = "medium"
94
+ LOW = "low"
95
+ UNKNOWN = "unknown"
96
+
97
+ VALUES = [HIGH, MEDIUM, LOW, UNKNOWN].freeze
98
+
99
+ module_function
100
+
101
+ def normalize(value)
102
+ return nil if value.nil?
103
+
104
+ normalized = value.to_s.strip.downcase
105
+ return normalized if VALUES.include?(normalized)
106
+
107
+ UNKNOWN
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IpgeolocationSdk
4
+ class IpGeolocationError < StandardError
5
+ attr_reader :cause
6
+
7
+ def initialize(message, cause: nil)
8
+ super(message)
9
+ @cause = cause
10
+ end
11
+ end
12
+
13
+ class ValidationError < IpGeolocationError; end
14
+ class SerializationError < IpGeolocationError; end
15
+ class TransportError < IpGeolocationError; end
16
+ class RequestTimeoutError < TransportError; end
17
+
18
+ class ApiError < IpGeolocationError
19
+ attr_reader :status_code, :api_message
20
+
21
+ def initialize(message, status_code, api_message = nil)
22
+ super(message)
23
+ @status_code = status_code
24
+ @api_message = api_message
25
+ end
26
+ end
27
+
28
+ class BadRequestError < ApiError; end
29
+ class UnauthorizedError < ApiError; end
30
+ class NotFoundError < ApiError; end
31
+ class MethodNotAllowedError < ApiError; end
32
+ class PayloadTooLargeError < ApiError; end
33
+ class UnsupportedMediaTypeError < ApiError; end
34
+ class LockedError < ApiError; end
35
+ class RateLimitError < ApiError; end
36
+ class ClientClosedRequestError < ApiError; end
37
+ class ServerError < ApiError; end
38
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module IpgeolocationSdk
6
+ module JsonOutput
7
+ module_function
8
+
9
+ def to_json(value, mode = JsonOutputMode::COMPACT)
10
+ write(value, mode, pretty: false)
11
+ end
12
+
13
+ def to_pretty_json(value, mode = JsonOutputMode::COMPACT)
14
+ write(value, mode, pretty: true)
15
+ end
16
+
17
+ def write(value, mode, pretty:)
18
+ normalized_mode = JsonOutputMode.normalize(mode)
19
+ payload = Serde.to_plain_data(value, normalized_mode)
20
+ if pretty
21
+ JSON.pretty_generate(payload)
22
+ else
23
+ JSON.generate(payload)
24
+ end
25
+ rescue StandardError => error
26
+ raise SerializationError.new("Failed to serialize output as JSON", cause: error)
27
+ end
28
+ private_class_method :write
29
+ end
30
+ end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IpgeolocationSdk
4
+ class ApiResponse < ValueObject
5
+ attributes :data, :metadata
6
+ end
7
+
8
+ class ApiResponseMetadata < ValueObject
9
+ attributes :credits_charged, :successful_records, :status_code, :duration_ms, :raw_headers
10
+
11
+ def initialize(
12
+ credits_charged: nil,
13
+ successful_records: nil,
14
+ status_code:,
15
+ duration_ms:,
16
+ raw_headers: {}
17
+ )
18
+ validate_optional_integer(credits_charged, "credits_charged")
19
+ validate_optional_integer(successful_records, "successful_records")
20
+ validate_status_code(status_code)
21
+ validate_duration(duration_ms)
22
+
23
+ normalized_headers = normalize_headers(raw_headers)
24
+
25
+ super(
26
+ credits_charged: credits_charged,
27
+ successful_records: successful_records,
28
+ status_code: status_code,
29
+ duration_ms: duration_ms,
30
+ raw_headers: normalized_headers
31
+ )
32
+ end
33
+
34
+ def header_values(name)
35
+ raise TypeError, "header name must be a string" unless name.is_a?(String)
36
+
37
+ normalized_name = name.strip
38
+ raise ArgumentError, "header name must not be blank" if normalized_name.empty?
39
+
40
+ raw_headers.each do |key, values|
41
+ return values if key.casecmp?(normalized_name)
42
+ end
43
+
44
+ EMPTY_HEADER_VALUES
45
+ end
46
+
47
+ def first_header_value(name)
48
+ header_values(name).first
49
+ end
50
+
51
+ EMPTY_HEADER_VALUES = [].freeze
52
+
53
+ private
54
+
55
+ def validate_optional_integer(value, field)
56
+ return if value.nil?
57
+ raise TypeError, "#{field} must be an integer" unless value.is_a?(Integer)
58
+ raise ArgumentError, "#{field} must be greater than or equal to zero" if value.negative?
59
+ end
60
+
61
+ def validate_status_code(value)
62
+ raise TypeError, "status_code must be an integer" unless value.is_a?(Integer)
63
+ raise ArgumentError, "status_code must be between 100 and 599" unless value.between?(100, 599)
64
+ end
65
+
66
+ def validate_duration(value)
67
+ raise TypeError, "duration_ms must be an integer" unless value.is_a?(Integer)
68
+ raise ArgumentError, "duration_ms must be greater than or equal to zero" if value.negative?
69
+ end
70
+
71
+ def normalize_headers(input)
72
+ raise TypeError, "raw_headers must be a hash" unless input.is_a?(Hash)
73
+
74
+ input.each_with_object({}) do |(key, values), result|
75
+ next if key.nil?
76
+
77
+ name = key.to_s
78
+ next if name.strip.empty?
79
+ raise TypeError, "raw_headers values must be arrays of strings" unless values.is_a?(Array)
80
+ normalized_values = values.map do |value|
81
+ raise TypeError, "raw_headers values must contain only strings" unless value.is_a?(String)
82
+
83
+ value
84
+ end
85
+ result[name] = normalized_values.freeze
86
+ end.freeze
87
+ end
88
+ end
89
+
90
+ class Abuse < ValueObject
91
+ attributes :route, :country, :name, :organization, :kind, :address, :emails, :phone_numbers
92
+ end
93
+
94
+ class Asn < ValueObject
95
+ attributes :as_number, :organization, :country, :type, :domain, :date_allocated, :rir
96
+ end
97
+
98
+ class Company < ValueObject
99
+ attributes :name, :type, :domain
100
+ end
101
+
102
+ class CountryMetadata < ValueObject
103
+ attributes :calling_code, :tld, :languages
104
+ end
105
+
106
+ class Currency < ValueObject
107
+ attributes :code, :name, :symbol
108
+ end
109
+
110
+ class DstTransition < ValueObject
111
+ attributes :utc_time, :duration, :gap, :date_time_after, :date_time_before, :overlap
112
+ end
113
+
114
+ class Location < ValueObject
115
+ attributes :continent_code, :continent_name, :country_code2, :country_code3,
116
+ :country_name, :country_name_official, :country_capital, :state_prov,
117
+ :state_code, :district, :city, :locality, :accuracy_radius, :confidence,
118
+ :dma_code, :zipcode, :latitude, :longitude, :is_eu, :country_flag,
119
+ :geoname_id, :country_emoji
120
+ end
121
+
122
+ class Network < ValueObject
123
+ attributes :connection_type, :route, :is_anycast
124
+ end
125
+
126
+ class Security < ValueObject
127
+ attributes :threat_score, :is_tor, :is_proxy, :proxy_provider_names,
128
+ :proxy_confidence_score, :proxy_last_seen, :is_residential_proxy,
129
+ :is_vpn, :vpn_provider_names, :vpn_confidence_score, :vpn_last_seen,
130
+ :is_relay, :relay_provider_name, :is_anonymous, :is_known_attacker,
131
+ :is_bot, :is_spam, :is_cloud_provider, :cloud_provider_name
132
+ end
133
+
134
+ class TimeZoneInfo < ValueObject
135
+ attributes :name, :offset, :offset_with_dst, :current_time, :current_time_unix,
136
+ :current_tz_abbreviation, :current_tz_full_name, :standard_tz_abbreviation,
137
+ :standard_tz_full_name, :is_dst, :dst_savings, :dst_exists,
138
+ :dst_tz_abbreviation, :dst_tz_full_name, :dst_start, :dst_end
139
+ end
140
+
141
+ class UserAgentDevice < ValueObject
142
+ attributes :name, :type, :brand, :cpu
143
+ end
144
+
145
+ class UserAgentEngine < ValueObject
146
+ attributes :name, :type, :version, :version_major
147
+ end
148
+
149
+ class UserAgentOperatingSystem < ValueObject
150
+ attributes :name, :type, :version, :version_major, :build
151
+ end
152
+
153
+ class UserAgent < ValueObject
154
+ attributes :user_agent_string, :name, :type, :version, :version_major,
155
+ :device, :engine, :operating_system
156
+ end
157
+
158
+ class IpGeolocationResponse < ValueObject
159
+ attributes :ip, :domain, :hostname, :location, :country_metadata, :network,
160
+ :currency, :asn, :company, :security, :abuse, :time_zone, :user_agent
161
+ end
162
+
163
+ class BulkLookupSuccess < ValueObject
164
+ attributes :data
165
+
166
+ def success
167
+ true
168
+ end
169
+
170
+ alias success? success
171
+ end
172
+
173
+ class BulkLookupErrorDetails < ValueObject
174
+ attributes :message
175
+ end
176
+
177
+ class BulkLookupError < ValueObject
178
+ attributes :error
179
+
180
+ def success
181
+ false
182
+ end
183
+
184
+ alias success? success
185
+ end
186
+ end