observer-rust-lib 0.1.1

MIT-licensed Rust integration library for Observer
Documentation
# observer-rust-lib

Minimal Rust-facing provider micro-library for Observer.

If you are new to this surface, start with `HOWTO.md` before reading the individual snippets and starters.

This wraps the existing low-level Rust provider crates with the same human-first DX shape already used in C and TypeScript:

- authors write `describe!(...)`, `test!(...)`, `it!(...)`, and `expect(...)`
- default identity is derived deterministically from explicit suite path plus test title
- optional `id = ...` provides a refactor-stable override when needed
- observation is bounded and opt-in through `ctx.observe().*`
- direct host and embedded `observe` dispatch are owned by the library

The common path stays human-first. The deterministic boundary stays explicit.

## Files

- `HOWTO.md`: detailed user manual covering authoring, determinism, host transport, inventory derivation, and end-to-end workflow
- `src/lib.rs`: public API
- `examples/example_smoke.rs`: tiny collection and execution example
- `examples/host_example.rs`: tiny direct `list` and `run` host example
- `examples/host_embed_example.rs`: own-main style `observe` namespace example
- `starter/`: runnable project-shaped example with Cargo build, provider host build, inventory derivation, suite run, and snapshot verification
- `starter-embedded/`: runnable app-shaped example where the application keeps `main()` and routes `observe ...` through its own CLI
- `starter-embedded-failure/`: runnable failing companion for the embedded app-shaped path
- `starter-failure/`: runnable failing companion showing the same provider flow with one intentionally failing exported test

If you want the full end-to-end workflow rather than isolated snippets, start with `starter/`, then read `starter-embedded/`, and then compare those with `starter-failure/` and `starter-embedded-failure/`.

## Minimal Shape

```rust
use observer_rust_lib::{collect_tests, describe, expect, test};

fn connect() -> bool {
    true
}

let tests = collect_tests(|| {
    describe!("database", {
        test!("access to the database", |ctx| {
            ctx.stdout("ok\n");
            expect(connect()).to_be_truthy();
        });
    });
})?;
```

When `id` is omitted, Observer derives a deterministic identity from suite path, test title, and duplicate occurrence order.

If a test wants a refactor-stable identity, it opts into `id` explicitly:

```rust
test!("access to the database", id = "database/access", |ctx| {
    expect(true).to_be_truthy();
});
```

If a test wants to emit observational data, it uses the author context directly:

```rust
test!("access to the database", id = "database/access", |ctx| {
    assert!(ctx.observe().metric("wall_time_ns", 104233.0));
    assert!(ctx.observe().vector("request_latency_ns", &[1000.0, 1100.0, 980.0]));
    assert!(ctx.observe().tag("resource_path", "fixtures/config.json"));
    expect(true).to_be_truthy();
});
```

## Validation Rules

- explicit `id`, when present, must be non-empty
- resolved canonical identities must be unique
- resolved targets must be unique
- deterministic sorting is by canonical name, then target

In this first cut, the resolved identity is used for both canonical name and target.

## Test

```sh
cargo test -p observer-rust-lib
```

## Smoke Example

```sh
cargo run -q -p observer-rust-lib --example example_smoke
```

## Host Example

```sh
cargo run -q -p observer-rust-lib --example host_example -- list
cargo run -q -p observer-rust-lib --example host_example -- observe --target pkg::smoke --timeout-ms 1000
cargo run -q -p observer-rust-lib --example host_example -- run --target pkg::fail --timeout-ms 1000
```

The library owns the standard provider host transport for Rust too. A direct host can stay nearly trivial:

```rust
use observer_rust_lib::{collect_tests, describe, expect, observer_host_main, test};

fn main() {
    let tests = collect_tests(|| {
        describe!("pkg", {
            test!("smoke test", id = "pkg::smoke", |ctx| {
                ctx.stdout("ok\n");
                expect(true).to_be_truthy();
            });
        });
    })
    .expect("collection should validate");

    let exit_code = match observer_host_main("rust", &tests) {
        Ok(()) => 0,
        Err(error) => {
            eprintln!("{error}");
            2
        }
    };
    std::process::exit(exit_code);
}
```

For developer-facing usage, prefer `observe`. `run` remains available for compatibility with the standardized outer provider contract.

## Own Main Integration

If a project already owns its CLI, the library can also serve an embedded `observe` namespace:

```rust
use observer_rust_lib::{collect_tests, describe, expect, observer_host_dispatch_embedded, test};

fn app_main() {
    println!("app main");
}

fn main() {
    let tests = collect_tests(|| {
        describe!("pkg", {
            test!("embedded smoke test", id = "pkg::embedded-smoke", |ctx| {
                ctx.stdout("ok\n");
                expect(true).to_be_truthy();
            });
        });
    })
    .expect("collection should validate");

    let args = std::env::args().collect::<Vec<_>>();
    if args.get(1).map(String::as_str) == Some("observe") {
        let exit_code = match observer_host_dispatch_embedded("rust", "observe", &tests, args) {
            Ok(()) => 0,
            Err(error) => {
                eprintln!("{error}");
                2
            }
        };
        std::process::exit(exit_code);
    }

    app_main();
}
```

Compile and run that path with:

```sh
cargo run -q -p observer-rust-lib --example host_embed_example -- observe list
cargo run -q -p observer-rust-lib --example host_embed_example -- observe --target pkg::embedded-smoke --timeout-ms 1000
cargo run -q -p observer-rust-lib --example host_embed_example
```