{"record":{"id":"82dcc5edbcfe8739","repo":"a-b-street/abstreet","slug":"bad-cityname","errorCode":null,"errorMessage":"Bad CityName {}","messagePattern":"Bad CityName (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"abstio/src/abst_paths.rs","lineNumber":164,"sourceCode":"            }\n        }\n        cities\n    }\n\n    /// Returns all maps in a city based on importer config.\n    pub fn list_all_maps_in_city_from_importer_config(&self) -> Vec<MapName> {\n        crate::list_dir(format!(\"importer/config/{}/{}\", self.country, self.city))\n            .into_iter()\n            .filter(|path| path.ends_with(\".geojson\"))\n            .map(|path| MapName::from_city(self, &basename(path)))\n            .collect()\n    }\n\n    /// Parses a CityName from something like \"gb/london\"; the inverse of `to_path`.\n    pub fn parse(x: &str) -> Result<CityName> {\n        let parts = x.split('/').collect::<Vec<_>>();\n        if parts.len() != 2 || parts[0].len() != 2 {\n            bail!(\"Bad CityName {}\", x);\n        }\n        Ok(CityName::new(parts[0], parts[1]))\n    }\n\n    /// Expresses the city as a path, like \"gb/london\"; the inverse of `parse`.\n    pub fn to_path(&self) -> String {\n        format!(\"{}/{}\", self.country, self.city)\n    }\n\n    /// Stringify the city name for debug messages. Don't implement `std::fmt::Display`, to force\n    /// callers to explicitly opt into this description, which could change.\n    pub fn describe(&self) -> String {\n        format!(\"{} ({})\", self.city, self.country)\n    }\n\n    /// Constructs the path to some city-scoped data/input.\n    pub fn input_path<I: AsRef<str>>(&self, file: I) -> String {\n        path(format!(","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/a-b-street/abstreet/blob/0964f29315820c91b171b585eb51e300164e9197/abstio/src/abst_paths.rs#L146-L182","documentation":"CityName::parse expects a string of the form \"country/city\" where the country part is exactly two characters (like \"gb/london\"). Anything else — wrong number of slash-separated parts or a country code not two chars long — is rejected with \"Bad CityName\".","triggerScenarios":"Calling CityName::parse with strings missing a slash (\"london\"), with extra segments (\"gb/camden/london\"), with a non-2-letter country code (\"gbr/london\"), or with an empty string.","commonSituations":"Hand-editing city paths in configs or CLI args; on web, deriving the city name from a URL path that includes more segments; older data layouts with different nesting.","solutions":["Pass a properly formatted \"cc/city\" string with a two-letter country code.","If the input is a full path or URL, extract the last two components (e.g. take the two path segments after the data/system prefix) before parsing.","Use CityName::new directly when you already have separate country and city components."],"exampleFix":"// before\nlet name = CityName::parse(\"gb/camden/london\").ok();\n// after\nlet name = CityName::new(\"gb\".to_string(), \"london\".to_string());","handlingStrategy":"validation","validationCode":"fn is_valid_city_name(s: &str) -> bool {\n    let parts: Vec<&str> = s.split('/').collect();\n    parts.len() == 2 && parts[0].len() == 2\n}","typeGuard":null,"tryCatchPattern":"match CityName::parse(input) {\n    Ok(name) => name,\n    Err(err) => { log::warn!(\"{}\", err); CityName::new(\"gb\".into(), \"london\".into()) }\n}","preventionTips":["Prefer CityName::new(country, city) over parsing free-form strings.","Validate user-supplied city strings before calling parse.","Extract only the last two path segments when deriving a CityName from a full path."],"tags":["parsing","validation","city-name"],"backgroundTag":"invalid-argument-format","analyzedSha":"0964f29315820c91b171b585eb51e300164e9197","analyzedAt":"2026-09-13T18:02:03.421Z","contentChangedAt":"2026-09-13T18:02:03.421Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}