wip-rs
Rust building blocks for the Web Interface Protocol (WIP).
The repository is a resolver-3 virtual Cargo workspace whose packages all live
under crates/*. It provides a transport-independent protocol model, the
standard framework-independent WIP over HTTP v1 binding, a stateful
presentation-independent client runtime, a Rust interface-authoring facade, a
request-scoped host runtime, a bounded Linux filesystem projection server, an
interactive terminal client, a standalone Text View signature library, and a
private public-API conformance consumer. The
repository root is not itself a package.
Crate graph and responsibilities
Published libraries
wip-text-view
└── wip-protocol
wip-client
├── wip-http
│ └── wip-protocol
└── wip-protocol
wip-host
├── wip-interface
│ ├── wip-interface-macros
│ └── wip-protocol
└── wip-protocol
Workspace-only applications and conformance consumers
wip-tui
├── wip-client
├── wip-http
└── wip-protocol
wip-fs-server
├── wip-host
├── wip-http
├── wip-interface
└── wip-protocol
wip-public-api-example
├── wip-host
├── wip-interface
└── wip-protocol
| Crate | Responsibility | Does not own |
|---|---|---|
wip-text-view |
Standalone, validated single-Object and single-Interface Text View signatures with full declarations, summaries and safe escapes. | Retrieval, cache, placement context, wire schema, UI layout, parsing or call-target reconstruction. |
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-http |
Standard WIP over HTTP v1 endpoint/route derivation, UTF-8 JSON codecs, direct success bodies, and error/status classification. | HTTP I/O, URL scheme or TLS policy, authentication schemes, server frameworks, filesystem projection, or client presentation/cache policy. |
wip-client |
Endpoint/security-context-isolated Known Space, normalized object and interface observations, intent-driven object and descriptor coverage with bounded request deduplication, immutable operation call contexts, stale transitions, and bounded histories. | HTTP I/O, credentials, persistent cache, automatic operation retries, or presentation widgets/events. |
wip-tui |
Keyboard-only interactive Worldspace tree, selected Entry and qualified operation panes, descriptor-driven argument editing, bounded result/diagnostic rendering, terminal lifecycle, and HTTP I/O workers over wip-client. |
Independent cache/validator semantics, automatic operation retries, Host internals, backend paths, or persistent state. |
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 |
Scoped interface registration and publication lifetimes, explicit object membership, validator checks, interface-qualified dispatch against one host-owned instance, and descriptor-guided argument decoding inside that same resolution boundary. | Backend atomicity, global object lookup, or network serving. |
wip-fs-server |
Linux directory-handle-confined request-time projection, complete depth-bounded observations and bounded file operations, standard wip-http dispatch, and a loopback HTTP binary. |
A new wire profile, implicit symlink following, non-loopback cleartext, authentication policy, TLS termination, or filesystem-wide preregistration. |
wip-public-api-example |
An independently compiled consumer of the protocol, authoring, and host public crates. Its DTOs and traits are application-owned; its implementation and backend handle remain host-private. | Reusable library API or a production server. |
Published crates
The 0.2.0 release consists of these seven crates:
wip-protocolwip-interface-macros— implementation dependency re-exported bywip-interfacewip-interfacewip-hostwip-httpwip-clientwip-text-view
The wip-text-view library's
render_object(&Object) API needs no path;
render_interface(&Interface) takes one borrowed entity pairing the complete
structured reference with its descriptor, without changing Protocol wire fields.
Both return one complete plain-text signature or a typed error.
The filesystem server, terminal client, and public-API example are workspace-only packages and set publish = false. All workspace packages require Rust 1.88 or newer and are licensed under either Apache-2.0 or MIT at your option.
See RELEASING.md for the dependency-ordered crates.io procedure and CHANGELOG.md for release contents.
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 wip-http
maps standard routes/envelopes to CallOperationRequest and
CallOperationResponse without placing transport-specific types in descriptors
or application traits.
Structured Interface references
The 0.2.0 Protocol/HTTP API uses explicit InterfaceReference { scope, name } records and optional scope_ref observation/precondition metadata.
Legacy opaque-string wire references are rejected. See MIGRATION.md
for construction examples, validation boundaries and downstream migration status.
The filesystem default publishes /::file.read, /::symlink.inspect and
/::file.write; external compositions explicitly choose Scope/local names.
The TUI retains structured references for selection and dispatch, while normal
screens use absolute scope::name#operation labels with safely quoted components.
These labels are display-only: they are never parsed as cache keys or call targets,
and scope refs/validators remain runtime metadata rather than normal-screen labels.
Client scope invalidation is reflected in the UI before another explicit call.
End-to-end example
crates/wip-public-api-example/src/lib.rs
is the complete public-API example and
crates/wip-public-api-example/src/main.rs
runs it. It demonstrates all of the following:
- public application DTOs derived as
WipTyperecords, enums, and unions, including canonical entry paths and schema-opaque JSON; - two public traits declared with
#[wip::interface]; - a private host struct implementing both traits;
- explicit publication of both interfaces as an ordered set on one object;
- separate observation of structured interface references, descriptors, and descriptor validators;
- calls qualified by
target.path, object validator, interface reference and validator, operation, and schema-neutralValuearguments; - validated result values decoded back to the typed application result;
- independent dispatch of the same
statusoperation name on both interfaces; - distinct domain rejection, rollback-confirmed and outcome-unknown
host/backend failures,
PermissionDenied, stale validator recovery, interface membership, and lookup failure codes; and - canonical failure precedence when several request fields are invalid.
Run it inside the development shell:
nix develop
cargo run --package wip-public-api-example
The public-only conformance suite is in
crates/wip-public-api-example/tests/end_to_end.rs.
It covers canonical object/descriptor shape, ordered interfaces,
schema-neutral record/enum/union/entry/JSON values, fully qualified calls, and
the distinct domain, host, authorization, validator-recovery, and interface
failure classes. Exhaustive public type construction and matching make legacy
ObjectId, descriptor name, and semantic Value variants compile-time
regressions. Unit tests in the public crates additionally cover every host
dispatch failure, InvalidRequest versus descriptor-bound InvalidArguments,
invalid local responses, validators, same-named operations, and exhaustive
per-interaction protocol error-code sets.
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
&selfreceiver; - 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 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<T> |
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<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.
Migration from the pre-canonical value API
The old generic wip::Entry<K, V> 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:
let mut interfaces = wip_host::InterfaceRegistry::new();
let scope = wip_host::Scope::new("/service")?;
let messenger = interfaces.register(&scope, "messenger", MessengerDefinition, None)?;
// Inside the provider coherent backend boundary, not a stale scope prelookup:
let current = wip_host::ScopeObservation::new(&scope, ¤t_scope_object);
let published = wip_host::PublishedObject::new(request_scoped_object)
.expose(&messenger)?
.bind_scope(current);
let protocol_object = published.protocol_object_at("/service/item")?;
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 collisions use exact (scope, name) pairs,
not Rust Definition identity; the same Definition may be registered at multiple
coordinates. Structured references remain outside the Descriptor.
Scope carries a private Object publication lifetime. Keep it for ordinary state
updates; retire it with remove_scope and create a fresh Scope on deletion,
replacement or movement, even at the same path. Providers attach a current
ScopeObservation from the scope Object inside the coherent target/backend
boundary, never from a separate prelookup. The scope Object need not expose any
interfaces. Fetch uses ObjectProvider::fetch_interface(&provider, ®istry, &reference) to observe the visible scope Object and return its optional ref,
Descriptor and Interface validator together; retained artifacts alone are not
publication. Calls require the current attached lifetime and check supplied
scope_ref at membership precedence; omission adds no identity precondition.
Refs, Object validators and Interface validators retain distinct responsibilities.
See MIGRATION.md for the provider atomicity and lifecycle contract.
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 scope lifetime/optional scope_ref and 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. A
structurally malformed call is InvalidRequest; only argument values checked
after the current Interface Descriptor is fixed are InvalidArguments. A Host
must never return a wire InvalidResult: Clients keep invalid Object,
Descriptor, operation-result, and error responses as local InvalidResponse
failures, while unsupported descriptor formats remain local
UnsupportedDescriptorFormat failures.
For operation calls, Internal and ResourceLimitExceeded guarantee that the
operation did not run or that its effects were rolled back.
OperationOutcomeUnknown is the only protocol code that makes no such
guarantee, so Clients must not automatically retry it. wip::HostError
therefore requires both a HostErrorKind and an EffectGuarantee; implementation
panic, result encoding, and result validation default to an unknown outcome.
Host-private diagnostics are bounded locally but never copied into protocol
messages.
ExecutionContext carries a runtime-neutral cancellation token, monotonic
deadline, and optional Host-defined resource budget through resolution and
execution. Cancellation and deadlines are cooperative: the runtime checks them
immediately before typed invocation, while providers enforce them during backend
work. A pre-invocation limit failure is ResourceLimitExceeded; after invocation
it is safe to use that code only when non-commit or rollback is confirmed.
Providers remain responsible for backend atomicity. with_object returns a typed
ProviderResult, and execute_operation receives an OperationAttempt that
separates NotStarted from Started. A transactional provider holds its
transaction across the callback, commits successful attempts, and may call
OperationAttempt::confirm_rollback only after every operation effect has been
rolled back. Without that proof, post-invocation failures remain
OperationOutcomeUnknown. Mutable operations must still make validator
comparison and backend commit atomic and return a validator describing the state
that becomes committed.
Interactive terminal client
crates/wip-tui provides the wip-tui executable. It starts
from Worldspace /, lazily expands materialized tree nodes, displays current
object/interface/operation metadata, validates all canonical descriptor types in
a field editor, and requires a separate confirmation action before every call.
It uses the stateful wip-client runtime for request correlation, observed
validators, stale transitions, and unknown outcomes. One canonical http or
https endpoint is used unchanged for request construction, transport, and
cache partitioning. Scheme, TLS, certificates, authentication, and proxy policy
belong to the HTTP implementation and deployment. Full startup, key binding,
layout, validator, and filesystem-operation examples are in the
package README.
Filesystem projection server
crates/wip-fs-server contains the practical server
binary and its full security model, configuration, curl walkthrough, and
resource/failure semantics. It resolves every object from current filesystem
state, uses pinned directory-relative handles without following symlinks, omits
non-UTF-8 backend names, returns complete recursive observations through the
requested depth or a resource-limit failure, and publishes validator-protected
replacement only in explicit read-write mode.
The built-in HTTP listener is loopback-only. Externally visible URL schemes,
TLS termination, certificates, authentication, and proxy routing are deployment
concerns rather than separate WIP profiles.
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 test --workspace --doc
cargo clippy --workspace --all-targets -- -D warnings
RUSTDOCFLAGS='-D warnings' cargo doc --workspace --no-deps
cargo run --package wip-public-api-example
git diff --check
cargo test --workspace --all-targets includes the public-only conformance
consumer, runtime failure tests, and trybuild compile-fail suites. The separate
doctest, rustdoc, executable, and whitespace checks are all required acceptance
steps.