tor_persist/
hsnickname.rs
1use std::fmt::{self, Display};
4use std::str::FromStr;
5
6use derive_more::{From, Into};
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10use crate::slug::Slug;
11
12#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[derive(derive_more::Display, From, Into, Serialize, Deserialize)]
27#[serde(try_from = "String", into = "String")]
28pub struct HsNickname(Slug);
29
30impl FromStr for HsNickname {
31 type Err = InvalidNickname;
32
33 fn from_str(s: &str) -> Result<Self, Self::Err> {
34 Self::new(s.to_string())
35 }
36}
37
38#[derive(Clone, Debug, Hash, Eq, PartialEq, Error)]
40#[non_exhaustive]
41#[error("Invalid syntax for hidden service nickname")]
42pub struct InvalidNickname {}
43
44impl HsNickname {
45 pub fn new(s: String) -> Result<HsNickname, InvalidNickname> {
49 Ok(Self(s.try_into().map_err(|_| InvalidNickname {})?))
50 }
51}
52
53impl From<HsNickname> for String {
54 fn from(nick: HsNickname) -> String {
55 nick.0.into()
56 }
57}
58
59impl TryFrom<String> for HsNickname {
60 type Error = InvalidNickname;
61 fn try_from(s: String) -> Result<HsNickname, InvalidNickname> {
62 Self::new(s)
63 }
64}
65
66impl AsRef<str> for HsNickname {
67 fn as_ref(&self) -> &str {
68 self.0.as_ref()
69 }
70}
71
72#[cfg(feature = "state-dir")]
73impl crate::state_dir::InstanceIdentity for HsNickname {
74 fn kind() -> &'static str {
75 "hss"
76 }
77 fn write_identity(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 Display::fmt(self, f)
79 }
80}
81
82#[cfg(test)]
83mod test {
84 #![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_duration_subtraction)]
94 #![allow(clippy::useless_vec)]
95 #![allow(clippy::needless_pass_by_value)]
96 use super::*;
98
99 #[test]
100 fn mk() {
101 assert_eq!(HsNickname::new("".into()), Err(InvalidNickname {}));
102 assert_eq!(HsNickname::new("-a".into()), Err(InvalidNickname {}));
103 assert_eq!(HsNickname::new("b.".into()), Err(InvalidNickname {}));
104 assert_eq!(HsNickname::new("_c".into()).unwrap().to_string(), "_c");
105 assert_eq!(&HsNickname::new("x".into()).unwrap().to_string(), "x");
106 }
107
108 #[test]
109 fn serde() {
110 #[derive(Serialize, Deserialize, Debug)]
112 struct T {
113 n: HsNickname,
114 }
115 let j = serde_json::from_str(r#"{ "n": "x" }"#).unwrap();
116 let t: T = serde_json::from_value(j).unwrap();
117 assert_eq!(&t.n.to_string(), "x");
118
119 assert_eq!(&serde_json::to_string(&t).unwrap(), r#"{"n":"x"}"#);
120
121 let j = serde_json::from_str(r#"{ "n": "!" }"#).unwrap();
122 let e = serde_json::from_value::<T>(j).unwrap_err();
123 assert!(e.to_string().contains("Invalid syntax"), "wrong msg {e:?}");
124 }
125
126 #[test]
127 fn empty_nickname() {
128 assert_eq!(
129 HsNickname::new("".to_string()).unwrap_err(),
130 InvalidNickname {}
131 );
132 assert_eq!(
133 HsNickname::try_from("".to_string()).unwrap_err(),
134 InvalidNickname {}
135 );
136 assert_eq!(HsNickname::from_str("").unwrap_err(), InvalidNickname {});
137 }
138}