sn0int_common/
id.rs

1use crate::errors::*;
2use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
3use std::fmt;
4use std::result;
5use std::str::FromStr;
6
7#[inline(always)]
8fn valid_char(c: char) -> bool {
9    nom::character::is_alphanumeric(c as u8) || c == '-'
10}
11
12pub fn valid_name(name: &str) -> Result<()> {
13    if token(name).is_ok() {
14        Ok(())
15    } else {
16        bail!("String contains invalid character")
17    }
18}
19
20fn module(s: &str) -> nom::IResult<&str, ModuleID> {
21    let (input, (author, _, name)) =
22        nom::sequence::tuple((token, nom::bytes::complete::tag("/"), token))(s)?;
23    Ok((
24        input,
25        ModuleID {
26            author: author.to_string(),
27            name: name.to_string(),
28        },
29    ))
30}
31
32#[inline]
33fn token(s: &str) -> nom::IResult<&str, &str> {
34    nom::bytes::complete::take_while1(valid_char)(s)
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38pub struct ModuleID {
39    pub author: String,
40    pub name: String,
41}
42
43impl fmt::Display for ModuleID {
44    fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
45        write!(w, "{}/{}", self.author, self.name)
46    }
47}
48
49impl FromStr for ModuleID {
50    type Err = Error;
51
52    fn from_str(s: &str) -> Result<ModuleID> {
53        let (trailing, module) =
54            module(s).map_err(|err| anyhow!("Failed to parse module id: {:?}", err))?;
55        if !trailing.is_empty() {
56            bail!("Trailing data in module id");
57        }
58        Ok(module)
59    }
60}
61
62impl Serialize for ModuleID {
63    fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
64    where
65        S: Serializer,
66    {
67        serializer.serialize_str(&self.to_string())
68    }
69}
70
71impl<'de> Deserialize<'de> for ModuleID {
72    fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
73    where
74        D: Deserializer<'de>,
75    {
76        let s = String::deserialize(deserializer)?;
77        FromStr::from_str(&s).map_err(de::Error::custom)
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn verify_valid() {
87        let result = ModuleID::from_str("kpcyrd/foo").expect("parse");
88        assert_eq!(
89            result,
90            ModuleID {
91                author: "kpcyrd".to_string(),
92                name: "foo".to_string(),
93            }
94        );
95    }
96
97    #[test]
98    fn verify_trailing_slash() {
99        let result = ModuleID::from_str("kpcyrd/foo/");
100        println!("{:?}", result);
101        assert!(result.is_err());
102    }
103
104    #[test]
105    fn verify_trailing_data() {
106        let result = ModuleID::from_str("kpcyrd/foo/x");
107        println!("{:?}", result);
108        assert!(result.is_err());
109    }
110
111    #[test]
112    fn verify_empty_author() {
113        let result = ModuleID::from_str("/foo");
114        println!("{:?}", result);
115        assert!(result.is_err());
116    }
117
118    #[test]
119    fn verify_empty_name() {
120        let result = ModuleID::from_str("kpcyrd/");
121        println!("{:?}", result);
122        assert!(result.is_err());
123    }
124
125    #[test]
126    fn verify_missing_slash() {
127        let result = ModuleID::from_str("kpcyrdfoo");
128        println!("{:?}", result);
129        assert!(result.is_err());
130    }
131
132    #[test]
133    fn verify_one_slash() {
134        let result = ModuleID::from_str("/");
135        println!("{:?}", result);
136        assert!(result.is_err());
137    }
138
139    #[test]
140    fn verify_two_slash() {
141        let result = ModuleID::from_str("//");
142        println!("{:?}", result);
143        assert!(result.is_err());
144    }
145
146    #[test]
147    fn verify_empty_str() {
148        let result = ModuleID::from_str("");
149        println!("{:?}", result);
150        assert!(result.is_err());
151    }
152
153    #[test]
154    fn verify_dots() {
155        let result = ModuleID::from_str("../..");
156        println!("{:?}", result);
157        assert!(result.is_err());
158    }
159}