a-b-street/abstreet · error

Bad CityName

Error message

Bad CityName {}

What it means

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".

Solutions

  1. Pass a properly formatted "cc/city" string with a two-letter country code.
  2. 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.
  3. Use CityName::new directly when you already have separate country and city components.

Example fix

// before
let name = CityName::parse("gb/camden/london").ok();
// after
let name = CityName::new("gb".to_string(), "london".to_string());
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_city_name(s: &str) -> bool {
    let parts: Vec<&str> = s.split('/').collect();
    parts.len() == 2 && parts[0].len() == 2
}

Try / catch

match CityName::parse(input) {
    Ok(name) => name,
    Err(err) => { log::warn!("{}", err); CityName::new("gb".into(), "london".into()) }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13). Data as JSON: /api/errors/82dcc5edbcfe8739. Report an issue: GitHub.

Appendix: source

Thrown at abstio/src/abst_paths.rs:164

            }
        }
        cities
    }

    /// Returns all maps in a city based on importer config.
    pub fn list_all_maps_in_city_from_importer_config(&self) -> Vec<MapName> {
        crate::list_dir(format!("importer/config/{}/{}", self.country, self.city))
            .into_iter()
            .filter(|path| path.ends_with(".geojson"))
            .map(|path| MapName::from_city(self, &basename(path)))
            .collect()
    }

    /// Parses a CityName from something like "gb/london"; the inverse of `to_path`.
    pub fn parse(x: &str) -> Result<CityName> {
        let parts = x.split('/').collect::<Vec<_>>();
        if parts.len() != 2 || parts[0].len() != 2 {
            bail!("Bad CityName {}", x);
        }
        Ok(CityName::new(parts[0], parts[1]))
    }

    /// Expresses the city as a path, like "gb/london"; the inverse of `parse`.
    pub fn to_path(&self) -> String {
        format!("{}/{}", self.country, self.city)
    }

    /// Stringify the city name for debug messages. Don't implement `std::fmt::Display`, to force
    /// callers to explicitly opt into this description, which could change.
    pub fn describe(&self) -> String {
        format!("{} ({})", self.city, self.country)
    }

    /// Constructs the path to some city-scoped data/input.
    pub fn input_path<I: AsRef<str>>(&self, file: I) -> String {
        path(format!(

View on GitHub (pinned to 0964f29315)