Skip to content

feat(codec): implement Candid schema parser and codegen renderer#186

Open
b3hr4d wants to merge 1 commit into
mainfrom
feat/candid-codec-slices-2-3
Open

feat(codec): implement Candid schema parser and codegen renderer#186
b3hr4d wants to merge 1 commit into
mainfrom
feat/candid-codec-slices-2-3

Conversation

@b3hr4d

@b3hr4d b3hr4d commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

This PR implements Slices 2 & 3 of the Candid Codec API:

  1. Slice 3 (Parser Schema):

    • Exposes directly from Rust WASM in @ic-reactor/parser.
    • Traverses the Candid compiler's AST recursively to output a structured JSON schema.
    • Automatically appends TS type definitions after parser compilation.
    • Adds vitest suite for parser-schema validation.
  2. Slice 2 (Codegen Renderer):

    • Implements in @ic-reactor/codegen.
    • Performs topological sorting of declarations to resolve dependency order.
    • Gracefully resolves cyclic references without infinite recursion.
    • Generates fully compliant @ic-reactor/cod schema builders and types.
  3. Playground Integration:

    • Refactors the web playground example to use the official parseDid and generateCodecDeclarations APIs.
    • Cleans up the deprecated regex parser module.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67226dcfaf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

} else {
let fields_json: Vec<String> = fields.iter().map(|f| {
let label = match &*f.id {
candid_parser::candid::types::Label::Id(id) => id.to_string(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve explicit numeric Candid field IDs

When a DID uses explicit numeric labels in a non-tuple record/variant (e.g. record { 0 : nat; name : text }), this converts Label::Id(0) to the schema name "0". The renderer then emits a normal property "0": ...; @icp-sdk/core hashes that string label instead of treating it as field id 0 (numeric IDs are represented as _0_ in the JS IDL), so the generated codec is wire-incompatible for those records/variants. Preserve the label kind or encode numeric labels in the form the IDL builder expects.

Useful? React with 👍 / 👎.

} else if (method.returns.length === 1) {
retCode = `, ${renderType(method.returns[0], " ")}`
} else {
retCode = `, c.tuple([${method.returns.map((r) => renderType(r, " ")).join(", ")}])`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Represent multi-value returns as separate returns

For methods with more than one return value, this emits a single c.tuple(...) return codec. CandidServiceCodec.toIDL() always wraps returnCodec.toIDL() in a one-element returns array, so a DID like m : () -> (nat, text) becomes [IDL.Tuple(IDL.Nat, IDL.Text)] instead of [IDL.Nat, IDL.Text]; actors generated from this codec will have the wrong Candid signature for real multi-return methods.

Useful? React with 👍 / 👎.

Comment on lines +99 to +101
if (visiting.has(name)) {
// Cycle detected: break cycle and return
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle recursive type references lazily

When a schema contains a recursive type (for example type Node = record { next : opt Node }), breaking the DFS cycle here still lets rendering emit export const Node = c.record({ next: c.opt(Node) }). That reference is evaluated while Node is in its temporal dead zone, so importing the generated module throws before any codec can be used; cycles need a lazy/recursive codec representation or should be rejected instead of sorted eagerly.

Useful? React with 👍 / 👎.

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.

1 participant