{"record":{"id":"03d73d72ea6e5a24","repo":"hcengineering/platform","slug":"failed-to-deserialize-as-standardpatchoperation","errorCode":null,"errorMessage":"Failed to deserialize as StandardPatchOperation: {}. Also failed to deserialize as HulyPatchOperation: {}","messagePattern":"Failed to deserialize as StandardPatchOperation: (.+?)\\. Also failed to deserialize as HulyPatchOperation: (.+?)","errorType":"exception","errorClass":"serde::de::Error","httpStatus":400,"severity":"error","filePath":"foundations/hulylake/server/src/patch.rs","lineNumber":73,"sourceCode":"\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum PatchOperation {\n    Huly(HulyPatchOperation),\n    Standard(StandardPatchOperation),\n}\n\nimpl<'de> serde::Deserialize<'de> for PatchOperation {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        let value = Value::deserialize(deserializer)?;\n\n        let op = serde_json::from_value::<StandardPatchOperation>(value.clone());\n        if let Ok(op) = op {\n            Ok(Self::Standard(op))\n        } else {\n            let standard_error = op.err().unwrap();\n            serde_json::from_value::<HulyPatchOperation>(value)\n                .map_err(|huly_error| {\n                    serde::de::Error::custom(format!(\n                        \"Failed to deserialize as StandardPatchOperation: {}. Also failed to deserialize as HulyPatchOperation: {}\",\n                        standard_error, huly_error\n                    ))\n                })\n                .map(Self::Huly)\n        }\n    }\n}\n\n#[derive(Debug, Error, PartialEq, Eq)]\npub enum HulyPatchError {\n    #[error(\"invalid number\")]\n    InvalidNumber,\n    #[error(\"patch error: {0}\")]\n    PatchError(String),","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/foundations/hulylake/server/src/patch.rs#L55-L91","documentation":"PatchOperation supports two wire formats: the newer StandardPatchOperation and the legacy HulyPatchOperation. deserialize tries Standard first; if that fails it tries Huly, and if both fail it returns a combined error listing both serde messages so the caller can see why neither format matched.","triggerScenarios":"Submitting a patch payload that is neither a valid StandardPatchOperation nor a valid HulyPatchOperation — e.g. unknown 'op' verb, missing required fields like 'attribute' or 'operations', or a JSON shape from a mismatched client version.","commonSituations":"Older clients sending legacy patch formats after a server update; hand-crafted test payloads with typo'd fields; version skew between transactor/client and hulylake server.","solutions":["Fix the patch payload to match the documented StandardPatchOperation schema","Upgrade/align client and server versions so both agree on the patch format","Read the two embedded serde errors to identify the exact missing/unknown field","If legacy format is intentional, migrate the producer to the standard format"],"exampleFix":"// before\nawait fetch(url, { method: 'POST', body: JSON.stringify({ op: 'set ', attribute: 'name' }) }) // trailing space / wrong shape\n// after\nawait fetch(url, { method: 'POST', body: JSON.stringify({ op: 'set', attribute: 'name', value: 'x' }) })","handlingStrategy":"validation","validationCode":"function isValidPatch(p: unknown): boolean {\n  return typeof p === 'object' && p !== null &&\n    'op' in p && typeof (p as any).op === 'string' &&\n    (ALLOWED_OPS as string[]).includes((p as any).op.trim())\n}","typeGuard":"function isStandardPatch(v: unknown): v is StandardPatchOperation {\n  return typeof v === 'object' && v !== null && typeof (v as any).op === 'string'\n}","tryCatchPattern":"try {\n  await sendPatch(patch)\n} catch (err) {\n  if (String(err).includes('Failed to deserialize')) {\n    const [stdErr] = String(err).match(/StandardPatchOperation: ([^.]+\\.)?/g) ?? []\n    logger.error('patch rejected', { stdErr })\n  }\n}","preventionTips":["Validate patch payloads against the StandardPatchOperation schema client-side","Keep client and server patch-format versions aligned","Parse both serde error messages from the combined error for debugging","Add contract tests covering every op type you send"],"tags":["rust","serde","deserialization","patch","schema"],"backgroundTag":"schema-validation-failed","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}