ipgeolocation_sdk 1.0.0 → 2.0.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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +288 -2003
  4. data/lib/ipgeolocation_sdk/client.rb +223 -0
  5. data/lib/ipgeolocation_sdk/client_config.rb +102 -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 +433 -0
  12. data/lib/ipgeolocation_sdk/transport.rb +109 -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 +29 -310
  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,223 @@
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 lookup_ip_geolocation(request = nil)
32
+ ensure_open
33
+ typed_request = to_lookup_request(request)
34
+ validate_lookup_request_base
35
+ Serde.validate_json_output(typed_request.output)
36
+
37
+ response, duration_ms = execute_with_metrics(build_lookup_http_request(typed_request))
38
+ raise Serde.to_api_error(response.status_code, response.body) unless success_status?(response.status_code)
39
+
40
+ ApiResponse.new(
41
+ data: Serde.parse_single_lookup(response.body),
42
+ metadata: Serde.to_metadata(response.status_code, duration_ms, response.headers)
43
+ )
44
+ end
45
+
46
+ def lookup_ip_geolocation_raw(request = nil)
47
+ ensure_open
48
+ typed_request = to_lookup_request(request)
49
+ validate_lookup_request_base
50
+
51
+ response, duration_ms = execute_with_metrics(build_lookup_http_request(typed_request))
52
+ raise Serde.to_api_error(response.status_code, response.body) unless success_status?(response.status_code)
53
+
54
+ ApiResponse.new(
55
+ data: response.body,
56
+ metadata: Serde.to_metadata(response.status_code, duration_ms, response.headers)
57
+ )
58
+ end
59
+
60
+ def bulk_lookup_ip_geolocation(request)
61
+ ensure_open
62
+ typed_request = to_bulk_request(request)
63
+ validate_bulk_request_base
64
+ Serde.validate_json_output(typed_request.output)
65
+
66
+ response, duration_ms = execute_with_metrics(build_bulk_http_request(typed_request))
67
+ raise Serde.to_api_error(response.status_code, response.body) unless success_status?(response.status_code)
68
+
69
+ ApiResponse.new(
70
+ data: Serde.parse_bulk_lookup(response.body),
71
+ metadata: Serde.to_metadata(response.status_code, duration_ms, response.headers)
72
+ )
73
+ end
74
+
75
+ def bulk_lookup_ip_geolocation_raw(request)
76
+ ensure_open
77
+ typed_request = to_bulk_request(request)
78
+ validate_bulk_request_base
79
+
80
+ response, duration_ms = execute_with_metrics(build_bulk_http_request(typed_request))
81
+ raise Serde.to_api_error(response.status_code, response.body) unless success_status?(response.status_code)
82
+
83
+ ApiResponse.new(
84
+ data: response.body,
85
+ metadata: Serde.to_metadata(response.status_code, duration_ms, response.headers)
86
+ )
87
+ end
88
+
89
+ def close
90
+ return if @closed
91
+
92
+ @closed = true
93
+ @transport.close if @owns_transport && @transport.respond_to?(:close)
94
+ nil
95
+ end
96
+
97
+ private
98
+
99
+ def ensure_open
100
+ raise ValidationError, "client is closed" if @closed
101
+ end
102
+
103
+ def validate_lookup_request_base
104
+ return unless @config.api_key.nil? && @config.request_origin.nil?
105
+
106
+ raise ValidationError, "single lookup requires api_key or request_origin in client config"
107
+ end
108
+
109
+ def validate_bulk_request_base
110
+ raise ValidationError, "bulk lookup requires api_key in client config" if @config.api_key.nil?
111
+ end
112
+
113
+ def to_lookup_request(request)
114
+ case request
115
+ when nil
116
+ LookupIpGeolocationRequest.new
117
+ when LookupIpGeolocationRequest
118
+ request
119
+ when Hash
120
+ LookupIpGeolocationRequest.new(**request)
121
+ else
122
+ raise TypeError, "request must be a LookupIpGeolocationRequest, hash, or nil"
123
+ end
124
+ end
125
+
126
+ def to_bulk_request(request)
127
+ case request
128
+ when BulkLookupIpGeolocationRequest
129
+ request
130
+ when Hash
131
+ BulkLookupIpGeolocationRequest.new(**request)
132
+ when nil
133
+ raise ValidationError, "request must not be nil"
134
+ else
135
+ raise TypeError, "request must be a BulkLookupIpGeolocationRequest or hash"
136
+ end
137
+ end
138
+
139
+ def build_lookup_http_request(request)
140
+ output = request.output || ResponseFormat::JSON
141
+ query = Serde.build_query(
142
+ "apiKey" => @config.api_key,
143
+ "ip" => request.ip,
144
+ "lang" => request.lang,
145
+ "include" => request.include.empty? ? nil : request.include.join(","),
146
+ "fields" => request.fields.empty? ? nil : request.fields.join(","),
147
+ "excludes" => request.excludes.empty? ? nil : request.excludes.join(","),
148
+ "output" => output
149
+ )
150
+
151
+ headers = Serde.merge_headers(
152
+ request.headers,
153
+ @config.request_origin.nil? ? nil : { "Origin" => [@config.request_origin].freeze },
154
+ {
155
+ "User-Agent" => Serde.resolve_user_agent_header(
156
+ request.user_agent,
157
+ request.headers,
158
+ self.class.default_user_agent
159
+ ),
160
+ "Accept" => [output == ResponseFormat::XML ? "application/xml" : "application/json"].freeze
161
+ }.freeze
162
+ )
163
+
164
+ HttpRequestData.new(
165
+ url: "#{@config.base_url}/v3/ipgeo#{query}",
166
+ method: "GET",
167
+ headers: headers,
168
+ body: nil,
169
+ connect_timeout: @config.connect_timeout,
170
+ read_timeout: @config.read_timeout
171
+ )
172
+ end
173
+
174
+ def build_bulk_http_request(request)
175
+ output = request.output || ResponseFormat::JSON
176
+ query = Serde.build_query(
177
+ "apiKey" => @config.api_key,
178
+ "lang" => request.lang,
179
+ "include" => request.include.empty? ? nil : request.include.join(","),
180
+ "fields" => request.fields.empty? ? nil : request.fields.join(","),
181
+ "excludes" => request.excludes.empty? ? nil : request.excludes.join(","),
182
+ "output" => output
183
+ )
184
+
185
+ payload = JSON.generate({ ips: request.ips })
186
+ headers = Serde.merge_headers(
187
+ request.headers,
188
+ @config.request_origin.nil? ? nil : { "Origin" => [@config.request_origin].freeze },
189
+ {
190
+ "User-Agent" => Serde.resolve_user_agent_header(
191
+ request.user_agent,
192
+ request.headers,
193
+ self.class.default_user_agent
194
+ ),
195
+ "Accept" => [output == ResponseFormat::XML ? "application/xml" : "application/json"].freeze,
196
+ "Content-Type" => ["application/json"].freeze
197
+ }.freeze
198
+ )
199
+
200
+ HttpRequestData.new(
201
+ url: "#{@config.base_url}/v3/ipgeo-bulk#{query}",
202
+ method: "POST",
203
+ headers: headers,
204
+ body: payload,
205
+ connect_timeout: @config.connect_timeout,
206
+ read_timeout: @config.read_timeout
207
+ )
208
+ rescue JSON::GeneratorError => error
209
+ raise SerializationError.new("Failed to serialize bulk lookup request body", cause: error)
210
+ end
211
+
212
+ def execute_with_metrics(request)
213
+ started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
214
+ response = @transport.send_request(request)
215
+ duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).floor
216
+ [response, [duration_ms, 0].max]
217
+ end
218
+
219
+ def success_status?(status_code)
220
+ status_code / 100 == 2
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,102 @@
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
+ private
30
+
31
+ def normalize_api_key(value)
32
+ return nil if value.nil?
33
+ raise TypeError, "api_key must be a string" unless value.is_a?(String)
34
+
35
+ normalized = value.strip
36
+ raise ValidationError, "api_key must not be blank" if normalized.empty?
37
+
38
+ normalized
39
+ end
40
+
41
+ def normalize_request_origin(value)
42
+ return nil if value.nil?
43
+ raise TypeError, "request_origin must be a string" unless value.is_a?(String)
44
+
45
+ normalized = value.strip
46
+ raise ValidationError, "request_origin must not be blank" if normalized.empty?
47
+ raise ValidationError, "request_origin must not contain CR or LF" if normalized.include?("\r") || normalized.include?("\n")
48
+
49
+ uri = parse_uri(normalized, "request_origin must be an absolute http or https origin")
50
+
51
+ unless %w[http https].include?(uri.scheme) && uri.host
52
+ raise ValidationError, "request_origin must be an absolute http or https origin"
53
+ end
54
+ raise ValidationError, "request_origin must not include a path" unless ["", "/"].include?(uri.path.to_s)
55
+ raise ValidationError, "request_origin must not include params, query, or fragment" unless [nil, ""].include?(uri.query) && [nil, ""].include?(uri.fragment)
56
+ raise ValidationError, "request_origin must not include userinfo" unless uri.userinfo.nil?
57
+
58
+ "#{uri.scheme}://#{uri.host}#{port_suffix(uri)}"
59
+ end
60
+
61
+ def normalize_base_url(value)
62
+ raise TypeError, "base_url must be a string" unless value.is_a?(String)
63
+
64
+ normalized = value.strip.sub(%r{/+\z}, "")
65
+ raise ValidationError, "base_url must not be blank" if normalized.empty?
66
+
67
+ uri = parse_uri(normalized, "base_url must be an absolute http or https URL")
68
+
69
+ unless %w[http https].include?(uri.scheme) && uri.host
70
+ raise ValidationError, "base_url must be an absolute http or https URL"
71
+ end
72
+ raise ValidationError, "base_url must not include params, query, or fragment" unless [nil, ""].include?(uri.query) && [nil, ""].include?(uri.fragment)
73
+ raise ValidationError, "base_url must not include userinfo" unless uri.userinfo.nil?
74
+
75
+ normalized
76
+ end
77
+
78
+ def normalize_timeout(value, field)
79
+ raise TypeError, "#{field} must be a number" unless value.is_a?(Numeric) && !value.is_a?(TrueClass) && !value.is_a?(FalseClass)
80
+
81
+ normalized = value.to_f
82
+ raise ValidationError, "#{field} must be finite" unless normalized.finite?
83
+ raise ValidationError, "#{field} must be greater than zero" unless normalized.positive?
84
+
85
+ normalized
86
+ end
87
+
88
+ def parse_uri(value, message)
89
+ URI.parse(value)
90
+ rescue URI::InvalidURIError
91
+ raise ValidationError, message
92
+ end
93
+
94
+ def port_suffix(uri)
95
+ return "" if uri.port.nil?
96
+ return "" if uri.scheme == "http" && uri.port == 80
97
+ return "" if uri.scheme == "https" && uri.port == 443
98
+
99
+ ":#{uri.port}"
100
+ end
101
+ end
102
+ 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