Share the schema vocabulary between the hosts - #48
Merged
Merged
Conversation
Step 4 of specs/features/cli-and-host-seams.md: `crates/accent-proust-schema-config`, the declarative half of a schema written once. The key lists, the refusal of an unknown key with the path to it, and the mapping onto `Schema` were the WebAssembly host's alone; they are the crate's now, and that host reads through it -- its walker went from 438 lines to 166: a newtype over `JsValue`, and the three reasons a browser adds to a refusal. The crate walks a `Declaration` trait rather than a `Value`. Converting a host's whole configuration first would fail on a JavaScript function written under `validate` at the conversion, with a message about functions; the browser's tests pin a message about hooks, at the key. Seven methods let the walk refuse keys before it reads values, and each host keeps its own reading. `Value` implements the trait too. Errors are a path and a kind. The kind is what lets a host add the reason that is its own -- why a hook cannot cross into WebAssembly is the browser's sentence -- while the crate's own `Display` says what is true everywhere, and the wasm tests' paths and messages survive unchanged. `nodes.tag` is refused and `children` may name `tag`, as both hosts now agree.
Eight findings from the review of #48. Three were regressions against what the WebAssembly host accepted before the extraction, and each is pinned by a test now. A property whose getter throws was read as absent, so a whole block could be silently dropped -- the failure the crate exists to refuse. The trait's `get` returns `Result<Option<Self>, Error>` with an `Unreadable` kind, and the host maps a failed `Reflect::get` to it. An explicitly `undefined` variable became an error where it used to be `null`; it is `null` again, because a variable may be any value and `user: session?.user` means that. And a schema object was accepted only if plain, because the value converter's strictness was reused for key-by-key reading; any object but an array or a function is a schema now, and the plain-object question stays the converter's. `Expected` carries the shape found as well as the shape wanted, so the message says "expected an object, not a string" and a host can match on a function wherever one was written. `Error::explained` appends a host's reason to the vocabulary's own sentence, so the browser's three reasons no longer retype it -- and it gained back the two the extraction had lost, for a function in a value position and a pattern in `matches`. A non-string `errorLevel` is a wrong shape, not the unknown name `""`. The changelog names the one input that narrowed, a `type` nested inside a list; the members are counted as members rather than as three.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Step 4 of
specs/features/cli-and-host-seams.md:crates/accent-proust-schema-config, the declarative half of a schema written once, and the WebAssembly host moved onto it in the same PR — extracting without moving the only existing consumer would have left two copies, which is the state this step exists to prevent. Conformance holds at95 green, 10 annotated, 0 failing (of 105).What the crate is
The key lists (
TOP_LEVEL,SCHEMA_KEYS,ATTRIBUTE_KEYS,SLOT_KEYS), the refusal of an unknown key with the path to it, and the mapping from a declaration ontoSchema,SchemaAttribute,SchemaSlot,ValidationType,RenderPolicy,ErrorLevelandSchemaMatches.declare(&root) -> Result<Declared, Error>;Declared::apply(&mut MapSchemaSource) -> Option<Variables>does the one thing both hosts do identically — merge over the built-ins, a redeclared key keeping its position and taking the new value, which is what the corpus's fence-hook case depends on.The one design decision (decision 10 in the spec)
The crate walks a
Declarationtrait, not aValue. Converting a host's whole configuration into the library'sValuefirst would fail on a JavaScript function written undervalidateat the conversion, with a message about functions — and the wasm tests pin a message about hooks, at the key. Seven methods (shape,as_bool,as_str,keys,get,items,to_value) let the walk refuse keys before it reads any value, and convert a value only where the vocabulary carries one through (default,variables). Each host keeps its own reading, which is what the spec's "each keeps only its own deserialisation" meant.Valueimplements the trait too, so the crate's own tests need no host.Errors
Error { path, kind }with a#[non_exhaustive] ErrorKind. The crate'sDisplaysays what is true everywhere; a host matches the kind to add the reason that is its own. The wasm host does exactly that fortransform/validate,functionsandpartials, and every path and message its tests assert survives unchanged —config.tags.callout.validate+ "code does not cross",config.tags.callout.attributes.type.type+"Str", the node-name list, "looked up by its name", "regular expression",config.variables.when.The wasm host
config.rsgoes from 438 lines to 166: a newtypeJs(JsValue)implementing the trait,buildin six lines, andexplain.path.rsis gone (the sharedPathhas the same scheme);value.rsreturns the sharedErrorand drops its ownvariables(). Unions are one level deep now — a list inside a list is refused — where the old walker recursed; upstream never nests, and the walk is otherwise as deep as the document chose.Tests
16 in
crates/accent-proust-schema-config/tests/declare.rs, overValue: every key mapped, each level's unknown-key path and expected list,nodes.tagrefused,children: ["tag"]allowed, unknown node type with the list, unknown attribute type with its path, one-level unions,matchesas a pattern,render: trueon a schema, wrong shapes, variables carried whole,NoCounterpart's message,applykeeping position and taking the value, and the key lists as the format. None panics. The wasm suite (11 + 17) passes unchanged.Workspace
Member, not default member, with its own
Schema configCI job, gate commands and rows in AGENT.md. Depends on the library by path alone, like the other members.Review fixes
Eight findings from
/code-review, all taken; three were regressions against the wasm host's previous behaviour and each is pinned by a new wasm test:Declaration::getnow returnsResult<Option<Self>, Error>, with anUnreadablekind; the host maps a failedReflect::getto it. Absent and unreadable are different answers, and the second used to read as the first.undefinedvariable isnullagain, as the deletedvariables()had it.Object.create(base), a proxy. The plain-object strictness belongs to the value converter, which still applies it todefaultand variables.Expected { what, got }: the message names the shape found ("expected an object, not a string"), and the host matchesShape::Other("function")to say code does not cross — which restores the two reasons the extraction had lost (a custom attribute type, a pattern inmatches).Error::explained(reason)is the seam;explainno longer retypes the vocabulary's sentence.errorLevelis a wrong shape, not the unknown name"".typelist); AGENT.md and the root manifest count the members rather than three.