drawbridge_type/tree/
name.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use 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}