vpndetection 5.9.0 → 5.9.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.
- checksums.yaml +4 -4
- data/lib/vpndetection/client.rb +2 -2
- data/lib/vpndetection/transport.rb +99 -9
- data/lib/vpndetection/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b8d521a64e7bfd1680e5e158d30a340602191565c1c6e0332662d546b163caa
|
|
4
|
+
data.tar.gz: 40dce453e2eca0badd693bdf188ab891d9ce43d77dc5dcaccf5b15ba9606cc9a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0a52df204a1d0a4fdc8c7bc88c73b37dd26611efbd58708053d72a02f4b01aa1c83d1c2453b13b2e08217e9386e38ecd4042614284fb5e0302032b3e66789f49
|
|
7
|
+
data.tar.gz: be8c767fc9bb2924505c16e89be1db7848fca3b725bcf045e5a5f21903389c4ca613a0dc605d9a2df02a4aafc3b90dabaa6044f3a1b157d646bca7537f419bb2
|
data/lib/vpndetection/client.rb
CHANGED
|
@@ -345,9 +345,9 @@ module VPNDetection
|
|
|
345
345
|
def batch_answer(ip, body)
|
|
346
346
|
served = body['results'][ip]
|
|
347
347
|
failed = body['errors'][ip]
|
|
348
|
-
if Transport.
|
|
348
|
+
if Transport.conforms?(served, 'LookupResponse')
|
|
349
349
|
Result.new(served)
|
|
350
|
-
elsif
|
|
350
|
+
elsif Transport.conforms?(failed, 'BatchLookupError')
|
|
351
351
|
Error.from_entry(failed['status'], failed['error'])
|
|
352
352
|
else
|
|
353
353
|
Error.new(:server_error, "the batch answer carried no readable entry for #{ip}", status: 200)
|
|
@@ -86,11 +86,18 @@ module VPNDetection
|
|
|
86
86
|
# the spec requires. The generated reader raised a RuntimeError, a
|
|
87
87
|
# JSON::ParserError or an ArgumentError for those, none of them retried, and
|
|
88
88
|
# handed an empty or non-object body back as nil, as if it were the answer.
|
|
89
|
+
#
|
|
90
|
+
# Every member present is checked against the type the spec gives it, at any
|
|
91
|
+
# depth, before the answer is handed back: the generated reader converts with
|
|
92
|
+
# `to_i`, so `"version":"x"` came back as 0 and `"version":"3"` as 3, and it
|
|
93
|
+
# skips a list member sent as null, so `"versions": null` came back as nil
|
|
94
|
+
# (5.9.0, measured 2026-10-20).
|
|
89
95
|
def deserialize(response, return_type)
|
|
90
96
|
data = super
|
|
91
|
-
|
|
97
|
+
raise Error.new(:server_error, "the API answered with no #{return_type}", status: response.code) if data.nil?
|
|
92
98
|
|
|
93
|
-
|
|
99
|
+
Transport.check_type!(JSON.parse(response.body.to_s), return_type.to_s, 'the answer')
|
|
100
|
+
data
|
|
94
101
|
rescue Error
|
|
95
102
|
raise
|
|
96
103
|
rescue StandardError => e
|
|
@@ -182,6 +189,7 @@ module VPNDetection
|
|
|
182
189
|
|
|
183
190
|
body = parse_object(response)
|
|
184
191
|
begin
|
|
192
|
+
check_type!(body, 'Entitlement', 'the answer')
|
|
185
193
|
Entitlement.build_from_hash(body)
|
|
186
194
|
rescue StandardError => e
|
|
187
195
|
raise Error.new(:server_error, "could not read the answer as Entitlement: #{e.message}",
|
|
@@ -194,15 +202,21 @@ module VPNDetection
|
|
|
194
202
|
raise Error.from_status(response.code, response.headers, response.body) unless response.success?
|
|
195
203
|
|
|
196
204
|
body = parse_object(response)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
205
|
+
begin
|
|
206
|
+
check_type!(body, 'LookupResponse', 'the answer')
|
|
207
|
+
rescue TypeError => e
|
|
208
|
+
raise Error.new(:server_error, "could not read the answer as LookupResponse: #{e.message}",
|
|
209
|
+
status: response.code)
|
|
210
|
+
end
|
|
211
|
+
Result.new(body)
|
|
200
212
|
end
|
|
201
213
|
|
|
202
|
-
# Whether a parsed
|
|
203
|
-
#
|
|
204
|
-
def self.
|
|
205
|
-
|
|
214
|
+
# Whether a parsed value is the generated `type`: a batch entry that is not
|
|
215
|
+
# is that address's error alone.
|
|
216
|
+
def self.conforms?(value, type)
|
|
217
|
+
check_type!(value, type, 'the entry')
|
|
218
|
+
rescue TypeError
|
|
219
|
+
false
|
|
206
220
|
end
|
|
207
221
|
|
|
208
222
|
# The two maps of a batch answer, each present even when empty. An answer
|
|
@@ -269,5 +283,81 @@ module VPNDetection
|
|
|
269
283
|
end
|
|
270
284
|
|
|
271
285
|
private_class_method :transport_failure?, :parse_object, :oauth_refusal
|
|
286
|
+
|
|
287
|
+
# The members the spec requires of each answer a lookup, a batch, the
|
|
288
|
+
# entitlement or a database call reads. One that is not nullable must be
|
|
289
|
+
# there and not null. Every plan's lookup carries `ip` and `is_vpn`, and a
|
|
290
|
+
# detail's members are as optional as the plan makes them.
|
|
291
|
+
# test/client_test.rb holds this to spec/openapi.yaml.
|
|
292
|
+
REQUIRED = {
|
|
293
|
+
'LookupResponse' => %i[ip is_vpn],
|
|
294
|
+
'VpnDetail' => [],
|
|
295
|
+
'ClassDetail' => [],
|
|
296
|
+
'ProxyDetail' => [],
|
|
297
|
+
'PubproxyDetail' => [],
|
|
298
|
+
'BatchLookupError' => %i[status error],
|
|
299
|
+
'Entitlement' => %i[org_id apikey plan usage],
|
|
300
|
+
'EntitlementApikey' => %i[id expires allowed_cidrs],
|
|
301
|
+
'EntitlementPlan' => %i[key tier],
|
|
302
|
+
'EntitlementUsage' => %i[requests quota hard_limit window_start window_end],
|
|
303
|
+
'DatabaseList' => %i[databases],
|
|
304
|
+
'Database' => %i[base name summary license_type starts expires renews_at notice_due_at in_term standing
|
|
305
|
+
versions],
|
|
306
|
+
'DatabaseVersion' => %i[id version formats],
|
|
307
|
+
'DatabaseFormatSize' => %i[format bytes],
|
|
308
|
+
'DatabaseMetadata' => %i[id updated entries schema],
|
|
309
|
+
'DatabaseMetadataColumn' => %i[name type],
|
|
310
|
+
'DatabaseChecksumsResponse' => %i[id format checksums],
|
|
311
|
+
'DbChecksums' => %i[md5 sha1 sha256 sha512],
|
|
312
|
+
'DownloadList' => %i[downloads],
|
|
313
|
+
'Download' => %i[dataset_id format outcome sample bytes http_status apikey_id client_ip user_agent created],
|
|
314
|
+
}.freeze
|
|
315
|
+
|
|
316
|
+
# True when the parsed `value` has the generated `type`, a model's members
|
|
317
|
+
# recursively; raises TypeError naming where it does not. No list entry or
|
|
318
|
+
# map value the spec describes may be null: through 5.9.0 one came back as
|
|
319
|
+
# nil, or as 0 in `size`, and a lookup's `"is_hosting": "yes"` came back as
|
|
320
|
+
# the answer.
|
|
321
|
+
def self.check_type!(value, type, where)
|
|
322
|
+
raise TypeError, "#{where} is null" if value.nil?
|
|
323
|
+
|
|
324
|
+
typed =
|
|
325
|
+
case type
|
|
326
|
+
when 'String', 'Date', 'Time' then value.is_a?(String)
|
|
327
|
+
when 'Integer' then value.is_a?(Integer)
|
|
328
|
+
when 'Float' then value.is_a?(Numeric)
|
|
329
|
+
when 'Boolean' then [true, false].include?(value)
|
|
330
|
+
when 'Object' then true
|
|
331
|
+
when /\AArray<(?<inner>.+)>\z/
|
|
332
|
+
inner = Regexp.last_match(:inner)
|
|
333
|
+
value.is_a?(Array) && value.each_with_index.all? { |v, i| check_type!(v, inner, "#{where}[#{i}]") }
|
|
334
|
+
when /\AHash<String, ?(?<inner>.+)>\z/
|
|
335
|
+
inner = Regexp.last_match(:inner)
|
|
336
|
+
value.is_a?(Hash) && value.all? { |k, v| check_type!(v, inner, "#{where}.#{k}") }
|
|
337
|
+
else
|
|
338
|
+
model = VPNDetection.const_get(type)
|
|
339
|
+
if model.respond_to?(:openapi_types)
|
|
340
|
+
value.is_a?(Hash) && model.openapi_types.all? { |attr, member| check_member!(model, attr, member, value, where) }
|
|
341
|
+
else
|
|
342
|
+
value.is_a?(String) # an enum
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
return true if typed
|
|
346
|
+
|
|
347
|
+
raise TypeError, "#{where} is not a #{type}"
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# A member absent or null reads as absent, unless the spec requires it and
|
|
351
|
+
# does not make it nullable.
|
|
352
|
+
def self.check_member!(model, attr, member, value, where)
|
|
353
|
+
key = model.attribute_map[attr].to_s
|
|
354
|
+
return check_type!(value[key], member.to_s, "#{where}.#{key}") unless value[key].nil?
|
|
355
|
+
return true unless REQUIRED.fetch(model.name.split('::').last).include?(attr)
|
|
356
|
+
return true if model.openapi_nullable.include?(attr)
|
|
357
|
+
|
|
358
|
+
raise TypeError, "#{where}.#{key} is #{value.key?(key) ? 'null' : 'missing'}"
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
private_class_method :check_member!
|
|
272
362
|
end
|
|
273
363
|
end
|
data/lib/vpndetection/version.rb
CHANGED