diff --git a/src/ident.rs b/src/ident.rs index ca16413..be803ce 100644 --- a/src/ident.rs +++ b/src/ident.rs @@ -1,6 +1,5 @@ //! Resource identifiers. -use std::borrow::Borrow; use std::cmp::Ordering; use std::error::Error; use std::fmt; @@ -20,34 +19,40 @@ use crate::protocol::{Decode, Encode}; /// /// A resource identifier is a string divided into a "namespace" part and a /// "path" part. For instance `minecraft:apple` and `valence:frobnicator` are -/// both valid identifiers. +/// both valid identifiers. A string must match the regex +/// `^([a-z0-9_.-]+:)?[a-z0-9_.-\/]+$` to be considered valid. /// /// If the namespace part is left off (the part before and including the colon) /// the namespace is considered to be "minecraft" for the purposes of equality, /// ordering, and hashing. /// -/// A string must match the regex `^([a-z0-9_-]+:)?[a-z0-9_\/.-]+$` to be a -/// valid identifier. +/// # Contract +/// +/// The type `S` must meet the following criteria: +/// - All calls to [`AsRef::as_ref`] and [`Borrow::borrow`][borrow] while the +/// string is wrapped in `Ident` must return the same value. +/// +/// [borrow]: std::borrow::Borrow::borrow #[derive(Copy, Clone, Debug)] pub struct Ident { string: S, path_start: usize, } -impl> Ident { +impl> Ident { pub fn new(string: S) -> Result> { let check_namespace = |s: &str| { !s.is_empty() && s.chars() - .all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '-')) + .all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-')) }; let check_path = |s: &str| { !s.is_empty() && s.chars() - .all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '/' | '.' | '-')) + .all(|c| matches!(c, 'a'..='z' | '0'..='9' | '_' | '.' | '-' | '/')) }; - let str = string.borrow(); + let str = string.as_ref(); match str.split_once(':') { Some((namespace, path)) if check_namespace(namespace) && check_path(path) => { @@ -70,24 +75,24 @@ impl> Ident { if self.path_start == 0 { "minecraft" } else { - &self.string.borrow()[..self.path_start - 1] + &self.string.as_ref()[..self.path_start - 1] } } pub fn path(&self) -> &str { - &self.string.borrow()[self.path_start..] + &self.string.as_ref()[self.path_start..] } /// Returns the underlying string as a `str`. pub fn as_str(&self) -> &str { - self.string.borrow() + self.string.as_ref() } /// Borrows the underlying string and returns it as an `Ident`. This /// operation is infallible and no checks need to be performed. pub fn as_str_ident(&self) -> Ident<&str> { Ident { - string: self.string.borrow(), + string: self.string.as_ref(), path_start: self.path_start, } } @@ -98,7 +103,7 @@ impl> Ident { pub fn to_owned_ident(&self) -> Ident where S: ToOwned, - S::Owned: Borrow, + S::Owned: AsRef, { Ident { string: self.string.to_owned(), @@ -110,33 +115,13 @@ impl> Ident { pub fn into_inner(self) -> S { self.string } -} -/// The error type created when an [`Ident`] cannot be parsed from a -/// string. Contains the offending string. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct IdentError(pub S); - -impl fmt::Debug for IdentError -where - S: Borrow, -{ - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_tuple("IdentError").field(&self.0.borrow()).finish() + /// Consumes the identifier and returns the underlying string. + pub fn get(self) -> S { + self.string } } -impl fmt::Display for IdentError -where - S: Borrow, -{ - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "invalid resource identifier \"{}\"", self.0.borrow()) - } -} - -impl Error for IdentError where S: Borrow {} - impl FromStr for Ident { type Err = IdentError; @@ -155,18 +140,18 @@ impl TryFrom for Ident { impl From> for String where - S: Into + Borrow, + S: Into + AsRef, { fn from(id: Ident) -> Self { if id.path_start == 0 { - format!("minecraft:{}", id.string.borrow()) + format!("minecraft:{}", id.string.as_ref()) } else { id.string.into() } } } -impl> fmt::Display for Ident { +impl> fmt::Display for Ident { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}:{}", self.namespace(), self.path()) } @@ -174,20 +159,20 @@ impl> fmt::Display for Ident { impl PartialEq> for Ident where - S: Borrow, - T: Borrow, + S: AsRef, + T: AsRef, { fn eq(&self, other: &Ident) -> bool { self.namespace() == other.namespace() && self.path() == other.path() } } -impl Eq for Ident where S: Borrow {} +impl Eq for Ident where S: AsRef {} impl PartialOrd> for Ident where - S: Borrow, - T: Borrow, + S: AsRef, + T: AsRef, { fn partial_cmp(&self, other: &Ident) -> Option { (self.namespace(), self.path()).partial_cmp(&(other.namespace(), other.path())) @@ -196,7 +181,7 @@ where impl Ord for Ident where - S: Borrow, + S: AsRef, { fn cmp(&self, other: &Self) -> Ordering { (self.namespace(), self.path()).cmp(&(other.namespace(), other.path())) @@ -205,7 +190,7 @@ where impl Hash for Ident where - S: Borrow, + S: AsRef, { fn hash(&self, state: &mut H) { (self.namespace(), self.path()).hash(state); @@ -226,7 +211,7 @@ where impl<'de, T> Deserialize<'de> for Ident where - T: Deserialize<'de> + Borrow, + T: Deserialize<'de> + AsRef, { fn deserialize(deserializer: D) -> Result where @@ -244,7 +229,7 @@ impl Encode for Ident { impl Decode for Ident where - S: Decode + Borrow + Send + Sync + 'static, + S: Decode + AsRef + Send + Sync + 'static, { fn decode(r: &mut &[u8]) -> anyhow::Result { Ok(Ident::new(S::decode(r)?)?) @@ -260,6 +245,31 @@ where } } +/// The error type created when an [`Ident`] cannot be parsed from a +/// string. Contains the offending string. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct IdentError(pub S); + +impl fmt::Debug for IdentError +where + S: AsRef, +{ + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_tuple("IdentError").field(&self.0.as_ref()).finish() + } +} + +impl fmt::Display for IdentError +where + S: AsRef, +{ + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "invalid resource identifier \"{}\"", self.0.as_ref()) + } +} + +impl Error for IdentError where S: AsRef {} + /// Convenience macro for constructing an [`Ident`] from a format /// string. ///