1use crate::PaymentError;
2
3#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5pub struct PhoneNumber(String);
6
7impl PhoneNumber {
8 pub fn new(value: impl AsRef<str>) -> Result<Self, PaymentError> {
15 let value = value.as_ref().trim();
16 let digits = value.strip_prefix('+').unwrap_or(value);
17 if !(8..=15).contains(&digits.len()) || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
18 return Err(PaymentError::InvalidPhoneNumber(value.to_owned()));
19 }
20
21 Ok(Self(format!("+{digits}")))
22 }
23
24 pub fn new_digits(value: impl AsRef<str>) -> Result<Self, PaymentError> {
30 Self::new(value)
31 }
32
33 #[inline]
35 #[must_use]
36 pub fn as_e164(&self) -> &str {
37 &self.0
38 }
39
40 #[inline]
42 #[must_use]
43 pub fn digits(&self) -> &str {
44 &self.0[1..]
45 }
46}
47
48impl AsRef<str> for PhoneNumber {
49 #[inline]
50 fn as_ref(&self) -> &str {
51 self.as_e164()
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn new_normalizes_number() {
61 let phone = PhoneNumber::new("260971234567").expect("phone should be valid");
62 let phone_from_digits =
63 PhoneNumber::new_digits("260971234567").expect("phone should be valid");
64
65 assert_eq!(phone.as_e164(), "+260971234567");
66 assert_eq!(phone.digits(), "260971234567");
67 assert_eq!(phone_from_digits.as_ref(), "+260971234567");
68 }
69
70 #[test]
71 fn new_rejects_short_number() {
72 assert!(matches!(
73 PhoneNumber::new("123"),
74 Err(PaymentError::InvalidPhoneNumber(_))
75 ));
76 }
77}