Files
2026-10-09 00:35:06 +09:00

10 KiB

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.

[dependencies]
wip-text-view = "0.2.0"
wip-protocol = "0.2.0"

Single-entity API

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

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.

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