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() {}