psmatcher 0.4.0-alpha.0

A pub/sub matcher algorithm implementation
Documentation
// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

pub mod helpers;
pub(crate) mod serialization;

use indexmap::IndexMap;
use serde::{Deserialize, Serialize};

use crate::error::MatcherError;

/// AttributeMap uses IndexMap to maintain insertion order of attributes, which is crucial for
/// efficient pub/sub matching. The tree-based matching algorithm (from Aguilera et al., 1999)
/// benefits from evaluating more selective predicates first, as this can eliminate non-matching
/// subscriptions earlier in the traversal.
///
/// By ordering attributes from most to least selective (i.e., attributes that filter out the most
/// subscriptions first), users can significantly improve matching performance.
///
/// For optimal performance, place high-selectivity attributes (like unique IDs or rare values)
/// before low-selectivity ones (like common status flags).
pub type AttributeMap<K, V> = IndexMap<K, V>;

pub const TOPIC_STR: &str = "topic";

/// Represents the type of an attribute in an event
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AttributeValue {
    /// String attribute
    String(String),
    /// Integer attribute
    Integer(i64),
    /// Float attribute
    Float(f64),
    /// Boolean attribute
    Boolean(bool),
    /// List of attribute values
    List(Vec<AttributeValue>),
    /// Map of attribute values
    Map(AttributeMap<String, AttributeValue>),
}

impl AttributeValue {
    pub fn as_str(&self) -> Option<&str> {
        match self {
            AttributeValue::String(s) => Some(s),
            _ => None,
        }
    }
}

/// Trait that must be implemented by any event type that can be matched
pub trait Event {
    /// Returns the topic of the event
    fn topic(&self) -> &str;

    /// Returns the value of an attribute by name
    fn get_attribute(&self, name: &str) -> Option<&AttributeValue>;

    /// Returns all attributes of the event
    fn attributes(&self) -> &AttributeMap<String, AttributeValue>;
}

/// A default implementation of the Event trait
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DefaultEvent {
    /// The attributes of the event, including the topic
    attributes: AttributeMap<String, AttributeValue>,
}

impl DefaultEvent {
    /// Creates a new event with the given topic
    pub fn new<T: Into<String>>(topic: T) -> Self {
        let mut attributes = AttributeMap::new();
        attributes.insert(TOPIC_STR.to_string(), AttributeValue::String(topic.into()));
        Self { attributes }
    }

    /// Adds an attribute to the event
    pub fn with_attribute<T: Into<String>>(mut self, name: T, value: AttributeValue) -> Self {
        self.attributes.insert(name.into(), value);
        self
    }

    /// Creates an event from a map of attributes
    pub(crate) fn from_attributes(
        attributes: AttributeMap<String, AttributeValue>,
    ) -> Result<Self, MatcherError> {
        if let Some(AttributeValue::String(_)) = attributes.get(TOPIC_STR) {
            Ok(Self { attributes })
        } else {
            Err(MatcherError::MissingAttribute(TOPIC_STR.to_string()))
        }
    }
}

impl Event for DefaultEvent {
    fn topic(&self) -> &str {
        if let AttributeValue::String(topic) = &self.attributes[TOPIC_STR] {
            topic
        } else {
            // This should never happen if the event is created properly
            ""
        }
    }

    fn get_attribute(&self, name: &str) -> Option<&AttributeValue> {
        self.attributes.get(name)
    }

    fn attributes(&self) -> &AttributeMap<String, AttributeValue> {
        &self.attributes
    }
}