use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock, PoisonError};
use arc_swap::ArcSwap;
use google_cloud_pubsub::client::Publisher as GcpPublisher;
pub(crate) fn topic_key<'a>(project: &str, topic: &'a str) -> &'a str {
topic
.strip_prefix("projects/")
.and_then(|rest| rest.strip_prefix(project))
.and_then(|rest| rest.strip_prefix("/topics/"))
.unwrap_or(topic)
}
#[derive(Debug, Default)]
pub(crate) struct TopicPublishers {
by_topic: ArcSwap<HashMap<Box<str>, GcpPublisher>>,
adding: Mutex<()>,
}
impl TopicPublishers {
pub(crate) fn with<Out>(
&self,
key: &str,
build: impl FnOnce() -> GcpPublisher,
use_it: impl FnOnce(&GcpPublisher) -> Out,
) -> Out {
if let Some(publisher) = self.by_topic.load().get(key) {
return use_it(publisher);
}
let _adding = self.adding.lock().unwrap_or_else(PoisonError::into_inner);
let current = self.by_topic.load_full();
if let Some(publisher) = current.get(key) {
return use_it(publisher);
}
let publisher = build();
let mut next = HashMap::clone(¤t);
next.insert(Box::from(key), publisher.clone());
self.by_topic.store(Arc::new(next));
use_it(&publisher)
}
pub(crate) fn all(&self) -> Vec<GcpPublisher> {
self.by_topic.load().values().cloned().collect()
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.by_topic.load().len()
}
}
#[derive(Debug, Default)]
pub(crate) struct FirstTopic(OnceLock<(Box<str>, GcpPublisher)>);
impl FirstTopic {
pub(crate) fn get(&self, topic: &str) -> Option<&GcpPublisher> {
self.0
.get()
.filter(|(first, _)| **first == *topic)
.map(|(_, publisher)| publisher)
}
pub(crate) fn remember(&self, topic: &str, publisher: &GcpPublisher) {
if self.0.get().is_none() {
let _ = self.0.set((Box::from(topic), publisher.clone()));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_short_id_and_the_full_name_of_one_topic_share_a_key() {
assert_eq!(topic_key("shop", "orders"), "orders");
assert_eq!(topic_key("shop", "projects/shop/topics/orders"), "orders");
}
#[test]
fn a_topic_of_another_project_keeps_its_full_name() {
assert_eq!(
topic_key("shop", "projects/billing/topics/orders"),
"projects/billing/topics/orders"
);
assert_eq!(
topic_key("shop", "projects/shopping/topics/orders"),
"projects/shopping/topics/orders"
);
}
}