Skip to main content

borderless_pkg/
author.rs

1use serde::de::{Error as DeError, Visitor};
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::fmt;
4use std::str::FromStr;
5
6/// Specifies the author of some package
7///
8/// The author should be serialized into:
9/// "Author-Name <Author-E-Mail>"
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct Author {
12    pub name: String,
13    pub email: Option<String>,
14}
15
16impl Author {
17    pub fn new<S: AsRef<str>>(name: S, email: Option<S>) -> Self {
18        Self {
19            name: name.as_ref().to_string(),
20            email: email.map(|s| s.as_ref().to_string()),
21        }
22    }
23}
24
25impl std::fmt::Display for Author {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        if let Some(mail) = &self.email {
28            write!(f, "{} <{}>", self.name, mail)
29        } else {
30            write!(f, "{}", self.name)
31        }
32    }
33}
34
35impl FromStr for Author {
36    type Err = String;
37    fn from_str(s: &str) -> Result<Self, Self::Err> {
38        if let Some(start) = s.find('<') {
39            if let Some(end) = s[start + 1..].find('>') {
40                let name_part = s[..start].trim();
41                let email_part = &s[start + 1..start + 1 + end];
42                if name_part.is_empty() {
43                    return Err("Author name is empty".into());
44                }
45                if email_part.is_empty() {
46                    return Err("E-mail is empty inside <..>".into());
47                }
48                Ok(Author {
49                    name: name_part.to_string(),
50                    email: Some(email_part.to_string()),
51                })
52            } else {
53                Err("Missing closing '>' in author mail definition".into())
54            }
55        } else {
56            // No '<', so entire string is the name
57            let name_trim = s.trim();
58            if name_trim.is_empty() {
59                Err("Author string is empty".into())
60            } else {
61                Ok(Author {
62                    name: name_trim.to_string(),
63                    email: None,
64                })
65            }
66        }
67    }
68}
69
70impl Serialize for Author {
71    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
72    where
73        S: Serializer,
74    {
75        let as_str = self.to_string();
76        serializer.serialize_str(&as_str)
77    }
78}
79
80struct AuthorVisitor;
81
82impl<'de> Visitor<'de> for AuthorVisitor {
83    type Value = Author;
84    fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
85        write!(
86            fmt,
87            "a string of the form \"Name <email@…>\" or just \"Name\""
88        )
89    }
90    fn visit_str<E>(self, v: &str) -> Result<Author, E>
91    where
92        E: DeError,
93    {
94        Author::from_str(v).map_err(DeError::custom)
95    }
96    fn visit_string<E>(self, v: String) -> Result<Author, E>
97    where
98        E: DeError,
99    {
100        Author::from_str(&v).map_err(DeError::custom)
101    }
102}
103
104impl<'de> Deserialize<'de> for Author {
105    fn deserialize<D>(deserializer: D) -> Result<Author, D::Error>
106    where
107        D: Deserializer<'de>,
108    {
109        deserializer.deserialize_string(AuthorVisitor)
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn author_to_string() {
119        let author = Author::new("Klaus", Some("klaus@klausen.de"));
120        assert_eq!(author.to_string(), "Klaus <klaus@klausen.de>");
121        let author = Author::new("Klaus Kinski", None);
122        assert_eq!(author.to_string(), "Klaus Kinski");
123    }
124
125    #[test]
126    fn author_from_string() {
127        let author = "Klaus <klaus@klausen.de>";
128        let a = Author::from_str(&author);
129        assert!(a.is_ok(), "{}", a.unwrap_err());
130        assert_eq!(
131            a.unwrap(),
132            Author {
133                name: "Klaus".to_string(),
134                email: Some("klaus@klausen.de".to_string())
135            }
136        );
137        let author = "Klaus Kinski";
138        let a = Author::from_str(&author);
139        assert!(a.is_ok(), "{}", a.unwrap_err());
140        assert_eq!(
141            a.unwrap(),
142            Author {
143                name: "Klaus Kinski".to_string(),
144                email: None,
145            }
146        );
147    }
148
149    #[test]
150    fn author_deserialize() {
151        let author = r#""Klaus <klaus@klausen.de>""#;
152        let a: Result<Author, _> = serde_json::from_str(author);
153        assert!(a.is_ok(), "{}", a.unwrap_err());
154        assert_eq!(
155            a.unwrap(),
156            Author {
157                name: "Klaus".to_string(),
158                email: Some("klaus@klausen.de".to_string())
159            }
160        );
161        let author = r#""Klaus Kinski""#;
162        let a: Result<Author, _> = serde_json::from_str(author);
163        assert!(a.is_ok(), "{}", a.unwrap_err());
164        assert_eq!(
165            a.unwrap(),
166            Author {
167                name: "Klaus Kinski".to_string(),
168                email: None,
169            }
170        );
171    }
172
173    #[test]
174    fn author_serialize() {
175        let author = Author::new("Klaus", Some("klaus@klausen.de"));
176        let s = serde_json::to_string(&author);
177        assert!(s.is_ok(), "{}", s.unwrap_err());
178        assert_eq!(s.unwrap(), r#""Klaus <klaus@klausen.de>""#);
179
180        let author = Author::new("Klaus Kinski", None);
181        let s = serde_json::to_string(&author);
182        assert!(s.is_ok(), "{}", s.unwrap_err());
183        assert_eq!(s.unwrap(), r#""Klaus Kinski""#);
184    }
185
186    #[test]
187    fn author_unescaped_email() {
188        let author = "Klaus <foo";
189        let a = Author::from_str(&author);
190        assert!(a.is_err());
191    }
192}