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 != '_' && 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 assert!(Name::new("actor.1".to_string()).is_ok());
428 assert!(Name::new("com.example.actor".to_string()).is_ok());
429 }
430
431 #[test]
432 fn test_invalid_name() {
433 assert_eq!(Name::new("".to_string()).unwrap_err(), NameError::Empty);
434 assert_eq!(
435 Name::new("-invalid".to_string()).unwrap_err(),
436 NameError::InvalidStartChar('-')
437 );
438 assert_eq!(
439 Name::new(".invalid".to_string()).unwrap_err(),
440 NameError::InvalidStartChar('.')
441 );
442 assert_eq!(
443 Name::new("invalid-".to_string()).unwrap_err(),
444 NameError::InvalidEndChar('-')
445 );
446 assert_eq!(
447 Name::new("invalid.".to_string()).unwrap_err(),
448 NameError::InvalidEndChar('.')
449 );
450 assert_eq!(
451 Name::new("has invalid char!".to_string()).unwrap_err(),
452 NameError::InvalidChar(' ')
453 );
454 assert_eq!(
455 Name::new("too_long_name_that_exceeds_thirty_two_characters".to_string()).unwrap_err(),
456 NameError::TooLong(48)
457 );
458 assert_eq!(
459 Name::new("1starts_with_number".to_string()).unwrap_err(),
460 NameError::InvalidStartChar('1')
461 );
462 }
463
464 #[test]
465 fn test_valid_package_name() {
466 assert!(PackageName::new("com.example.project".to_string()).is_ok());
467 assert!(PackageName::new("a_b.c_d.e1".to_string()).is_ok());
468 assert!(PackageName::new("example".to_string()).is_ok());
469 assert!(PackageName::new("_internal.pkg".to_string()).is_ok());
470 }
471
472 #[test]
473 fn test_invalid_package_name() {
474 assert_eq!(
475 PackageName::new("".to_string()).unwrap_err(),
476 PackageNameError::Empty
477 );
478 assert_eq!(
479 PackageName::new(".leading".to_string()).unwrap_err(),
480 PackageNameError::InvalidStartChar('.')
481 );
482 assert_eq!(
483 PackageName::new("trailing.".to_string()).unwrap_err(),
484 PackageNameError::InvalidEndChar('.')
485 );
486 assert_eq!(
487 PackageName::new("com..example".to_string()).unwrap_err(),
488 PackageNameError::EmptySegment
489 );
490 assert_eq!(
491 PackageName::new("com.1example".to_string()).unwrap_err(),
492 PackageNameError::InvalidSegmentStartChar('1')
493 );
494 assert_eq!(
495 PackageName::new("Com.Example".to_string()).unwrap_err(),
496 PackageNameError::InvalidChar('C')
497 );
498 assert_eq!(
499 PackageName::new("com.exa$mple".to_string()).unwrap_err(),
500 PackageNameError::InvalidChar('$')
501 );
502 }
503
504 #[test]
505 fn test_valid_service_name() {
506 assert!(ServiceName::new("Echo".to_string()).is_ok());
507 assert!(ServiceName::new("UserService".to_string()).is_ok());
508 assert!(ServiceName::new("HTTPV1".to_string()).is_ok());
509 assert!(ServiceName::new("Service_Name".to_string()).is_ok());
510 }
511
512 #[test]
513 fn test_invalid_service_name() {
514 assert_eq!(
515 ServiceName::new("".to_string()).unwrap_err(),
516 ServiceNameError::Empty
517 );
518 assert_eq!(
519 ServiceName::new("service".to_string()).unwrap_err(),
520 ServiceNameError::InvalidStartChar('s')
521 );
522 assert_eq!(
523 ServiceName::new("_Service".to_string()).unwrap_err(),
524 ServiceNameError::InvalidStartChar('_')
525 );
526 assert_eq!(
527 ServiceName::new("Service-".to_string()).unwrap_err(),
528 ServiceNameError::InvalidChar('-')
529 );
530 assert_eq!(
531 ServiceName::new("Service_".to_string()).unwrap_err(),
532 ServiceNameError::InvalidEndChar('_')
533 );
534 }
535
536 #[test]
537 fn test_valid_method_name() {
538 assert!(MethodName::new("echo".to_string()).is_ok());
539 assert!(MethodName::new("doWork".to_string()).is_ok());
540 assert!(MethodName::new("get_v1".to_string()).is_ok());
541 }
542
543 #[test]
544 fn test_invalid_method_name() {
545 assert_eq!(
546 MethodName::new("".to_string()).unwrap_err(),
547 MethodNameError::Empty
548 );
549 assert_eq!(
550 MethodName::new("1call".to_string()).unwrap_err(),
551 MethodNameError::InvalidStartChar('1')
552 );
553 assert_eq!(
554 MethodName::new("Call".to_string()).unwrap_err(),
555 MethodNameError::InvalidStartChar('C')
556 );
557 assert_eq!(
558 MethodName::new("do-".to_string()).unwrap_err(),
559 MethodNameError::InvalidChar('-')
560 );
561 assert_eq!(
562 MethodName::new("do_".to_string()).unwrap_err(),
563 MethodNameError::InvalidEndChar('_')
564 );
565 }
566}