# 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
});
```
If a test wants to emit observational data, it uses the author context directly:
```rust
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
```