{"record":{"id":"eeadb6612ed009ac","repo":"risingwavelabs/risingwave","slug":"e-eeadb6","errorCode":null,"errorMessage":"{e}","messagePattern":"\\{e\\}","errorType":"validation","errorClass":"SinkError","httpStatus":null,"severity":"error","filePath":"src/connector/src/sink/file_sink/s3.rs","lineNumber":142,"sourceCode":"#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct S3Sink;\n\nimpl UnknownFields for S3Config {\n    fn unknown_fields(&self) -> HashMap<String, String> {\n        self.unknown_fields.clone()\n    }\n}\n\ncrate::impl_sink_unknown_fields!(S3Config);\n\nimpl OpendalSinkBackend for S3Sink {\n    type Properties = S3Config;\n\n    const SINK_NAME: &'static str = S3_SINK;\n\n    fn from_btreemap(btree_map: BTreeMap<String, String>) -> Result<Self::Properties> {\n        let config = serde_json::from_value::<S3Config>(serde_json::to_value(btree_map).unwrap())\n            .map_err(|e| SinkError::Config(anyhow!(e)))?;\n        if config.r#type != SINK_TYPE_APPEND_ONLY && config.r#type != SINK_TYPE_UPSERT {\n            return Err(SinkError::Config(anyhow!(\n                \"`{}` must be {}, or {}\",\n                SINK_TYPE_OPTION,\n                SINK_TYPE_APPEND_ONLY,\n                SINK_TYPE_UPSERT\n            )));\n        }\n        Ok(config)\n    }\n\n    fn new_operator(properties: S3Config) -> Result<Operator> {\n        FileSink::<S3Sink>::new_s3_sink(&properties.common)\n    }\n\n    fn get_path(properties: Self::Properties) -> String {\n        properties.common.path.unwrap_or_default()\n    }","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/src/sink/file_sink/s3.rs#L124-L160","documentation":"`S3Config::from_btreemap` serializes the sink's WITH-options BTreeMap to JSON and deserializes it into `S3Config`. Any field failing serde deserialization (unknown/invalid value, wrong shape) is mapped to `SinkError::Config(anyhow!(e))`, producing this error with the serde message.","triggerScenarios":"`CREATE SINK ... WITH (connector='s3', ...)` where one of the S3 options does not deserialize into S3Config — e.g. a value with the wrong type/shape, an unparseable URL, or an option typed as bool/int given a non-numeric string.","commonSituations":"Quoting mistakes leaving nested JSON-shaped options malformed; passing `bucket_url`/`path` values with unexpected characters; schema changes to S3Config making older saved properties invalid.","solutions":["Read the embedded serde message to identify the offending field and fix its value","Check each WITH option against the S3Config struct definition (types and expected formats)","Remove unsupported options and rely on defaults; only pass documented S3 sink options"],"exampleFix":"// before\nWITH (connector='s3', bucket_url='s3://bucket', path='out/', match_path='true')\n// after\nWITH (connector='s3', bucket_url='s3://bucket', path='out/')","handlingStrategy":"validation","validationCode":"// Validate S3 options before submitting\nconst s3Opts = { connector: 's3', bucket_url: 's3://bucket', path: 'out/' };\nfor (const [k, v] of Object.entries(s3Opts)) {\n  if (typeof v !== 'string' || v.length === 0) throw new Error(`invalid value for ${k}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep option values as simple strings; avoid nested JSON-shaped values","Match option names/types against the documented S3Config","Re-validate saved sink properties after connector version upgrades"],"tags":["serde","config","s3","sink"],"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"}