# canaad-core
Parse, validate, and canonicalize AAD contexts per RFC 8785.
## use it
Parse existing JSON:
```rust
use canaad_core::parse;
let json = r#"{"v":1,"tenant":"org_abc","resource":"secrets/db","purpose":"encryption"}"#;
let ctx = parse(json)?;
let canonical = ctx.canonicalize_string()?;
```
Build from scratch:
```rust
use canaad_core::AadContext;
let ctx = AadContext::new("org_abc", "secrets/db", "encryption")?
.with_timestamp(1706400000)?
.with_string_extension("x_vault_cluster", "us-east-1")?;
let bytes = ctx.canonicalize()?;
```
Use the builder if you prefer:
```rust
use canaad_core::AadContext;
let ctx = AadContext::builder()
.tenant("org_abc")
.resource("secrets/db")
.purpose("encryption")
.extension_string("x_vault_cluster", "us-east-1")
.build()?;
```
Builder defers all validation to `build()` — invalid extensions surface as errors, not silent drops.
## what it checks
- `v` must be 1
- `tenant`: 1-256 bytes, no NUL
- `resource`: 1-1024 bytes, no NUL
- `purpose`: 1+ bytes, no NUL
- `ts`: optional, 0 to 2^53-1
- extensions: `x_<app>_<field>` pattern, values are strings or integers
- no duplicate keys (custom scanner, not serde_json)
- 16 KiB max serialized size
All 16 error variants are strongly typed via `AadError`. Run `cargo doc -p canaad-core --open` for the full API.
## spec
See [aad-spec.md](../../aad-spec.md) for the complete specification and test vectors.