oa_gateway_core/route.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
//! Addressing key used by [`crate::Engine`].
//!
//! The engine compares these for equality only. It does not treat `topic`
//! as a path and does not interpret `type_hint`.
/// Topic plus an optional type hint.
///
/// `topic` is the only required coordinate. `type_hint` is whatever
/// discriminator the publishing adapter has: an OWP message name, a UCI
/// type, a PDU type. A subscription with `type_hint: None` matches every
/// type on that topic. A publish with no hint reaches wildcards only.
///
/// Neither field is validated. Matching is exact string equality, not a
/// hierarchy, even though [`Display`](Self#impl-Display-for-RouteKey)
/// looks like a path.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RouteKey {
/// Engine topic. Often a UCI message name when bridging ActiveMQ.
pub topic: String,
/// Protocol discriminator. `None` is a wildcard subscription, or an
/// untyped publish.
pub type_hint: Option<String>,
}
impl RouteKey {
/// A wildcard: every type on `topic`.
#[must_use]
pub fn topic(topic: impl Into<String>) -> Self {
Self {
topic: topic.into(),
type_hint: None,
}
}
/// One type on `topic`. A publish with this hint also reaches
/// [`Self::topic`] subscribers on the same topic.
#[must_use]
pub fn typed(topic: impl Into<String>, type_hint: impl Into<String>) -> Self {
Self {
topic: topic.into(),
type_hint: Some(type_hint.into()),
}
}
/// Whether this key has no type hint.
///
/// True for both a wildcard subscription and an untyped publish.
/// Those two roles match different sets; this predicate does not
/// distinguish them.
#[must_use]
pub fn is_wildcard(&self) -> bool {
self.type_hint.is_none()
}
}
impl std::fmt::Display for RouteKey {
/// Formats as `topic/hint`, or `topic/` when there is no hint.
///
/// Trailing slashes on `topic` are stripped only in the wildcard
/// form, so `demo/` and `demo` display the same when untyped.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.type_hint {
Some(hint) => write!(f, "{}/{}", self.topic, hint),
None => write!(f, "{}/", self.topic.trim_end_matches('/')),
}
}
}