194 lines
10 KiB
Markdown
194 lines
10 KiB
Markdown
# wip-text-view
|
|
|
|
Standalone Object and Interface signature rendering for WIP Text View, following
|
|
`4.2-text-view.md` at reference revision `6086f3c` and the Protocol semantics in
|
|
`3-protocol.md`. Only `wip-protocol` and the Rust standard library are dependencies.
|
|
No Client, Host, HTTP I/O, cache, UI framework or retrieval service is required.
|
|
|
|
```toml
|
|
[dependencies]
|
|
wip-text-view = "0.2.0"
|
|
wip-protocol = "0.2.0"
|
|
```
|
|
|
|
## Single-entity API
|
|
|
|
```rust
|
|
use wip_protocol::{Object, InterfaceReference, InterfaceDescriptor, INTERFACE_FORMAT_V1};
|
|
use wip_text_view::{Interface, render_object, render_interface};
|
|
|
|
let reference = InterfaceReference { scope: "/".into(), name: "example".into() };
|
|
let object = Object {
|
|
name: "item".into(), description: None, interfaces: vec![reference.clone()],
|
|
r#ref: None, validator: None,
|
|
};
|
|
let object_signature = render_object(&object)?;
|
|
assert_eq!(object_signature,
|
|
"object {\n name: \"item\";\n interfaces: [\"/::example\"];\n}\n");
|
|
|
|
let descriptor = InterfaceDescriptor {
|
|
format: INTERFACE_FORMAT_V1.into(), documentation: None,
|
|
types: vec![], operations: vec![],
|
|
};
|
|
let interface = Interface { reference: &reference, descriptor: &descriptor };
|
|
let interface_signature = render_interface(&interface)?;
|
|
assert_eq!(interface_signature, "interface \"/::example\" {\n}\n");
|
|
# Ok::<(), wip_text_view::RenderError>(())
|
|
```
|
|
|
|
`Interface<'a>` is one borrowed SDK input entity containing a complete structured
|
|
reference and its corresponding descriptor, not a wire schema extension. The
|
|
caller pairs them from a coherent observation or publication. The descriptor has
|
|
no identity, so a renderer cannot prove the pairing's provenance or freshness.
|
|
For example, a caller with a validated `FetchInterfaceResponse` can borrow its
|
|
`interface` and `descriptor`; it does not pass `scope_ref` or validators.
|
|
|
|
An Object has no placement path. This API always emits `object { ... }`, never
|
|
requests a path and never infers one from name or ref. It requires no descriptor.
|
|
An Interface needs no Object, separate name/scope/context arguments or lookup.
|
|
There is no document aggregator, configuration or details selector.
|
|
|
|
## Typed display tokens
|
|
|
|
```rust
|
|
use wip_protocol::{Object, InterfaceReference, InterfaceDescriptor, INTERFACE_FORMAT_V1};
|
|
use wip_text_view::{
|
|
Interface, TokenKind, tokenize_object, tokenize_interface, to_plain_text,
|
|
render_object, render_interface,
|
|
};
|
|
|
|
let reference = InterfaceReference { scope: "/".into(), name: "example".into() };
|
|
let object = Object {
|
|
name: "item".into(), description: None, interfaces: vec![reference.clone()],
|
|
r#ref: None, validator: None,
|
|
};
|
|
let object_tokens = tokenize_object(&object)?;
|
|
assert_eq!(object_tokens[0].kind(), TokenKind::Keyword);
|
|
assert_eq!(object_tokens[0].text(), "object");
|
|
assert_eq!(to_plain_text(&object_tokens), render_object(&object)?);
|
|
|
|
let descriptor = InterfaceDescriptor {
|
|
format: INTERFACE_FORMAT_V1.into(), documentation: None,
|
|
types: vec![], operations: vec![],
|
|
};
|
|
let input = Interface { reference: &reference, descriptor: &descriptor };
|
|
let tokens = tokenize_interface(&input)?;
|
|
for token in &tokens {
|
|
// Custom renderers can select presentation by role without reparsing text.
|
|
if token.kind() == TokenKind::Reference {
|
|
assert_eq!(token.text(), "\"/::example\"");
|
|
}
|
|
}
|
|
let joined: String = tokens.iter().map(|token| token.text()).collect();
|
|
assert_eq!(to_plain_text(&tokens), joined);
|
|
assert_eq!(to_plain_text(&tokens), render_interface(&input)?);
|
|
# Ok::<(), wip_text_view::RenderError>(())
|
|
```
|
|
|
|
`tokenize_object` / `tokenize_interface` return `Result<Vec<Token>, RenderError>`.
|
|
They generate **directly from the input structure**, not by lexing a completed
|
|
Text View string. Each token owns its text (and can outlive the input) and exposes
|
|
`kind()` / `text()`. Existing render APIs use the same token generation followed
|
|
by `to_plain_text(&[Token])`; there is no second grammar/escape/layout engine.
|
|
|
|
### Kind and splitting contract
|
|
|
|
| Kind | One token contains |
|
|
| --- | --- |
|
|
| `Keyword` | One grammar keyword, Object property label, or primitive type keyword |
|
|
| `TypeName` | One type declaration name or Named type reference |
|
|
| `OperationName` | One Operation declaration name |
|
|
| `ParameterName` / `FieldName` | One parameter / record field name |
|
|
| `CaseName` | One enum or union case name |
|
|
| `String` | One complete quoted Object name or description |
|
|
| `Reference` | One complete quoted absolute Interface reference, with both quoting layers |
|
|
| `Documentation` | Entire `/// "summary"` content, including marker and internal space, excluding indentation/LF |
|
|
| `Symbol` | One punctuation mark, except the indivisible two-character `->` arrow |
|
|
| `Whitespace` | One inter-token ASCII space |
|
|
| `Newline` | One LF, including the final LF |
|
|
| `Indentation` | All leading spaces of a non-root line, two spaces per depth level |
|
|
|
|
Quoted identifiers retain their semantic role even when named like keywords.
|
|
Quotes, backticks and all WIP escapes are part of the data token, never separate
|
|
symbols. Host spaces inside quoted data remain in that token. Adjacent symbols
|
|
are separate; no empty tokens or zero-depth indentation tokens are emitted.
|
|
`TokenKind` is non-exhaustive so consumers should handle future roles with a
|
|
fallback. The API does not distinguish type declaration names from Named uses,
|
|
or enum cases from union cases; both share the documented role.
|
|
|
|
All layout is included. Concatenate token text **without separators**, not with
|
|
`join(" ")`. `to_plain_text` is only that concatenation: no interpretation,
|
|
validation, quoting, escaping or layout completion. An empty slice yields `""`;
|
|
a filtered/reordered slice is allowed but need not be a complete signature.
|
|
|
|
Token text is escaped for **WIP Text View**, not for HTML or other destinations.
|
|
Custom renderers must escape for their destination and treat Host text as data,
|
|
not markup or instructions. This crate supplies no HTML, ANSI, color or theme
|
|
renderer/adapter and no external text lexer/parser or AST.
|
|
|
|
## Output and validation
|
|
|
|
- UTF-8 plain text with LF, two-space indentation and a final LF, without ANSI,
|
|
Markdown fences, syntax highlighting or provider envelopes. Nonempty composites
|
|
and parameter lists use multiple lines; layout is deterministic here, but not a
|
|
byte-canonical protocol format.
|
|
- All Object references remain in Host order. All types, then all operations,
|
|
fields, parameters and cases remain in declaration order, without elision.
|
|
- Missing vs empty description/documentation, optional vs unit, no payload vs
|
|
unit payload, and empty record/enum/union/declaration sets remain distinct.
|
|
- Full absolute references use component quoting **and** outer JSON-string quoting.
|
|
Identifiers use keywords/backtick rules. All Host strings use standard JSON
|
|
escapes plus DEL/C1, Unicode line/paragraph separator and bidi-control escapes.
|
|
No normalization, case conversion, URL encoding or input mutation occurs.
|
|
- Documentation summaries are displayed in their prescribed positions, including
|
|
return documentation between `)` and `->`. Details are not displayed. Host text
|
|
is data, not renderer instructions or markup; quoting alone is not a prompt
|
|
injection defense. AI adapters must preserve the trust boundary.
|
|
- Object ref/validator, children, domain data and Interface scope_ref/validator
|
|
are not signature content. No annotations are inferred from operation names.
|
|
|
|
The render functions return `Result<String, RenderError>`; token generation
|
|
returns `Result<Vec<Token>, RenderError>`. Neither returns partial signatures. Resource checks happen first, followed by existing Protocol validators:
|
|
Object shape and reference uniqueness, or Interface reference shape plus descriptor
|
|
structure, duplicate names, unknown Named references and cycles. Descriptor format
|
|
is checked after semantics: valid but unsupported formats return
|
|
`UnsupportedDescriptorFormat`, distinct from `InvalidDescriptor`. Supported format
|
|
is exactly `wip-interface/1`. Other validation errors preserve the Protocol's typed
|
|
`ValidationError` (also exposed as the error source).
|
|
|
|
Bounded input limits are **1 MiB** of total string/opaque byte data, **16,384**
|
|
declarations/fields/parameters/cases/type expressions/references, **256** named type
|
|
declarations, and **64** TypeExpr nesting levels. Details and Object runtime
|
|
metadata count toward input bytes even though they are not rendered. A limit
|
|
returns `RenderError::ResourceLimit`, never truncates or supplies `...`. Counts
|
|
bound allocation and recursive validation before those validators run; these are
|
|
library safety limits, not Protocol restrictions.
|
|
|
|
Without path/context, this library cannot attest Object scope ancestry, placement,
|
|
observation consistency, identity, membership, authorization or currentness.
|
|
Callers validate those at their observation boundary. Callers also own retrieval,
|
|
resolution, cache, selection, multiple-signature arrangement and AI/UI adaptation.
|
|
Text is display-only, not serialization, a digest, identity/cache key or call
|
|
target. No parser or round-trip API is supplied.
|
|
|
|
## Validation
|
|
|
|
`tests/signatures.rs` is an external consumer of the public APIs. Goldens cover
|
|
the spec's Object, issue.read, CloseResult, search/return documentation and
|
|
identifier examples (with permitted multiline layout), plus nested documentation.
|
|
The suite checks all TypeExpr variants/positions, ordering and empty distinctions,
|
|
keyword and Unicode names, both reference quoting layers, all required control
|
|
escapes, input immutability, metadata exclusion and typed rejection cases. Every
|
|
existing case compares the render result (including exact errors) with token
|
|
text concatenation and `to_plain_text`. Additional tests assert complete Object
|
|
token splitting, all semantic roles, keyword collisions, atomic escaped data,
|
|
all documentation positions, layout tokens and ownership beyond input lifetime.
|
|
Input budgets bound token counts per node, indentation depth and escape expansion;
|
|
token generation checks them before any rendering/validation traversal.
|
|
|
|
```console
|
|
cargo test -p wip-text-view
|
|
cargo clippy -p wip-text-view --all-targets -- -D warnings
|
|
RUSTDOCFLAGS='-D warnings' cargo doc -p wip-text-view --no-deps
|
|
```
|