2026-08-31 21:01:50 +09:00
2026-08-31 21:01:50 +09:00
2026-08-31 21:01:50 +09:00
2026-08-31 21:01:50 +09:00

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

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 Values, 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 OperationCall/OperationResult without placing transport-specific types in descriptors or application traits.

End-to-end example

src/lib.rs is the complete public-API example and 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:

nix develop
cargo run --package wip-public-api-example

The normal integration test is in 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:

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<bool>;
}

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<T> return;
  • () only as the application return when no value is produced; and
  • direct Option<T> only for an omittable parameter.

WipType derives support named-field structs, all-unit enums, and enums whose variants each contain 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; mixed/named-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
() as an operation result no returned value
bool boolean
integer primitives JavaScript-lossless integer
f32, f64 finite number
String string
wip::Bytes bytes
wip::JsonValue JSON
wip::Entry<K, V> entry
Vec<T> list
named-field #[derive(wip::WipType)] struct named record
all-unit #[derive(wip::WipType)] enum named enum
all-newtype #[derive(wip::WipType)] enum named tagged union

Option<T> means that a direct DTO field or operation parameter may be omitted; it is not protocol null. JSON null remains available inside wip::JsonValue.

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:

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; descriptor names and Rust type names are not used as independent wire 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:

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.

S
Description
No description provided
Readme
1 MiB
Languages
Rust 100%