risingwavelabs/risingwave · error · SinkError

`{}` must be {}, or {}

Error message

`{}` must be {}, or {}

What it means

This error is thrown when creating a GCS file sink whose `type` property is neither 'append-only' nor 'upsert'. The sink connector validates the user-provided `sink.type` option against the two supported sink types and rejects anything else with a SinkError::Config. It is a fail-fast validation in `from_btreemap` before the sink is ever instantiated.

Source

Thrown at src/connector/src/sink/file_sink/gcs.rs:100

            .layer(LoggingLayer::default())
            .layer(RetryLayer::default());
        Ok(operator)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GcsSink;

impl OpendalSinkBackend for GcsSink {
    type Properties = GcsConfig;

    const SINK_NAME: &'static str = GCS_SINK;

    fn from_btreemap(btree_map: BTreeMap<String, String>) -> Result<Self::Properties> {
        let config = serde_json::from_value::<GcsConfig>(serde_json::to_value(btree_map).unwrap())
            .map_err(|e| SinkError::Config(anyhow!(e)))?;
        if config.r#type != SINK_TYPE_APPEND_ONLY && config.r#type != SINK_TYPE_UPSERT {
            return Err(SinkError::Config(anyhow!(
                "`{}` must be {}, or {}",
                SINK_TYPE_OPTION,
                SINK_TYPE_APPEND_ONLY,
                SINK_TYPE_UPSERT
            )));
        }
        Ok(config)
    }

    fn new_operator(properties: GcsConfig) -> Result<Operator> {
        FileSink::<GcsSink>::new_gcs_sink(properties)
    }

    fn get_path(properties: Self::Properties) -> String {
        properties.common.path
    }

    fn get_engine_type() -> super::opendal_sink::EngineType {

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Set the WITH option to a supported value: `type = 'append-only'` or `type = 'upsert'`
  2. Check for typos in the value (exact strings 'append-only' and 'upsert')
  3. If upsert semantics are needed with a file sink, note file sinks require append-only output, so prefer `type = 'append-only'` combined with `force_append_only = 'true'` on the encode

Example fix

// before
WITH (
  connector = 'gcs',
  type = 'append'
)
// after
WITH (
  connector = 'gcs',
  type = 'append-only'
)
Defensive patterns

Strategy: validation

Validate before calling

let supported = ["append-only", "upsert"];
let sink_type = props.get("type").map(|s| s.as_str()).unwrap_or("");
if !supported.contains(&sink_type) {
    return Err(format!("GCS sink requires type to be one of {:?}, got '{}'", supported, sink_type));
}

Type guard

fn is_valid_sink_type(v: &str) -> bool {
    matches!(v, "append-only" | "upsert")
}

Prevention

When it happens

Trigger: Creating a GCS sink where the WITH option `type` is set to an unsupported value (e.g. `type = 'debezium'`, `type = 'append'`, misspelled 'append-only'), or where `type` deserializes into a GcsConfig with an unexpected string.

Common situations: Typo in the sink type option; copying config from a non-file sink connector that uses other type values; assuming the option name is something other than `type` and a stale/empty value is parsed.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/86770e88d7b0cda6. Report an issue: GitHub.