1
//! `HsNickname` module itself is private, but `HsNickname` etc. are re-exported
2

            
3
use std::fmt::{self, Display};
4
use std::str::FromStr;
5

            
6
use derive_more::{From, Into};
7
use serde::{Deserialize, Serialize};
8
use thiserror::Error;
9

            
10
use crate::slug::Slug;
11

            
12
/// Nickname (local identifier) for a Tor hidden service
13
///
14
/// Used to look up this services's
15
/// keys, state, configuration, etc,
16
/// and distinguish them from other services.
17
///
18
/// An `HsNickname` must be a valid [`Slug`].
19
/// See [slug](crate::slug) for the syntactic requirements.
20
//
21
// NOTE: if at some point we decide HsNickname should have a more restrictive syntax/charset than
22
// Slug, we should remember to also update `KeySpecifierComponent::from_component` (it
23
// should return an error if the specified string is a valid Slug, but not a valid
24
// HsNickname).
25
#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] //
26
#[derive(derive_more::Display, From, Into, Serialize, Deserialize)]
27
#[serde(try_from = "String", into = "String")]
28
pub struct HsNickname(Slug);
29

            
30
impl FromStr for HsNickname {
31
    type Err = InvalidNickname;
32

            
33
6316
    fn from_str(s: &str) -> Result<Self, Self::Err> {
34
6316
        Self::new(s.to_string())
35
6316
    }
36
}
37

            
38
/// Local nickname for Tor Hidden Service (`.onion` service) was syntactically invalid
39
#[derive(Clone, Debug, Hash, Eq, PartialEq, Error)]
40
#[non_exhaustive]
41
#[error("Invalid syntax for hidden service nickname")]
42
pub struct InvalidNickname {}
43

            
44
impl HsNickname {
45
    /// Create a new `HsNickname` from a `String`
46
    ///
47
    /// Returns an error if the syntax is not valid
48
10803
    pub fn new(s: String) -> Result<HsNickname, InvalidNickname> {
49
10810
        Ok(Self(s.try_into().map_err(|_| InvalidNickname {})?))
50
10803
    }
51
}
52

            
53
impl From<HsNickname> for String {
54
2
    fn from(nick: HsNickname) -> String {
55
2
        nick.0.into()
56
2
    }
57
}
58

            
59
impl TryFrom<String> for HsNickname {
60
    type Error = InvalidNickname;
61
3040
    fn try_from(s: String) -> Result<HsNickname, InvalidNickname> {
62
3040
        Self::new(s)
63
3040
    }
64
}
65

            
66
impl AsRef<str> for HsNickname {
67
    fn as_ref(&self) -> &str {
68
        self.0.as_ref()
69
    }
70
}
71

            
72
#[cfg(feature = "state-dir")]
73
impl crate::state_dir::InstanceIdentity for HsNickname {
74
779
    fn kind() -> &'static str {
75
779
        "hss"
76
779
    }
77
779
    fn write_identity(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78
779
        Display::fmt(self, f)
79
779
    }
80
}
81

            
82
#[cfg(test)]
83
mod test {
84
    // @@ begin test lint list maintained by maint/add_warning @@
85
    #![allow(clippy::bool_assert_comparison)]
86
    #![allow(clippy::clone_on_copy)]
87
    #![allow(clippy::dbg_macro)]
88
    #![allow(clippy::mixed_attributes_style)]
89
    #![allow(clippy::print_stderr)]
90
    #![allow(clippy::print_stdout)]
91
    #![allow(clippy::single_char_pattern)]
92
    #![allow(clippy::unwrap_used)]
93
    #![allow(clippy::unchecked_time_subtraction)]
94
    #![allow(clippy::useless_vec)]
95
    #![allow(clippy::needless_pass_by_value)]
96
    #![allow(clippy::string_slice)] // See arti#2571
97
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
98
    use super::*;
99

            
100
    #[test]
101
    fn mk() {
102
        assert_eq!(HsNickname::new("".into()), Err(InvalidNickname {}));
103
        assert_eq!(HsNickname::new("-a".into()), Err(InvalidNickname {}));
104
        assert_eq!(HsNickname::new("b.".into()), Err(InvalidNickname {}));
105
        assert_eq!(HsNickname::new("_c".into()).unwrap().to_string(), "_c");
106
        assert_eq!(&HsNickname::new("x".into()).unwrap().to_string(), "x");
107
    }
108

            
109
    #[test]
110
    fn serde() {
111
        // TODO: clone-and-hack with tor_keymgr::::key_specifier::test::serde
112
        #[derive(Serialize, Deserialize, Debug)]
113
        struct T {
114
            n: HsNickname,
115
        }
116
        let j = serde_json::from_str(r#"{ "n": "x" }"#).unwrap();
117
        let t: T = serde_json::from_value(j).unwrap();
118
        assert_eq!(&t.n.to_string(), "x");
119

            
120
        assert_eq!(&serde_json::to_string(&t).unwrap(), r#"{"n":"x"}"#);
121

            
122
        let j = serde_json::from_str(r#"{ "n": "!" }"#).unwrap();
123
        let e = serde_json::from_value::<T>(j).unwrap_err();
124
        assert!(e.to_string().contains("Invalid syntax"), "wrong msg {e:?}");
125
    }
126

            
127
    #[test]
128
    fn empty_nickname() {
129
        assert_eq!(
130
            HsNickname::new("".to_string()).unwrap_err(),
131
            InvalidNickname {}
132
        );
133
        assert_eq!(
134
            HsNickname::try_from("".to_string()).unwrap_err(),
135
            InvalidNickname {}
136
        );
137
        assert_eq!(HsNickname::from_str("").unwrap_err(), InvalidNickname {});
138
    }
139
}