claim169_core/model/
enums.rs

1use serde::{Deserialize, Serialize};
2
3/// Gender values as defined in MOSIP Claim 169
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(into = "i64", try_from = "i64")]
6pub enum Gender {
7    Male = 1,
8    Female = 2,
9    Other = 3,
10}
11
12impl From<Gender> for i64 {
13    fn from(g: Gender) -> i64 {
14        g as i64
15    }
16}
17
18impl TryFrom<i64> for Gender {
19    type Error = &'static str;
20
21    fn try_from(value: i64) -> Result<Self, Self::Error> {
22        match value {
23            1 => Ok(Gender::Male),
24            2 => Ok(Gender::Female),
25            3 => Ok(Gender::Other),
26            _ => Err("invalid gender value"),
27        }
28    }
29}
30
31/// Marital status values as defined in MOSIP Claim 169
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(into = "i64", try_from = "i64")]
34pub enum MaritalStatus {
35    Unmarried = 1,
36    Married = 2,
37    Divorced = 3,
38}
39
40impl From<MaritalStatus> for i64 {
41    fn from(m: MaritalStatus) -> i64 {
42        m as i64
43    }
44}
45
46impl TryFrom<i64> for MaritalStatus {
47    type Error = &'static str;
48
49    fn try_from(value: i64) -> Result<Self, Self::Error> {
50        match value {
51            1 => Ok(MaritalStatus::Unmarried),
52            2 => Ok(MaritalStatus::Married),
53            3 => Ok(MaritalStatus::Divorced),
54            _ => Err("invalid marital status value"),
55        }
56    }
57}
58
59/// Photo format for the binary image field (key 17)
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(into = "i64", try_from = "i64")]
62pub enum PhotoFormat {
63    Jpeg = 1,
64    Jpeg2000 = 2,
65    Avif = 3,
66    Webp = 4,
67}
68
69impl From<PhotoFormat> for i64 {
70    fn from(f: PhotoFormat) -> i64 {
71        f as i64
72    }
73}
74
75impl TryFrom<i64> for PhotoFormat {
76    type Error = &'static str;
77
78    fn try_from(value: i64) -> Result<Self, Self::Error> {
79        match value {
80            1 => Ok(PhotoFormat::Jpeg),
81            2 => Ok(PhotoFormat::Jpeg2000),
82            3 => Ok(PhotoFormat::Avif),
83            4 => Ok(PhotoFormat::Webp),
84            _ => Err("invalid photo format value"),
85        }
86    }
87}
88
89/// Biometric data format
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91#[serde(into = "i64", try_from = "i64")]
92pub enum BiometricFormat {
93    Image = 0,
94    Template = 1,
95    Sound = 2,
96    BioHash = 3,
97}
98
99impl From<BiometricFormat> for i64 {
100    fn from(f: BiometricFormat) -> i64 {
101        f as i64
102    }
103}
104
105impl TryFrom<i64> for BiometricFormat {
106    type Error = &'static str;
107
108    fn try_from(value: i64) -> Result<Self, Self::Error> {
109        match value {
110            0 => Ok(BiometricFormat::Image),
111            1 => Ok(BiometricFormat::Template),
112            2 => Ok(BiometricFormat::Sound),
113            3 => Ok(BiometricFormat::BioHash),
114            _ => Err("invalid biometric format value"),
115        }
116    }
117}
118
119/// Image sub-formats
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
121#[serde(into = "i64", try_from = "i64")]
122pub enum ImageSubFormat {
123    Png,
124    Jpeg,
125    Jpeg2000,
126    Avif,
127    Webp,
128    Tiff,
129    Wsq,
130    VendorSpecific(i64),
131}
132
133impl From<ImageSubFormat> for i64 {
134    fn from(f: ImageSubFormat) -> i64 {
135        match f {
136            ImageSubFormat::Png => 0,
137            ImageSubFormat::Jpeg => 1,
138            ImageSubFormat::Jpeg2000 => 2,
139            ImageSubFormat::Avif => 3,
140            ImageSubFormat::Webp => 4,
141            ImageSubFormat::Tiff => 5,
142            ImageSubFormat::Wsq => 6,
143            ImageSubFormat::VendorSpecific(v) => v,
144        }
145    }
146}
147
148impl TryFrom<i64> for ImageSubFormat {
149    type Error = &'static str;
150
151    fn try_from(value: i64) -> Result<Self, Self::Error> {
152        match value {
153            0 => Ok(ImageSubFormat::Png),
154            1 => Ok(ImageSubFormat::Jpeg),
155            2 => Ok(ImageSubFormat::Jpeg2000),
156            3 => Ok(ImageSubFormat::Avif),
157            4 => Ok(ImageSubFormat::Webp),
158            5 => Ok(ImageSubFormat::Tiff),
159            6 => Ok(ImageSubFormat::Wsq),
160            100..=200 => Ok(ImageSubFormat::VendorSpecific(value)),
161            _ => Err("invalid image sub-format value"),
162        }
163    }
164}
165
166/// Template sub-formats
167#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
168#[serde(into = "i64", try_from = "i64")]
169pub enum TemplateSubFormat {
170    Ansi378,
171    Iso19794_2,
172    Nist,
173    VendorSpecific(i64),
174}
175
176impl From<TemplateSubFormat> for i64 {
177    fn from(f: TemplateSubFormat) -> i64 {
178        match f {
179            TemplateSubFormat::Ansi378 => 0,
180            TemplateSubFormat::Iso19794_2 => 1,
181            TemplateSubFormat::Nist => 2,
182            TemplateSubFormat::VendorSpecific(v) => v,
183        }
184    }
185}
186
187impl TryFrom<i64> for TemplateSubFormat {
188    type Error = &'static str;
189
190    fn try_from(value: i64) -> Result<Self, Self::Error> {
191        match value {
192            0 => Ok(TemplateSubFormat::Ansi378),
193            1 => Ok(TemplateSubFormat::Iso19794_2),
194            2 => Ok(TemplateSubFormat::Nist),
195            100..=200 => Ok(TemplateSubFormat::VendorSpecific(value)),
196            _ => Err("invalid template sub-format value"),
197        }
198    }
199}
200
201/// Sound sub-formats
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
203#[serde(into = "i64", try_from = "i64")]
204pub enum SoundSubFormat {
205    Wav = 0,
206    Mp3 = 1,
207}
208
209impl From<SoundSubFormat> for i64 {
210    fn from(f: SoundSubFormat) -> i64 {
211        f as i64
212    }
213}
214
215impl TryFrom<i64> for SoundSubFormat {
216    type Error = &'static str;
217
218    fn try_from(value: i64) -> Result<Self, Self::Error> {
219        match value {
220            0 => Ok(SoundSubFormat::Wav),
221            1 => Ok(SoundSubFormat::Mp3),
222            _ => Err("invalid sound sub-format value"),
223        }
224    }
225}
226
227/// Biometric sub-format (unified across format types)
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
229#[serde(untagged)]
230pub enum BiometricSubFormat {
231    Image(ImageSubFormat),
232    Template(TemplateSubFormat),
233    Sound(SoundSubFormat),
234    Raw(i64),
235}
236
237impl BiometricSubFormat {
238    pub fn from_format_and_value(format: BiometricFormat, value: i64) -> Self {
239        match format {
240            BiometricFormat::Image => ImageSubFormat::try_from(value)
241                .map(BiometricSubFormat::Image)
242                .unwrap_or(BiometricSubFormat::Raw(value)),
243            BiometricFormat::Template => TemplateSubFormat::try_from(value)
244                .map(BiometricSubFormat::Template)
245                .unwrap_or(BiometricSubFormat::Raw(value)),
246            BiometricFormat::Sound => SoundSubFormat::try_from(value)
247                .map(BiometricSubFormat::Sound)
248                .unwrap_or(BiometricSubFormat::Raw(value)),
249            BiometricFormat::BioHash => BiometricSubFormat::Raw(value),
250        }
251    }
252
253    pub fn to_value(&self) -> i64 {
254        match self {
255            BiometricSubFormat::Image(f) => (*f).into(),
256            BiometricSubFormat::Template(f) => (*f).into(),
257            BiometricSubFormat::Sound(f) => (*f).into(),
258            BiometricSubFormat::Raw(v) => *v,
259        }
260    }
261}
262
263/// Verification status of the decoded QR
264#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
265#[serde(rename_all = "lowercase")]
266pub enum VerificationStatus {
267    Verified,
268    Failed,
269    Skipped,
270}
271
272impl std::fmt::Display for VerificationStatus {
273    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274        match self {
275            VerificationStatus::Verified => write!(f, "verified"),
276            VerificationStatus::Failed => write!(f, "failed"),
277            VerificationStatus::Skipped => write!(f, "skipped"),
278        }
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285
286    // ========== Gender Tests ==========
287    #[test]
288    fn test_gender_conversion() {
289        assert_eq!(i64::from(Gender::Male), 1);
290        assert_eq!(i64::from(Gender::Female), 2);
291        assert_eq!(i64::from(Gender::Other), 3);
292        assert_eq!(Gender::try_from(1).unwrap(), Gender::Male);
293        assert_eq!(Gender::try_from(2).unwrap(), Gender::Female);
294        assert_eq!(Gender::try_from(3).unwrap(), Gender::Other);
295        assert!(Gender::try_from(0).is_err());
296        assert!(Gender::try_from(4).is_err());
297        assert!(Gender::try_from(99).is_err());
298    }
299
300    // ========== MaritalStatus Tests ==========
301    #[test]
302    fn test_marital_status_conversion() {
303        assert_eq!(i64::from(MaritalStatus::Unmarried), 1);
304        assert_eq!(i64::from(MaritalStatus::Married), 2);
305        assert_eq!(i64::from(MaritalStatus::Divorced), 3);
306        assert_eq!(
307            MaritalStatus::try_from(1).unwrap(),
308            MaritalStatus::Unmarried
309        );
310        assert_eq!(MaritalStatus::try_from(2).unwrap(), MaritalStatus::Married);
311        assert_eq!(MaritalStatus::try_from(3).unwrap(), MaritalStatus::Divorced);
312        assert!(MaritalStatus::try_from(0).is_err());
313        assert!(MaritalStatus::try_from(4).is_err());
314    }
315
316    // ========== PhotoFormat Tests ==========
317    #[test]
318    fn test_photo_format_conversion() {
319        assert_eq!(i64::from(PhotoFormat::Jpeg), 1);
320        assert_eq!(i64::from(PhotoFormat::Jpeg2000), 2);
321        assert_eq!(i64::from(PhotoFormat::Avif), 3);
322        assert_eq!(i64::from(PhotoFormat::Webp), 4);
323        assert_eq!(PhotoFormat::try_from(1).unwrap(), PhotoFormat::Jpeg);
324        assert_eq!(PhotoFormat::try_from(2).unwrap(), PhotoFormat::Jpeg2000);
325        assert_eq!(PhotoFormat::try_from(3).unwrap(), PhotoFormat::Avif);
326        assert_eq!(PhotoFormat::try_from(4).unwrap(), PhotoFormat::Webp);
327        assert!(PhotoFormat::try_from(0).is_err());
328        assert!(PhotoFormat::try_from(5).is_err());
329    }
330
331    // ========== BiometricFormat Tests ==========
332    #[test]
333    fn test_biometric_format_conversion() {
334        assert_eq!(i64::from(BiometricFormat::Image), 0);
335        assert_eq!(i64::from(BiometricFormat::Template), 1);
336        assert_eq!(i64::from(BiometricFormat::Sound), 2);
337        assert_eq!(i64::from(BiometricFormat::BioHash), 3);
338        assert_eq!(
339            BiometricFormat::try_from(0).unwrap(),
340            BiometricFormat::Image
341        );
342        assert_eq!(
343            BiometricFormat::try_from(1).unwrap(),
344            BiometricFormat::Template
345        );
346        assert_eq!(
347            BiometricFormat::try_from(2).unwrap(),
348            BiometricFormat::Sound
349        );
350        assert_eq!(
351            BiometricFormat::try_from(3).unwrap(),
352            BiometricFormat::BioHash
353        );
354        assert!(BiometricFormat::try_from(-1).is_err());
355        assert!(BiometricFormat::try_from(4).is_err());
356    }
357
358    // ========== ImageSubFormat Tests ==========
359    #[test]
360    fn test_image_subformat_all_variants() {
361        assert_eq!(i64::from(ImageSubFormat::Png), 0);
362        assert_eq!(i64::from(ImageSubFormat::Jpeg), 1);
363        assert_eq!(i64::from(ImageSubFormat::Jpeg2000), 2);
364        assert_eq!(i64::from(ImageSubFormat::Avif), 3);
365        assert_eq!(i64::from(ImageSubFormat::Webp), 4);
366        assert_eq!(i64::from(ImageSubFormat::Tiff), 5);
367        assert_eq!(i64::from(ImageSubFormat::Wsq), 6);
368        assert_eq!(i64::from(ImageSubFormat::VendorSpecific(150)), 150);
369
370        assert_eq!(ImageSubFormat::try_from(0).unwrap(), ImageSubFormat::Png);
371        assert_eq!(ImageSubFormat::try_from(1).unwrap(), ImageSubFormat::Jpeg);
372        assert_eq!(
373            ImageSubFormat::try_from(2).unwrap(),
374            ImageSubFormat::Jpeg2000
375        );
376        assert_eq!(ImageSubFormat::try_from(3).unwrap(), ImageSubFormat::Avif);
377        assert_eq!(ImageSubFormat::try_from(4).unwrap(), ImageSubFormat::Webp);
378        assert_eq!(ImageSubFormat::try_from(5).unwrap(), ImageSubFormat::Tiff);
379        assert_eq!(ImageSubFormat::try_from(6).unwrap(), ImageSubFormat::Wsq);
380    }
381
382    #[test]
383    fn test_image_subformat_vendor_specific() {
384        let vendor = ImageSubFormat::try_from(150).unwrap();
385        assert!(matches!(vendor, ImageSubFormat::VendorSpecific(150)));
386        assert_eq!(i64::from(vendor), 150);
387
388        // Test edge cases of vendor specific range
389        let vendor_100 = ImageSubFormat::try_from(100).unwrap();
390        assert!(matches!(vendor_100, ImageSubFormat::VendorSpecific(100)));
391
392        let vendor_200 = ImageSubFormat::try_from(200).unwrap();
393        assert!(matches!(vendor_200, ImageSubFormat::VendorSpecific(200)));
394    }
395
396    #[test]
397    fn test_image_subformat_invalid_values() {
398        // Values between standard formats and vendor range should fail
399        assert!(ImageSubFormat::try_from(7).is_err());
400        assert!(ImageSubFormat::try_from(50).is_err());
401        assert!(ImageSubFormat::try_from(99).is_err());
402        // Values outside vendor range should fail
403        assert!(ImageSubFormat::try_from(201).is_err());
404        assert!(ImageSubFormat::try_from(-1).is_err());
405    }
406
407    // ========== TemplateSubFormat Tests ==========
408    #[test]
409    fn test_template_subformat_all_variants() {
410        assert_eq!(i64::from(TemplateSubFormat::Ansi378), 0);
411        assert_eq!(i64::from(TemplateSubFormat::Iso19794_2), 1);
412        assert_eq!(i64::from(TemplateSubFormat::Nist), 2);
413        assert_eq!(i64::from(TemplateSubFormat::VendorSpecific(175)), 175);
414
415        assert_eq!(
416            TemplateSubFormat::try_from(0).unwrap(),
417            TemplateSubFormat::Ansi378
418        );
419        assert_eq!(
420            TemplateSubFormat::try_from(1).unwrap(),
421            TemplateSubFormat::Iso19794_2
422        );
423        assert_eq!(
424            TemplateSubFormat::try_from(2).unwrap(),
425            TemplateSubFormat::Nist
426        );
427    }
428
429    #[test]
430    fn test_template_subformat_vendor_specific() {
431        let vendor = TemplateSubFormat::try_from(100).unwrap();
432        assert!(matches!(vendor, TemplateSubFormat::VendorSpecific(100)));
433
434        let vendor = TemplateSubFormat::try_from(200).unwrap();
435        assert!(matches!(vendor, TemplateSubFormat::VendorSpecific(200)));
436    }
437
438    #[test]
439    fn test_template_subformat_invalid_values() {
440        assert!(TemplateSubFormat::try_from(3).is_err());
441        assert!(TemplateSubFormat::try_from(50).is_err());
442        assert!(TemplateSubFormat::try_from(99).is_err());
443        assert!(TemplateSubFormat::try_from(201).is_err());
444        assert!(TemplateSubFormat::try_from(-1).is_err());
445    }
446
447    // ========== SoundSubFormat Tests ==========
448    #[test]
449    fn test_sound_subformat_conversion() {
450        assert_eq!(i64::from(SoundSubFormat::Wav), 0);
451        assert_eq!(i64::from(SoundSubFormat::Mp3), 1);
452        assert_eq!(SoundSubFormat::try_from(0).unwrap(), SoundSubFormat::Wav);
453        assert_eq!(SoundSubFormat::try_from(1).unwrap(), SoundSubFormat::Mp3);
454        assert!(SoundSubFormat::try_from(2).is_err());
455        assert!(SoundSubFormat::try_from(-1).is_err());
456    }
457
458    // ========== BiometricSubFormat Tests ==========
459    #[test]
460    fn test_biometric_subformat_from_format() {
461        // Image format
462        let sub = BiometricSubFormat::from_format_and_value(BiometricFormat::Image, 6);
463        assert!(matches!(
464            sub,
465            BiometricSubFormat::Image(ImageSubFormat::Wsq)
466        ));
467
468        let sub = BiometricSubFormat::from_format_and_value(BiometricFormat::Image, 0);
469        assert!(matches!(
470            sub,
471            BiometricSubFormat::Image(ImageSubFormat::Png)
472        ));
473
474        // Template format
475        let sub = BiometricSubFormat::from_format_and_value(BiometricFormat::Template, 1);
476        assert!(matches!(
477            sub,
478            BiometricSubFormat::Template(TemplateSubFormat::Iso19794_2)
479        ));
480
481        let sub = BiometricSubFormat::from_format_and_value(BiometricFormat::Template, 0);
482        assert!(matches!(
483            sub,
484            BiometricSubFormat::Template(TemplateSubFormat::Ansi378)
485        ));
486
487        // Sound format
488        let sub = BiometricSubFormat::from_format_and_value(BiometricFormat::Sound, 0);
489        assert!(matches!(
490            sub,
491            BiometricSubFormat::Sound(SoundSubFormat::Wav)
492        ));
493
494        let sub = BiometricSubFormat::from_format_and_value(BiometricFormat::Sound, 1);
495        assert!(matches!(
496            sub,
497            BiometricSubFormat::Sound(SoundSubFormat::Mp3)
498        ));
499
500        // BioHash format - always returns Raw
501        let sub = BiometricSubFormat::from_format_and_value(BiometricFormat::BioHash, 0);
502        assert!(matches!(sub, BiometricSubFormat::Raw(0)));
503
504        let sub = BiometricSubFormat::from_format_and_value(BiometricFormat::BioHash, 42);
505        assert!(matches!(sub, BiometricSubFormat::Raw(42)));
506    }
507
508    #[test]
509    fn test_biometric_subformat_invalid_returns_raw() {
510        // Invalid image sub-format returns Raw
511        let sub = BiometricSubFormat::from_format_and_value(BiometricFormat::Image, 50);
512        assert!(matches!(sub, BiometricSubFormat::Raw(50)));
513
514        // Invalid template sub-format returns Raw
515        let sub = BiometricSubFormat::from_format_and_value(BiometricFormat::Template, 50);
516        assert!(matches!(sub, BiometricSubFormat::Raw(50)));
517
518        // Invalid sound sub-format returns Raw
519        let sub = BiometricSubFormat::from_format_and_value(BiometricFormat::Sound, 99);
520        assert!(matches!(sub, BiometricSubFormat::Raw(99)));
521    }
522
523    #[test]
524    fn test_biometric_subformat_to_value() {
525        // Image
526        let sub = BiometricSubFormat::Image(ImageSubFormat::Jpeg);
527        assert_eq!(sub.to_value(), 1);
528
529        let sub = BiometricSubFormat::Image(ImageSubFormat::VendorSpecific(150));
530        assert_eq!(sub.to_value(), 150);
531
532        // Template
533        let sub = BiometricSubFormat::Template(TemplateSubFormat::Iso19794_2);
534        assert_eq!(sub.to_value(), 1);
535
536        let sub = BiometricSubFormat::Template(TemplateSubFormat::VendorSpecific(175));
537        assert_eq!(sub.to_value(), 175);
538
539        // Sound
540        let sub = BiometricSubFormat::Sound(SoundSubFormat::Mp3);
541        assert_eq!(sub.to_value(), 1);
542
543        // Raw
544        let sub = BiometricSubFormat::Raw(999);
545        assert_eq!(sub.to_value(), 999);
546    }
547
548    // ========== VerificationStatus Tests ==========
549    #[test]
550    fn test_verification_status_display() {
551        assert_eq!(format!("{}", VerificationStatus::Verified), "verified");
552        assert_eq!(format!("{}", VerificationStatus::Failed), "failed");
553        assert_eq!(format!("{}", VerificationStatus::Skipped), "skipped");
554    }
555
556    #[test]
557    fn test_verification_status_json_serialization() {
558        let verified = VerificationStatus::Verified;
559        let json = serde_json::to_string(&verified).unwrap();
560        assert_eq!(json, "\"verified\"");
561
562        let failed = VerificationStatus::Failed;
563        let json = serde_json::to_string(&failed).unwrap();
564        assert_eq!(json, "\"failed\"");
565
566        let skipped = VerificationStatus::Skipped;
567        let json = serde_json::to_string(&skipped).unwrap();
568        assert_eq!(json, "\"skipped\"");
569
570        // Deserialization
571        let parsed: VerificationStatus = serde_json::from_str("\"verified\"").unwrap();
572        assert_eq!(parsed, VerificationStatus::Verified);
573    }
574
575    // ========== Enum Copy/Debug/Eq Tests ==========
576    #[test]
577    fn test_gender_traits() {
578        let g1 = Gender::Male;
579        let g2 = g1; // Copy
580        assert_eq!(g1, g2);
581        assert_ne!(g1, Gender::Female);
582        // Debug
583        assert!(format!("{:?}", g1).contains("Male"));
584    }
585
586    #[test]
587    fn test_verification_status_traits() {
588        let v1 = VerificationStatus::Verified;
589        let v2 = v1; // Copy
590        assert_eq!(v1, v2);
591        assert_ne!(v1, VerificationStatus::Failed);
592        // Debug
593        assert!(format!("{:?}", v1).contains("Verified"));
594    }
595
596    #[test]
597    fn test_biometric_subformat_copy() {
598        let sub1 = BiometricSubFormat::Image(ImageSubFormat::Jpeg);
599        let sub2 = sub1; // Copy
600        assert_eq!(sub1, sub2);
601    }
602}