#![allow(clippy::missing_panics_doc)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(feature = "nightly", feature(error_in_core))]
#[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::{
convert::{TryFrom, TryInto},
fmt,
hash::{self, Hash},
num::{NonZeroU32, NonZeroU8},
ops::Range,
};
#[cfg(all(not(feature = "std"), feature = "nightly"))]
use core::error;
#[cfg(feature = "std")]
use std::{borrow::ToOwned, error};
mod cow;
use cow::TriCow;
#[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::{parse_f_component, parse_nss, parse_q_component, parse_r_component};
#[cfg(feature = "serde")]
mod serde;
fn is_valid_nid(s: &str) -> bool {
(2..=32).contains(&s.len())
&& !s.starts_with('-')
&& s.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-')
}
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(any(feature = "std", feature = "nightly"))]
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> {
const fn nid_range(&self) -> Range<usize> {
let start = URN_PREFIX.len();
start..start + self.nid_len.get() as usize
}
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
}
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
})
}
fn pre_q_component_end(&self) -> usize {
self.r_component_range()
.unwrap_or_else(|| self.nss_range())
.end
}
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
})
}
fn pre_f_component_end(&self) -> usize {
self.q_component_range()
.or_else(|| self.r_component_range())
.unwrap_or_else(|| self.nss_range())
.end
}
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]
pub fn as_str(&self) -> &str {
&self.urn
}
#[must_use]
pub fn nid(&self) -> &str {
&self.urn[self.nid_range()]
}
pub fn set_nid(&mut self, nid: &str) -> Result<()> {
if !is_valid_nid(nid) {
return Err(Error::InvalidNid);
}
let mut nid = TriCow::Borrowed(nid);
nid.make_lowercase(..)?;
let range = self.nid_range();
self.urn.replace_range(range, &nid)?;
self.nid_len = NonZeroU8::new(nid.len().try_into().unwrap()).unwrap();
Ok(())
}
#[must_use]
pub fn nss(&self) -> &str {
&self.urn[self.nss_range()]
}
pub fn set_nss(&mut self, nss: &str) -> Result<()> {
let mut nss = TriCow::Borrowed(nss);
if nss.is_empty() || parse_nss(&mut nss, 0)? != 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();
self.urn.replace_range(range, &nss)?;
self.nss_len = nss_len;
Ok(())
}
#[must_use]
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 mut rc = TriCow::Borrowed(rc);
if rc.is_empty() || parse_r_component(&mut rc, 0)? != 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()
};
self.urn.replace_range(range, &rc)?;
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]
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 mut qc = TriCow::Borrowed(qc);
if qc.is_empty() || parse_q_component(&mut qc, 0)? != 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()
};
self.urn.replace_range(range, &qc)?;
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]
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 mut fc = TriCow::Borrowed(fc);
if parse_f_component(&mut fc, 0)? != 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)?;
} 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 PartialEq for UrnSlice<'_> {
fn eq(&self, other: &Self) -> bool {
self.urn[..self.nss_range().end] == other.urn[..other.nss_range().end]
}
}
impl Eq for UrnSlice<'_> {}
impl Hash for UrnSlice<'_> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.urn[..self.nss_range().end].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> {
fn cow_push_str(c: &mut TriCow, s: &str) {
if let TriCow::Owned(c) = c {
c.push_str(s);
} else {
unreachable!("cow must be owned to use this function")
}
}
if !is_valid_nid(self.nid) {
return Err(Error::InvalidNid);
}
let mut s = TriCow::Owned(URN_PREFIX.to_owned());
{
let s = &mut s;
cow_push_str(s, self.nid);
cow_push_str(s, NID_NSS_SEPARATOR);
let nss_start = s.len();
cow_push_str(s, self.nss);
if self.nss.is_empty() || parse_nss(s, nss_start)? != s.len() {
return Err(Error::InvalidNss);
}
if let Some(rc) = self.r_component {
cow_push_str(s, RCOMP_PREFIX);
let rc_start = s.len();
cow_push_str(s, rc);
if rc.is_empty() || parse_r_component(s, rc_start)? != s.len() {
return Err(Error::InvalidRComponent);
}
}
if let Some(qc) = self.q_component {
cow_push_str(s, QCOMP_PREFIX);
let qc_start = s.len();
cow_push_str(s, qc);
if qc.is_empty() || parse_q_component(s, qc_start)? != s.len() {
return Err(Error::InvalidQComponent);
}
}
if let Some(fc) = self.f_component {
cow_push_str(s, FCOMP_PREFIX);
let fc_start = s.len();
cow_push_str(s, fc);
if parse_f_component(s, fc_start)? != s.len() {
return Err(Error::InvalidFComponent);
}
}
}
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;
#[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(feature = "alloc")]
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);
}
}
}