Skip to main content

vin_decode/
country.rs

1//! ISO 3779 country code → country name mapping.
2//!
3//! VIN positions 1-2 encode a country range. This is *not* exhaustive — many
4//! prefix ranges aren't assigned — and it returns `None` for unknown codes so
5//! the caller can fall back to plant_country from the WMI metadata table.
6
7/// Map a 2-char country code (VIN positions 1-2) to a country name.
8///
9/// Recognises the prefixes covered by the test fixtures we ship: EU, North
10/// America, key Asian and South American manufacturers. For unmapped ranges,
11/// returns `None`.
12pub fn country_from_code(code: &str) -> Option<&'static str> {
13    if code.len() != 2 {
14        return None;
15    }
16    let bytes = code.as_bytes();
17    let c0 = bytes[0] as char;
18    let c1 = bytes[1] as char;
19
20    // North America
21    match c0 {
22        '1' | '4' | '5' => return Some("United States"),
23        '2' => return Some("Canada"),
24        '3' => {
25            return Some(match c1 {
26                'A'..='W' => "Mexico",
27                _ => "Costa Rica",
28            });
29        }
30        // Oceania
31        '6' => return Some("Australia"),
32        '7' => return Some("New Zealand"),
33        // South America
34        '8' => {
35            return Some(match c1 {
36                'A'..='E' => "Argentina",
37                'F'..='K' => "Chile",
38                'L'..='R' => "Ecuador",
39                'S'..='W' => "Peru",
40                'X'..='Z' => "Venezuela",
41                _ => "South America",
42            });
43        }
44        '9' => return Some("Brazil"),
45        _ => {}
46    }
47
48    // Africa
49    if let 'A'..='H' = c0 {
50        return Some(match c0 {
51            'A' => match c1 {
52                'A'..='H' => "South Africa",
53                _ => "Ivory Coast",
54            },
55            'B' => match c1 {
56                'A'..='E' => "Angola",
57                'F'..='K' => "Kenya",
58                _ => "Tanzania",
59            },
60            'C' => match c1 {
61                'A'..='E' => "Benin",
62                'F'..='K' => "Madagascar",
63                _ => "Tunisia",
64            },
65            'D' => match c1 {
66                'A'..='E' => "Egypt",
67                'F'..='K' => "Morocco",
68                _ => "Zambia",
69            },
70            _ => "Africa",
71        });
72    }
73
74    // Asia
75    if let 'J'..='R' = c0 {
76        return Some(match c0 {
77            'J' => "Japan",
78            'K' => match c1 {
79                'A'..='E' => "Sri Lanka",
80                'F'..='K' => "Israel",
81                'L'..='R' => "South Korea",
82                _ => "Kazakhstan",
83            },
84            'L' => "China",
85            'M' => match c1 {
86                'A'..='E' => "India",
87                'F'..='K' => "Indonesia",
88                'L'..='R' => "Thailand",
89                _ => "Myanmar",
90            },
91            'N' => match c1 {
92                'A'..='E' => "Iran",
93                'F'..='K' => "Pakistan",
94                _ => "Turkey",
95            },
96            'P' => match c1 {
97                'A'..='E' => "Philippines",
98                'F'..='K' => "Singapore",
99                _ => "Malaysia",
100            },
101            'R' => match c1 {
102                'A'..='E' => "United Arab Emirates",
103                'F'..='K' => "Taiwan",
104                _ => "Vietnam",
105            },
106            _ => "Asia",
107        });
108    }
109
110    // Europe (S-Z)
111    if let 'S'..='Z' = c0 {
112        return Some(match c0 {
113            'S' => match c1 {
114                'A'..='M' => "United Kingdom",
115                'N'..='T' => "Germany",
116                'U'..='Z' => "Poland",
117                _ => "Latvia",
118            },
119            'T' => match c1 {
120                'A'..='H' => "Switzerland",
121                'J'..='P' => "Czech Republic",
122                'R'..='V' => "Hungary",
123                _ => "Portugal",
124            },
125            'U' => match c1 {
126                'H'..='M' => "Denmark",
127                'N'..='T' => "Ireland",
128                'U'..='Z' => "Romania",
129                _ => "Slovakia",
130            },
131            'V' => match c1 {
132                'A'..='E' => "Austria",
133                'F'..='R' => "France",
134                'S'..='W' => "Spain",
135                _ => "Yugoslavia/Serbia",
136            },
137            'W' => "Germany",
138            'X' => match c1 {
139                'A'..='E' => "Bulgaria",
140                'F'..='K' => "Greece",
141                'L'..='R' => "Netherlands",
142                _ => "Russia",
143            },
144            'Y' => match c1 {
145                'A'..='E' => "Belgium",
146                'F'..='K' => "Finland",
147                'L'..='R' => "Malta",
148                'S'..='W' => "Sweden",
149                _ => "Norway",
150            },
151            'Z' => match c1 {
152                'A'..='R' => "Italy",
153                _ => "Slovenia",
154            },
155            _ => "Europe",
156        });
157    }
158
159    None
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn vininfo_fixtures() {
168        assert_eq!(country_from_code("95"), Some("Brazil"));
169        assert_eq!(country_from_code("92"), Some("Brazil"));
170        assert_eq!(country_from_code("MD"), Some("India"));
171        assert_eq!(country_from_code("XT"), Some("Russia"));
172        assert_eq!(country_from_code("W0"), Some("Germany"));
173        assert_eq!(country_from_code("WV"), Some("Germany"));
174        assert_eq!(country_from_code("VF"), Some("France"));
175        assert_eq!(country_from_code("5N"), Some("United States"));
176        assert_eq!(country_from_code("6F"), Some("Australia"));
177        assert_eq!(country_from_code("JS"), Some("Japan"));
178        assert_eq!(country_from_code("TM"), Some("Czech Republic"));
179    }
180
181    #[test]
182    fn unknown_returns_none() {
183        assert_eq!(country_from_code(""), None);
184        assert_eq!(country_from_code("A"), None);
185        assert_eq!(country_from_code("ABC"), None);
186        // '0' is reserved/unassigned
187        assert_eq!(country_from_code("0A"), None);
188    }
189}