scanii-ruby 1.1.0 → 1.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0418285adf3f56f3389b379b9eedadd5b281591cd2c7e62ad05092a8e8b22595'
4
- data.tar.gz: a61ce21d24bc78f557e09394868fc0c50b9fb2777b8649dce59b017a7d6a427c
3
+ metadata.gz: 6eafeea25fdb7195d2f1a0f9599fac6f3a815872b403c170573b9d585c87ea76
4
+ data.tar.gz: 72cb2cd424e382e147c5464f9b8bc6edcd16dcd1887897669ffddabf760e515f
5
5
  SHA512:
6
- metadata.gz: 504165ee4f450fa8266675e1eb9d43d54a44e76547a5a8fe870865b4123c1563a4d9bf693df893855ecef9197288beb0e5da3425bf3a4524aa76cc1d02bbec25
7
- data.tar.gz: 897b47ed149ea571105dde92462e61d69f43fd496f20a50200dc64d0bce2e2ab7b52ade2837de7d5d6ac5a0f25f5de93f98f0d0ad5781f474b076d8c76c874e0
6
+ metadata.gz: 3cc93b317de609aa3499ff76343b8a3376c5083211b3bf3fe8b2968f1777211040993e750be8fc1602f39bb2f3cde41cbb4d7cbf3ad7072efd45bdece58a23fd
7
+ data.tar.gz: dc2bfe69e31ccbc2f1ca08dcb1bcc1f8d2c4b38ae800b9c63ed7909689bdaf33454edf4b760125bc7ac6c34b7656a7e7192909b4a795c60c24915e869a73a457
data/CHANGELOG.md CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  All notable changes to `scanii-ruby` are documented here. Versions follow [SemVer](https://semver.org).
4
4
 
5
+ ## [1.3.0] — deprecate AUTO endpoint
6
+
7
+ ### Added
8
+
9
+ - `Scanii::Target` — typed regional endpoint class with constants `Scanii::Target::US1`,
10
+ `EU1`, `EU2`, `AP1`, `AP2`, `CA1`. Pass to `Scanii::Client.new(endpoint:)` instead of a
11
+ bare URL string for ergonomics and IDE autocomplete. The `endpoint:` keyword still accepts
12
+ bare URL strings (e.g. for scanii-cli), so this is purely additive.
13
+
14
+ ### Deprecated
15
+
16
+ - Default `endpoint:` parameter (`https://api.scanii.com`) — latency-based routing does not
17
+ guarantee which region processes your data. Pass an explicit regional endpoint
18
+ (`Scanii::Target::US1`, `Scanii::Target::EU1`, etc.) for data residency compliance.
19
+ Constructing a client without an explicit endpoint now emits a `warn` message.
20
+ Will be removed in a future major version.
21
+
22
+ ## [1.2.0] — v2.2 surface
23
+
24
+ ### New API
25
+
26
+ - `Scanii::Client#retrieve_trace(id)` → `Scanii::TraceResult` or `nil` — retrieves the
27
+ ordered processing event trace for a scan via `GET /files/{id}/trace`. Returns `nil` on 404
28
+ (no trace for that id). v2.2 preview surface; API shape may shift before marked stable.
29
+ - `Scanii::Client#process_from_url(location, callback: nil, metadata: nil)` →
30
+ `Scanii::ProcessingResult` — submits a URL for synchronous scanning via `POST /files` with
31
+ `location` as a multipart/form-data field. Distinct from `fetch`, which submits to
32
+ `/files/fetch` for asynchronous server-side fetching. `location` must be a String URL.
33
+ v2.2 preview surface.
34
+ - `Scanii::TraceResult` — new result class with `id`, `events`, `request_id`, `host_id`,
35
+ `raw_response`.
36
+ - `Scanii::TraceEvent` — new model with `timestamp` (String) and `message` (String).
37
+
38
+ ### Deprecations
39
+
40
+ - `Scanii::ProcessingResult#error` — deprecated. The server never populates this field on
41
+ successful responses; errors arrive as non-2xx HTTP responses that raise `Scanii::Error`
42
+ subclasses. The field still exists and emits a runtime `warn` on access. Will be removed
43
+ in a future major version.
44
+
5
45
  ## 1.1.0 — Streaming standardization
6
46
 
7
47
  Adds stream-based `process` and `process_async` methods, aligning scanii-ruby with the
data/README.md CHANGED
@@ -35,7 +35,7 @@ Scan a file from disk:
35
35
  ```ruby
36
36
  require "scanii"
37
37
 
38
- client = Scanii::Client.new(key: "your-key", secret: "your-secret")
38
+ client = Scanii::Client.new(key: "your-key", secret: "your-secret", endpoint: Scanii::Target::US1)
39
39
 
40
40
  result = client.process_file("./file.pdf")
41
41
  puts "findings: #{result.findings.inspect}"
@@ -60,8 +60,10 @@ puts "findings: #{result.findings.inspect}"
60
60
  | `process_file(path, metadata:, callback:)` | `POST /files` | `Scanii::ProcessingResult` |
61
61
  | `process_async(io, filename:, content_type:, metadata:, callback:)` | `POST /files/async` | `Scanii::PendingResult` |
62
62
  | `process_async_file(path, metadata:, callback:)` | `POST /files/async` | `Scanii::PendingResult` |
63
+ | `process_from_url(location, callback:, metadata:)` | `POST /files` | `Scanii::ProcessingResult` (v2.2 preview) |
63
64
  | `fetch(url, metadata:, callback:)` | `POST /files/fetch` | `Scanii::PendingResult` |
64
65
  | `retrieve(id)` | `GET /files/{id}` | `Scanii::ProcessingResult` |
66
+ | `retrieve_trace(id)` | `GET /files/{id}/trace` | `Scanii::TraceResult` or `nil` (v2.2 preview) |
65
67
  | `ping` | `GET /ping` | `true` |
66
68
  | `create_auth_token(timeout_seconds)` | `POST /auth/tokens` | `Scanii::AuthToken` |
67
69
  | `retrieve_auth_token(id)` | `GET /auth/tokens/{id}` | `Scanii::AuthToken` |
@@ -75,19 +77,21 @@ Full API reference: <https://scanii.github.io/openapi/v22/>.
75
77
  client = Scanii::Client.new(
76
78
  key: "k",
77
79
  secret: "s",
78
- endpoint: "https://api-eu1.scanii.com"
80
+ endpoint: Scanii::Target::EU1
79
81
  )
80
82
  ```
81
83
 
82
- | Region | Endpoint |
84
+ The `endpoint:` keyword accepts either a `Scanii::Target` constant or a bare URL String (useful for scanii-cli, e.g. `endpoint: "http://localhost:4000"`).
85
+
86
+ | Constant | Endpoint |
83
87
  |---|---|
84
- | Auto (default) | `https://api.scanii.com` |
85
- | US 1 | `https://api-us1.scanii.com` |
86
- | EU 1 | `https://api-eu1.scanii.com` |
87
- | EU 2 | `https://api-eu2.scanii.com` |
88
- | AP 1 | `https://api-ap1.scanii.com` |
89
- | AP 2 | `https://api-ap2.scanii.com` |
90
- | CA 1 | `https://api-ca1.scanii.com` |
88
+ | `Scanii::Target::US1` | `https://api-us1.scanii.com` |
89
+ | `Scanii::Target::EU1` | `https://api-eu1.scanii.com` |
90
+ | `Scanii::Target::EU2` | `https://api-eu2.scanii.com` |
91
+ | `Scanii::Target::AP1` | `https://api-ap1.scanii.com` |
92
+ | `Scanii::Target::AP2` | `https://api-ap2.scanii.com` |
93
+ | `Scanii::Target::CA1` | `https://api-ca1.scanii.com` |
94
+ | ~~Auto (default)~~ | ~~`https://api.scanii.com`~~ — **deprecated**, does not guarantee regional data placement |
91
95
 
92
96
  ## Errors
93
97
 
@@ -118,10 +122,10 @@ Per SDK Principle 3, the SDK does not retry on the caller's behalf — backoff a
118
122
  Mint a short-lived token server-side and authenticate with it from a less-trusted client:
119
123
 
120
124
  ```ruby
121
- server_client = Scanii::Client.new(key: "k", secret: "s")
125
+ server_client = Scanii::Client.new(key: "k", secret: "s", endpoint: Scanii::Target::US1)
122
126
  token = server_client.create_auth_token(300)
123
127
 
124
- token_client = Scanii::Client.new(token: token.id)
128
+ token_client = Scanii::Client.new(token: token.id, endpoint: Scanii::Target::US1)
125
129
  token_client.ping
126
130
  ```
127
131
 
data/lib/scanii/client.rb CHANGED
@@ -31,11 +31,25 @@ module Scanii
31
31
  # @param key [String, nil] API key (mutually exclusive with token)
32
32
  # @param secret [String, nil] API secret (required when key is set)
33
33
  # @param token [String, nil] auth-token id (mutually exclusive with key/secret)
34
- # @param endpoint [String] base URL; defaults to https://api.scanii.com
34
+ # @param endpoint [Scanii::Target, String] base URL or {Scanii::Target} constant;
35
+ # defaults to https://api.scanii.com (deprecated)
36
+ # @deprecated The default endpoint (https://api.scanii.com) uses latency-based routing
37
+ # and does not guarantee which region processes your data. Pass an explicit regional
38
+ # endpoint for data residency compliance: {Scanii::Target::US1}, {Scanii::Target::EU1},
39
+ # {Scanii::Target::EU2}, {Scanii::Target::AP1}, {Scanii::Target::AP2},
40
+ # {Scanii::Target::CA1}. A bare URL String is also accepted (e.g. for scanii-cli).
41
+ # Will be removed in a future major version.
35
42
  # @param timeout [Integer] open + read timeout in seconds; default 60
36
43
  # @param user_agent [String, nil] optional fragment prepended to the SDK's default User-Agent
37
44
  def initialize(key: nil, secret: nil, token: nil, endpoint: DEFAULT_ENDPOINT,
38
45
  timeout: DEFAULT_TIMEOUT, user_agent: nil)
46
+ if endpoint == DEFAULT_ENDPOINT
47
+ warn "[scanii] DEPRECATION: No explicit endpoint set; defaulting to " \
48
+ "#{DEFAULT_ENDPOINT} (AUTO routing). This does not guarantee regional data " \
49
+ "placement. Pass an explicit regional endpoint (e.g. Scanii::Target::US1) " \
50
+ "for data residency compliance. The AUTO default will be removed in a " \
51
+ "future major version."
52
+ end
39
53
  @auth_header = build_auth_header(key, secret, token)
40
54
  @endpoint = endpoint.to_s.sub(%r{/+\z}, "")
41
55
  raise ArgumentError, "endpoint must not be empty" if @endpoint.empty?
@@ -184,6 +198,70 @@ module Scanii
184
198
  ProcessingResult.from_response(resp_body, headers)
185
199
  end
186
200
 
201
+ # Retrieve the processing event trace for a previously submitted scan.
202
+ #
203
+ # Returns nil when no trace exists for the given id (HTTP 404).
204
+ #
205
+ # This is a v2.2 preview surface; the API shape may shift before it is
206
+ # marked stable.
207
+ #
208
+ # @param id [String] processing id returned by process or process_file
209
+ # @see https://scanii.github.io/openapi/v22/ GET /files/{id}/trace
210
+ # @return [Scanii::TraceResult, nil]
211
+ def retrieve_trace(id)
212
+ raise ArgumentError, "id must not be empty" if id.nil? || id.empty?
213
+
214
+ status, resp_body, headers = request("GET", "/files/#{url_encode(id)}/trace")
215
+ return nil if status == 404
216
+
217
+ raise_for_status(status, resp_body, headers) unless status == 200
218
+ TraceResult.from_response(resp_body, headers)
219
+ end
220
+
221
+ # Submit a remote URL for synchronous scanning.
222
+ #
223
+ # Sends the URL as a +location+ field in a multipart/form-data POST to
224
+ # +/files+. The Scanii server fetches and scans the URL synchronously and
225
+ # returns a ProcessingResult. This is distinct from {#fetch}, which submits
226
+ # to +/files/fetch+ for asynchronous server-side fetching.
227
+ #
228
+ # +location+ must be a String URL. This matches the existing {#fetch}
229
+ # String-URL convention and the Java reference (processFromUrl(String)).
230
+ #
231
+ # This is a v2.2 preview surface; the API shape may shift before it is
232
+ # marked stable.
233
+ #
234
+ # @param location [String] URL of the content to scan
235
+ # @param callback [String, nil] URL to POST the result to on completion
236
+ # @param metadata [Hash{String=>String}, nil] arbitrary key/value pairs attached to the result
237
+ # @see https://scanii.github.io/openapi/v22/ POST /files
238
+ # @return [Scanii::ProcessingResult]
239
+ def process_from_url(location, callback: nil, metadata: nil)
240
+ raise ArgumentError, "location must not be empty" if location.nil? || location.to_s.empty?
241
+
242
+ fields = build_text_fields(metadata, callback)
243
+ fields["location"] = location.to_s
244
+
245
+ boundary = Multipart.make_boundary
246
+ body = String.new(encoding: Encoding::BINARY)
247
+ fields.each do |name, value|
248
+ body << "--#{boundary}\r\n".b
249
+ body << "Content-Disposition: form-data; name=\"#{name}\"\r\n".b
250
+ body << "Content-Type: text/plain; charset=UTF-8\r\n\r\n".b
251
+ body << value.to_s.b
252
+ body << "\r\n".b
253
+ end
254
+ body << "--#{boundary}--\r\n".b
255
+
256
+ status, resp_body, headers = post(
257
+ "/files",
258
+ body: body,
259
+ content_type: Multipart.make_content_type(boundary)
260
+ )
261
+ raise_for_status(status, resp_body, headers) unless status == 201
262
+ ProcessingResult.from_response(resp_body, headers)
263
+ end
264
+
187
265
  # Verify that the configured credentials reach the API.
188
266
  #
189
267
  # @see https://scanii.github.io/openapi/v22/ GET /ping
@@ -9,7 +9,7 @@ module Scanii
9
9
  # @see https://scanii.github.io/openapi/v22/
10
10
  class ProcessingResult
11
11
  attr_reader :id, :findings, :checksum, :content_length, :content_type,
12
- :metadata, :creation_date, :error,
12
+ :metadata, :creation_date,
13
13
  :request_id, :host_id, :resource_location, :raw_response
14
14
 
15
15
  def initialize(id:, findings:, checksum:, content_length:, content_type:,
@@ -22,13 +22,23 @@ module Scanii
22
22
  @content_type = content_type
23
23
  @metadata = metadata
24
24
  @creation_date = creation_date
25
- @error = error
25
+ @_error = error
26
26
  @request_id = request_id
27
27
  @host_id = host_id
28
28
  @resource_location = resource_location
29
29
  @raw_response = raw_response
30
30
  end
31
31
 
32
+ # @deprecated The server never populates this field on successful responses;
33
+ # errors arrive as non-2xx HTTP responses that raise Scanii::Error
34
+ # subclasses. Will be removed in a future major version.
35
+ def error
36
+ warn "[DEPRECATION] `Scanii::ProcessingResult#error` is deprecated; " \
37
+ "rescue Scanii::Error (and its subclasses) to handle server-side errors. " \
38
+ "Will be removed in a future major version."
39
+ @_error
40
+ end
41
+
32
42
  def self.from_response(body, headers)
33
43
  json = body.nil? || body.empty? ? {} : JSON.parse(body)
34
44
 
@@ -0,0 +1,50 @@
1
+ module Scanii
2
+ # Scanii regional API endpoints.
3
+ #
4
+ # Pass one of the predefined regional constants (e.g. {US1}) to
5
+ # {Scanii::Client#initialize} via the +endpoint:+ keyword. The constructor
6
+ # also accepts an arbitrary URL String for testing against scanii-cli or
7
+ # other local mocks:
8
+ #
9
+ # Scanii::Client.new(key: "k", secret: "s", endpoint: Scanii::Target::US1)
10
+ # Scanii::Client.new(key: "k", secret: "s", endpoint: Scanii::Target.new("http://localhost:4000"))
11
+ #
12
+ # +Scanii::Target::AUTO+ (latency-based routing) is intentionally not provided —
13
+ # customer data residency / chain-of-custody compliance requires an explicit
14
+ # regional choice.
15
+ #
16
+ # @see https://scanii.github.io/openapi/v22/
17
+ class Target
18
+ attr_reader :url
19
+
20
+ # @param url [String] base URL for the target endpoint
21
+ def initialize(url)
22
+ raise ArgumentError, "Target URL must be a non-empty String" if url.nil? || url.to_s.empty?
23
+
24
+ @url = url.to_s
25
+ end
26
+
27
+ # Coerce to String (the base URL). Lets +Scanii::Target+ instances be used
28
+ # interchangeably with String URLs in +endpoint:+.
29
+ def to_s
30
+ @url
31
+ end
32
+
33
+ def ==(other)
34
+ other.is_a?(Target) && other.url == @url
35
+ end
36
+
37
+ alias eql? ==
38
+
39
+ def hash
40
+ @url.hash
41
+ end
42
+
43
+ US1 = new("https://api-us1.scanii.com").freeze
44
+ EU1 = new("https://api-eu1.scanii.com").freeze
45
+ EU2 = new("https://api-eu2.scanii.com").freeze
46
+ AP1 = new("https://api-ap1.scanii.com").freeze
47
+ AP2 = new("https://api-ap2.scanii.com").freeze
48
+ CA1 = new("https://api-ca1.scanii.com").freeze
49
+ end
50
+ end
@@ -0,0 +1,20 @@
1
+ module Scanii
2
+ # A single processing event in a {Scanii::TraceResult}.
3
+ #
4
+ # @see https://scanii.github.io/openapi/v22/
5
+ class TraceEvent
6
+ attr_reader :timestamp, :message
7
+
8
+ def initialize(timestamp:, message:)
9
+ @timestamp = timestamp
10
+ @message = message
11
+ end
12
+
13
+ def self.from_hash(hash)
14
+ new(
15
+ timestamp: hash["timestamp"]&.to_s,
16
+ message: hash["message"]&.to_s
17
+ )
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ require "json"
2
+
3
+ module Scanii
4
+ # Result of Client#retrieve_trace — ordered processing events for a scan.
5
+ #
6
+ # This is a v2.2 preview surface; the API shape may shift before it is
7
+ # marked stable.
8
+ #
9
+ # @see https://scanii.github.io/openapi/v22/
10
+ class TraceResult
11
+ attr_reader :id, :events, :request_id, :host_id, :raw_response
12
+
13
+ def initialize(id:, events:, request_id:, host_id:, raw_response:)
14
+ @id = id
15
+ @events = events
16
+ @request_id = request_id
17
+ @host_id = host_id
18
+ @raw_response = raw_response
19
+ end
20
+
21
+ def self.from_response(body, headers)
22
+ json = body.nil? || body.empty? ? {} : JSON.parse(body)
23
+
24
+ new(
25
+ id: (json["id"] || "").to_s,
26
+ events: Array(json["events"]).map { |e| TraceEvent.from_hash(e) },
27
+ request_id: headers["x-scanii-request-id"],
28
+ host_id: headers["x-scanii-host-id"],
29
+ raw_response: body
30
+ )
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,3 @@
1
1
  module Scanii
2
- VERSION = "1.1.0".freeze
2
+ VERSION = "1.3.0".freeze
3
3
  end
data/lib/scanii.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  require_relative "scanii/version"
2
2
  require_relative "scanii/error"
3
+ require_relative "scanii/target"
3
4
  require_relative "scanii/processing_result"
4
5
  require_relative "scanii/pending_result"
5
6
  require_relative "scanii/auth_token"
7
+ require_relative "scanii/trace_event"
8
+ require_relative "scanii/trace_result"
6
9
  require_relative "scanii/multipart"
7
10
  require_relative "scanii/client"
8
11
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scanii-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scanii
@@ -80,6 +80,9 @@ files:
80
80
  - lib/scanii/multipart.rb
81
81
  - lib/scanii/pending_result.rb
82
82
  - lib/scanii/processing_result.rb
83
+ - lib/scanii/target.rb
84
+ - lib/scanii/trace_event.rb
85
+ - lib/scanii/trace_result.rb
83
86
  - lib/scanii/version.rb
84
87
  homepage: https://github.com/scanii/scanii-ruby
85
88
  licenses: