id_effect 0.2.0

Effect<A, E, R> (sync + async), context/layers, pipe — interpreter-style, no bundled executor
Documentation
# The Unknown Type — Unvalidated Wire Data

`Unknown` is the type for data that hasn't been validated yet. Think of it as a typed `serde_json::Value` — it can hold any shape of data, but you can't do anything useful with it until you run it through a schema.

## Creating Unknown Values

```rust
use id_effect::schema::Unknown;

// From a JSON string
let u: Unknown = Unknown::from_json_str(r#"{"name": "Alice", "age": 30}"#)?;

// From a serde_json Value
let v: serde_json::Value = serde_json::json!({ "name": "Alice" });
let u: Unknown = Unknown::from_serde_json(v);

// From raw parts
let u: Unknown = Unknown::object([
    ("name", Unknown::string("Alice")),
    ("age",  Unknown::integer(30)),
]);

// Primitives
let s: Unknown = Unknown::string("hello");
let n: Unknown = Unknown::integer(42);
let b: Unknown = Unknown::boolean(true);
let null: Unknown = Unknown::null();
let arr: Unknown = Unknown::array([Unknown::integer(1), Unknown::integer(2)]);
```

## Why Not serde_json::Value Directly?

`serde_json::Value` is an excellent data type, but it's stringly typed: `value["name"]` gives you an `Option<&Value>` and there's no structure around parse errors, path tracking, or accumulation. `Unknown` wraps the same idea but integrates with id_effect's schema parser, which gives you:

- **Path tracking** — "error at `.users[3].email`"
- **Accumulated errors** — all failures in one parse, not just the first
- **Composable schemas** — build complex validators from simple primitives

## Inspecting Unknown Values

You don't normally inspect `Unknown` directly — you run it through a schema. But when debugging:

```rust
// Check what shape the value has
match u.kind() {
    UnknownKind::Object(fields) => { /* … */ }
    UnknownKind::Array(elems)   => { /* … */ }
    UnknownKind::String(s)      => { /* … */ }
    UnknownKind::Integer(n)     => { /* … */ }
    UnknownKind::Float(f)       => { /* … */ }
    UnknownKind::Boolean(b)     => { /* … */ }
    UnknownKind::Null            => { /* … */ }
}

// Access a field without parsing (returns Option<&Unknown>)
let name: Option<&Unknown> = u.field("name");
```

## The Parse Boundary

`Unknown` is your import type. At every IO boundary — HTTP handler, NATS message, config file, database row — convert incoming data to `Unknown` first, then parse it with a schema:

```rust
async fn handle_request(body: Bytes) -> Effect<CreateUserResponse, ApiError, Deps> {
    effect! {
        // Convert raw bytes to Unknown
        let raw = Unknown::from_json_bytes(&body)
            .map_err(ApiError::InvalidJson)?;

        // Parse Unknown into a typed, validated struct
        let req: CreateUserRequest = ~ parse_schema(create_user_schema(), raw);

        // Now req is fully trusted — proceed with domain logic
        ~ create_user(req)
    }
}
```

Nothing beyond the parse boundary sees `Unknown`. Domain functions only accept validated types.

## Unknown and Serde

If you have existing `serde`-deserializable types, use the serde bridge (requires the `schema-serde` feature):

```rust
use id_effect::schema::serde_bridge::unknown_from_serde_json;

// Deserialise via serde, then convert to Unknown for schema validation
let value: serde_json::Value = serde_json::from_str(input)?;
let u: Unknown = unknown_from_serde_json(value);
```

This lets you incrementally adopt the schema system without rewriting all your serde impls at once.