# wip-rs Rust building blocks for the Web Interface Protocol (WIP). The repository currently provides a transport-independent protocol model, a Rust interface-authoring facade, and a request-scoped host runtime. HTTP and other transport bindings are intentionally outside the supported slice. ## Crate graph and responsibilities ```text wip-public-api-example (workspace-only executable, integration tests, doctest) ├── wip-protocol logical declarations, values, calls, and validation ├── wip-interface application DTO codecs and interface authoring facade │ ├── wip-protocol │ └── wip-interface-macros proc-macro implementation detail └── wip-host descriptor registry, publication, and qualified dispatch ├── wip-interface └── wip-protocol ``` | Crate | Responsibility | Does not own | | --- | --- | --- | | `wip-protocol` | Transport-independent descriptors, object/call/result types, logical `Value`s, validation, and stable protocol error categories. | Application DTOs, implementations, persistence, or wire encoding. | | `wip-interface` | The public `WipType`/`interface` authoring API, Rust-to-protocol codecs, generated descriptors, and typed implementation dispatch. | Object publication, backend lifetime, or transport policy. | | `wip-interface-macros` | Procedural-macro expansion used by `wip-interface`. It is not an application dependency. | A supported direct user API. | | `wip-host` | Opaque interface registration, explicit object membership, validator checks, and interface-qualified dispatch against one host-owned instance. | Backend atomicity, global object lookup, or network serving. | | `wip-public-api-example` | An independently compiled consumer of only the three public crates. Its DTOs and traits are application-owned; its implementation and backend handle remain host-private. | Reusable library API or a production server. | The boundary is intentional: protocol types describe what can cross a transport; application DTOs opt into that model with `WipType`; a host-private implementation owns credentials, handles, and backend state; and a future transport maps its own routes/envelopes to `CallOperationRequest` and `CallOperationResponse` without placing transport-specific types in descriptors or application traits. ## End-to-end example [`src/lib.rs`](src/lib.rs) is the complete public-API example and [`src/main.rs`](src/main.rs) runs it. It demonstrates all of the following: 1. public application DTOs derived as `WipType` records and unions; 2. two public traits declared with `#[wip::interface]`; 3. a private host struct implementing both traits; 4. explicit publication of both interfaces on one object; 5. registry observation and validation of generated descriptors; 6. calls made with interface references, operation names, and `Value` arguments; 7. validated result values decoded back to the typed application result; 8. independent dispatch of the same `status` operation name on both interfaces; 9. a domain rejection returned as data and a backend failure returned as a host error. Run it inside the development shell: ```console nix develop cargo run --package wip-public-api-example ``` The normal integration test is in [`tests/end_to_end.rs`](tests/end_to_end.rs). Unit tests beside the example additionally cover every host dispatch failure, invalid arguments and results, interface membership, validators, and same-named operations. ## Interface authoring Applications depend on `wip-interface`, normally imported as `wip`: ```rust use wip_interface as wip; #[derive(wip::WipType)] struct Message { /// Message body. text: String, } #[wip::interface] pub trait Messenger { /// Sends a message. /// /// # Returns /// Whether the application accepted the message. fn send( &self, /// Message to send. message: Message, ) -> wip::OperationResult; } ``` The `interface` attribute and `WipType` derive are re-exported by the facade; users do not import `wip-interface-macros` or generated private support symbols. The generated `MessengerDefinition` constructs an `InterfaceDescriptor`, decodes named arguments, encodes results, validates both boundaries, and provides typed dispatch for any `T: Messenger`. ### Supported Rust syntax A supported interface is a public, non-generic trait containing methods with: - exactly an immutable `&self` receiver; - simple named, owned parameters; - an explicit `wip::OperationResult` return; - `()` only as the application return when no value is produced; and - direct `Option` only for an omittable parameter. `WipType` derives support named-field structs, all-unit enums, and unions whose cases may be unit variants or variants containing exactly one unnamed payload. The macros reject private, generic, inherited, unsafe, auto, or associated-item traits; const, async, unsafe, extern, variadic, generic, default-bodied, or non-`&self` methods; borrowed/custom-lifetime values; tuple/unit/generic structs; Rust unions; named-field/multi-field enum variants; explicit discriminants; nested `Option`; and `Option` in returns, collections, entries, or union payloads. These diagnostics are compile-fail tested under `crates/wip-interface/tests/ui`. ### Rust type mapping | Rust | Protocol | | --- | --- | | `()` | unit / `Value::Unit` | | `bool` | boolean | | integer primitives | JavaScript-lossless integer | | `f32`, `f64` | finite number | | `String` | string | | `wip::Bytes` | bytes | | `wip::JsonValue` | JSON interpreted over the schema-neutral `Value` tree | | `wip::Entry` | canonical absolute Worldspace path encoded as a string | | `Vec` | list | | named-field `#[derive(wip::WipType)]` struct | named record | | all-unit `#[derive(wip::WipType)]` enum | named enum encoded as a string | | mixed unit/newtype or all-newtype enum | named union encoded as a discriminator record | `Option` means that a direct DTO field or operation parameter may be omitted; it is not protocol null. JSON null remains available inside `wip::JsonValue`. #### Migration from the pre-canonical value API The old generic `wip::Entry` represented an application key/value pair and is no longer available. Use `wip::Entry::new("/canonical/path")` for a Worldspace entry path and place any associated application data in a record. `Entry` encodes as `Value::String` and validates the canonical absolute path. The semantic `Value::Json`, `Value::Entry`, `Value::Enum`, and `Value::Union` variants have also been removed. JSON recursively uses `Unit`, primitive, `List`, and `Record` values and rejects `Bytes`; enums use `String`; unions use a `Record` containing `$case` and, for payload-bearing cases, `value`. Unit operation results are represented explicitly as `Value::Unit`. Rust doc comments become descriptor metadata. For an operation, a `# Returns` section documents the return declaration; text before that section documents the operation itself. ## Host publication and dispatch Applications use `wip-host` to register generated definitions and explicitly choose the ordered interface set for each object publication: ```rust,ignore let mut interfaces = wip_host::InterfaceRegistry::new(); let messenger = interfaces.register(MessengerDefinition, None)?; let published = wip_host::PublishedObject::new(request_scoped_object) .expose(&messenger)?; let protocol_object = published.protocol_object(); ``` `expose` compiles only when the object implements the generated definition's interface trait. Implementing a trait does not expose it automatically, empty interface sets are valid, insertion order is preserved, and duplicate references are rejected. Registry references are opaque and remain outside the descriptor; generated descriptors contain neither those references nor Rust trait identity. Dispatch is qualified by interface reference and operation name. The host checks current object identity and validator policy, current interface membership, interface validator, descriptor operation, arguments, and result while retaining the selected object and registered artifact. Host-private fields and backend handles are never projected by `WipObject`. Application domain failures belong in a derived result union and are returned through `Ok`. `wip::HostError` is reserved for runtime/backend failures. Invalid arguments are rejected before invocation; malformed encoded results are rejected as internal host failures and never returned as application data. Providers remain responsible for backend atomicity. Enter a transaction, lock, stable-handle scope, or conditional-update primitive before constructing and dispatching the request-scoped `PublishedObject`, and retain it until dispatch returns. The runtime never validates one object and then resolves a second snapshot, but it cannot make an arbitrary external backend atomic by itself. ## Validation Enter the dev shell and run the acceptance commands exactly: ```console nix develop cargo fmt --all -- --check cargo check --workspace --all-targets cargo test --workspace --all-targets cargo clippy --workspace --all-targets -- -D warnings ``` `cargo test --workspace --all-targets` includes the public example integration test, runtime failure tests, and `trybuild` compile-fail suites. Run `cargo test --workspace --doc` when validating the checked rustdoc examples separately.