# 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 and targets a canonical Worldspace path. An `ObjectProvider` validates that path before resolution, maps malformed paths to `InvalidRequest`, then resolves a valid path exactly once inside `with_object`; the host checks the supplied object validator, current interface membership, interface validator, descriptor operation, operation-specific object-validator policy, arguments, and authorization in canonical precedence. Path and children remain provider resolution context and are never projected by `WipObject`; host-private fields and backend handles are also excluded. 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 reported as `InvalidResult` and never returned as application data. Validator, membership, authorization, and result failures retain their distinct canonical protocol error codes. Providers remain responsible for backend atomicity. Enter a transaction, lock, stable-handle scope, or conditional-update primitive before invoking the `with_object` callback, and retain it until the callback returns. Mutable operations must make validator comparison and backend commit atomic and return a validator for the committed state. 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.