1use chrono::NaiveDate;
4use std::collections::BTreeMap;
5use strum::{Display, EnumString};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display)]
9pub enum Sex {
10 #[strum(serialize = "1")]
11 Male,
12 #[strum(serialize = "2")]
13 Female,
14 #[strum(serialize = "9")]
16 NotSpecified,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, EnumString, Display)]
21#[strum(ascii_case_insensitive)]
22pub enum EyeColor {
23 #[strum(serialize = "BLK")]
24 Black,
25 #[strum(serialize = "BLU")]
26 Blue,
27 #[strum(serialize = "BRO", serialize = "BRN")]
28 Brown,
29 #[strum(serialize = "GRY")]
30 Gray,
31 #[strum(serialize = "GRN")]
32 Green,
33 #[strum(serialize = "HAZ")]
34 Hazel,
35 #[strum(serialize = "MAR")]
36 Maroon,
37 #[strum(serialize = "PNK")]
38 Pink,
39 #[strum(serialize = "DIC")]
40 Dichromatic,
41 #[strum(serialize = "UNK")]
42 Unknown,
43 #[strum(default)]
44 Other(String),
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, EnumString, Display)]
49#[strum(ascii_case_insensitive)]
50pub enum HairColor {
51 #[strum(serialize = "BAL")]
52 Bald,
53 #[strum(serialize = "BLK")]
54 Black,
55 #[strum(serialize = "BLN")]
56 Blond,
57 #[strum(serialize = "BRO", serialize = "BRN")]
58 Brown,
59 #[strum(serialize = "GRY")]
60 Gray,
61 #[strum(serialize = "RED")]
62 Red,
63 #[strum(serialize = "SDY")]
64 Sandy,
65 #[strum(serialize = "WHI")]
66 White,
67 #[strum(serialize = "UNK")]
68 Unknown,
69 #[strum(default)]
70 Other(String),
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display)]
75pub enum Country {
76 #[strum(serialize = "USA")]
77 Usa,
78 #[strum(serialize = "CAN")]
79 Canada,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display)]
84pub enum Compliance {
85 #[strum(serialize = "F")]
87 Compliant,
88 #[strum(serialize = "N")]
90 NonCompliant,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum Height {
96 Inches(u16),
98 Centimetres(u16),
100}
101
102impl Height {
103 pub(crate) fn parse(raw: &str) -> Option<Self> {
104 let trimmed = raw.trim();
106 let digits: String = trimmed.chars().take_while(|c| c.is_ascii_digit()).collect();
107 if digits.is_empty() {
108 return None;
109 }
110 let value: u16 = digits.parse().ok()?;
111 let unit = trimmed[digits.len()..].trim().to_ascii_lowercase();
112 match unit.as_str() {
113 "" | "in" => Some(Height::Inches(value)),
115 "cm" => Some(Height::Centimetres(value)),
116 _ => None,
118 }
119 }
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display)]
126pub enum Truncation {
127 #[strum(serialize = "T")]
129 Truncated,
130 #[strum(serialize = "N")]
132 NotTruncated,
133 #[strum(serialize = "U")]
135 Unknown,
136}
137
138#[derive(Debug, Clone)]
140pub struct AamvaHeader {
141 pub iin: String,
142 pub aamva_version: u8,
143 pub jurisdiction_version: u8,
144 pub entry_count: u8,
145 pub subfiles: Vec<SubfileDesignator>,
146}
147
148#[derive(Debug, Clone)]
150pub struct SubfileDesignator {
151 pub subfile_type: String,
154 pub offset: usize,
156 pub length: usize,
159}
160
161#[derive(Debug, Clone, Default)]
163pub struct AamvaLicense {
164 pub header: Option<AamvaHeader>,
165
166 pub elements: BTreeMap<String, String>,
168
169 pub family_name: Option<String>,
171 pub first_name: Option<String>,
172 pub middle_name: Option<String>,
173 pub name_suffix: Option<String>,
174 pub family_name_truncation: Option<Truncation>,
175 pub first_name_truncation: Option<Truncation>,
176 pub middle_name_truncation: Option<Truncation>,
177
178 pub document_number: Option<String>,
180 pub document_discriminator: Option<String>,
181 pub country: Option<Country>,
182 pub jurisdiction: Option<String>,
183
184 pub date_of_birth: Option<NaiveDate>,
186 pub issue_date: Option<NaiveDate>,
187 pub expiry_date: Option<NaiveDate>,
188 pub card_revision_date: Option<NaiveDate>,
189 pub under_18_until: Option<NaiveDate>,
190 pub under_19_until: Option<NaiveDate>,
191 pub under_21_until: Option<NaiveDate>,
192
193 pub sex: Option<Sex>,
195 pub eye_color: Option<EyeColor>,
196 pub hair_color: Option<HairColor>,
197 pub height: Option<Height>,
198 pub weight_lb: Option<u32>,
199 pub weight_kg: Option<u32>,
200 pub weight_range: Option<u8>,
201
202 pub address_street_1: Option<String>,
204 pub address_street_2: Option<String>,
205 pub city: Option<String>,
206 pub postal_code: Option<String>,
207
208 pub vehicle_class: Option<String>,
210 pub restrictions: Option<String>,
211 pub endorsements: Option<String>,
212
213 pub organ_donor: Option<bool>,
215 pub veteran: Option<bool>,
216 pub compliance: Option<Compliance>,
217}