codesynapse-grpc 0.1.3

gRPC service layer for codesynapse code intelligence graph queries
Documentation
use tokio::sync::broadcast;

#[derive(Clone, Debug, PartialEq)]
pub enum Event {
    NodeAdded {
        id: String,
        label: String,
        source_file: String,
    },
    EdgeAdded {
        source: String,
        target: String,
        relation: String,
    },
    NodeRemoved {
        id: String,
    },
    GraphReset,
}

const CHANNEL_CAPACITY: usize = 256;

pub struct EventBus {
    tx: broadcast::Sender<Event>,
}

impl EventBus {
    pub fn new() -> Self {
        let (tx, _) = broadcast::channel(CHANNEL_CAPACITY);
        Self { tx }
    }

    pub fn subscribe(&self) -> broadcast::Receiver<Event> {
        self.tx.subscribe()
    }

    pub fn emit(&self, event: Event) {
        let _ = self.tx.send(event);
    }
}

impl Default for EventBus {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_subscribe_and_receive_graph_reset() {
        let bus = EventBus::new();
        let mut rx = bus.subscribe();
        bus.emit(Event::GraphReset);
        let ev = rx.recv().await.unwrap();
        assert_eq!(ev, Event::GraphReset);
    }

    #[tokio::test]
    async fn test_node_added_event_roundtrip() {
        let bus = EventBus::new();
        let mut rx = bus.subscribe();
        bus.emit(Event::NodeAdded {
            id: "n1".to_string(),
            label: "Foo".to_string(),
            source_file: "a.rs".to_string(),
        });
        let ev = rx.recv().await.unwrap();
        assert!(matches!(ev, Event::NodeAdded { ref id, .. } if id == "n1"));
    }

    #[tokio::test]
    async fn test_edge_added_event_roundtrip() {
        let bus = EventBus::new();
        let mut rx = bus.subscribe();
        bus.emit(Event::EdgeAdded {
            source: "a".to_string(),
            target: "b".to_string(),
            relation: "calls".to_string(),
        });
        let ev = rx.recv().await.unwrap();
        assert!(matches!(ev, Event::EdgeAdded { ref source, .. } if source == "a"));
    }

    #[tokio::test]
    async fn test_multiple_subscribers_both_receive() {
        let bus = EventBus::new();
        let mut rx1 = bus.subscribe();
        let mut rx2 = bus.subscribe();
        bus.emit(Event::GraphReset);
        assert_eq!(rx1.recv().await.unwrap(), Event::GraphReset);
        assert_eq!(rx2.recv().await.unwrap(), Event::GraphReset);
    }

    #[tokio::test]
    async fn test_multiple_events_received_in_order() {
        let bus = EventBus::new();
        let mut rx = bus.subscribe();
        bus.emit(Event::NodeAdded {
            id: "1".to_string(),
            label: "A".to_string(),
            source_file: "f.rs".to_string(),
        });
        bus.emit(Event::NodeAdded {
            id: "2".to_string(),
            label: "B".to_string(),
            source_file: "f.rs".to_string(),
        });
        let first = rx.recv().await.unwrap();
        let second = rx.recv().await.unwrap();
        assert!(matches!(first, Event::NodeAdded { ref id, .. } if id == "1"));
        assert!(matches!(second, Event::NodeAdded { ref id, .. } if id == "2"));
    }

    #[test]
    fn test_emit_with_no_subscribers_does_not_panic() {
        let bus = EventBus::new();
        bus.emit(Event::GraphReset);
    }
}