{"record":{"id":"e37df22b8595e1e5","repo":"Hmbown/CodeWhale","slug":"custom-provider-name-may-only-use-letters-numbers-and","errorCode":null,"errorMessage":"custom provider name may only use letters, numbers, '-' and '_'","messagePattern":"custom provider name may only use letters, numbers, '-' and '_'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/config_persistence.rs","lineNumber":702,"sourceCode":"\nfn normalize_custom_provider_id(raw: &str) -> anyhow::Result<String> {\n    use anyhow::bail;\n\n    let value = raw.trim();\n    if value.is_empty() {\n        bail!(\"custom provider name is required\");\n    }\n    if value == \"__custom__\" {\n        bail!(\"custom provider name is reserved\");\n    }\n    if crate::config::ApiProvider::parse(value).is_some() {\n        bail!(\"custom provider name must not shadow a built-in provider\");\n    }\n    if !value\n        .chars()\n        .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-'))\n    {\n        bail!(\"custom provider name may only use letters, numbers, '-' and '_'\");\n    }\n    Ok(value.to_string())\n}\n\nfn normalize_custom_provider_base_url(raw: &str) -> anyhow::Result<String> {\n    use anyhow::bail;\n\n    let value = raw.trim().trim_end_matches('/');\n    if value.is_empty() {\n        bail!(\"custom provider base URL is required\");\n    }\n    let parsed = reqwest::Url::parse(value)\n        .map_err(|err| anyhow::anyhow!(\"custom provider base URL is invalid: {err}\"))?;\n    if !matches!(parsed.scheme(), \"http\" | \"https\") || parsed.host_str().is_none() {\n        bail!(\"custom provider base URL must be an http(s) URL with a host\");\n    }\n    Ok(value.to_string())\n}","sourceCodeStart":684,"sourceCodeEnd":720,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/config_persistence.rs#L684-L720","documentation":"Custom provider names are restricted to ASCII alphanumerics, underscores, and hyphens so they form safe TOML table keys and identifiers. Any other character (space, dot, slash, non-ASCII) causes a bail.","triggerScenarios":"Calling persist_custom_provider with a name containing e.g. a space, dot, or non-ASCII character.","commonSituations":"Users typing \"My Provider\" or \"my.provider\" into a custom-provider form.","solutions":["Restrict the name to letters, digits, '_' and '-' (e.g. \"my_provider-2\").","Validate the pattern in the UI before calling the API."],"exampleFix":"// before\npersist_custom_provider(path, \"My Provider.com\", &url, ...)?;\n// after\npersist_custom_provider(path, \"My_Provider\", &url, ...)?;","handlingStrategy":"validation","validationCode":"let ok = name.trim().chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-'));\nif !ok { return Err(anyhow::anyhow!(\"invalid provider name characters\")); }","typeGuard":"fn is_safe_name(name: &str) -> bool { let v = name.trim(); !v.is_empty() && v.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-')) }","tryCatchPattern":"match persist_custom_provider(path, name, &url, ...) {\n    Err(e) if e.to_string().contains(\"letters, numbers\") => sanitize_and_retry(name),\n    other => other,\n}","preventionTips":["Constrain name inputs to [A-Za-z0-9_-] in the UI.","Sanitize/slugify user-provided names before persisting."],"tags":["validation","custom-provider","format"],"backgroundTag":"invalid-identifier-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}