1use std::fmt::{self, Display};
2use std::ops::{Deref, DerefMut};
3use std::str::FromStr;
4use thiserror::Error;
5
6#[derive(Debug, Clone, Eq, PartialEq, Hash)]
28pub struct PackageName(String);
29
30#[derive(Debug, Clone, Eq, PartialEq, Hash)]
31pub struct ServiceName(String);
32
33#[derive(Debug, Clone, Eq, PartialEq, Hash)]
34pub struct MethodName(String);
35
36#[derive(Debug, Clone, Eq, PartialEq, Hash)]
45pub struct Name(String);
46
47#[derive(Debug, Error, Eq, PartialEq)]
48pub enum NameError {
49 #[error("Name is empty")]
50 Empty,
51
52 #[error("Name exceeds 32 characters, length: {0}")]
53 TooLong(usize),
54
55 #[error("Name must start with an alphabetic character, found: {0}")]
56 InvalidStartChar(char),
57
58 #[error("Name must end with an alphanumeric character, found: {0}")]
59 InvalidEndChar(char),
60
61 #[error("Name contains invalid character: {0}")]
62 InvalidChar(char),
63}
64
65impl Name {
66 pub fn new(name: String) -> Result<Self, NameError> {
72 if name.is_empty() {
73 return Err(NameError::Empty);
74 }
75 if name.len() > 32 {
76 return Err(NameError::TooLong(name.len()));
77 }
78 let mut chars = name.chars();
79 let first = chars.next().ok_or(NameError::Empty)?;
80 if !first.is_alphabetic() {
81 return Err(NameError::InvalidStartChar(first));
82 }
83 let mut last = first;
84 for c in chars {
85 last = c;
86 if !c.is_alphanumeric() && c != '-' && c != '_' {
87 return Err(NameError::InvalidChar(c));
88 }
89 }
90 if !last.is_alphanumeric() {
91 return Err(NameError::InvalidEndChar(last));
92 }
93 Ok(Self(name))
94 }
95}
96
97impl FromStr for Name {
98 type Err = NameError;
99 fn from_str(s: &str) -> Result<Self, Self::Err> {
100 Self::new(s.to_string())
101 }
102}
103
104impl Deref for Name {
105 type Target = String;
106 fn deref(&self) -> &Self::Target {
107 &self.0
108 }
109}
110
111impl DerefMut for Name {
112 fn deref_mut(&mut self) -> &mut Self::Target {
113 &mut self.0
114 }
115}
116
117impl TryFrom<String> for Name {
118 type Error = NameError;
119 fn try_from(s: String) -> Result<Self, Self::Error> {
120 Self::new(s)
121 }
122}
123
124impl Display for Name {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 write!(f, "{}", self.0)
127 }
128}
129
130#[derive(Debug, Error, Eq, PartialEq)]
133pub enum PackageNameError {
134 #[error("Package name is empty")]
135 Empty,
136
137 #[error("Package name exceeds 256 characters, length: {0}")]
138 TooLong(usize),
139
140 #[error("Package name must not start with a digit or '.', found: {0}")]
141 InvalidStartChar(char),
142
143 #[error("Package name must not end with '.', found: {0}")]
144 InvalidEndChar(char),
145
146 #[error("Package name contains invalid character: {0}")]
147 InvalidChar(char),
148
149 #[error("Package name contains empty segment")]
150 EmptySegment,
151
152 #[error("Package name segment must not start with a digit, found: {0}")]
153 InvalidSegmentStartChar(char),
154}
155
156impl PackageName {
157 pub fn new(name: String) -> Result<Self, PackageNameError> {
158 const MAX_LEN: usize = 256;
159 if name.is_empty() {
160 return Err(PackageNameError::Empty);
161 }
162 if name.len() > MAX_LEN {
163 return Err(PackageNameError::TooLong(name.len()));
164 }
165
166 let mut chars = name.chars();
167 let first = chars.next().ok_or(PackageNameError::Empty)?;
168
169 if first == '.' || first.is_ascii_digit() {
171 return Err(PackageNameError::InvalidStartChar(first));
172 }
173 if first.is_alphabetic() && first.is_uppercase() {
174 return Err(PackageNameError::InvalidChar(first));
175 }
176 if !(first.is_alphanumeric() || first == '_') {
177 return Err(PackageNameError::InvalidChar(first));
179 }
180
181 let mut last = first;
182 let mut at_segment_start = false; for c in chars {
185 if c == '.' {
187 if last == '.' {
188 return Err(PackageNameError::EmptySegment);
189 }
190 at_segment_start = true;
191 last = c;
192 continue;
193 }
194
195 if at_segment_start && c.is_ascii_digit() {
197 return Err(PackageNameError::InvalidSegmentStartChar(c));
198 }
199 at_segment_start = false;
200
201 if !(c.is_alphanumeric() || c == '_') {
203 return Err(PackageNameError::InvalidChar(c));
204 }
205 if c.is_alphabetic() && c.is_uppercase() {
207 return Err(PackageNameError::InvalidChar(c));
208 }
209
210 last = c;
211 }
212
213 if last == '.' {
214 return Err(PackageNameError::InvalidEndChar(last));
215 }
216
217 Ok(Self(name))
218 }
219}
220
221impl FromStr for PackageName {
222 type Err = PackageNameError;
223 fn from_str(s: &str) -> Result<Self, Self::Err> {
224 Self::new(s.to_string())
225 }
226}
227
228impl Deref for PackageName {
229 type Target = String;
230 fn deref(&self) -> &Self::Target {
231 &self.0
232 }
233}
234
235impl DerefMut for PackageName {
236 fn deref_mut(&mut self) -> &mut Self::Target {
237 &mut self.0
238 }
239}
240
241impl TryFrom<String> for PackageName {
242 type Error = PackageNameError;
243 fn try_from(s: String) -> Result<Self, Self::Error> {
244 Self::new(s)
245 }
246}
247
248impl Display for PackageName {
249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250 write!(f, "{}", self.0)
251 }
252}
253
254#[derive(Debug, Error, Eq, PartialEq)]
257pub enum ServiceNameError {
258 #[error("Service name is empty")]
259 Empty,
260
261 #[error("Service name exceeds 64 characters, length: {0}")]
262 TooLong(usize),
263
264 #[error("Service name must start with an uppercase alphabetic character, found: {0}")]
265 InvalidStartChar(char),
266
267 #[error("Service name must end with an alphanumeric character, found: {0}")]
268 InvalidEndChar(char),
269
270 #[error("Service name contains invalid character: {0}")]
271 InvalidChar(char),
272}
273
274impl ServiceName {
275 pub fn new(name: String) -> Result<Self, ServiceNameError> {
276 const MAX_LEN: usize = 64;
277 if name.is_empty() {
278 return Err(ServiceNameError::Empty);
279 }
280 if name.len() > MAX_LEN {
281 return Err(ServiceNameError::TooLong(name.len()));
282 }
283
284 let mut chars = name.chars();
285 let first = chars.next().ok_or(ServiceNameError::Empty)?;
286 if !first.is_alphabetic() || !first.is_uppercase() {
287 return Err(ServiceNameError::InvalidStartChar(first));
288 }
289 let mut last = first;
290 for c in chars {
291 if !(c.is_alphanumeric() || c == '_') {
292 return Err(ServiceNameError::InvalidChar(c));
293 }
294 last = c;
295 }
296 if !last.is_alphanumeric() {
297 return Err(ServiceNameError::InvalidEndChar(last));
298 }
299 Ok(Self(name))
300 }
301}
302
303impl FromStr for ServiceName {
304 type Err = ServiceNameError;
305 fn from_str(s: &str) -> Result<Self, Self::Err> {
306 Self::new(s.to_string())
307 }
308}
309
310impl Deref for ServiceName {
311 type Target = String;
312 fn deref(&self) -> &Self::Target {
313 &self.0
314 }
315}
316
317impl DerefMut for ServiceName {
318 fn deref_mut(&mut self) -> &mut Self::Target {
319 &mut self.0
320 }
321}
322
323impl TryFrom<String> for ServiceName {
324 type Error = ServiceNameError;
325 fn try_from(s: String) -> Result<Self, Self::Error> {
326 Self::new(s)
327 }
328}
329
330impl Display for ServiceName {
331 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 write!(f, "{}", self.0)
333 }
334}
335
336#[derive(Debug, Error, Eq, PartialEq)]
339pub enum MethodNameError {
340 #[error("Method name is empty")]
341 Empty,
342
343 #[error("Method name exceeds 64 characters, length: {0}")]
344 TooLong(usize),
345
346 #[error("Method name must start with a lowercase alphabetic character, found: {0}")]
347 InvalidStartChar(char),
348
349 #[error("Method name must end with an alphanumeric character, found: {0}")]
350 InvalidEndChar(char),
351
352 #[error("Method name contains invalid character: {0}")]
353 InvalidChar(char),
354}
355
356impl MethodName {
357 pub fn new(name: String) -> Result<Self, MethodNameError> {
358 const MAX_LEN: usize = 64;
359 if name.is_empty() {
360 return Err(MethodNameError::Empty);
361 }
362 if name.len() > MAX_LEN {
363 return Err(MethodNameError::TooLong(name.len()));
364 }
365
366 let mut chars = name.chars();
367 let first = chars.next().ok_or(MethodNameError::Empty)?;
368 if !first.is_alphabetic() || !first.is_lowercase() {
369 return Err(MethodNameError::InvalidStartChar(first));
370 }
371 let mut last = first;
372 for c in chars {
373 if !(c.is_alphanumeric() || c == '_') {
374 return Err(MethodNameError::InvalidChar(c));
375 }
376 last = c;
377 }
378 if !last.is_alphanumeric() {
379 return Err(MethodNameError::InvalidEndChar(last));
380 }
381 Ok(Self(name))
382 }
383}
384
385impl FromStr for MethodName {
386 type Err = MethodNameError;
387 fn from_str(s: &str) -> Result<Self, Self::Err> {
388 Self::new(s.to_string())
389 }
390}
391
392impl Deref for MethodName {
393 type Target = String;
394 fn deref(&self) -> &Self::Target {
395 &self.0
396 }
397}
398
399impl DerefMut for MethodName {
400 fn deref_mut(&mut self) -> &mut Self::Target {
401 &mut self.0
402 }
403}
404
405impl TryFrom<String> for MethodName {
406 type Error = MethodNameError;
407 fn try_from(s: String) -> Result<Self, Self::Error> {
408 Self::new(s)
409 }
410}
411
412impl Display for MethodName {
413 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
414 write!(f, "{}", self.0)
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[test]
423 fn test_valid_name() {
424 assert!(Name::new("valid-name_123".to_string()).is_ok());
425 assert!(Name::new("A".to_string()).is_ok());
426 assert!(Name::new("actor-1".to_string()).is_ok());
427 }
428
429 #[test]
430 fn test_invalid_name() {
431 assert_eq!(Name::new("".to_string()).unwrap_err(), NameError::Empty);
432 assert_eq!(
433 Name::new("-invalid".to_string()).unwrap_err(),
434 NameError::InvalidStartChar('-')
435 );
436 assert_eq!(
437 Name::new("invalid-".to_string()).unwrap_err(),
438 NameError::InvalidEndChar('-')
439 );
440 assert_eq!(
441 Name::new("has invalid char!".to_string()).unwrap_err(),
442 NameError::InvalidChar(' ')
443 );
444 assert_eq!(
445 Name::new("too_long_name_that_exceeds_thirty_two_characters".to_string()).unwrap_err(),
446 NameError::TooLong(48)
447 );
448 assert_eq!(
449 Name::new("1starts_with_number".to_string()).unwrap_err(),
450 NameError::InvalidStartChar('1')
451 );
452 }
453
454 #[test]
455 fn test_valid_package_name() {
456 assert!(PackageName::new("com.example.project".to_string()).is_ok());
457 assert!(PackageName::new("a_b.c_d.e1".to_string()).is_ok());
458 assert!(PackageName::new("example".to_string()).is_ok());
459 assert!(PackageName::new("_internal.pkg".to_string()).is_ok());
460 }
461
462 #[test]
463 fn test_invalid_package_name() {
464 assert_eq!(
465 PackageName::new("".to_string()).unwrap_err(),
466 PackageNameError::Empty
467 );
468 assert_eq!(
469 PackageName::new(".leading".to_string()).unwrap_err(),
470 PackageNameError::InvalidStartChar('.')
471 );
472 assert_eq!(
473 PackageName::new("trailing.".to_string()).unwrap_err(),
474 PackageNameError::InvalidEndChar('.')
475 );
476 assert_eq!(
477 PackageName::new("com..example".to_string()).unwrap_err(),
478 PackageNameError::EmptySegment
479 );
480 assert_eq!(
481 PackageName::new("com.1example".to_string()).unwrap_err(),
482 PackageNameError::InvalidSegmentStartChar('1')
483 );
484 assert_eq!(
485 PackageName::new("Com.Example".to_string()).unwrap_err(),
486 PackageNameError::InvalidChar('C')
487 );
488 assert_eq!(
489 PackageName::new("com.exa$mple".to_string()).unwrap_err(),
490 PackageNameError::InvalidChar('$')
491 );
492 }
493
494 #[test]
495 fn test_valid_service_name() {
496 assert!(ServiceName::new("Echo".to_string()).is_ok());
497 assert!(ServiceName::new("UserService".to_string()).is_ok());
498 assert!(ServiceName::new("HTTPV1".to_string()).is_ok());
499 assert!(ServiceName::new("Service_Name".to_string()).is_ok());
500 }
501
502 #[test]
503 fn test_invalid_service_name() {
504 assert_eq!(
505 ServiceName::new("".to_string()).unwrap_err(),
506 ServiceNameError::Empty
507 );
508 assert_eq!(
509 ServiceName::new("service".to_string()).unwrap_err(),
510 ServiceNameError::InvalidStartChar('s')
511 );
512 assert_eq!(
513 ServiceName::new("_Service".to_string()).unwrap_err(),
514 ServiceNameError::InvalidStartChar('_')
515 );
516 assert_eq!(
517 ServiceName::new("Service-".to_string()).unwrap_err(),
518 ServiceNameError::InvalidChar('-')
519 );
520 assert_eq!(
521 ServiceName::new("Service_".to_string()).unwrap_err(),
522 ServiceNameError::InvalidEndChar('_')
523 );
524 }
525
526 #[test]
527 fn test_valid_method_name() {
528 assert!(MethodName::new("echo".to_string()).is_ok());
529 assert!(MethodName::new("doWork".to_string()).is_ok());
530 assert!(MethodName::new("get_v1".to_string()).is_ok());
531 }
532
533 #[test]
534 fn test_invalid_method_name() {
535 assert_eq!(
536 MethodName::new("".to_string()).unwrap_err(),
537 MethodNameError::Empty
538 );
539 assert_eq!(
540 MethodName::new("1call".to_string()).unwrap_err(),
541 MethodNameError::InvalidStartChar('1')
542 );
543 assert_eq!(
544 MethodName::new("Call".to_string()).unwrap_err(),
545 MethodNameError::InvalidStartChar('C')
546 );
547 assert_eq!(
548 MethodName::new("do-".to_string()).unwrap_err(),
549 MethodNameError::InvalidChar('-')
550 );
551 assert_eq!(
552 MethodName::new("do_".to_string()).unwrap_err(),
553 MethodNameError::InvalidEndChar('_')
554 );
555 }
556}