{"record":{"id":"5bbdada96a7e7675","repo":"risingwavelabs/risingwave","slug":"topic-name-cannot-be-empty","errorCode":null,"errorMessage":"topic name cannot be empty","messagePattern":"topic name cannot be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/connector/src/source/pulsar/topic.rs","lineNumber":168,"sourceCode":"    let parts: Vec<&str> = rest.splitn(3, '/').collect();\n\n    if parts.len() != 3 {\n        bail!(\n            \"invalid topic name '{}'; it must be in the format <tenant>/<namespace>/<topic>\",\n            rest\n        );\n    }\n\n    let parsed_topic = Topic {\n        domain: domain.to_owned(),\n        tenant: parts[0].to_owned(),\n        namespace: parts[1].to_owned(),\n        topic: parts[2].to_owned(),\n        partition_index: get_partition_index(complete_topic.as_str())?,\n    };\n\n    if parsed_topic.topic.is_empty() {\n        bail!(\"topic name cannot be empty\".to_owned());\n    }\n\n    Ok(parsed_topic)\n}\n\npub(crate) async fn check_topic_exists(\n    client: &Pulsar<TokioExecutor>,\n    topic: &Topic,\n) -> Result<()> {\n    // issue about api `get_topics_of_namespace`:\n    // for partitioned topic, the api will return all sub-topic of the topic instead of the topic itself\n    // Reduce async state machine size (see `clippy::large_futures`).\n    let topics_on_broker = Box::pin(client.get_topics_of_namespace(\n        format!(\"{}/{}\", topic.tenant, topic.namespace),\n        LookupMode::All,\n    ))\n    .await?;\n    if !topics_on_broker.contains(&topic.to_string()) {","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/src/source/pulsar/topic.rs#L150-L186","documentation":"The final component of a Pulsar topic (the topic name itself) must be non-empty. parse_topic constructs the Topic struct and then rejects it if the topic field is an empty string, since an empty leaf name is not addressable on the broker.","triggerScenarios":"Calling parse_topic with a URL ending in '/', e.g. 'persistent://public/default/' or 'persistent://public/default//', producing parts[2] == \"\".","commonSituations":"Trailing slash left over from copy-pasting a broker console URL, or programmatically building topic strings with template placeholders left blank.","solutions":["Remove the trailing slash and supply a real topic name","Use 'persistent://public/default/mytopic' style input","Trim the topic string before passing it to the connector"],"exampleFix":"// before\nlet topic = parse_topic(\"persistent://public/default/\");\n// after\nlet topic = parse_topic(\"persistent://public/default/mytopic\");","handlingStrategy":"validation","validationCode":"function validateNonEmptyLeaf(topic) {\n  const leaf = (topic.split('://')[1] ?? topic).split('/').pop();\n  if (!leaf) throw new Error(`topic name cannot be empty: ${topic}`);\n}","typeGuard":"const hasNonEmptyLeaf = (t) => Boolean((t.split('://')[1] ?? t).split('/').pop());","tryCatchPattern":"try { parse_topic(input); } catch (e) { if (String(e).includes('topic name cannot be empty')) { stripTrailingSlashAndRetry(input); } else { throw e; } }","preventionTips":["Trim trailing slashes from copied broker console URLs","Never build topic strings from unvalidated template placeholders","Trim whitespace before passing topic strings to the connector"],"tags":["pulsar","config","empty-value"],"backgroundTag":"empty-required-field","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}