risingwavelabs/risingwave · error · SinkError
`{}` must be {}, or {}
Error message
`{}` must be {}, or {} What it means
After deserializing AzblobConfig, the azblob sink validates that the user-supplied r#type option equals 'append-only' or 'upsert'. Any other value (or an empty/misspelled one) is rejected with SinkError::Config naming the option and its two allowed values. This is an explicit enumeration check, not a serde failure.
Source
Thrown at src/connector/src/sink/file_sink/azblob.rs:115
impl UnknownFields for AzblobConfig {
fn unknown_fields(&self) -> HashMap<String, String> {
self.unknown_fields.clone()
}
}
crate::impl_sink_unknown_fields!(AzblobConfig);
impl OpendalSinkBackend for AzblobSink {
type Properties = AzblobConfig;
const SINK_NAME: &'static str = AZBLOB_SINK;
fn from_btreemap(btree_map: BTreeMap<String, String>) -> Result<Self::Properties> {
let config =
serde_json::from_value::<AzblobConfig>(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: AzblobConfig) -> Result<Operator> {
FileSink::<AzblobSink>::new_azblob_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
- Set type='append-only' if the source emits no updates/deletes.
- Set type='upsert' if the source contains updates/deletes and you want key-based overwrite semantics.
- Match the value exactly: lowercase with the hyphen, as printed in the error.
- Do not leave the option empty or abbreviated; one of the two literal values is required.
Example fix
// before WITH (connector = 'azblob', type = 'append'); // after WITH (connector = 'azblob', type = 'append-only');
Defensive patterns
Strategy: validation
Validate before calling
match opts.get("r#type").map(String::as_str) {
Some("append-only") | Some("upsert") => Ok(()),
other => Err(format!("azblob sink r#type must be 'append-only' or 'upsert', got {other:?}")),
} Try / catch
match AzblobSink::from_btreemap(opts) {
Ok(cfg) => proceed(cfg),
Err(SinkError::Config(e)) => eprintln!("invalid sink type: {e:#}"),
Err(e) => return Err(e),
} Prevention
- Copy the type value verbatim from the error message or docs: 'append-only' or 'upsert'.
- Never hand-type the value; reuse a known-good DDL template.
- Remember the value is case-sensitive and hyphenated.
- Choose 'upsert' whenever the upstream can retract or update records.
When it happens
Trigger: CREATE SINK ... WITH (connector='azblob', type='<something-else>') where type is not exactly 'append-only' or 'upsert' — e.g. type='append', 'overwrite', 'insert', or wrong casing like 'Append-Only'.
Common situations: Copying sink syntax from other systems with different type names; guessing the value instead of reading docs; using uppercase or hyphen-less variants; leaving a placeholder value in the DDL.
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
- `commit_checkpoint_interval` must be greater than 0
- config deserialization error: {e}
- `{}` must be {}, or {}
- `{}` must be {}, or {}
- `{}` must be {}, or {}
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/6818cd2f21272a0f.
Report an issue: GitHub.