218 lines
9.7 KiB
Markdown
218 lines
9.7 KiB
Markdown
# 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<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 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:
|
|
|
|
```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.
|