Skip to main content

iii_sdk/
lib.rs

1pub mod builtin_triggers;
2pub mod channels;
3pub mod engine;
4pub mod error;
5pub mod helpers;
6pub mod iii;
7pub mod protocol;
8pub mod stream_provider;
9pub mod structs;
10pub mod triggers;
11pub mod types;
12
13/// Public runtime/worker types. (Stage 1 submodule grouping.)
14pub mod runtime {
15    pub use crate::iii::{
16        FunctionInfo, FunctionRef, IIIConnectionState, TriggerInfo, TriggerTypeRef,
17        WorkerIdentityMode, WorkerInfo, WorkerMetadata,
18    };
19}
20
21/// Public trigger types. (Stage 1 submodule grouping.)
22pub mod trigger {
23    pub use crate::builtin_triggers::IIITrigger;
24    pub use crate::triggers::{Trigger, TriggerConfig, TriggerHandler};
25}
26
27/// Public channel types. (Stage 1 submodule grouping.)
28pub mod channel {
29    pub use crate::channels::{ChannelReader, ChannelWriter, StreamChannelRef};
30    pub use crate::types::Channel;
31}
32
33/// Public error types. (Stage 1 submodule grouping.)
34pub mod errors {
35    pub use crate::error::{Error, InvocationError};
36}
37
38// No `internal` submodule for Rust: the internal types grouped under
39// `iii-sdk/internal` (Node) and `iii.internal` (Python) have no crate-root
40// equivalent here. There is no `InternalHttpRequest` (the Rust SDK uses
41// `iii_helpers::http::HttpRequest`), and the stream result types
42// (`StreamSetResult`, `StreamUpdateResult`, `StreamDeleteResult`) live in `iii_helpers::stream`
43// and are consumed inside `stream_provider.rs`, they are not re-exported at
44// the crate root. Grouping them here would re-surface clean-break helpers
45// types into the SDK, which the `compile_fail` doctests below deliberately
46// forbid. Hence the `internal` grouping is a no-op for Rust.
47
48pub use error::{Error, InvocationError};
49pub use iii::TelemetryOptions;
50pub use iii::{IIIClient, RegisterFunction, RegisterTriggerType, WorkerIdentityMode};
51pub use iii_helpers::queue::EnqueueResult;
52pub use protocol::{Message, TriggerAction};
53pub use stream_provider::IStream;
54pub use structs::MiddlewareFunctionInput;
55pub use types::{StreamRequest, StreamResponse};
56
57/// Configuration options passed to [`register_worker`].
58///
59/// # Examples
60/// ```rust,no_run
61/// use iii_sdk::{register_worker, InitOptions};
62///
63/// let worker = register_worker("ws://localhost:49134", InitOptions::default());
64/// ```
65#[derive(Debug, Clone, Default)]
66pub struct InitOptions {
67    /// Custom worker metadata. Auto-detected if `None`. In managed identity
68    /// mode, process-wide identity variables override matching fields.
69    pub metadata: Option<iii::WorkerMetadata>,
70    /// Custom HTTP headers sent during the WebSocket handshake.
71    pub headers: Option<std::collections::HashMap<String, String>>,
72    /// OpenTelemetry configuration.
73    pub otel: Option<iii_helpers::observability::OtelConfig>,
74    /// Namespace this worker belongs to. In managed mode, resolution order is
75    /// `namespace` > env `III_NAMESPACE` > `None`. In explicit mode, the
76    /// environment is not used.
77    ///
78    /// It scopes more than the registration. The worker and its functions
79    /// register here, and everything the worker does afterwards follows it: a
80    /// [`IIIClient::trigger`] resolves its target here and a
81    /// [`IIIClient::register_trigger`] binds here, unless the call names
82    /// another namespace.
83    pub namespace: Option<String>,
84    /// Selects whether process-wide managed identity variables can override
85    /// this connection's metadata. Auxiliary connections should use
86    /// [`WorkerIdentityMode::Explicit`].
87    pub identity: WorkerIdentityMode,
88}
89
90/// Register the worker with a iii instance, returns a connected worker client.
91/// The WebSocket connection is established automatically in a dedicated
92/// background thread with its own tokio runtime.
93///
94/// Call [`IIIClient::shutdown`] before the end of `main` to cleanly stop the
95/// connection and join the background thread. In Rust the process exits
96/// when `main` returns, terminating all threads, so `shutdown()` must be
97/// called while `main` is still running.
98///
99/// # Arguments
100/// * `address` - WebSocket URL of the III engine (e.g. `ws://localhost:49134`).
101/// * `options` - Configuration for worker metadata and OTel.
102///
103/// # Examples
104/// ```rust,no_run
105/// use iii_sdk::{register_worker, InitOptions};
106///
107/// let worker = register_worker("ws://localhost:49134", InitOptions::default());
108/// // register functions, handle events, etc.
109/// worker.shutdown(); // cleanly stops the connection thread
110/// ```
111/// Engine address used when neither an explicit address nor `III_URL` is set.
112///
113/// The IPv4 loopback is spelled out on purpose: `localhost` can resolve to
114/// `::1` on a host whose engine only listens on IPv4.
115pub const DEFAULT_ENGINE_URL: &str = "ws://127.0.0.1:49134";
116
117/// Resolves the engine address from the environment: `III_URL`, else
118/// [`DEFAULT_ENGINE_URL`].
119pub fn engine_url_from_env() -> String {
120    std::env::var("III_URL")
121        .ok()
122        .filter(|url| !url.trim().is_empty())
123        .unwrap_or_else(|| DEFAULT_ENGINE_URL.to_string())
124}
125
126/// Register the worker using the engine address from the environment.
127///
128/// The supervisor that spawned this process — `iii compose`, a container
129/// runtime, systemd — sets `III_URL`, the same way it sets `III_NAMESPACE` and
130/// `III_WORKER_NAME`. Rust has no default arguments, so this is the zero-address
131/// form; [`register_worker`] with an explicit address is unchanged.
132///
133/// # Examples
134/// ```rust,no_run
135/// use iii_sdk::{register_worker_from_env, InitOptions};
136///
137/// // III_URL when set, ws://127.0.0.1:49134 otherwise.
138/// let worker = register_worker_from_env(InitOptions::default());
139/// worker.shutdown();
140/// ```
141pub fn register_worker_from_env(options: InitOptions) -> IIIClient {
142    register_worker(&engine_url_from_env(), options)
143}
144
145pub fn register_worker(address: &str, options: InitOptions) -> IIIClient {
146    let InitOptions {
147        metadata,
148        headers,
149        otel,
150        namespace,
151        identity,
152    } = options;
153
154    let iii = IIIClient::with_identity(address, metadata.unwrap_or_default(), identity);
155
156    match identity {
157        WorkerIdentityMode::Managed => {
158            // options.namespace > III_NAMESPACE > None (engine applies its default).
159            if let Some(ns) = iii::resolve_namespace(namespace) {
160                iii.set_namespace(ns);
161            }
162        }
163        WorkerIdentityMode::Explicit => {
164            if let Some(ns) = namespace {
165                iii.set_namespace(ns);
166            }
167        }
168    }
169
170    if let Some(h) = headers {
171        iii.set_headers(h);
172    }
173
174    if let Some(cfg) = otel {
175        iii.set_otel_config(cfg);
176    }
177
178    iii.connect();
179
180    iii
181}
182
183// ---------------------------------------------------------------------------
184// Compile-fail doctests: these enforce that the four channel items relocated
185// to `helpers` are NOT reachable at the crate root. They live here (not in
186// `tests/`) because `cargo test --doc` only picks up doctests inside `src/`.
187// ---------------------------------------------------------------------------
188
189/// ```compile_fail
190/// use iii_sdk::ChannelDirection;
191/// ```
192#[allow(dead_code)]
193fn _ensure_channel_direction_not_top_level() {}
194
195/// ```compile_fail
196/// use iii_sdk::ChannelItem;
197/// ```
198#[allow(dead_code)]
199fn _ensure_channel_item_not_top_level() {}
200
201/// ```compile_fail
202/// use iii_sdk::extract_channel_refs;
203/// ```
204#[allow(dead_code)]
205fn _ensure_extract_channel_refs_not_top_level() {}
206
207/// ```compile_fail
208/// use iii_sdk::is_channel_ref;
209/// ```
210#[allow(dead_code)]
211fn _ensure_is_channel_ref_not_top_level() {}
212
213// ---------------------------------------------------------------------------
214// Compile-fail doctest: enforces that `create_channel` (relocated to
215// `helpers`) is no longer callable on `IIIClient`.
216// ---------------------------------------------------------------------------
217
218/// ```compile_fail
219/// let iii = iii_sdk::IIIClient::new("ws://x");
220/// iii.create_channel(None);
221/// ```
222#[allow(dead_code)]
223fn _ensure_create_channel_not_on_instance() {}
224
225// ---------------------------------------------------------------------------
226// Stage 1 runtime submodule: runtime/worker types are reachable at their new
227// canonical path `iii_sdk::runtime`.
228// ---------------------------------------------------------------------------
229
230/// ```rust,no_run
231/// use iii_sdk::runtime::{
232///     FunctionInfo, FunctionRef, IIIConnectionState, TriggerInfo, TriggerTypeRef, WorkerInfo,
233///     WorkerMetadata,
234/// };
235/// ```
236#[allow(dead_code)]
237fn _ensure_runtime_submodule_path() {}
238
239/// ```compile_fail
240/// use iii_sdk::IIIConnectionState;
241/// ```
242#[allow(dead_code)]
243fn _ensure_connection_state_not_top_level() {}
244
245// ---------------------------------------------------------------------------
246// Stage 1 trigger submodule: trigger types are reachable at their new
247// canonical path `iii_sdk::trigger`.
248// ---------------------------------------------------------------------------
249
250/// ```rust,no_run
251/// use iii_sdk::trigger::{IIITrigger, Trigger, TriggerConfig, TriggerHandler};
252/// ```
253#[allow(dead_code)]
254fn _ensure_trigger_submodule_path() {}
255
256// ---------------------------------------------------------------------------
257// Stage 1 channel submodule: channel types are reachable at their new
258// canonical path `iii_sdk::channel`.
259// ---------------------------------------------------------------------------
260
261/// ```rust,no_run
262/// use iii_sdk::channel::{Channel, ChannelReader, ChannelWriter, StreamChannelRef};
263/// ```
264#[allow(dead_code)]
265fn _ensure_channel_submodule_path() {}
266
267// ---------------------------------------------------------------------------
268// Stage 1 errors submodule: the renamed error type is reachable at its new
269// canonical path `iii_sdk::errors::Error`.
270// ---------------------------------------------------------------------------
271
272/// ```rust,no_run
273/// use iii_sdk::errors::Error;
274/// fn _takes(_e: Error) {}
275/// ```
276#[allow(dead_code)]
277fn _ensure_errors_submodule_path() {}
278
279// ---------------------------------------------------------------------------
280// 0.20 clean break: the deprecated crate-root re-exports and renamed aliases
281// are removed. The relocated types live under their canonical submodule paths
282// (`iii_sdk::{trigger,channel,runtime}`) and the renamed types use their new
283// names (`IIIClient`, `Error`, `TelemetryOptions`).
284// ---------------------------------------------------------------------------
285
286/// ```compile_fail
287/// use iii_sdk::{Channel, ChannelReader, ChannelWriter, StreamChannelRef};
288/// ```
289#[allow(dead_code)]
290fn _ensure_channel_types_not_top_level() {}
291
292/// ```compile_fail
293/// use iii_sdk::{IIITrigger, Trigger, TriggerConfig, TriggerHandler};
294/// ```
295#[allow(dead_code)]
296fn _ensure_trigger_types_not_top_level() {}
297
298/// ```compile_fail
299/// use iii_sdk::{FunctionInfo, FunctionRef, TriggerInfo, TriggerTypeRef, WorkerInfo, WorkerMetadata};
300/// ```
301#[allow(dead_code)]
302fn _ensure_runtime_types_not_top_level() {}
303
304/// ```compile_fail
305/// use iii_sdk::III;
306/// ```
307#[allow(dead_code)]
308fn _ensure_renamed_client_alias_removed() {}
309
310/// ```compile_fail
311/// use iii_sdk::IIIError;
312/// ```
313#[allow(dead_code)]
314fn _ensure_renamed_error_alias_removed() {}
315
316/// ```compile_fail
317/// use iii_sdk::WorkerTelemetryMeta;
318/// ```
319#[allow(dead_code)]
320fn _ensure_renamed_telemetry_alias_removed() {}
321
322// ---------------------------------------------------------------------------
323// Stream types relocated to `iii_helpers::stream`: they are no longer reachable
324// at the crate root, and are reachable from the helpers submodule.
325// ---------------------------------------------------------------------------
326
327/// ```compile_fail
328/// use iii_sdk::{StreamChangeEvent, StreamJoinLeaveEvent};
329/// ```
330#[allow(dead_code)]
331fn _ensure_stream_events_not_top_level() {}
332
333/// ```compile_fail
334/// use iii_sdk::{StreamTriggerConfig, StreamJoinLeaveTriggerConfig};
335/// ```
336#[allow(dead_code)]
337fn _ensure_stream_trigger_configs_not_top_level() {}
338
339/// ```compile_fail
340/// use iii_sdk::{UpdateOp, StreamGetInput};
341/// ```
342#[allow(dead_code)]
343fn _ensure_stream_io_types_not_top_level() {}
344
345/// ```rust,no_run
346/// use iii_helpers::stream::{StreamChangeEvent, StreamJoinLeaveEvent};
347/// fn _takes(_a: StreamChangeEvent, _b: StreamJoinLeaveEvent) {}
348/// ```
349#[allow(dead_code)]
350fn _ensure_stream_events_helpers_path() {}
351
352// ---------------------------------------------------------------------------
353// engine submodule grouping: engine constants and the remote handler type are
354// reachable only at their canonical path `iii_sdk::engine`. Rust folds this
355// grouping into the existing `engine` module (the file `engine.rs`) rather than
356// a separate `pub mod engine { ... }` block, which would clash with it.
357// ---------------------------------------------------------------------------
358
359/// ```rust,no_run
360/// use iii_sdk::engine::{EngineFunctions, EngineTriggers, RemoteFunctionHandler};
361/// let _ = (EngineFunctions::LIST_FUNCTIONS, EngineTriggers::LOG);
362/// fn _takes(_h: RemoteFunctionHandler) {}
363/// ```
364#[allow(dead_code)]
365fn _ensure_engine_submodule_path() {}
366
367/// ```compile_fail
368/// use iii_sdk::{EngineFunctions, EngineTriggers};
369/// ```
370#[allow(dead_code)]
371fn _ensure_engine_constants_not_top_level() {}
372
373/// ```rust,no_run
374/// use iii_sdk::errors::InvocationError;
375/// fn _takes(_e: InvocationError) {}
376/// ```
377#[allow(dead_code)]
378fn _ensure_invocation_error_path() {}
379
380/// ```rust,no_run
381/// use iii_sdk::{StreamRequest, StreamResponse};
382/// fn _takes(_req: StreamRequest, _res: StreamResponse) {}
383/// ```
384#[allow(dead_code)]
385fn _ensure_stream_request_response_path() {}
386
387// ---------------------------------------------------------------------------
388// protocol submodule grouping: the low-level protocol message and
389// register-input types are reachable only at their canonical path
390// `iii_sdk::protocol` and are no longer re-exported at the crate root.
391// ---------------------------------------------------------------------------
392
393/// ```rust,no_run
394/// use iii_sdk::protocol::{
395///     ErrorBody, FunctionMessage, RegisterFunctionMessage, RegisterTriggerInput,
396///     RegisterTriggerMessage, RegisterTriggerTypeMessage, TriggerRequest,
397/// };
398/// ```
399#[allow(dead_code)]
400fn _ensure_protocol_submodule_path() {}
401
402/// ```compile_fail
403/// use iii_sdk::{
404///     ErrorBody, FunctionMessage, RegisterFunctionMessage, RegisterTriggerInput,
405///     RegisterTriggerMessage, RegisterTriggerTypeMessage, TriggerRequest,
406/// };
407/// ```
408#[allow(dead_code)]
409fn _ensure_protocol_types_not_top_level() {}
410
411// ---------------------------------------------------------------------------
412// EnqueueResult is re-exported at the crate root for convenience alongside
413// `TriggerAction`, mirroring its canonical home in `iii_helpers::queue`.
414// ---------------------------------------------------------------------------
415
416/// ```rust,no_run
417/// use iii_sdk::EnqueueResult;
418/// ```
419#[allow(dead_code)]
420fn _ensure_enqueue_result_at_root() {}