Repository navigation
[2.x] Format decoded nodes into formatted node trees - #1218
Conversation
|
trevor-cortex
left a comment
There was a problem hiding this comment.
Summary
Adds formatDecodedNode, which walks a decoded node tree and returns the same tree with text / degraded on every node, plus inlined display attributes (label, skip, flatten, flattenPrefix on struct fields; label, skipInnerData on enum variants). Leaf formatters are overridable per kind via options; public keys go through the existing formatAddress. Also adds getDecodedNodeAtPath for resolving PathString expressions against decoded or formatted nodes, and tightens DecodedPublicKeyTypeNode['value'] to Address (which lets formatPublicKey drop its cast).
The implementation is clean and the design holds together well: formatted nodes are structural supertypes of decoded nodes so everything in decoded.ts keeps working, degraded propagation follows "is it in the text" rather than "is it in the tree" (and the tests pin that down for skip: 'always' and skipInnerData), and FormattedNodeMap + kind satisfies never keep the switch exhaustive. I checked the link-transparency question — visitDefinedTypeLink unwraps to the linked type at decode time, so flatten and embed correctly see nested linked structs as structTypeNode without special handling.
Things to address
- Missing changeset.
CONTRIBUTING.mdrequires one for any user-facing change, and@codama/dynamic-codecsis public and versions independently (it's outside thefixedgroup). This is a new API surface, so aminorbump vianpx changeset addseems right. Worth mentioning theDecodedPublicKeyTypeNodevalue type change in it too, since anyone building decoded nodes by hand with a plainstringwill now needaddress(...).
Minor / optional
- The
formatEnumoverride only applies when formatting through anenumTypeNode; a bareenumVariantTypeNodecomputes its label directly (inline comment on thecase). Low stakes, but worth either documenting or unifying. embedwraps empty composite text as(), e.g.Items: ()for an empty nested array or struct, whereasformatEnumVariantalready special-casesdata.text !== ''. Might be intentional as a signal of emptiness — just flagging the asymmetry (inline comment).
Notes for subsequent reviewers
- Verify no other workspace package (e.g. dynamic-parsers or its tests) constructs
DecodedPublicKeyTypeNodewith a plain string literal —tscacross the monorepo should catch it, but worth a glance. getDecodedNodeAtPathdeliberately returnsundefinedfor out-of-range array/set indices but throws for out-of-range tuple indices and unknown struct fields (schema violations vs. data-dependent misses). Documented and tested; just don't mistake it for an inconsistency.skip: 'whenInjected'fields are included intext— the formatter has no notion of what was injected, and the README is explicit that onlyalwaysis excluded. That's a reasonable boundary to leave to callers.
f7b49a9 to
3c31a64
Compare
trevor-cortex
left a comment
There was a problem hiding this comment.
Re-review
Re-fetched everything. Both points from my previous pass are addressed:
- Enum variant label:
getEnumVariantLabelnow lives inutils.ts(néebytes.ts) and is shared byformatEnumand the bareenumVariantTypeNodecase, so the default path is identical, and theformatEnumoption doc states that it only applies through enums. The new "same default label as their enum" test pins it down. I checked for other./bytesimporters — onlycodecs.tsandformat.ts, both updated. ()for empty nested containers: now documented as deliberate in the README, theformatDecodedNodeJSDoc and a dedicated test. Fine by me.
No new issues in the code. The remaining changes (utils.ts rename, formatPublicKey cast removal) are mechanical.
Still open
- Changeset. There's still no
.changeset/*.mdin this PR. Since this is stacked on10-08-add_formatters_for_booleans_bytes_enums_and_public_keys, it may be that a single@codama/dynamic-codecschangeset lower in the stack covers the whole formatting feature — if so, ignore this. Otherwise one is needed perCONTRIBUTING.md, ideally also mentioning theDecodedPublicKeyTypeNode['value']→Addresstightening.
Nit (non-blocking)
- In
formatted.test.ts, the test named "it formats enum variants on their own" actually formats the enum and readsformatted.variant, so it exercises theenumTypeNodepath, not the standalone variant path (the test just above it does). Either rename it or callformatDecodedNode(decoded.variant)so the name matches.
Approving on the assumption the changeset is handled in the stack.
8ccd675 to
9d0f436
Compare
767c109 to
f8fc4b9
Compare
9d0f436 to
f248caa
Compare
f8fc4b9 to
f40518b
Compare
bd6992d to
a4e66a2
Compare
f40518b to
14de7dc
Compare
a4e66a2 to
4b6216e
Compare
14de7dc to
00533d8
Compare
4b6216e to
b612a56
Compare
00533d8 to
9631f7d
Compare
b612a56 to
f524ac1
Compare
e2c4639 to
537cb6f
Compare
385c7a6 to
7557a83
Compare
1c12de9 to
fd23a47
Compare
7a72c81 to
aba69a3
Compare
0ec09b4 to
fd60dd3
Compare
79bc588 to
1ae87a5
Compare
03dcf54 to
35c299a
Compare
1ae87a5 to
d09bf71
Compare
35c299a to
df5a57f
Compare

This PR adds
formatDecodedNode, which formats a decoded node of any kind, e.g. a whole account, into a formatted node tree. Formatted nodes extend their decoded nodes, so they keep the same shape and child names, plus the text and display attributes needed to present them.text. Leaves use the formatter of their kind, and containers join the text of their children, e.g.Limit (Price: 100)for an enum with data. Struct fields skippedalwaysare left out, and flattened ones are replaced by their prefixed fields.label,skip,flattenandflattenPrefix, and enum variants theirlabelandskipInnerData, with defaults. Laying the tree out, e.g. as nested rows, is left to the caller.decimalscannot be resolved are written as their raw value and markeddegraded, as is every node whose text includes them.formatBoolean: boolean => (boolean.value ? 'Yes' : 'No'). Public keys go throughformatAddress.getDecodedNodeAtPathfinds the node a path expression points to, in decoded or formatted nodes, e.g. to fill an interpolated intent.DecodedPublicKeyTypeNode's value is now typed as anAddress.