1use std::{fmt, str::FromStr};
4
5use git_ext::ref_format::{Component, RefString};
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
19pub struct TypeName(String);
20
21impl TypeName {
22 pub fn as_str(&self) -> &str {
23 &self.0
24 }
25}
26
27impl fmt::Display for TypeName {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 f.write_str(self.0.as_str())
30 }
31}
32
33#[derive(Error, Debug)]
34#[error("the type name '{invalid}' is invalid")]
35pub struct TypeNameParse {
36 invalid: String,
37}
38
39impl FromStr for TypeName {
40 type Err = TypeNameParse;
41
42 fn from_str(s: &str) -> Result<Self, Self::Err> {
43 let split = s.split('.');
44 for component in split {
45 if component.is_empty() {
46 return Err(TypeNameParse {
47 invalid: s.to_string(),
48 });
49 }
50 if !component.chars().all(char::is_alphanumeric) {
51 return Err(TypeNameParse {
52 invalid: s.to_string(),
53 });
54 }
55 }
56 Ok(TypeName(s.to_string()))
57 }
58}
59
60impl From<&TypeName> for Component<'_> {
61 fn from(name: &TypeName) -> Self {
62 let refstr = RefString::try_from(name.0.to_string())
63 .expect("collaborative object type names are valid ref strings");
64 Component::from_refstr(refstr)
65 .expect("collaborative object type names are valid refname components")
66 }
67}
68
69#[cfg(test)]
70mod test {
71 use std::str::FromStr as _;
72
73 use super::TypeName;
74
75 #[test]
76 fn valid_typenames() {
77 assert!(TypeName::from_str("abc.def.ghi").is_ok());
78 assert!(TypeName::from_str("abc.123.ghi").is_ok());
79 assert!(TypeName::from_str("1bc.123.ghi").is_ok());
80 assert!(TypeName::from_str(".abc.123.ghi").is_err());
81 assert!(TypeName::from_str("abc.123.ghi.").is_err());
82 }
83}