{"record":{"id":"d4d218878069d2e1","repo":"jdx/mise","slug":"invalid-cargo-crate-name-name","errorCode":null,"errorMessage":"invalid Cargo crate name: {name:?}","messagePattern":"invalid Cargo crate name: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/backend/cargo.rs","lineNumber":521,"sourceCode":"        } else if let Some((user, repo)) = self.tool_name().split_once('/') {\n            format!(\"https://github.com/{user}/{repo}.git\").parse().ok()\n        } else {\n            None\n        }\n    }\n}\n\nfn format_tool_options(options: &[&'static str]) -> String {\n    options\n        .iter()\n        .map(|option| format!(\"`{option}`\"))\n        .collect::<Vec<_>>()\n        .join(\", \")\n}\n\nfn get_crate_url(name: &str) -> eyre::Result<Url> {\n    if name.is_empty() || !name.is_ascii() {\n        bail!(\"invalid Cargo crate name: {name:?}\");\n    }\n    let name = name.to_lowercase();\n    let url = match name.len() {\n        1 => format!(\"https://index.crates.io/1/{name}\"),\n        2 => format!(\"https://index.crates.io/2/{name}\"),\n        3 => format!(\"https://index.crates.io/3/{}/{name}\", &name[..1]),\n        _ => format!(\n            \"https://index.crates.io/{}/{}/{name}\",\n            &name[..2],\n            &name[2..4]\n        ),\n    };\n    Ok(url.parse()?)\n}\n\nfn parse_crate_versions(response: &str) -> eyre::Result<Vec<VersionInfo>> {\n    let mut versions = vec![];\n    for version in Deserializer::from_str(response).into_iter::<CrateVersion>() {","sourceCodeStart":503,"sourceCodeEnd":539,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/backend/cargo.rs#L503-L539","documentation":"get_crate_url builds the sparse crates.io index URL for a crate name, which must be non-empty and ASCII (crate names are ASCII per cargo's rules; the name is lowercased and split into index shards). An empty or non-ASCII name cannot map to a valid index path, so mise bails.","triggerScenarios":"Resolving the remote version list for a cargo: tool whose crate name is \"\" or contains non-ASCII characters (called from _list_remote_versions, and exercised by the crate_index_url_rejects_* tests).","commonSituations":"Typo or empty version specifier in mise.toml such as `cargo:\"\"` or `cargo:@1.0`; pasting a crate name containing a Unicode look-alike character; programmatic/templated tool names where a variable expands to empty.","solutions":["Correct the crate name in mise.toml/.tool-versions to the exact ASCII crate name from crates.io.","Remove empty tool entries or unexpanded template variables from the config.","Verify with `mise ls-remote cargo:<name>` once the name is fixed."],"exampleFix":"# before (mise.toml)\n[tools]\ncargo:\"café-cli\" = \"latest\"\n\n# after\n[tools]\ncargo:\"cafe-cli\" = \"latest\"","handlingStrategy":"validation","validationCode":"// rust\nfn valid_crate_name(name: &str) -> bool {\n    !name.is_empty() && name.is_ascii()\n}\n// call before configuring cargo:<name>\nassert!(valid_crate_name(\"ripgrep\"));","typeGuard":"fn is_ascii_crate_name(name: &str) -> bool {\n    !name.is_empty() && name.is_ascii()\n}","tryCatchPattern":null,"preventionTips":["Copy crate names verbatim from crates.io","Validate config after templating so empty expansions are caught","Run `mise ls-remote cargo:<name>` to sanity-check a name before pinning it"],"tags":["cargo","crates-io","validation","naming"],"backgroundTag":"invalid-identifier-format","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}