hsnickname.rs
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 fn from_str(s: &str) -> Result<Self, Self::Err> { 34 Self::new(s.to_string()) 35 } 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 pub fn new(s: String) -> Result<HsNickname, InvalidNickname> { 49 Ok(Self(s.try_into().map_err(|_| InvalidNickname {})?)) 50 } 51 } 52 53 impl From<HsNickname> for String { 54 fn from(nick: HsNickname) -> String { 55 nick.0.into() 56 } 57 } 58 59 impl TryFrom<String> for HsNickname { 60 type Error = InvalidNickname; 61 fn try_from(s: String) -> Result<HsNickname, InvalidNickname> { 62 Self::new(s) 63 } 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 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)] 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_duration_subtraction)] 94 #![allow(clippy::useless_vec)] 95 #![allow(clippy::needless_pass_by_value)] 96 //! <!-- @@ end test lint list maintained by maint/add_warning @@ --> 97 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 // TODO: clone-and-hack with tor_keymgr::::key_specifier::test::serde 111 #[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 }