Skip to content

[2.x] Format decoded nodes into formatted node trees - #1218

Merged
lorisleiva merged 1 commit into
mainfrom
10-08-format_decoded_nodes_into_formatted_node_trees
Oct 9, 2026
Merged

lorisleiva merged 1 commit into
mainfrom
10-08-format_decoded_nodes_into_formatted_node_trees

Conversation

@lorisleiva

@lorisleiva lorisleiva commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

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.

const formatted = formatDecodedNode(account, { formatAddress: address => names.get(address) ?? address });
formatted.text; // "Amount: 1.5 USDC, Owner: Alice, Config: (Fee: 25 bps)"

assertIsFormattedNode(formatted.data, 'structTypeNode');
formatted.data.fields[0]; // { label: 'Amount', text: '1.5 USDC', skip: 'never', flatten: false, type: … }

getDecodedNodeAtPath(formatted, pathString('config.fee'))?.text; // "25 bps"
  • Text: every node has a 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 skipped always are left out, and flattened ones are replaced by their prefixed fields.
  • Display attributes: struct fields carry their label, skip, flatten and flattenPrefix, and enum variants their label and skipInnerData, with defaults. Laying the tree out, e.g. as nested rows, is left to the caller.
  • Degraded values: amounts whose decimals cannot be resolved are written as their raw value and marked degraded, as is every node whose text includes them.
  • Overrides: the formatters of every leaf kind can be overridden, e.g. formatBoolean: boolean => (boolean.value ? 'Yes' : 'No'). Public keys go through formatAddress.
  • Paths: getDecodedNodeAtPath finds the node a path expression points to, in decoded or formatted nodes, e.g. to fill an interpolated intent.
  • Public keys: DecodedPublicKeyTypeNode's value is now typed as an Address.

@lorisleiva lorisleiva changed the title Format decoded nodes into formatted node trees [2.x] Format decoded nodes into formatted node trees Oct 8, 2026
@changeset-bot

changeset-bot Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: df5a57f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@lorisleiva

Copy link
Copy Markdown
Member Author

@trevor-cortex

@trevor-cortex trevor-cortex left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.md requires one for any user-facing change, and @codama/dynamic-codecs is public and versions independently (it's outside the fixed group). This is a new API surface, so a minor bump via npx changeset add seems right. Worth mentioning the DecodedPublicKeyTypeNode value type change in it too, since anyone building decoded nodes by hand with a plain string will now need address(...).

Minor / optional

  • The formatEnum override only applies when formatting through an enumTypeNode; a bare enumVariantTypeNode computes its label directly (inline comment on the case). Low stakes, but worth either documenting or unifying.
  • embed wraps empty composite text as (), e.g. Items: () for an empty nested array or struct, whereas formatEnumVariant already special-cases data.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 DecodedPublicKeyTypeNode with a plain string literal — tsc across the monorepo should catch it, but worth a glance.
  • getDecodedNodeAtPath deliberately returns undefined for 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 in text — the formatter has no notion of what was injected, and the README is explicit that only always is excluded. That's a reasonable boundary to leave to callers.

Comment thread packages/dynamic-codecs/src/formatted.ts
Comment thread packages/dynamic-codecs/src/formatted.ts
@lorisleiva
lorisleiva force-pushed the 10-08-format_decoded_nodes_into_formatted_node_trees branch from f7b49a9 to 3c31a64 Compare October 9, 2026 08:54

@trevor-cortex trevor-cortex left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review

Re-fetched everything. Both points from my previous pass are addressed:

  • Enum variant label: getEnumVariantLabel now lives in utils.ts (née bytes.ts) and is shared by formatEnum and the bare enumVariantTypeNode case, so the default path is identical, and the formatEnum option doc states that it only applies through enums. The new "same default label as their enum" test pins it down. I checked for other ./bytes importers — only codecs.ts and format.ts, both updated.
  • () for empty nested containers: now documented as deliberate in the README, the formatDecodedNode JSDoc 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/*.md in this PR. Since this is stacked on 10-08-add_formatters_for_booleans_bytes_enums_and_public_keys, it may be that a single @codama/dynamic-codecs changeset lower in the stack covers the whole formatting feature — if so, ignore this. Otherwise one is needed per CONTRIBUTING.md, ideally also mentioning the DecodedPublicKeyTypeNode['value'] → Address tightening.

Nit (non-blocking)

  • In formatted.test.ts, the test named "it formats enum variants on their own" actually formats the enum and reads formatted.variant, so it exercises the enumTypeNode path, not the standalone variant path (the test just above it does). Either rename it or call formatDecodedNode(decoded.variant) so the name matches.

Approving on the assumption the changeset is handled in the stack.

@lorisleiva
lorisleiva force-pushed the 10-08-add_formatters_for_booleans_bytes_enums_and_public_keys branch from 8ccd675 to 9d0f436 Compare October 9, 2026 09:08
@lorisleiva
lorisleiva force-pushed the 10-08-format_decoded_nodes_into_formatted_node_trees branch 2 times, most recently from 767c109 to f8fc4b9 Compare October 9, 2026 09:11
@lorisleiva
lorisleiva force-pushed the 10-08-add_formatters_for_booleans_bytes_enums_and_public_keys branch from 9d0f436 to f248caa Compare October 9, 2026 09:11
@lorisleiva
lorisleiva force-pushed the 10-08-format_decoded_nodes_into_formatted_node_trees branch from f8fc4b9 to f40518b Compare October 9, 2026 09:12
@lorisleiva
lorisleiva force-pushed the 10-08-add_formatters_for_booleans_bytes_enums_and_public_keys branch 2 times, most recently from bd6992d to a4e66a2 Compare October 9, 2026 09:13
@lorisleiva
lorisleiva force-pushed the 10-08-format_decoded_nodes_into_formatted_node_trees branch from f40518b to 14de7dc Compare October 9, 2026 09:13
@lorisleiva
lorisleiva force-pushed the 10-08-add_formatters_for_booleans_bytes_enums_and_public_keys branch from a4e66a2 to 4b6216e Compare October 9, 2026 09:14
@lorisleiva
lorisleiva force-pushed the 10-08-format_decoded_nodes_into_formatted_node_trees branch from 14de7dc to 00533d8 Compare October 9, 2026 09:14
@lorisleiva
lorisleiva force-pushed the 10-08-add_formatters_for_booleans_bytes_enums_and_public_keys branch from 4b6216e to b612a56 Compare October 9, 2026 09:15
@lorisleiva
lorisleiva force-pushed the 10-08-format_decoded_nodes_into_formatted_node_trees branch from 00533d8 to 9631f7d Compare October 9, 2026 09:15
@lorisleiva
lorisleiva force-pushed the 10-08-add_formatters_for_booleans_bytes_enums_and_public_keys branch from b612a56 to f524ac1 Compare October 9, 2026 09:16
@lorisleiva
lorisleiva force-pushed the 10-08-format_decoded_nodes_into_formatted_node_trees branch 2 times, most recently from e2c4639 to 537cb6f Compare October 9, 2026 09:17
@lorisleiva
lorisleiva force-pushed the 10-08-add_formatters_for_booleans_bytes_enums_and_public_keys branch 2 times, most recently from 385c7a6 to 7557a83 Compare October 9, 2026 09:17
@lorisleiva
lorisleiva force-pushed the 10-08-format_decoded_nodes_into_formatted_node_trees branch 2 times, most recently from 1c12de9 to fd23a47 Compare October 9, 2026 09:18
@lorisleiva
lorisleiva force-pushed the 10-08-add_formatters_for_booleans_bytes_enums_and_public_keys branch 2 times, most recently from 7a72c81 to aba69a3 Compare October 9, 2026 09:18
@lorisleiva
lorisleiva force-pushed the 10-08-format_decoded_nodes_into_formatted_node_trees branch 2 times, most recently from 0ec09b4 to fd60dd3 Compare October 9, 2026 09:19
@lorisleiva
lorisleiva force-pushed the 10-08-add_formatters_for_booleans_bytes_enums_and_public_keys branch 2 times, most recently from 79bc588 to 1ae87a5 Compare October 9, 2026 09:19
@lorisleiva
lorisleiva force-pushed the 10-08-format_decoded_nodes_into_formatted_node_trees branch 2 times, most recently from 03dcf54 to 35c299a Compare October 9, 2026 09:20
@lorisleiva
lorisleiva force-pushed the 10-08-add_formatters_for_booleans_bytes_enums_and_public_keys branch from 1ae87a5 to d09bf71 Compare October 9, 2026 09:20
Base automatically changed from 10-08-add_formatters_for_booleans_bytes_enums_and_public_keys to main October 9, 2026 09:20
@lorisleiva
lorisleiva force-pushed the 10-08-format_decoded_nodes_into_formatted_node_trees branch from 35c299a to df5a57f Compare October 9, 2026 09:20
@lorisleiva
lorisleiva marked this pull request as ready for review October 9, 2026 09:20
@lorisleiva
lorisleiva merged commit cfde14c into main Oct 9, 2026
0 of 2 checks passed
@lorisleiva
lorisleiva deleted the 10-08-format_decoded_nodes_into_formatted_node_trees branch October 9, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants