/ crates / tor-persist / src / hsnickname.rs
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  }