Skip to content

Latest commit

 

History

History
317 lines (237 loc) · 8.14 KB

File metadata and controls

317 lines (237 loc) · 8.14 KB

IO Cookbook

This cookbook provides deterministic implementation patterns for the current IO runtime. Use these patterns when writing examples, agent workflows, or production-oriented AIC programs.

1. Interactive Input With Typed Fallbacks

Use std.io read APIs and branch on IoError instead of panicking.

import std.io;

fn read_name() -> String effects { io } {
    match prompt("Name: ") {
        Ok(value) => value,
        Err(EndOfInput) => "guest",
        Err(InvalidInput) => "guest",
        Err(Io) => "guest",
    }
}

Reference: examples/io/interactive_greeter.aic.

2. File Processing With Handle APIs

Prefer handle APIs for multi-line workflows.

import std.fs;

fn unwrap_handle(v: Result[FileHandle, FsError]) -> FileHandle {
    match v {
        Ok(handle) => handle,
        Err(_) => FileHandle { handle: 0 },
    }
}

fn process() -> Int effects { fs } {
    let reader = unwrap_handle(open_read("in.txt"));
    let first = file_read_line(reader);
    let _ = first;
    let _ = file_close(reader);
    0
}

References:

  • examples/io/file_processor.aic
  • examples/io/line_reader.aic

2b. Buffered Copy With stream_copy

Use std.io.stream_copy when you want to move bytes between a reader and writer without hand-rolling the loop.

import std.io;

fn relay(reader: Reader, writer: Writer) -> Int effects { io } {
    match stream_copy(reader, writer) {
        Ok(count) => count,
        Err(_) => 0,
    }
}

Reference: examples/io/stream_copy.aic.

3. Tee-Style Logging (stdout + stderr + file)

Use stdout for user-facing status, stderr for operator diagnostics, and std.fs for persistent logs.

import std.io;
import std.fs;

fn log_once(path: String) -> Int effects { io, fs } {
    let _ = append_text(path, "INFO startup\n");
    println_str("INFO startup");
    eprint_str("WARN startup");
    flush_stdout();
    flush_stderr();
    0
}

Reference: examples/io/log_tee.aic.

4. Environment-Driven Config

Use std.env.get for optional values and keep defaults local.

import std.env;

fn config_token() -> String effects { env } {
    match get("AIC_TOKEN") {
        Ok(value) => value,
        Err(NotFound) => "default-token",
        Err(_) => "default-token",
    }
}

Reference: examples/io/env_config.aic.

5. Subprocess Pipelines

Use run/pipe for short commands, and run_with when cwd/env/stdin must be controlled.

import std.proc;
import std.vec;

fn pipeline_demo() -> Result[ProcOutput, ProcError] effects { proc, env } {
    let mut stages: Vec[String] = vec.vec_of("printf 'hello'");
    stages = vec.push(stages, "cat");
    pipe_chain(stages)
}

Reference: examples/io/subprocess_pipeline.aic.

6. Retry + Timeout Utilities

Use std.retry for exponential backoff with optional jitter, and with_timeout for deadline checks around operations.

import std.retry;

fn default_policy() -> RetryConfig {
    default_retry_config()
}

References:

  • examples/io/retry_with_jitter.aic
  • docs/examples/retry-workflow.md

7. Binary Framing With std.buffer

Use ByteBuffer for protocol layouts that require endian-aware integers, null-terminated strings, and backpatching.

import std.buffer;

fn frame() -> Result[ByteBuffer, BufferError] {
    match new_growable_buffer(32, 512) {
        Err(err) => Err(err),
        Ok(buf) => {
            let write_len = buf_write_i32_be(buf, 0);
            let write_kind = buf_write_u8(buf, 1);
            let write_tag = buf_write_cstring(buf, "msg");
            let end = buf_position(buf);
            let patch_len = buf_patch_u32_be(buf, 0, end);
            let _a = write_len;
            let _b = write_kind;
            let _c = write_tag;
            let _d = patch_len;
            Ok(buf)
        }
    }
}

Reference: examples/data/binary_protocol.aic.

Use buf_close when a frame buffer is no longer needed in long-lived workers to release memory eagerly.

8. Crypto Patterns (Hashes, HMAC, PBKDF2)

Use byte-level checks for digest validation and prefer typed decode/derive error handling.

import std.crypto;
import std.bytes;

fn bytes_or_empty(v: Result[Bytes, CryptoError]) -> Bytes {
    match v {
        Ok(value) => value,
        Err(_) => bytes.empty(),
    }
}

fn scram_seed(password: String) -> Bytes {
    bytes_or_empty(pbkdf2_sha256(password, bytes.from_string("salt"), 4096, 32))
}

Reference: examples/crypto/pg_scram_auth.aic.

9. Platform Caveat Patterns

When cross-platform behavior differs, branch on typed errors.

import std.net;

fn connect_or_skip(addr: String) -> Int effects { net } {
    match tcp_connect(addr, 500) {
        Ok(_) => 1,
        Err(Io) => 0,
        Err(_) => 0,
    }
}

Current runtime caveats to account for:

  • Windows std.net: TCP loopback plus async accept/recv wait/cancel/shutdown client-transport paths are smoke-backed; UDP/DNS/socket-tuning share the backend but are not yet Windows-smoke-backed, and unsupported socket-option paths can surface NetError::Io.
  • Windows std.proc: spawn, run, run_with, run_timeout, pipe, and pipe_chain can surface ProcError::Io; wait, kill, and is_running can surface ProcError::UnknownProcess.
  • Windows std.signal: unsupported and returns SignalError::UnsupportedPlatform.

10. HTTP Server and Router Validation

Use std.http_server for synchronous request/response shaping and std.router for deterministic route matching; pair them with std.config when request-driven startup config must be loaded before accepting traffic.

import std.http_server;
import std.router;
import std.config;

fn ready() -> Int effects { net, fs, env } {
    let cfg = load_env_prefix("APP_");
    let _ = cfg;
    let router0 = match new_router() {
        Ok(value) => value,
        Err(_) => return 0,
    };
    let router1 = match add(router0, "GET", "/health", 1) {
        Ok(value) => value,
        Err(_) => return 0,
    };
    let response = text_response(200, "ready");
    let _ = router1;
    let _ = response;
    1
}

Reference: examples/io/http_router.aic and examples/io/config_loading.aic.

11. TLS Client Handshake + Typed Fallback

Use std.tls for encrypted transport and branch on TlsError for deterministic behavior in environments that do not provide TLS backend support.

import std.tls;
import std.bytes;

fn tls_connected(stream: TlsStream) -> Int effects { net } {
    let sent = tls_send_bytes(stream, bytes.from_string("HEAD / HTTP/1.0\n\n"));
    let recv = tls_recv_bytes(stream, 256, 3000);
    let closed = tls_close(stream);
    let _a = sent;
    let _b = recv;
    let _c = closed;
    1
}

fn tls_probe(addr: String, host: String) -> Int effects { net } {
    let cfg = unsafe_insecure_tls_config(Some(host));
    match tls_connect_addr(addr, cfg, 3000) {
        Ok(stream) => tls_connected(stream),
        Err(ProtocolError) => 0,
        Err(Io) => 0,
        Err(_) => 0,
    }
}

Reference: examples/io/tls_connect.aic. Policy: docs/security-ops/tls-policy.v1.json.

12. Unified Secure Error Contract

Normalize module-specific errors to machine-readable secure error metadata for deterministic branching.

import std.secure_errors;
import std.buffer;

fn protocol_category_or_unknown(v: Result[Int, BufferError]) -> String {
    match v {
        Ok(_) => "ok",
        Err(err) => secure_errors.buffer_error_info(err).category,
    }
}

Reference: examples/io/secure_error_contract.aic. Contract: docs/errors/secure-networking-error-contract.v1.json.

13. Postgres TLS/SCRAM Canonical Replay

Use the canonical replay example when implementing production-style secure protocol clients with deterministic failure semantics.

import std.secure_errors;

fn classify(info: SecureErrorInfo) -> String {
    info.code
}

What this flow covers:

  • startup/binary framing (std.buffer)
  • SCRAM proof derivation (std.crypto)
  • TLS secure-default + unsafe audit path (std.tls)
  • retry and timeout behavior (std.retry)
  • pool-capacity contract semantics (PoolErrorContract)

Reference example: examples/io/postgres_tls_scram_reference.aic. Replay artifact: docs/security-ops/postgres-tls-scram-replay.v1.json.