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}