{"record":{"id":"427f58c5f7ca51d1","repo":"risingwavelabs/risingwave","slug":"dynamic-serde-json-deserialization-error-for-turb","errorCode":null,"errorMessage":"(dynamic serde_json deserialization error for Turbopuffer sink properties)","messagePattern":"\\(dynamic serde_json deserialization error for Turbopuffer sink properties\\)","errorType":"validation","errorClass":"SinkError::Config","httpStatus":null,"severity":"error","filePath":"src/connector/src/sink/turbopuffer.rs","lineNumber":100,"sourceCode":"\n    #[serde(flatten)]\n    pub unknown_fields: std::collections::HashMap<String, String>,\n}\n\ncrate::impl_sink_unknown_fields!(TurbopufferConfig);\n\nimpl EnforceSecret for TurbopufferConfig {\n    const ENFORCE_SECRET_PROPERTIES: phf::Set<&'static str> = phf::phf_set! {\n        \"api_key\",\n    };\n}\n\nimpl TurbopufferConfig {\n    fn from_btreemap(values: BTreeMap<String, String>) -> Result<Self> {\n        let config = serde_json::from_value::<TurbopufferConfig>(\n            serde_json::to_value(values).expect(\"serialize sink properties\"),\n        )\n        .map_err(|e| SinkError::Config(anyhow!(e)))?;\n        if config.write_batch_size == 0 {\n            return Err(SinkError::Config(anyhow!(\n                \"`write_batch_size` must be greater than 0\"\n            )));\n        }\n        if config.max_linger_second == 0 {\n            return Err(SinkError::Config(anyhow!(\n                \"`max_linger_second` must be greater than 0\"\n            )));\n        }\n        if config.num_shards == Some(0) {\n            return Err(SinkError::Config(anyhow!(\n                \"`num_shards` must be greater than 0\"\n            )));\n        }\n        Ok(config)\n    }\n}","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/src/sink/turbopuffer.rs#L82-L118","documentation":"TurbopufferConfig::from_btreemap deserializes the sink's properties map into TurbopufferConfig via JSON round-trip; serde reports missing/invalid fields, and the sink wraps it as a Config error. It means required sink WITH options are missing or have wrong types.","triggerScenarios":"CREATE SINK ... WITH (connector='turbopuffer', ...) where required properties (e.g. api_key, namespace_column, region) are missing or have wrong types.","commonSituations":"Typo in property name; missing api key; passing numeric options as wrong JSON type; stale property names after connector updates.","solutions":["Check the error detail for which field failed (missing field 'x' / invalid type) and add/fix that WITH property","Verify against current Turbopuffer sink docs for required property names","Quote string values correctly in the CREATE SINK statement"],"exampleFix":"// before\nWITH (connector = 'turbopuffer', region = 'aws-us-east-1') // missing api_key\n// after\nWITH (connector = 'turbopuffer', region = 'aws-us-east-1', api_key = '...', namespace_column = 'ns');","handlingStrategy":"validation","validationCode":"const required = ['api_key','region']; const missing = required.filter(k => !(k in props)); if (missing.length) throw new Error('missing: ' + missing)","typeGuard":"const isTurbopufferConfig = (c) => typeof c?.api_key === 'string' && typeof c?.region === 'string';","tryCatchPattern":"catch (SinkError::Config(e)) { read serde detail (missing field / invalid type) and fix the WITH option }","preventionTips":["Validate all required WITH options before CREATE SINK","Keep option names in sync with current connector docs","Test sink DDL in a dev environment first"],"tags":["turbopuffer","config","serde","deserialization"],"backgroundTag":"schema-validation-failed","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}