radicle_cob/
type_name.rs

1// Copyright © 2022 The Radicle Link Contributors
2
3use std::{fmt, str::FromStr};
4
5use git_ext::ref_format::{Component, RefString};
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9/// The typename of an object. Valid typenames MUST be sequences of
10/// alphanumeric characters separated by a period. The name must start
11/// and end with an alphanumeric character
12///
13/// # Examples
14///
15/// * `abc.def`
16/// * `xyz.rad.issues`
17/// * `xyz.rad.patches.releases`
18#[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}