Skip to content

Parsers silently produce empty strings for missing/non-string required fields #50

@gyorgybalazsi

Description

@gyorgybalazsi

Follow-up from Copilot review of #46.

Context

Every flat-event parser extracts required fields with .as_str().unwrap_or(""). If the server response contains a matching event envelope but is missing or has non-string values for contractId, templateId, createdEventBlob, or fields inside createArgument, the parsers silently produce empty strings and continue. Downstream callers then fail with misleading errors — e.g., a missing contractId from create_deposit_account produces:

Created DepositAccount  not found in active contracts

…instead of a clear parse-time error like \contractId` missing or not a string`.

This pattern is pre-existing — the tree-shape parsers used the same unwrap_or("") shape, and #41 / #46 carried it forward unchanged. It is not a regression from either of those PRs, but it is a real shipping-confidence issue.

Copilot flagged it on PR #46:

Affected sites (all 7 parsers)

  • src/mint_redeem/mint.rs:114 parse_created_deposit_account_cidcontractId
  • src/mint_redeem/redeem.rs:138 parse_created_withdraw_account_cidcontractId
  • src/mint_redeem/redeem.rs:165 parse_submit_withdraw_responsecontractId, plus createdEventBlob and createArgument go into JsActiveContract without validation
  • src/transfer.rs:607 parse_transfer_response_valuesenderChangeCids filter-maps via as_str() (drops non-strings silently); output.value.transferInstructionCid uses as_str() early-return is OK but doesn't distinguish missing-vs-not-yet
  • src/consolidate.rs parse_consolidate_response — extracts receiverHoldingCids with similar lenient pattern
  • src/split.rs parse_split_responseoutput_cid returns from as_str().ok_or(...) (already strict); senderChangeCids lenient
  • src/credentials.rs:448 parse_accept_credential_offer_responsecontractId, createdEventBlob, createArgument

The strictness varies site-by-site today. A consistent treatment would be helpful.

Proposal

Replace as_str().unwrap_or("") with as_str().ok_or_else(|| format!(\"{} missing or not a string in <Event>\", field)) for every required field across all 7 parsers. Specifically:

  • contractId is always required → strict.
  • templateId is used in suffix-match — empty matches nothing, so the existing pattern accidentally produces correct behavior. Still strict is clearer.
  • createdEventBlob — sometimes empty in practice (Canton omits it when irrelevant), so it should remain unwrap_or(\"\"). Document inline.
  • createArgument is required for the from_active_contract calls in submit_withdraw and accept_credential_offer.

Plus: extend the fixture tests in each mod parser_tests block with one more case: malformed_event_missing_required_field_returns_err — fixture has the matching template but a missing contractId, parser should Err, not Ok(\"\").

Risk

Small behavioral change: callers that previously received a misleading downstream error now receive a clearer parse-time error. No code path that currently returns Ok would start returning Err for a previously-working response — only malformed responses change behavior.

Out of scope

Metadata

Metadata

Assignees

No one assigned

    Labels

    maintenanceMaintenance, deps, and upgrade work

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions