{"id":51854370,"url":"https://github.com/f-squirrel/toy-tx-proc","last_synced_at":"2026-07-23T23:30:19.031Z","repository":{"id":369535328,"uuid":"1289401387","full_name":"f-squirrel/toy-tx-proc","owner":"f-squirrel","description":null,"archived":false,"fork":false,"pushed_at":"2026-07-05T19:00:53.000Z","size":45,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-05T20:18:23.503Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/f-squirrel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-04T17:47:12.000Z","updated_at":"2026-07-05T19:00:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/f-squirrel/toy-tx-proc","commit_stats":null,"previous_names":["f-squirrel/toy-tx-proc"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/f-squirrel/toy-tx-proc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f-squirrel%2Ftoy-tx-proc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f-squirrel%2Ftoy-tx-proc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f-squirrel%2Ftoy-tx-proc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f-squirrel%2Ftoy-tx-proc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/f-squirrel","download_url":"https://codeload.github.com/f-squirrel/toy-tx-proc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/f-squirrel%2Ftoy-tx-proc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35820855,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-23T02:00:06.683Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-23T23:30:18.416Z","updated_at":"2026-07-23T23:30:19.020Z","avatar_url":"https://github.com/f-squirrel.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Toy Payments Engine\n\nA Rust CLI that reads transaction rows from a CSV file, applies them to client\naccounts, handles disputes/resolves/chargebacks, and writes final account\nbalances as CSV.\n\n```bash\ncargo run -- transactions.csv \u003e accounts.csv\n```\n\nThe input file is the first and only CLI argument. `stdout` is reserved for the\naccount CSV. Malformed rows and rule violations are logged to `stderr` and\nskipped so valid later rows can still be processed.\n\n## Basics\n\nThe project is a standard Cargo crate and exposes the required interface:\n\n```bash\ncargo build\ncargo run -- transactions.csv \u003e accounts.csv\n```\n\nInput columns are `type`, `client`, `tx`, and `amount`. Output columns are\n`client`, `available`, `held`, `total`, and `locked`.\n\n## Completeness\n\nAll required transaction types are implemented:\n\n| Type         | Behavior                                                                                              |\n|--------------|-------------------------------------------------------------------------------------------------------|\n| `deposit`    | Increases `available`; `total` increases because it is derived from `available + held`.               |\n| `withdrawal` | Decreases `available` if sufficient funds exist; otherwise the row is skipped.                        |\n| `dispute`    | References an earlier deposit, moves its amount from `available` to `held`, leaves `total` unchanged. |\n| `resolve`    | Releases a currently disputed deposit, moving its amount from `held` back to `available`.             |\n| `chargeback` | Finalizes a dispute, removes the held amount from the account, and locks the account.                 |\n\n## Correctness\n\nMoney uses `rust_decimal::Decimal`, not floating point. Balance arithmetic is\nchecked and applied atomically: if an operation would overflow, balances and\nstored transaction state are left unchanged and the row is skipped.\n\n`total` is never stored separately; it is derived as `available + held` when\nrendering output. This prevents `total`, `available`, and `held` from drifting\nout of sync.\n\nThe type system carries part of the validation:\n\n- `ClientId` and `TxId` are distinct newtypes over `u16` and `u32`.\n- `Operation` is a tagged enum, so deposits and withdrawals require an amount,\n  while dispute lifecycle rows are represented without one.\n- Stored transactions are tagged by lifecycle state:\n  `Undisputed`, `Disputed`, or `ChargedBack`.\n\n## Safety and Robustness\n\nThe production code uses no `unsafe`. Bad input is handled as data, not as a\nreason to panic:\n\n- malformed CSV rows, unknown transaction types, missing deposit/withdrawal\n  amounts, negative amounts, duplicate accepted transaction IDs, insufficient\n  funds, client mismatches, and invalid dispute transitions are skipped and\n  logged to `stderr`;\n- diagnostics never go to `stdout`, so redirected output remains valid CSV;\n- missing CLI arguments, too many CLI arguments, unreadable input files, and\n  output write failures are fatal and exit non-zero.\n\n## Efficiency\n\nInput processing is streaming. The CSV parser yields one row at a time and the\nengine applies it immediately; the full input file is never loaded into memory.\nThe reader accepts any `std::io::Read`, so the same library path could be wired\nto a file, stdin, an in-memory buffer, or a TCP stream.\n\nThe engine keeps only the state needed after each row:\n\n- one account record per client;\n- a transaction map for accepted deposits and withdrawals, keyed by `tx`, so\n  future dispute/resolve/chargeback rows can find the original amount and owner.\n\nThe transaction map is the main memory tradeoff. It is necessary because dispute\nrows do not carry an amount. If this were bundled into a long-running server\nprocessing many concurrent streams, each stream would need its own engine state\nor an external store; for production-scale retention, the transaction map should\nbe backed by a database, bounded cache, or partitioned ledger rather than an\nunbounded in-process `HashMap`.\n\n## Maintainability\n\nThe binary is thin and the processing logic is reusable:\n\n- `src/main.rs` handles CLI arguments, logging, opening the input file, and\n  writing to stdout.\n- `src/lib.rs` exposes reusable modules.\n- `src/model.rs` defines transaction, account, and output-record types.\n- `src/engine.rs` owns the account state machine.\n- `src/io.rs` handles CSV parsing and writing.\n\nThe core engine is independent of files and stdout, which keeps business rules\ntestable without spawning the CLI. Comments are reserved for invariants and\nnon-obvious choices rather than restating each line of code.\n\n## Assumptions\n\nThese are the assumptions used by this implementation, including both the\nchallenge-provided assumptions and project-specific decisions where the\nrequirements leave room for interpretation.\n\n1. Each client has a single asset account.\n2. Accounts are created when a syntactically valid transaction row references a\n   new client. Parse-level malformed rows do not create accounts; a first\n   withdrawal that fails for insufficient funds may still leave a zero-balance\n   account row because the client was referenced.\n3. Client IDs are valid `u16` values and transaction IDs are valid `u32` values.\n4. Rows are processed chronologically in file order; client IDs and transaction\n   IDs do not need to be sorted.\n5. Output row order is not semantically important, but this implementation sorts\n   clients by ID for deterministic output.\n6. Output always includes the account CSV header, even when the input is empty\n   or every data row is skipped and there are no account rows.\n7. Input amounts are expected to have at most four decimal places. Output is\n   rendered with at most four decimal places and at least one decimal digit.\n8. Deposits and withdrawals require an `amount`. Dispute, resolve, and\n   chargeback rows get their amount from the referenced stored transaction.\n9. Negative deposit and withdrawal amounts are rejected. Zero-value deposits and\n   withdrawals are accepted as no-ops for balance math.\n10. Only deposits are disputable. Withdrawals are stored only to detect duplicate\n   accepted transaction IDs; a dispute referencing a withdrawal is ignored.\n11. A dispute/resolve/chargeback must refer to a transaction owned by the same\n    client. Cross-client references are ignored.\n12. A transaction can have only one active dispute at a time. After a resolve it\n    can be disputed again. After a chargeback it is terminal.\n13. A chargeback locks the account. Locked accounts reject later deposits,\n    withdrawals, and new disputes. Resolves and chargebacks on already-disputed\n    transactions can still proceed. Once locked, the account remains locked.\n14. Disputing a deposit can make `available` negative if the client has already\n    spent or withdrawn part of that deposit. This preserves the full hold amount\n    for the dispute.\n15. A chargeback of a disputed deposit can make `total` negative in the fraud\n    scenario where the client has already withdrawn funds from the disputed\n    deposit.\n16. If a duplicate accepted transaction ID appears, the later deposit or\n    withdrawal is ignored. A failed row does not reserve its transaction ID.\n17. Unknown transaction types, malformed rows, and rule violations are partner\n    or input errors; they are skipped rather than aborting the whole file.\n18. Whitespace around CSV fields is accepted.\n\n## Testing\n\nRun the full verification suite with:\n\n```bash\ncargo build\ncargo test\ncargo fmt --check\ncargo clippy -- -D warnings\n```\n\nThe project is tested at three levels:\n\n- Unit tests in `src/engine.rs` cover account state transitions, insufficient\n  funds, duplicate IDs, cross-client disputes, withdrawal disputes, redispute\n  rules, locked accounts, negative amounts, and atomic overflow handling.\n- Unit tests in `src/io.rs` and `src/model.rs` cover CSV deserialization,\n  required amounts, unknown transaction types, output formatting, derived\n  totals, and checked arithmetic.\n- Integration tests in `tests/golden.rs` auto-discover every\n  `tests/cases/*.input.csv` file, run the compiled binary end to end, and compare\n  stdout directly with the matching `*.expected.csv` file, ignoring only a\n  trailing-newline difference.\n\nThere are 30 golden cases documented in `tests/cases/README.md`. They include\nthe sample deposit/withdrawal scenario, all dispute lifecycle operations,\ninsufficient funds, missing and unknown rows, whitespace and precision, duplicate\nIDs, cross-client mismatches, account locking, negative balances after fraud,\nand zero/negative amount handling.\n\n`tests/cli.rs` covers CLI behavior that golden CSV cases do not: missing\narguments, too many arguments, missing files, stderr logging, and ensuring that\nwarnings never pollute stdout.\n\n## Dependencies\n\n| Crate                 | Purpose                                            |\n|-----------------------|----------------------------------------------------|\n| `serde`               | CSV record serialization/deserialization.          |\n| `csv`                 | Streaming CSV reader and writer.                   |\n| `rust_decimal`        | Exact decimal arithmetic for monetary values.      |\n| `thiserror`           | Typed errors for skipped rows and output failures. |\n| `log` + `env_logger`  | Diagnostics routed to `stderr`.                    |\n| `rust_decimal_macros` | Decimal literals in tests.                         |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ff-squirrel%2Ftoy-tx-proc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ff-squirrel%2Ftoy-tx-proc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ff-squirrel%2Ftoy-tx-proc/lists"}