wip-rs
Rust building blocks for the Web Interface Protocol (WIP).
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 directly. The generated
MessengerDefinition constructs an InterfaceDescriptor, decodes named
arguments, encodes results, validates both boundaries, and provides typed
dispatch for any T: Messenger.
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::wip_protocol::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> is accepted only for a direct DTO field or operation parameter and
means that the named value may be omitted. It is not protocol null. Domain
failures are modeled in the application result (usually a derived union) and
returned through Ok; wip::HostError is only for host/runtime failures.
Rust doc comments become descriptor metadata. For an operation, a # Returns
section documents the return declaration; text before that section documents
the operation itself.
The supported interface slice is deliberately non-generic: public traits,
&self, named parameters, and OperationResult<T>. Generic traits or methods,
associated items, custom lifetimes, inheritance, default method bodies, and
other receiver forms are rejected at their source spans.
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 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.
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.