Skip to content

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

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions