{"record":{"id":"bfd608c034326dad","repo":"nautechsystems/nautilus_trader","slug":"serialized-account-event-has-no-margins","errorCode":null,"errorMessage":"Serialized account event has no margins","messagePattern":"Serialized account event has no margins","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/infrastructure/src/sql/queries.rs","lineNumber":1118,"sourceCode":"            if !exists {\n                anyhow::bail!(\n                    \"Account event does not exist for account: {}\",\n                    account_event.account_id\n                );\n            }\n        }\n\n        let mut transaction = pool.begin().await?;\n        let event = serde_json::to_value(&account_event)\n            .map_err(|e| anyhow::anyhow!(\"Failed to serialize account event: {e}\"))?;\n        let balances = event\n            .get(\"balances\")\n            .cloned()\n            .ok_or_else(|| anyhow::anyhow!(\"Serialized account event has no balances\"))?;\n        let margins = event\n            .get(\"margins\")\n            .cloned()\n            .ok_or_else(|| anyhow::anyhow!(\"Serialized account event has no margins\"))?;\n\n        sqlx::query(\n            r#\"\n            INSERT INTO \"account\" (id) VALUES ($1) ON CONFLICT (id) DO NOTHING\n        \"#,\n        )\n        .bind(account_event.account_id.to_string())\n        .execute(&mut *transaction)\n        .await\n        .map(|_| ())\n        .map_err(|e| anyhow::anyhow!(\"Failed to insert into account table: {e}\"))?;\n\n        sqlx::query(r#\"\n            INSERT INTO \"account_event\" (\n                id, kind, account_id, base_currency, balances, margins, is_reported, ts_event, ts_init, created_at, updated_at\n            ) VALUES (\n                $1, $2, $3, $4, $5, $6, $7, $8, $9, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP\n            )","sourceCodeStart":1100,"sourceCodeEnd":1136,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/infrastructure/src/sql/queries.rs#L1100-L1136","documentation":"Identical in structure to the balances check: add_account extracts the \"margins\" key from the serialized AccountState and errors when it is missing, because the account_event INSERT binds margins in column $6. It fires when the event's serde representation has no \"margins\" field.","triggerScenarios":"add_account with an AccountState whose serialized form omits \"margins\" — custom serde attributes (skip/rename) on the margins field, a hand-built or older event type, or a round-trip through a format that dropped the key.","commonSituations":"Margin accounts not yet supported by a custom adapter so margins was removed/skipped; stale build after an upstream rename of the field; events reconstructed from external storage without margins and re-persisted.","solutions":["Inspect the serialized event's keys (serde_json::to_value) to confirm margins is absent.","Ensure AccountState is built from the current crate version whose Serialize emits \"margins\"; rebuild stale artifacts.","Remove skip/rename serde attributes on margins, or always pass an explicit empty Vec.","If the account type genuinely has no margins, persist an empty array/JSON null-compatible value instead of omitting the key."],"exampleFix":"// before: margins omitted for cash accounts\n#[serde(skip_serializing_if = \"Vec::is_empty\")]\npub margins: Vec<MarginBalance>,\n\n// after: always emit the key\npub margins: Vec<MarginBalance>,","handlingStrategy":"validation","validationCode":"let json = serde_json::to_value(&account_event)?;\nassert!(json.get(\"margins\").is_some(), \"AccountState serialization missing 'margins'\");","typeGuard":"fn has_margins(state: &AccountState) -> bool {\n    serde_json::to_value(state)\n        .ok()\n        .and_then(|v| v.get(\"margins\").cloned())\n        .is_some()\n}","tryCatchPattern":"match add_account(&pool, updated, state).await {\n    Err(e) if e.to_string().contains(\"no margins\") => {\n        log::error!(\"event type lost its margins field — check serde attrs/versions\");\n    }\n    other => other?,\n}","preventionTips":["Keep margins always serialized, even as an empty array for cash accounts.","Avoid serde rename/skip attributes on fields the DB layer reads by name.","Pin and rebuild the crate after upgrades to avoid stale field expectations.","Test add_account with both cash and margin AccountState fixtures."],"tags":["account","serialization","missing-field","postgres"],"backgroundTag":"missing-required-argument","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}