use derive_more::{Display, From, FromStr, Into};
use serde::{Deserialize, Serialize};
use ulid::Ulid;
use crate::error::Error;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Display, FromStr, From, Into,
)]
#[serde(transparent)]
pub struct PpnumId(pub Ulid);
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub struct Ppnum(String);
impl Ppnum {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for Ppnum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl std::str::FromStr for Ppnum {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s.to_owned())
}
}
impl TryFrom<String> for Ppnum {
type Error = Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
if s.len() == 11 && s.starts_with("777") && s.bytes().all(|b| b.is_ascii_digit()) {
Ok(Self(s))
} else {
Err(Error::InvalidPpnum(s))
}
}
}
impl From<Ppnum> for String {
fn from(p: Ppnum) -> Self {
p.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Display, From, Into)]
#[serde(transparent)]
pub struct UserId(pub String);
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Display, From, Into)]
#[serde(transparent)]
pub struct SessionId(pub String);
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Display, From, Into)]
#[serde(transparent)]
pub struct KeyId(pub String);
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use static_assertions::assert_impl_all;
assert_impl_all!(PpnumId: Send, Sync, Copy);
assert_impl_all!(Ppnum: Send, Sync);
assert_impl_all!(UserId: Send, Sync);
assert_impl_all!(SessionId: Send, Sync);
assert_impl_all!(KeyId: Send, Sync);
#[test]
fn valid_ppnum() {
assert!("77712345678".parse::<Ppnum>().is_ok());
assert!("77700000000".parse::<Ppnum>().is_ok());
assert!("77799999999".parse::<Ppnum>().is_ok());
}
#[test]
fn invalid_ppnum_wrong_prefix() {
assert!("12345678901".parse::<Ppnum>().is_err());
assert!("77812345678".parse::<Ppnum>().is_err());
}
#[test]
fn invalid_ppnum_wrong_length() {
assert!("7771234567".parse::<Ppnum>().is_err());
assert!("777123456789".parse::<Ppnum>().is_err());
assert!("".parse::<Ppnum>().is_err());
}
#[test]
fn invalid_ppnum_non_digits() {
assert!("777abcdefgh".parse::<Ppnum>().is_err());
assert!("7771234567a".parse::<Ppnum>().is_err());
}
#[test]
fn ppnum_serde_roundtrip() {
let ppnum: Ppnum = "77712345678".parse().unwrap();
let json = serde_json::to_string(&ppnum).unwrap();
assert_eq!(json, "\"77712345678\"");
let parsed: Ppnum = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, ppnum);
}
#[test]
fn ppnum_id_serde_roundtrip() {
let id = PpnumId(Ulid::nil());
let json = serde_json::to_string(&id).unwrap();
let parsed: PpnumId = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, id);
}
#[test]
fn user_id_from_string() {
let id = UserId::from("user-123".to_string());
assert_eq!(id.to_string(), "user-123");
}
#[test]
fn session_id_from_string() {
let id = SessionId::from("sess-abc".to_string());
assert_eq!(id.to_string(), "sess-abc");
}
#[test]
fn newtypes_prevent_mixing() {
fn takes_user_id(_: &UserId) {}
fn takes_session_id(_: &SessionId) {}
let user = UserId::from("id".to_string());
let session = SessionId::from("id".to_string());
takes_user_id(&user);
takes_session_id(&session);
}
}