Skip to main content

thaiidcard/
model.rs

1//! Data types for Thai National ID smart card data.
2
3/// All data read from a Thai National ID smart card.
4#[derive(Debug, Clone)]
5pub struct CardData {
6    pub personal: Option<Personal>,
7    pub card: Option<Card>,
8    pub nhso: Option<Nhso>,
9}
10
11/// Citizen's personal identification information.
12#[derive(Debug, Clone)]
13pub struct Personal {
14    pub cid: String,
15    pub name: Name,
16    pub name_en: Name,
17    pub dob: String,
18    pub gender: String,
19    pub card_issuer: String,
20    pub issue_date: String,
21    pub expire_date: String,
22    pub address: Address,
23    pub face_image: String,
24}
25
26/// Citizen's name broken into components.
27#[derive(Debug, Clone)]
28pub struct Name {
29    pub prefix: String,
30    pub first_name: String,
31    pub middle_name: String,
32    pub last_name: String,
33    pub full_name: String,
34}
35
36/// Registered address split into components.
37#[derive(Debug, Clone)]
38pub struct Address {
39    pub house_no: String,
40    pub moo: String,
41    pub soi: String,
42    pub street: String,
43    pub subdistrict: String,
44    pub district: String,
45    pub province: String,
46    pub full_address: String,
47}
48
49/// Laser-engraved card serial number.
50#[derive(Debug, Clone)]
51pub struct Card {
52    pub laser_id: String,
53}
54
55/// NHSO (National Health Security Office) insurance data.
56#[derive(Debug, Clone)]
57pub struct Nhso {
58    pub main_inscl: String,
59    pub sub_inscl: String,
60    pub main_hospital: String,
61    pub sub_hospital: String,
62    pub paid_type: String,
63    pub issue_date: String,
64    pub expire_date: String,
65    pub update_date: String,
66    pub change_hospital_amount: String,
67}
68
69/// Parse a "#"-delimited name string into a `Name`.
70pub fn parse_name(raw: &str) -> Name {
71    let parts: Vec<&str> = raw.split('#').collect();
72    let prefix = parts.first().unwrap_or(&"").to_string();
73    let first_name = parts.get(1).unwrap_or(&"").to_string();
74    let middle_name = parts.get(2).unwrap_or(&"").to_string();
75    let last_name = parts.get(3).unwrap_or(&"").to_string();
76
77    let full_name = if middle_name.is_empty() {
78        format!("{}{} {}", prefix, first_name, last_name)
79    } else {
80        format!("{}{} {} {}", prefix, first_name, middle_name, last_name)
81    };
82
83    Name {
84        prefix,
85        first_name,
86        middle_name,
87        last_name,
88        full_name,
89    }
90}
91
92/// Parse a "#"-delimited address string into an `Address`.
93pub fn parse_address(raw: &str) -> Address {
94    let parts: Vec<&str> = raw.split('#').collect();
95    let mut addr = Address {
96        house_no: String::new(),
97        moo: String::new(),
98        soi: String::new(),
99        street: String::new(),
100        subdistrict: String::new(),
101        district: String::new(),
102        province: String::new(),
103        full_address: String::new(),
104    };
105
106    if parts.is_empty() {
107        return addr;
108    }
109
110    addr.house_no = parts[0].to_string();
111
112    // Parse moo (village number) or soi (lane) from the second component
113    if parts.len() > 1 {
114        let trimmed = parts[1].trim();
115        if let Some(val) = trimmed.strip_prefix("หมู่ที่") {
116            addr.moo = val.trim().to_string();
117        }
118        if let Some(val) = trimmed.strip_prefix("ซอย") {
119            addr.soi = val.trim().to_string();
120        }
121    }
122
123    // Street is everything between index 2 and the last 3 items
124    if parts.len() > 4 {
125        let street_parts: Vec<&str> = parts[2..parts.len() - 3].to_vec();
126        addr.street = street_parts.join(" ").trim().to_string();
127    }
128
129    // Subdistrict (ตำบล or แขวง)
130    if parts.len() > 3 {
131        let sd = parts[parts.len() - 3].trim();
132        addr.subdistrict = sd
133            .strip_prefix("ตำบล")
134            .or_else(|| sd.strip_prefix("แขวง"))
135            .unwrap_or(sd)
136            .trim()
137            .to_string();
138    }
139
140    // District (อำเภอ or เขต)
141    if parts.len() > 2 {
142        let d = parts[parts.len() - 2].trim();
143        addr.district = d
144            .strip_prefix("อำเภอ")
145            .or_else(|| d.strip_prefix("เขต"))
146            .unwrap_or(d)
147            .trim()
148            .to_string();
149    }
150
151    // Province (จังหวัด)
152    if let Some(last) = parts.last() {
153        let p = last.trim();
154        addr.province = p.strip_prefix("จังหวัด").unwrap_or(p).trim().to_string();
155    }
156
157    let non_empty: Vec<&str> = parts.iter().filter(|s| !s.is_empty()).copied().collect();
158    addr.full_address = non_empty.join(" ");
159
160    addr
161}
162
163/// Convert a raw "YYYYMMDD" (Buddhist year) date to "YYYY-MM-DD" (Gregorian).
164pub fn format_date(raw: &str) -> String {
165    if raw.len() != 8 {
166        return String::new();
167    }
168    let year: i32 = raw[0..4].parse().unwrap_or(0);
169    let month = &raw[4..6];
170    let day = &raw[6..8];
171    format!("{:04}-{}-{}", year - 543, month, day)
172}