formats: Xml.encode writes a typed value as an XML document (step 3b of #78) #605
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Step 3b of the epic #78 (
std/xml). Steps 1 (#429, tree, reader, writer), 2 (#552, internal subset) and 3a (#570,Xml.decode) have landed onmain. Writing is the other half of typed binding: request bodies of S3 (CompleteMultipartUpload,Delete) and WebDAV (#20, #67) are XML that a program builds from a type.Goal
Xml().encode(value)writes anyEncodevalue as an XML document with the default mapping, the inverse ofXml.decode: a record is an element, a field is a child element.Source
XmlEncode), slice 8 (section 14), decisions 15 to 17 in section 16.std/toml/src/encoder.trb(TomlEncoder: anEncoderthat builds the tree, then the writer writes it) andToml.encode/Toml.valueare the layout model;std/xml/src/writer.trbwrites theXmlNodetree.Design record point
ENCODING.md slice 8, section 9. Touches the seed? No: the compiler does not import
std/xml, and no std name is looked up by string.Decisions (decided by the loop)
XmlEncoder with Encoder(std/xml/src/encoder.trb) builds anXmlNode.Documentwith one root element.Xml.encode<Value: Encode>(value): Result<String, XmlError>runs it and the existing writer (soindentapplies);Xml.tree<Value: Encode>(value): Result<XmlNode, XmlError>answers the tree, likeToml.value.Xmlgets no new option:naming(field names,Naming.PascalCasefor S3) applies to the way out too.Noneis anXmlError. No XML declaration is written (the tree path writes one withXmlDeclaration).Boolastrue/false, whole numbers andFloat/Decimalas their text,Stringas it is (the writer escapes),Bytesas base64 (likeTomlEncoder). A field that holdsNoneis left out, whichXml.decodereads back asNoneor the default. A nested record is a child element holding its fields. A sequence field is one child element per item, all with the field's name (the inverse of the repeated-children rule); an empty list writes nothing.XmlError. A record that announces no field (a wrapper) is its one value, and a variant is written in the shapeValueDecoderreads back, asTomlEncoder.variantNodedoes. Round trip (decode of encode) is pinned in tests for records, options, lists and nested records; a map or a variant that does not read back is documented, not forced.XmlErrorwith line 0 (the message names the chain of fields, likedecodeFailed): a root that is no record, a sequence directly inside a sequence,Noneinside a sequence, a map key that is no name, and a character XML 1.0 forbids (already refused by the writer).Format<XmlError>is not in this step. The trait (STREAMS.md section 10) isencodeAll(value): Bytesandencoded(): Stage<Item, Bytes>and says they never fail; JSON can, because it degrades (NaN becomesnull).Xml.encodecan fail on data (a NUL character in a string, a map key), and PANICS.md forbids a panic for a failure that depends on the data. So the trait needs a decision of its own (an additive failing twin with a default, or lossy replacement inencodeAllonly). That is step 3c, filed just in time after this one lands.Acceptance criteria
Xml().encode(value),Xml().tree(value)andXmlEncoderexist with the documented behaviour, each failure above is theXmlErrordescribed.std/xml/tests: a record of scalars;Optionleft out; aList<T>as repeated elements and an empty one; a nested record;Bytes;naming(PascalCaseandSnakeCase);indent; escaping of<,&and"in text; each unwritable case with its message;decode<T>(encode(v)) == vfor a type that has strings, numbers,Bool,Option, a list and a nested record.CompleteMultipartUploadrequest body (Partelements withPartNumberandETag,Naming.PascalCase) and compares the exact text; the xmlns attribute is out of scope (step 4) and the test says so.docs/standard-library/xml.mddocumentsencode,treeandXmlEncoderand the mapping; thestd/xmlpage summary stays within 240 characters (thedocs checklimit that #573 hit),lib.trbno longer says "There is noencodeyet"; ENCODING.md section 14 (row 8: step 3b done,Format<XmlError>is 3c) and section 16 (new decision point 18) record the step;skills/regenerated.sh tools/gates.sh agreen in forge CI;tools/check-commits.shclean;torb format --checkon the touched files.Size
One PR, two Conventional Commits:
feat(std/xml): XmlEncoder builds the document of a typed value(encoder and its unit tests) andfeat(std/xml): Xml.encode and Xml.tree write a typed value(the two methods, round-trip and S3 tests, docs, ENCODING.md, skills).Dependencies
Part of #78 (step 3b); needs #570 (landed as
cd8da8b5). Followed by step 3c (Format<XmlError>), then the mapping DSL (step 4); unblocks the request side of #20 and #67.std/xml#78std/xml#78[loop:torbscript] claim implement by session:loop-torbscript-20261030 until 2026-10-30T05:37Z
[loop:torbscript] checkpoint: branch loop/605-xml-encode pushed (2 commits: XmlEncoder; Xml.encode/tree + tests, docs, ENCODING.md, skills). Opening PR next.
[loop:torbscript] checkpoint: PR #637 is open, Argus requested, waiting for CI.
[loop:torbscript] checkpoint: PR #637 open, Argus requested. CI so far green: commit messages, pdisc, tier B, bootstrap, release-sync, cross-compiles. Still pending: gates, tier A, pack the seed, archives, playground.
[loop:torbscript] release implement: PR #637 opened, head
d6e06f0540; CI partly green (gates and tier A still pending). Open point for review: aBytesfield encoded through the derivedEncodecomes out as a list of ints, not base64 as the decisions say; base64 applies only wherebytes()is called directly. Either the derive path is fixed in this PR or the deviation is recorded. The item is in review; CI and Argus decide.