drawbridge_type/tree/
name.rs1use super::Path;
4
5use std::fmt::Display;
6use std::ops::Deref;
7use std::path::PathBuf;
8use std::str::FromStr;
9
10use anyhow::bail;
11use serde::de::Error;
12use serde::{Deserialize, Deserializer, Serialize};
13
14#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq, Serialize)]
15#[repr(transparent)]
16#[serde(transparent)]
17pub struct Name(String);
18
19impl Name {
20 #[inline]
21 fn validate(s: impl AsRef<str>) -> anyhow::Result<()> {
22 let s = s.as_ref();
23 if s.is_empty() {
24 bail!("empty entry name")
25 } else if s
26 .find(|c| !matches!(c, '0'..='9' | 'a'..='z' | 'A'..='Z' | '-' | '_' | '.' | ':'))
27 .is_some()
28 {
29 bail!("invalid characters in entry name")
30 } else {
31 Ok(())
32 }
33 }
34
35 pub fn join(self, name: Name) -> Path {
36 vec![self, name].into_iter().collect()
37 }
38}
39
40impl AsRef<str> for Name {
41 fn as_ref(&self) -> &str {
42 &self.0
43 }
44}
45
46impl AsRef<String> for Name {
47 fn as_ref(&self) -> &String {
48 &self.0
49 }
50}
51
52impl Deref for Name {
53 type Target = String;
54
55 fn deref(&self) -> &Self::Target {
56 &self.0
57 }
58}
59
60impl<'de> Deserialize<'de> for Name {
61 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
62 where
63 D: Deserializer<'de>,
64 {
65 let name = String::deserialize(deserializer)?;
66 name.try_into().map_err(D::Error::custom)
67 }
68}
69
70impl Display for Name {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 write!(f, "{}", self.0)
73 }
74}
75
76impl From<Name> for PathBuf {
77 fn from(name: Name) -> Self {
78 Self::from(name.0)
79 }
80}
81
82impl From<Name> for String {
83 fn from(name: Name) -> Self {
84 name.0
85 }
86}
87
88impl FromStr for Name {
89 type Err = anyhow::Error;
90
91 #[inline]
92 fn from_str(s: &str) -> Result<Self, Self::Err> {
93 Self::validate(s).map(|()| Self(s.into()))
94 }
95}
96
97impl TryFrom<String> for Name {
98 type Error = anyhow::Error;
99
100 #[inline]
101 fn try_from(s: String) -> Result<Self, Self::Error> {
102 Self::validate(&s).map(|()| Self(s))
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn from_str() {
112 assert!("/".parse::<Name>().is_err());
113 assert!("/test".parse::<Name>().is_err());
114 assert!("test/".parse::<Name>().is_err());
115
116 assert_eq!("foo".parse::<Name>().unwrap(), Name("foo".into()));
117 assert_eq!("some.txt".parse::<Name>().unwrap(), Name("some.txt".into()));
118 assert_eq!(
119 "my_wasm.wasm".parse::<Name>().unwrap(),
120 Name("my_wasm.wasm".into())
121 );
122 assert_eq!(
123 "not.a.cor-Rec.t.eX.tens.si0n_".parse::<Name>().unwrap(),
124 Name("not.a.cor-Rec.t.eX.tens.si0n_".into())
125 );
126 }
127}