#![allow(clippy::missing_panics_doc)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::{borrow::ToOwned, string::String};
#[cfg(feature = "alloc")]
use core::str::FromStr;
use core::{
cmp::Ordering,
convert::{TryFrom, TryInto},
fmt,
hash::{self, Hash},
num::{NonZeroU8, NonZeroU32},
ops::Range,
};
#[cfg(not(feature = "std"))]
use core::error;
#[cfg(feature = "std")]
use std::{borrow::ToOwned, error};
mod cow;
use cow::TriCow;
mod tables;
#[cfg(feature = "alloc")]
mod owned;
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub use owned::Urn;
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub mod percent;
#[cfg(not(feature = "alloc"))]
mod percent;
use percent::{
normalize_range, parse_f_component, parse_nss, parse_q_component, parse_r_component, validate_f_component, validate_nss, validate_q_component, validate_r_component,
};
#[cfg(feature = "serde")]
mod serde;
fn is_valid_nid(s: &str) -> bool {
let bytes = s.as_bytes();
if bytes.len() < 2 || bytes.len() > 32 || bytes[0] == b'-' {
return false;
}
bytes.iter().all(|&b| tables::BYTE_CLASS[b as usize] & tables::NID != 0)
}
fn check_nid(s: &str) -> Result<bool> {
let bytes = s.as_bytes();
if bytes.len() < 2 || bytes.len() > 32 || bytes[0] == b'-' {
return Err(Error::InvalidNid);
}
let mut has_upper = false;
for &b in bytes {
if tables::BYTE_CLASS[b as usize] & tables::NID == 0 {
return Err(Error::InvalidNid);
}
has_upper |= b.is_ascii_uppercase();
}
Ok(has_upper)
}
const URN_PREFIX: &str = "urn:";
const NID_NSS_SEPARATOR: &str = ":";
const RCOMP_PREFIX: &str = "?+";
const QCOMP_PREFIX: &str = "?=";
const FCOMP_PREFIX: &str = "#";
fn parse_urn(mut s: TriCow) -> Result<UrnSlice> {
if !s.is_char_boundary(URN_PREFIX.len()) {
return Err(Error::InvalidScheme);
}
s.make_lowercase(..URN_PREFIX.len())?;
if &s[..URN_PREFIX.len()] != URN_PREFIX {
return Err(Error::InvalidScheme);
}
let nid_start = URN_PREFIX.len();
let nid_end = nid_start
+ s[nid_start..].find(NID_NSS_SEPARATOR).ok_or_else(|| {
if is_valid_nid(&s[nid_start..]) {
Error::InvalidNss
} else {
Error::InvalidNid
}
})?;
if !is_valid_nid(&s[nid_start..nid_end]) {
return Err(Error::InvalidNid);
}
s.make_lowercase(nid_start..nid_end)?;
let nss_start = nid_end + NID_NSS_SEPARATOR.len();
let nss_end = parse_nss(&mut s, nss_start)?;
if nss_end == nss_start {
return Err(Error::InvalidNss);
}
let mut end = nss_end;
let mut last_component_error = Error::InvalidNss;
let r_component_len = if s[end..].starts_with(RCOMP_PREFIX) {
let rc_start = end + RCOMP_PREFIX.len();
end = parse_r_component(&mut s, rc_start)?;
last_component_error = Error::InvalidRComponent;
Some((end - rc_start).try_into().ok().and_then(NonZeroU32::new).ok_or(last_component_error)?)
} else {
None
};
let q_component_len = if s[end..].starts_with(QCOMP_PREFIX) {
let qc_start = end + QCOMP_PREFIX.len();
end = parse_q_component(&mut s, qc_start)?;
last_component_error = Error::InvalidQComponent;
Some((end - qc_start).try_into().ok().and_then(NonZeroU32::new).ok_or(last_component_error)?)
} else {
None
};
if s[end..].starts_with(FCOMP_PREFIX) {
let fc_start = end + FCOMP_PREFIX.len();
end = parse_f_component(&mut s, fc_start)?;
last_component_error = Error::InvalidFComponent;
}
if end < s.len() {
return Err(last_component_error);
}
Ok(UrnSlice {
urn: s,
nid_len: NonZeroU8::new((nid_end - nid_start).try_into().unwrap()).unwrap(),
nss_len: NonZeroU32::new((nss_end - nss_start).try_into().map_err(|_| Error::InvalidNss)?).unwrap(),
r_component_len,
q_component_len,
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
InvalidScheme,
InvalidNid,
InvalidNss,
InvalidRComponent,
InvalidQComponent,
InvalidFComponent,
AllocRequired,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::InvalidScheme => "invalid urn scheme",
Self::InvalidNid => "invalid urn nid (namespace id)",
Self::InvalidNss => "invalid urn nss (namespace-specific string)",
Self::InvalidRComponent => "invalid urn r-component",
Self::InvalidQComponent => "invalid urn q-component",
Self::InvalidFComponent => "invalid urn f-component (fragment)",
Self::AllocRequired => "an allocation was required, but not possible",
})
}
}
type Result<T, E = Error> = core::result::Result<T, E>;
#[cfg(feature = "std")]
impl error::Error for Error {}
pub struct UrnSlice<'a> {
urn: TriCow<'a>,
nid_len: NonZeroU8,
nss_len: NonZeroU32,
r_component_len: Option<NonZeroU32>,
q_component_len: Option<NonZeroU32>,
}
impl<'a> UrnSlice<'a> {
#[inline]
const fn nid_range(&self) -> Range<usize> {
let start = URN_PREFIX.len();
start..start + self.nid_len.get() as usize
}
#[inline]
const fn nss_range(&self) -> Range<usize> {
let start = self.nid_range().end + NID_NSS_SEPARATOR.len();
start..start + self.nss_len.get() as usize
}
#[inline]
fn r_component_range(&self) -> Option<Range<usize>> {
self.r_component_len.map(|r_component_len| {
let start = self.nss_range().end + RCOMP_PREFIX.len();
start..start + r_component_len.get() as usize
})
}
#[inline]
fn pre_q_component_end(&self) -> usize {
self.r_component_range().unwrap_or_else(|| self.nss_range()).end
}
#[inline]
fn q_component_range(&self) -> Option<Range<usize>> {
self.q_component_len.map(|q_component_len| {
let start = self.pre_q_component_end() + QCOMP_PREFIX.len();
start..start + q_component_len.get() as usize
})
}
#[inline]
fn pre_f_component_end(&self) -> usize {
self.q_component_range()
.or_else(|| self.r_component_range())
.unwrap_or_else(|| self.nss_range())
.end
}
#[cfg(feature = "alloc")]
#[inline]
pub(crate) fn into_owned(self) -> Urn {
Urn(UrnSlice {
urn: match self.urn {
TriCow::Owned(s) => TriCow::Owned(s),
TriCow::Borrowed(s) => TriCow::Owned(s.to_owned()),
TriCow::MutBorrowed(s) => TriCow::Owned(s.to_owned()),
},
nid_len: self.nid_len,
nss_len: self.nss_len,
r_component_len: self.r_component_len,
q_component_len: self.q_component_len,
})
}
#[inline]
fn f_component_start(&self) -> Option<usize> {
Some(self.pre_f_component_end()).filter(|x| *x < self.urn.len()).map(|x| x + FCOMP_PREFIX.len())
}
#[must_use]
#[inline]
pub fn as_str(&self) -> &str {
&self.urn
}
#[must_use]
#[inline]
pub fn nid(&self) -> &str {
&self.urn[self.nid_range()]
}
pub fn set_nid(&mut self, nid: &str) -> Result<()> {
let has_upper = check_nid(nid)?;
let nid_len = NonZeroU8::new(nid.len().try_into().unwrap()).unwrap();
let range = self.nid_range();
let start = range.start;
self.urn.replace_range(range, nid)?;
if has_upper {
self.urn.make_lowercase(start..start + nid.len())?;
}
self.nid_len = nid_len;
Ok(())
}
#[must_use]
#[inline]
pub fn nss(&self) -> &str {
&self.urn[self.nss_range()]
}
pub fn set_nss(&mut self, nss: &str) -> Result<()> {
let (end, needs_norm) = validate_nss(nss);
if nss.is_empty() || end != nss.len() {
return Err(Error::InvalidNss);
}
let nss_len = NonZeroU32::new(nss.len().try_into().map_err(|_| Error::InvalidNss)?).unwrap();
let range = self.nss_range();
let start = range.start;
self.urn.replace_range(range, nss)?;
if needs_norm {
normalize_range(&mut self.urn, start..start + nss.len())?;
}
self.nss_len = nss_len;
Ok(())
}
#[must_use]
#[inline]
pub fn r_component(&self) -> Option<&str> {
self.r_component_range().map(|range| &self.urn[range])
}
pub fn set_r_component(&mut self, r_component: Option<&str>) -> Result<()> {
if let Some(rc) = r_component {
let (end, needs_norm) = validate_r_component(rc);
if rc.is_empty() || end != rc.len() {
return Err(Error::InvalidRComponent);
}
let rc_len = rc.len().try_into().map_err(|_| Error::InvalidRComponent)?;
let range = if let Some(range) = self.r_component_range() {
range
} else {
let nss_end = self.nss_range().end;
self.urn.replace_range(nss_end..nss_end, RCOMP_PREFIX)?;
nss_end + RCOMP_PREFIX.len()..nss_end + RCOMP_PREFIX.len()
};
let start = range.start;
self.urn.replace_range(range, rc)?;
if needs_norm {
normalize_range(&mut self.urn, start..start + rc.len())?;
}
self.r_component_len = Some(NonZeroU32::new(rc_len).unwrap());
} else if let Some(mut range) = self.r_component_range() {
range.start -= RCOMP_PREFIX.len();
self.urn.replace_range(range, "")?;
self.r_component_len = None;
}
Ok(())
}
#[must_use]
#[inline]
pub fn q_component(&self) -> Option<&str> {
self.q_component_range().map(|range| &self.urn[range])
}
pub fn set_q_component(&mut self, q_component: Option<&str>) -> Result<()> {
if let Some(qc) = q_component {
let (end, needs_norm) = validate_q_component(qc);
if qc.is_empty() || end != qc.len() {
return Err(Error::InvalidQComponent);
}
let qc_len = qc.len().try_into().map_err(|_| Error::InvalidQComponent)?;
let range = if let Some(range) = self.q_component_range() {
range
} else {
let pre_qc_end = self.pre_q_component_end();
self.urn.replace_range(pre_qc_end..pre_qc_end, QCOMP_PREFIX)?;
pre_qc_end + QCOMP_PREFIX.len()..pre_qc_end + QCOMP_PREFIX.len()
};
let start = range.start;
self.urn.replace_range(range, qc)?;
if needs_norm {
normalize_range(&mut self.urn, start..start + qc.len())?;
}
self.q_component_len = Some(NonZeroU32::new(qc_len).unwrap());
} else if let Some(mut range) = self.q_component_range() {
range.start -= QCOMP_PREFIX.len();
self.urn.replace_range(range, "")?;
self.q_component_len = None;
}
Ok(())
}
#[must_use]
#[inline]
pub fn f_component(&self) -> Option<&str> {
self.f_component_start().map(|start| &self.urn[start..])
}
pub fn set_f_component(&mut self, f_component: Option<&str>) -> Result<()> {
if let Some(fc) = f_component {
let (end, needs_norm) = validate_f_component(fc);
if end != fc.len() {
return Err(Error::InvalidFComponent);
}
let start = if let Some(start) = self.f_component_start() {
start
} else {
let range = self.urn.len()..self.urn.len();
self.urn.replace_range(range, FCOMP_PREFIX)?;
self.urn.len()
};
let len = self.urn.len();
self.urn.replace_range(start..len, fc)?;
if needs_norm {
normalize_range(&mut self.urn, start..start + fc.len())?;
}
} else if let Some(start) = self.f_component_start() {
let len = self.urn.len();
self.urn.replace_range(start - FCOMP_PREFIX.len()..len, "")?;
}
Ok(())
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<'a> ToOwned for UrnSlice<'a> {
type Owned = Urn;
fn to_owned(&self) -> Self::Owned {
Urn::from(self)
}
}
impl fmt::Debug for UrnSlice<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "UrnSlice({})", self.as_str())
}
}
#[cfg(feature = "alloc")]
impl PartialEq<Urn> for UrnSlice<'_> {
fn eq(&self, other: &Urn) -> bool {
self == &other.0
}
}
impl AsRef<[u8]> for UrnSlice<'_> {
fn as_ref(&self) -> &[u8] {
self.urn.as_bytes()
}
}
impl AsRef<str> for UrnSlice<'_> {
fn as_ref(&self) -> &str {
&self.urn
}
}
impl UrnSlice<'_> {
#[inline]
fn eq_slice(&self) -> &str {
#[cfg(feature = "exact-eq")]
{
&self.urn[..]
}
#[cfg(not(feature = "exact-eq"))]
{
&self.urn[..self.nss_range().end]
}
}
}
impl PartialEq for UrnSlice<'_> {
fn eq(&self, other: &Self) -> bool {
self.eq_slice() == other.eq_slice()
}
}
impl Eq for UrnSlice<'_> {}
impl Ord for UrnSlice<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.eq_slice().cmp(other.eq_slice())
}
}
impl PartialOrd for UrnSlice<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Hash for UrnSlice<'_> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.eq_slice().hash(state);
}
}
impl fmt::Display for UrnSlice<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.write_str(&self.urn)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl FromStr for UrnSlice<'_> {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
parse_urn(TriCow::Owned(s.to_owned()))
}
}
impl<'a> TryFrom<&'a str> for UrnSlice<'a> {
type Error = Error;
fn try_from(value: &'a str) -> Result<Self> {
parse_urn(TriCow::Borrowed(value))
}
}
impl<'a> TryFrom<&'a mut str> for UrnSlice<'a> {
type Error = Error;
fn try_from(value: &'a mut str) -> Result<Self> {
parse_urn(TriCow::MutBorrowed(value))
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl TryFrom<String> for UrnSlice<'static> {
type Error = Error;
fn try_from(value: String) -> Result<Self> {
parse_urn(TriCow::Owned(value))
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[derive(Debug)]
#[must_use]
pub struct UrnBuilder<'a> {
nid: &'a str,
nss: &'a str,
r_component: Option<&'a str>,
q_component: Option<&'a str>,
f_component: Option<&'a str>,
}
#[cfg(feature = "alloc")]
impl<'a> UrnBuilder<'a> {
pub fn new(nid: &'a str, nss: &'a str) -> Self {
Self {
nid,
nss,
r_component: None,
q_component: None,
f_component: None,
}
}
pub fn nid(mut self, nid: &'a str) -> Self {
self.nid = nid;
self
}
pub fn nss(mut self, nss: &'a str) -> Self {
self.nss = nss;
self
}
pub fn r_component(mut self, r_component: Option<&'a str>) -> Self {
self.r_component = r_component;
self
}
pub fn q_component(mut self, q_component: Option<&'a str>) -> Self {
self.q_component = q_component;
self
}
pub fn f_component(mut self, f_component: Option<&'a str>) -> Self {
self.f_component = f_component;
self
}
pub fn build(self) -> Result<Urn> {
if !is_valid_nid(self.nid) {
return Err(Error::InvalidNid);
}
if self.nss.is_empty() {
return Err(Error::InvalidNss);
}
let (nss_end_in, nss_needs_norm) = validate_nss(self.nss);
if nss_end_in != self.nss.len() {
return Err(Error::InvalidNss);
}
let rc_needs_norm = if let Some(rc) = self.r_component {
let (end, needs_norm) = validate_r_component(rc);
if rc.is_empty() || end != rc.len() {
return Err(Error::InvalidRComponent);
}
needs_norm
} else {
false
};
let qc_needs_norm = if let Some(qc) = self.q_component {
let (end, needs_norm) = validate_q_component(qc);
if qc.is_empty() || end != qc.len() {
return Err(Error::InvalidQComponent);
}
needs_norm
} else {
false
};
let fc_needs_norm = if let Some(fc) = self.f_component {
let (end, needs_norm) = validate_f_component(fc);
if end != fc.len() {
return Err(Error::InvalidFComponent);
}
needs_norm
} else {
false
};
let total = URN_PREFIX.len()
+ self.nid.len()
+ NID_NSS_SEPARATOR.len()
+ self.nss.len()
+ self.r_component.map_or(0, |x| RCOMP_PREFIX.len() + x.len())
+ self.q_component.map_or(0, |x| QCOMP_PREFIX.len() + x.len())
+ self.f_component.map_or(0, |x| FCOMP_PREFIX.len() + x.len());
let mut buf = String::with_capacity(total);
buf.push_str(URN_PREFIX);
buf.push_str(self.nid);
buf.push_str(NID_NSS_SEPARATOR);
let nss_start = buf.len();
buf.push_str(self.nss);
let nss_end = buf.len();
let rc_range = self.r_component.map(|rc| {
buf.push_str(RCOMP_PREFIX);
let start = buf.len();
buf.push_str(rc);
start..buf.len()
});
let qc_range = self.q_component.map(|qc| {
buf.push_str(QCOMP_PREFIX);
let start = buf.len();
buf.push_str(qc);
start..buf.len()
});
let fc_range = self.f_component.map(|fc| {
buf.push_str(FCOMP_PREFIX);
let start = buf.len();
buf.push_str(fc);
start..buf.len()
});
let mut s = TriCow::Owned(buf);
if nss_needs_norm {
normalize_range(&mut s, nss_start..nss_end)?;
}
if rc_needs_norm {
normalize_range(&mut s, rc_range.as_ref().unwrap().clone())?;
}
if qc_needs_norm {
normalize_range(&mut s, qc_range.as_ref().unwrap().clone())?;
}
if fc_needs_norm {
normalize_range(&mut s, fc_range.as_ref().unwrap().clone())?;
}
Ok(Urn(UrnSlice {
urn: s,
nid_len: NonZeroU8::new(self.nid.len().try_into().unwrap()).unwrap(),
nss_len: NonZeroU32::new(self.nss.len().try_into().map_err(|_| Error::InvalidNss)?).unwrap(),
r_component_len: self
.r_component
.map(|x| {
x.len()
.try_into()
.map(|x| NonZeroU32::new(x).unwrap())
.map_err(|_| Error::InvalidRComponent)
})
.transpose()?,
q_component_len: self
.q_component
.map(|x| {
x.len()
.try_into()
.map(|x| NonZeroU32::new(x).unwrap())
.map_err(|_| Error::InvalidQComponent)
})
.transpose()?,
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "std"))]
use super::alloc::string::ToString;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use super::alloc::vec::Vec;
#[test]
fn it_works() {
UrnSlice::try_from("6òž¦*�").unwrap_err();
#[cfg(feature = "alloc")]
assert_eq!(
UrnSlice::try_from("urn:nbn:de:bvb:19-146642").unwrap(),
UrnBuilder::new("nbn", "de:bvb:19-146642").build().unwrap()
);
assert_eq!(UrnSlice::try_from("urn:nbn:de:bvb:19-146642").unwrap().to_string(), "urn:nbn:de:bvb:19-146642");
#[cfg(feature = "alloc")]
assert_eq!(
UrnSlice::try_from("urn:example:foo-bar-baz-qux?+CCResolve:cc=uk#test").unwrap(),
UrnBuilder::new("example", "foo-bar-baz-qux")
.r_component(Some("CCResolve:cc=uk"))
.f_component(Some("test"))
.build()
.unwrap()
);
assert_eq!(
UrnSlice::try_from("urn:example:foo-bar-baz-qux?+CCResolve:cc=uk#test")
.unwrap()
.f_component()
.unwrap(),
"test"
);
assert_eq!(
UrnSlice::try_from("urn:example:foo-bar-baz-qux?+CCResolve:cc=uk#test")
.unwrap()
.r_component()
.unwrap(),
"CCResolve:cc=uk"
);
assert_eq!(
UrnSlice::try_from("urn:example:foo-bar-baz-qux?+CCResolve:cc=uk#test").unwrap().to_string(),
"urn:example:foo-bar-baz-qux?+CCResolve:cc=uk#test",
);
#[cfg(feature = "alloc")]
assert_eq!(
"urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z"
.parse::<UrnSlice>()
.unwrap(),
UrnBuilder::new("example", "weather")
.q_component(Some("op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z"))
.build()
.unwrap()
);
assert_eq!(
UrnSlice::try_from("urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z")
.unwrap()
.to_string(),
"urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z"
);
#[cfg(all(feature = "alloc", not(feature = "exact-eq")))]
assert_eq!(
"uRn:eXaMpLe:%3d%3a?=aoiwnfuafo".parse::<UrnSlice>().unwrap(),
UrnBuilder::new("example", "%3D%3a").build().unwrap()
);
let mut arr = *b"uRn:eXaMpLe:%3d%3a?=aoiwnfuafo";
assert_eq!(
UrnSlice::try_from(core::str::from_utf8_mut(&mut arr[..]).unwrap()).unwrap().as_str(),
"urn:example:%3D%3A?=aoiwnfuafo",
);
assert_eq!(UrnSlice::try_from("urn:-example:abcd"), Err(Error::InvalidNid));
assert_eq!(UrnSlice::try_from("urn:example:/abcd"), Err(Error::InvalidNss));
assert_eq!(UrnSlice::try_from("urn:a:abcd"), Err(Error::InvalidNid));
assert_eq!(UrnSlice::try_from("urn:0123456789abcdef0123456789abcdef0:abcd"), Err(Error::InvalidNid));
let _ = UrnSlice::try_from("urn:0123456789abcdef0123456789abcdef:abcd").unwrap();
assert_eq!(UrnSlice::try_from("urn:example"), Err(Error::InvalidNss));
assert_eq!(UrnSlice::try_from("urn:example:"), Err(Error::InvalidNss));
assert_eq!(UrnSlice::try_from("urn:example:%"), Err(Error::InvalidNss));
assert_eq!(UrnSlice::try_from("urn:example:%a"), Err(Error::InvalidNss));
assert_eq!(UrnSlice::try_from("urn:example:%a_"), Err(Error::InvalidNss));
let mut arr = *b"urn:example:%a0?+";
assert_eq!(
UrnSlice::try_from(core::str::from_utf8_mut(&mut arr[..]).unwrap()),
Err(Error::InvalidRComponent)
);
let mut arr = *b"urn:example:%a0?+%a0?=";
assert_eq!(
UrnSlice::try_from(core::str::from_utf8_mut(&mut arr[..]).unwrap()),
Err(Error::InvalidQComponent)
);
let mut arr = *b"urn:example:%a0?+%a0?=a";
assert_eq!(
UrnSlice::try_from(core::str::from_utf8_mut(&mut arr[..]).unwrap())
.unwrap()
.r_component()
.unwrap(),
"%A0",
);
#[cfg(feature = "alloc")]
{
let mut urn = "urn:example:test".parse::<UrnSlice>().unwrap();
urn.set_f_component(Some("f-component")).unwrap();
assert_eq!(urn.f_component(), Some("f-component"));
assert_eq!(urn.as_str(), "urn:example:test#f-component");
urn.set_f_component(Some("")).unwrap();
assert_eq!(urn.f_component(), Some(""));
assert_eq!(urn.as_str(), "urn:example:test#");
urn.set_q_component(Some("abcd")).unwrap();
assert_eq!(urn.q_component(), Some("abcd"));
assert_eq!(urn.as_str(), "urn:example:test?=abcd#");
assert!(urn.set_q_component(Some("")).is_err());
urn.set_r_component(Some("%2a")).unwrap();
assert_eq!(urn.r_component(), Some("%2A"));
assert_eq!(urn.as_str(), "urn:example:test?+%2A?=abcd#");
urn.set_nid("a-b").unwrap();
assert_eq!(urn.as_str(), "urn:a-b:test?+%2A?=abcd#");
urn.set_r_component(None).unwrap();
assert_eq!(urn.as_str(), "urn:a-b:test?=abcd#");
assert_eq!(urn.r_component(), None);
}
}
#[test]
fn mut_str_normalizes_in_place() {
let mut buf = *b"uRn:eXaMpLe:Foo-Bar";
let buf_ptr = buf.as_ptr();
let s = core::str::from_utf8_mut(&mut buf[..]).unwrap();
let urn = UrnSlice::try_from(s).unwrap();
assert_eq!(urn.as_str().as_ptr(), buf_ptr);
assert_eq!(urn.as_str(), "urn:example:Foo-Bar");
}
#[cfg(all(feature = "alloc", not(feature = "exact-eq")))]
#[test]
fn ord_matches_eq() {
use core::cmp::Ordering;
let a = UrnSlice::try_from("urn:example:abc").unwrap();
let b = UrnSlice::try_from("urn:example:abd").unwrap();
let c = UrnSlice::try_from("urn:example:abc?=q").unwrap(); assert_eq!(a.cmp(&b), Ordering::Less);
assert_eq!(b.cmp(&a), Ordering::Greater);
assert_eq!(a.cmp(&c), Ordering::Equal);
assert_eq!(a, c);
let ao = Urn::try_from("urn:example:abc").unwrap();
let bo = Urn::try_from("urn:example:abd").unwrap();
assert!(ao < bo);
}
#[cfg(all(feature = "alloc", feature = "exact-eq"))]
#[test]
fn exact_eq_includes_rqf() {
use core::cmp::Ordering;
#[cfg(feature = "std")]
use core::hash::BuildHasher;
#[cfg(feature = "std")]
use std::collections::hash_map::RandomState;
let a = UrnSlice::try_from("urn:example:abc").unwrap();
let b = UrnSlice::try_from("urn:example:abc?=q").unwrap();
let c = UrnSlice::try_from("urn:example:abc?=q").unwrap();
let d = UrnSlice::try_from("urn:example:abc#frag").unwrap();
let e = UrnSlice::try_from("urn:example:abc?+r").unwrap();
assert_ne!(a, b);
assert_ne!(a, d);
assert_ne!(a, e);
assert_ne!(b, d);
assert_eq!(b, c);
assert_eq!(a.cmp(&b), Ordering::Less);
#[cfg(feature = "std")]
{
let rs = RandomState::new();
assert_eq!(rs.hash_one(&b), rs.hash_one(&c));
assert_ne!(rs.hash_one(&a), rs.hash_one(&b));
}
}
#[cfg(feature = "alloc")]
#[test]
fn decode_iter_matches_decode() {
use crate::percent::{decode_nss, decode_nss_iter};
let input = "hello%20world%21";
let via_iter: Result<Vec<u8>, _> = decode_nss_iter(input).collect();
assert_eq!(via_iter.unwrap(), decode_nss(input).unwrap().into_bytes());
let bad = "%zz";
let res: Result<Vec<u8>, _> = decode_nss_iter(bad).collect();
assert_eq!(res, Err(Error::InvalidNss));
}
}