use std::borrow::Cow;
use std::convert::TryFrom;
use arcstr::Substr;
use thiserror::Error;
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum TopicPatternError {
#[error(
"Invalid topic pattern '{pattern}': # wildcard can only be the last \
segment"
)]
HashPosition {
pattern: String,
},
#[error("Invalid wildcard usage: {usage}")]
WildcardUsage {
usage: String,
},
#[error("Topic pattern cannot be empty")]
EmptyTopic,
#[error(
"Topic pattern structure mismatch.\nOriginal: '{original}'\nCustom: \
'{custom}'\nHint: Both patterns must have the same parameter \
structure (same wildcards in same positions)"
)]
PatternStructureMismatch {
original: String,
custom: String,
},
}
impl TopicPatternError {
pub fn hash_position(pattern: impl Into<String>) -> Self {
Self::HashPosition {
pattern: pattern.into(),
}
}
pub fn wildcard_usage(usage: impl Into<String>) -> Self {
Self::WildcardUsage {
usage: usage.into(),
}
}
pub fn pattern_mismatch(
original: impl Into<String>,
custom: impl Into<String>,
) -> Self {
Self::PatternStructureMismatch {
original: original.into(),
custom: custom.into(),
}
}
}
impl From<std::convert::Infallible> for TopicPatternError {
fn from(_: std::convert::Infallible) -> Self {
unreachable!("Infallible can never be constructed")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TopicPatternItem {
Str(Substr),
Plus(Option<Substr>),
Hash(Option<Substr>),
}
impl TopicPatternItem {
pub fn as_str(&self) -> &str {
match self {
| TopicPatternItem::Str(s) => s,
| TopicPatternItem::Plus(_) => "+",
| TopicPatternItem::Hash(_) => "#",
}
}
pub fn as_wildcard(&self) -> Cow<'_, str> {
match self {
| TopicPatternItem::Plus(None) => Cow::Borrowed("+"),
| TopicPatternItem::Hash(None) => Cow::Borrowed("#"),
| TopicPatternItem::Plus(Some(name)) => {
Cow::Owned(format!("{{{name}}}"))
}
| TopicPatternItem::Hash(Some(name)) => {
Cow::Owned(format!("{{{name}:#}}"))
}
| TopicPatternItem::Str(s) => Cow::Borrowed(s),
}
}
pub fn param_name(&self) -> Option<Substr> {
match self {
| TopicPatternItem::Plus(Some(name))
| TopicPatternItem::Hash(Some(name)) => Some(name.clone()),
| _ => None,
}
}
pub fn is_wildcard(&self) -> bool {
matches!(self, TopicPatternItem::Plus(_) | TopicPatternItem::Hash(_))
}
}
impl From<&TopicPatternItem> for String {
fn from(item: &TopicPatternItem) -> Self {
item.as_str().to_string()
}
}
impl std::fmt::Display for TopicPatternItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl TryFrom<Substr> for TopicPatternItem {
type Error = TopicPatternError;
fn try_from(item: Substr) -> Result<Self, Self::Error> {
let res = match item.as_str() {
| "+" => TopicPatternItem::Plus(None),
| "#" => TopicPatternItem::Hash(None),
| _ if item.starts_with("{") && item.ends_with(":#}") => {
let inner =
item.trim_start_matches('{').trim_end_matches(":#}");
if inner.is_empty() {
return Err(TopicPatternError::wildcard_usage(
item.as_str(),
));
}
TopicPatternItem::Hash(Some(item.substr_from(inner)))
}
| _ if item.starts_with("{") && item.ends_with("}") => {
let inner = item.trim_start_matches('{').trim_end_matches("}");
if inner.is_empty() {
return Err(TopicPatternError::wildcard_usage(
item.as_str(),
));
}
TopicPatternItem::Plus(Some(item.substr_from(inner)))
}
| _ if item.contains(['+', '#']) => {
return Err(TopicPatternError::wildcard_usage(item.as_str()));
}
| _ => TopicPatternItem::Str(item),
};
Ok(res)
}
}