lastid_sdk/types/
client_id.rs1use std::fmt;
6use std::str::FromStr;
7
8use serde::{Deserialize, Serialize};
9
10use crate::error::LastIDError;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
32#[serde(try_from = "String", into = "String")]
33pub struct ClientId(String);
34
35impl ClientId {
36 pub fn new(value: impl Into<String>) -> Result<Self, LastIDError> {
43 let s = value.into();
44 Self::validate(&s)?;
45 Ok(Self(s))
46 }
47
48 #[must_use]
50 pub fn as_str(&self) -> &str {
51 &self.0
52 }
53
54 fn validate(s: &str) -> Result<(), LastIDError> {
56 if s.is_empty() || s.trim().is_empty() {
57 return Err(LastIDError::EmptyClientId);
58 }
59 Ok(())
60 }
61}
62
63impl fmt::Display for ClientId {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(f, "{}", self.0)
66 }
67}
68
69impl AsRef<str> for ClientId {
70 fn as_ref(&self) -> &str {
71 &self.0
72 }
73}
74
75impl From<ClientId> for String {
76 fn from(client_id: ClientId) -> Self {
77 client_id.0
78 }
79}
80
81impl TryFrom<String> for ClientId {
82 type Error = LastIDError;
83
84 fn try_from(value: String) -> Result<Self, Self::Error> {
85 Self::new(value)
86 }
87}
88
89impl TryFrom<&str> for ClientId {
90 type Error = LastIDError;
91
92 fn try_from(value: &str) -> Result<Self, Self::Error> {
93 Self::new(value)
94 }
95}
96
97impl FromStr for ClientId {
98 type Err = LastIDError;
99
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 Self::new(s)
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_valid_client_id() {
111 let id = ClientId::new("my-app").unwrap();
112 assert_eq!(id.as_str(), "my-app");
113 }
114
115 #[test]
116 fn test_valid_client_id_with_special_chars() {
117 let id = ClientId::new("app_123-test.com").unwrap();
118 assert_eq!(id.as_str(), "app_123-test.com");
119 }
120
121 #[test]
122 fn test_invalid_empty() {
123 assert!(ClientId::new("").is_err());
124 }
125
126 #[test]
127 fn test_invalid_whitespace_only() {
128 assert!(ClientId::new(" ").is_err());
129 assert!(ClientId::new("\t\n").is_err());
130 }
131
132 #[test]
133 fn test_display() {
134 let id = ClientId::new("test-app").unwrap();
135 assert_eq!(format!("{id}"), "test-app");
136 }
137
138 #[test]
139 fn test_from_str() {
140 let id: ClientId = "my-client".parse().unwrap();
141 assert_eq!(id.as_str(), "my-client");
142 }
143
144 #[test]
145 fn test_try_from_string() {
146 let id = ClientId::try_from("test".to_string()).unwrap();
147 assert_eq!(id.as_str(), "test");
148 }
149
150 #[test]
151 fn test_into_string() {
152 let id = ClientId::new("test").unwrap();
153 let s: String = id.into();
154 assert_eq!(s, "test");
155 }
156
157 #[test]
158 fn test_serde_roundtrip() {
159 let id = ClientId::new("my-app").unwrap();
160 let json = serde_json::to_string(&id).unwrap();
161 assert_eq!(json, "\"my-app\"");
162
163 let deserialized: ClientId = serde_json::from_str(&json).unwrap();
164 assert_eq!(deserialized, id);
165 }
166
167 #[test]
168 fn test_serde_invalid_empty() {
169 let result: Result<ClientId, _> = serde_json::from_str("\"\"");
170 assert!(result.is_err());
171 }
172}