{"record":{"id":"cbd24344b9cfe285","repo":"risingwavelabs/risingwave","slug":"invalid-topic-name-it-must-be-in-the-format","errorCode":null,"errorMessage":"invalid topic name '{}'; it must be in the format <tenant>/<namespace>/<topic>","messagePattern":"invalid topic name '(.+?)'; it must be in the format <tenant>/<namespace>/<topic>","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/connector/src/source/pulsar/topic.rs","lineNumber":153,"sourceCode":"    }\n\n    let parts: Vec<&str> = complete_topic.splitn(2, \"://\").collect();\n\n    let domain = match parts[0] {\n        PERSISTENT_DOMAIN | NON_PERSISTENT_DOMAIN => parts[0],\n        _ => {\n            bail!(\n                \"The domain only can be specified as 'persistent' or 'non-persistent'. Input domain is '{}'\",\n                parts[0]\n            );\n        }\n    };\n\n    let rest = parts[1];\n    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)","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/src/source/pulsar/topic.rs#L135-L171","documentation":"After stripping the domain, a Pulsar topic must have exactly three slash-separated components: tenant, namespace, and topic name. parse_topic splits the remainder with splitn(3,'/') and bails when the count is not 3, since it cannot build a valid Topic struct otherwise.","triggerScenarios":"Calling parse_topic with a rest path like 'tenant/topic' (missing namespace), 'tenant/namespace/topic/partition-extra', or a domain-only URL such as 'persistent://' with no path.","commonSituations":"Users providing only 'my-topic' with embedded slashes that do not resolve to 3 parts, truncating a copied topic URL, or accidentally appending a partition suffix that changes the part count.","solutions":["Provide the topic as '<tenant>/<namespace>/<topic>', e.g. 'public/default/events'","Verify the full string after '://' contains exactly two '/' separators","Remove any extra path segments or append the missing namespace/tenant"],"exampleFix":"// before\nlet topic = parse_topic(\"persistent://public/events\");\n// after\nlet topic = parse_topic(\"persistent://public/default/events\");","handlingStrategy":"validation","validationCode":"function validatePulsarTopicPath(topic) {\n  const rest = topic.split('://')[1] ?? topic;\n  if (rest.split('/').length !== 3) {\n    throw new Error(`topic must be <tenant>/<namespace>/<topic>: ${rest}`);\n  }\n}","typeGuard":"const isThreePartTopic = (t) => (t.split('://')[1] ?? t).split('/').length === 3;","tryCatchPattern":"try { parse_topic(input); } catch (e) { if (String(e).includes('invalid topic name')) { normalizeToTenantNsTopic(input); } else { throw e; } }","preventionTips":["Copy full topic URLs (persistent://tenant/ns/topic) rather than truncating","Strip partition suffixes like '-partition-0' before validating if using the non-partitioned name","Add config-time linting that checks the slash count of topic strings"],"tags":["pulsar","config","format"],"backgroundTag":"invalid-argument-format","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"}