agentis_pay_shared/types/
cpf.rs1use serde::{Deserialize, Deserializer, Serialize};
2use std::{
3 ops::Deref,
4 str::{Chars, FromStr},
5};
6
7#[derive(Debug, Clone, Serialize)]
8pub struct Cpf(String);
9
10impl Cpf {
11 pub fn format_and_mask(&self) -> String {
12 Self::format_and_mask_cpf(&self.0)
13 }
14
15 pub fn format_and_mask_cpf(cpf: &str) -> String {
16 format!("•••.{}.{}-••", &cpf[3..6], &cpf[6..9])
17 }
18}
19
20#[derive(Debug, PartialEq, Eq)]
21pub struct Error(&'static str);
22
23impl Error {
24 const LENGTH: Self = Error("length != 11");
25 const SYMBOLS: Self = Error("non-digit symbols");
26 const DIGITS: Self = Error("invalid check digits");
27}
28
29impl std::fmt::Display for Error {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 write!(f, "cpf error {}", self.0)
32 }
33}
34
35impl std::error::Error for Error {}
36
37impl<'de> Deserialize<'de> for Cpf {
38 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Cpf, D::Error> {
39 let raw = String::deserialize(deserializer)?;
40 raw.parse().map_err(serde::de::Error::custom)
41 }
42}
43
44impl Deref for Cpf {
45 type Target = String;
46
47 fn deref(&self) -> &Self::Target {
48 &self.0
49 }
50}
51
52impl FromStr for Cpf {
53 type Err = Error;
54
55 #[expect(clippy::similar_names, reason = "because it fits so well")]
56 fn from_str(s: &str) -> Result<Self, Self::Err> {
57 if s.len() > 11 {
58 return Err(Error::LENGTH);
59 }
60
61 let mut chars = s.chars();
62 let digit2 = read_back_digit(&mut chars)?;
63 let digit1 = read_back_digit(&mut chars)?;
64
65 let mut digit_acc = Some(digit1).filter(|d| *d == digit2);
66 let mut digit1_acc = 0;
67 let mut digit2_acc = 0;
68
69 for i in 0..9 {
70 let digit = read_front_digit(&mut chars)?;
71
72 digit_acc = digit_acc.filter(|d| *d == digit);
73 digit1_acc += digit * (10 - i);
74 digit2_acc += digit * (11 - i);
75 }
76
77 let expected_digit1 = (digit1_acc * 10 % 11) % 10;
78 let expected_digit2 = ((digit2_acc + 2 * expected_digit1) * 10 % 11) % 10;
79
80 if expected_digit1 != digit1 || expected_digit2 != digit2 || digit_acc.is_some() {
81 return Err(Error::DIGITS);
82 }
83
84 Ok(Cpf(s.to_owned()))
85 }
86}
87
88fn read_front_digit(chars: &mut Chars) -> Result<u32, Error> {
89 let char = chars.next().ok_or(Error::LENGTH)?;
90 char.to_digit(10).ok_or(Error::SYMBOLS)
91}
92
93fn read_back_digit(chars: &mut Chars) -> Result<u32, Error> {
94 let char = chars.next_back().ok_or(Error::LENGTH)?;
95 char.to_digit(10).ok_or(Error::SYMBOLS)
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn cpf_parsing() {
104 assert!("64780788099".parse::<Cpf>().is_ok());
105 assert!("34790194043".parse::<Cpf>().is_ok());
106 assert!("04584475016".parse::<Cpf>().is_ok());
107 assert!("76073649002".parse::<Cpf>().is_ok());
108 assert!("01234567890".parse::<Cpf>().is_ok());
109 assert!("24254794061".parse::<Cpf>().is_ok());
110 assert!("40654704040".parse::<Cpf>().is_ok());
111 assert!("54350515097".parse::<Cpf>().is_ok());
112 assert!("46337130006".parse::<Cpf>().is_ok());
113 assert!("25422669053".parse::<Cpf>().is_ok());
114 assert!("53266606019".parse::<Cpf>().is_ok());
115 assert!("60595030041".parse::<Cpf>().is_ok());
116 assert!("49274939035".parse::<Cpf>().is_ok());
117 assert!("71482987090".parse::<Cpf>().is_ok());
118 assert!("66611386025".parse::<Cpf>().is_ok());
119 assert!("30355706040".parse::<Cpf>().is_ok());
120 assert!("58012477009".parse::<Cpf>().is_ok());
121
122 assert_eq!("01234567891".parse::<Cpf>().unwrap_err(), Error::DIGITS);
123 assert_eq!("0123456789".parse::<Cpf>().unwrap_err(), Error::LENGTH);
124 assert_eq!("012345678901".parse::<Cpf>().unwrap_err(), Error::LENGTH);
125 assert_eq!("0123456789a".parse::<Cpf>().unwrap_err(), Error::SYMBOLS);
126
127 assert_eq!("00000000000".parse::<Cpf>().unwrap_err(), Error::DIGITS);
128 assert_eq!("11111111111".parse::<Cpf>().unwrap_err(), Error::DIGITS);
129 assert_eq!("22222222222".parse::<Cpf>().unwrap_err(), Error::DIGITS);
130 assert_eq!("33333333333".parse::<Cpf>().unwrap_err(), Error::DIGITS);
131 assert_eq!("44444444444".parse::<Cpf>().unwrap_err(), Error::DIGITS);
132 assert_eq!("55555555555".parse::<Cpf>().unwrap_err(), Error::DIGITS);
133 assert_eq!("66666666666".parse::<Cpf>().unwrap_err(), Error::DIGITS);
134 assert_eq!("77777777777".parse::<Cpf>().unwrap_err(), Error::DIGITS);
135 assert_eq!("88888888888".parse::<Cpf>().unwrap_err(), Error::DIGITS);
136 assert_eq!("99999999999".parse::<Cpf>().unwrap_err(), Error::DIGITS);
137 }
138}