{"record":{"id":"b433957dab5216fc","repo":"astrid-runtime/astrid","slug":"sparse-summary-deserializes","errorCode":null,"errorMessage":"sparse SUMMARY deserializes","messagePattern":"sparse SUMMARY deserializes","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-gateway/src/routes/sessions_tests.rs","lineNumber":600,"sourceCode":"        \"updated_at\": 1_719_000_100_i64,\n        \"archived\": false,\n        \"parent_session_id\": \"old-id\",\n        \"meta\": \"{\\\"k\\\":1}\"\n    });\n    let s: SessionSummary = serde_json::from_value(full).expect(\"frozen SUMMARY deserializes\");\n    assert_eq!(s.session_id, \"default\");\n    assert_eq!(s.title.as_deref(), Some(\"Planning\"));\n    assert_eq!(s.last_message_preview.as_deref(), Some(\"latest line\"));\n    assert!(!s.archived);\n    assert_eq!(s.meta.as_deref(), Some(\"{\\\"k\\\":1}\"));\n\n    // Minimal/sparse element: only the always-present fields.\n    let sparse = serde_json::json!({\n        \"session_id\": \"fresh\",\n        \"message_count\": 0,\n        \"archived\": true\n    });\n    let s: SessionSummary = serde_json::from_value(sparse).expect(\"sparse SUMMARY deserializes\");\n    assert!(s.title.is_none());\n    assert!(s.preview.is_none());\n    assert!(s.last_message_preview.is_none());\n    assert!(s.created_at.is_none());\n    assert!(s.meta.is_none());\n    assert!(s.archived);\n}\n\n#[test]\nfn parse_session_field_null_is_none_for_404() {\n    // Explicit null → None (the handler maps None to a 404).\n    let null_reply = serde_json::json!({ \"correlation_id\": \"c\", \"session\": null });\n    assert!(parse_session_field(&null_reply).unwrap().is_none());\n    // Absent `session` → None too.\n    let absent = serde_json::json!({ \"correlation_id\": \"c\" });\n    assert!(parse_session_field(&absent).unwrap().is_none());\n}\n","sourceCodeStart":582,"sourceCodeEnd":618,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/routes/sessions_tests.rs#L582-L618","documentation":"The same round-trip test also deserializes a minimal JSON object containing only session_id, message_count, and archived into SessionSummary with expect(\"sparse SUMMARY deserializes\"). A panic means the always-present fields or their types changed, or a previously-optional field became required so the sparse payload no longer deserializes.","triggerScenarios":"Making a field required that the sparse fixture omits (title, created_at, meta, etc.), changing types of session_id/message_count/archived, or removing #[serde(default)]/Option wrappers.","commonSituations":"Refactor tightening SessionSummary field optionality; switching archived from bool to an enum; changing message_count type; serde default attribute removed during cleanup.","solutions":["Restore Option/#[serde(default)] on fields that must remain absent-able in sparse responses","Update the sparse fixture if a field legitimately joined the always-present set","Read the serde error in the panic to find the missing/mistyped field","Keep the frozen wire contract documented so field optionality changes are deliberate"],"exampleFix":"// before\npub created_at: i64,\n// after\n#[serde(default)]\npub created_at: Option<i64>,","handlingStrategy":"type-guard","validationCode":"assert!(sparse.get(\"session_id\").is_some() && sparse.get(\"title\").is_none(),\n    \"sparse fixture must omit optional fields\");","typeGuard":"fn is_sparse_summary(v: &serde_json::Value) -> bool {\n    v.get(\"session_id\").is_some()\n        && v.get(\"message_count\").is_some()\n        && v.get(\"archived\").is_some()\n}","tryCatchPattern":"let s: SessionSummary = serde_json::from_value(sparse)\n    .unwrap_or_else(|e| panic!(\"sparse SUMMARY deserializes: {e}\"));","preventionTips":["Keep optional summary fields as Option with #[serde(default)]","Never promote a field to required without updating the sparse fixture","Review serde attribute diffs for optionality regressions"],"tags":["tests","serde","schema","optional-fields"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}