use std::cmp::{Ord, Ordering, PartialOrd};
use std::fmt;
#[cfg(feature = "serde_support")]
use serde::de::{self, Deserialize, Deserializer};
#[cfg(feature = "serde_support")]
use serde::ser::{Serialize, Serializer};
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct NamespaceableName {
components: String,
boundary: usize,
}
impl NamespaceableName {
#[inline]
pub fn plain<T>(name: T) -> Self
where
T: Into<String>,
{
let n = name.into();
assert!(!n.is_empty(), "Symbols and keywords cannot be unnamed.");
NamespaceableName {
components: n,
boundary: 0,
}
}
#[inline]
pub fn namespaced<N, T>(namespace: N, name: T) -> Self
where
N: AsRef<str>,
T: AsRef<str>,
{
let n = name.as_ref();
let ns = namespace.as_ref();
assert!(!n.is_empty(), "Symbols and keywords cannot be unnamed.");
assert!(
!ns.is_empty(),
"Symbols and keywords cannot have an empty non-null namespace."
);
let mut dest = String::with_capacity(n.len() + ns.len());
dest.push_str(ns);
dest.push('/');
dest.push_str(n);
let boundary = ns.len();
NamespaceableName {
components: dest,
boundary,
}
}
fn new<N, T>(namespace: Option<N>, name: T) -> Self
where
N: AsRef<str>,
T: AsRef<str>,
{
if let Some(ns) = namespace {
Self::namespaced(ns, name)
} else {
Self::plain(name.as_ref())
}
}
pub fn is_namespaced(&self) -> bool {
self.boundary > 0
}
#[inline]
pub fn is_backward(&self) -> bool {
self.name().starts_with('_')
}
#[inline]
pub fn is_forward(&self) -> bool {
!self.is_backward()
}
pub fn to_reversed(&self) -> NamespaceableName {
let name = self.name();
if let Some(stripped) = name.strip_prefix('_') {
Self::new(self.namespace(), stripped)
} else {
Self::new(self.namespace(), format!("_{}", name))
}
}
#[inline]
pub fn namespace(&self) -> Option<&str> {
if self.boundary > 0 {
Some(&self.components[0..self.boundary])
} else {
None
}
}
#[inline]
pub fn name(&self) -> &str {
if self.boundary == 0 {
&self.components
} else {
&self.components[(self.boundary + 1)..]
}
}
#[inline]
pub fn components(&self) -> (&str, &str) {
if self.boundary > 0 {
(
&self.components[0..self.boundary],
&self.components[(self.boundary + 1)..],
)
} else {
(&self.components[0..0], &self.components)
}
}
}
impl PartialOrd for NamespaceableName {
fn partial_cmp(&self, other: &NamespaceableName) -> Option<Ordering> {
Some(Ord::cmp(self, other))
}
}
impl Ord for NamespaceableName {
fn cmp(&self, other: &NamespaceableName) -> Ordering {
self.components().cmp(&other.components())
}
}
impl fmt::Debug for NamespaceableName {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("NamespaceableName")
.field("namespace", &self.namespace())
.field("name", &self.name())
.finish()
}
}
impl fmt::Display for NamespaceableName {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(&self.components)
}
}
#[cfg(feature = "serde_support")]
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename = "NamespaceableName")]
struct SerializedNamespaceableName<'a> {
namespace: Option<std::borrow::Cow<'a, str>>,
name: std::borrow::Cow<'a, str>,
}
#[cfg(feature = "serde_support")]
impl<'de> Deserialize<'de> for NamespaceableName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let separated = SerializedNamespaceableName::deserialize(deserializer)?;
if separated.name.is_empty() {
return Err(de::Error::custom("Empty name in keyword or symbol"));
}
if let Some(ns) = separated.namespace {
if ns.is_empty() {
Err(de::Error::custom(
"Empty but present namespace in keyword or symbol",
))
} else {
Ok(NamespaceableName::namespaced(ns, separated.name))
}
} else {
Ok(NamespaceableName::plain(separated.name))
}
}
}
#[cfg(feature = "serde_support")]
impl Serialize for NamespaceableName {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let ser = SerializedNamespaceableName {
namespace: self.namespace().map(std::borrow::Cow::Borrowed),
name: std::borrow::Cow::Borrowed(self.name()),
};
ser.serialize(serializer)
}
}
#[cfg(test)]
mod test {
use super::*;
use std::panic;
#[test]
fn test_new_invariants_maintained() {
assert!(
panic::catch_unwind(|| NamespaceableName::namespaced("", "foo")).is_err(),
"Empty namespace should panic"
);
assert!(
panic::catch_unwind(|| NamespaceableName::namespaced("foo", "")).is_err(),
"Empty name should panic"
);
assert!(
panic::catch_unwind(|| NamespaceableName::namespaced("", "")).is_err(),
"Should panic if both fields are empty"
);
}
#[test]
fn test_basic() {
let s = NamespaceableName::namespaced("aaaaa", "b");
assert_eq!(s.namespace(), Some("aaaaa"));
assert_eq!(s.name(), "b");
assert_eq!(s.components(), ("aaaaa", "b"));
let s = NamespaceableName::namespaced("b", "aaaaa");
assert_eq!(s.namespace(), Some("b"));
assert_eq!(s.name(), "aaaaa");
assert_eq!(s.components(), ("b", "aaaaa"));
}
#[test]
fn test_order() {
let n0 = NamespaceableName::namespaced("a", "aa");
let n1 = NamespaceableName::namespaced("aa", "a");
let n2 = NamespaceableName::namespaced("a", "ab");
let n3 = NamespaceableName::namespaced("aa", "b");
let n4 = NamespaceableName::namespaced("b", "ab");
let n5 = NamespaceableName::namespaced("ba", "b");
let n6 = NamespaceableName::namespaced("z", "zz");
let mut arr = [
n5.clone(),
n6.clone(),
n0.clone(),
n3.clone(),
n2.clone(),
n1.clone(),
n4.clone(),
];
arr.sort();
assert_eq!(
arr,
[
n0.clone(),
n2.clone(),
n1.clone(),
n3.clone(),
n4.clone(),
n5.clone(),
n6.clone(),
]
);
}
}