diff --git a/twiq/Cargo.lock b/twiq/Cargo.lock index 926bfc1f..bc5449b3 100644 --- a/twiq/Cargo.lock +++ b/twiq/Cargo.lock @@ -868,7 +868,12 @@ dependencies = [ name = "query_handler" version = "0.0.0" dependencies = [ + "anyhow", + "async-trait", "domain", + "thiserror", + "tokio", + "tracing", ] [[package]] @@ -1201,18 +1206,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0a539a918745651435ac7db7a18761589a94cd7e94cd56999f828bf73c8a57" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c251e90f708e16c49a16f4917dc2131e75222b72edfa9cb7f7c58ae56aae0c09" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", diff --git a/twiq/crates/query_handler/Cargo.toml b/twiq/crates/query_handler/Cargo.toml index 5021bc9a..67d73537 100644 --- a/twiq/crates/query_handler/Cargo.toml +++ b/twiq/crates/query_handler/Cargo.toml @@ -4,4 +4,11 @@ version = "0.0.0" edition = "2021" [dependencies] +async-trait = "0.1.57" domain = { path = "../domain" } +thiserror = "1.0.37" +tracing = "0.1.37" + +[dev-dependencies] +anyhow = "1.0.65" +tokio = { version = "1.21.2", features = ["full"] } diff --git a/twiq/crates/query_handler/src/in_memory_user_store.rs b/twiq/crates/query_handler/src/in_memory_user_store.rs new file mode 100644 index 00000000..646bdf22 --- /dev/null +++ b/twiq/crates/query_handler/src/in_memory_user_store.rs @@ -0,0 +1,106 @@ +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +use async_trait::async_trait; +use tracing::instrument; + +use crate::{ + user::User, + user_store::{Result, UserStore}, +}; + +type UserId = String; +type TwitterUserId = String; +type TwitterUserName = String; + +#[derive(Clone, Debug, Default)] +pub struct InMemoryUserStore { + users: Arc>>, + twitter_user_ids: Arc>>, + twitter_user_names: Arc>>, +} + +#[async_trait] +impl UserStore for InMemoryUserStore { + #[instrument] + async fn find_by_twitter_user_id( + &self, + twitter_user_id: &TwitterUserId, + ) -> Result> { + let users = self.users.lock().unwrap(); + let twitter_user_ids = self.twitter_user_ids.lock().unwrap(); + Ok(twitter_user_ids + .get(twitter_user_id) + .and_then(|user_id| users.get(user_id).cloned())) + } + + #[instrument] + async fn store(&self, before: Option, after: User) -> Result<()> { + let mut users = self.users.lock().unwrap(); + let mut twitter_user_ids = self.twitter_user_ids.lock().unwrap(); + let mut twitter_user_names = self.twitter_user_names.lock().unwrap(); + + match before { + None => {} + Some(before) => { + users.remove(&before.user_id); + twitter_user_ids.remove(&before.twitter_user_id); + twitter_user_names.remove(&before.twitter_user_name); + } + } + + let user = users.get_mut(&after.user_id); + let twitter_user_id = twitter_user_ids.get_mut(&after.twitter_user_id); + let twitter_user_name = twitter_user_names.get_mut(&after.twitter_user_name); + match (user, twitter_user_id, twitter_user_name) { + (None, None, None) => { + users.insert(after.user_id.clone(), after.clone()); + twitter_user_ids.insert(after.twitter_user_id.clone(), after.user_id.clone()); + twitter_user_names.insert(after.twitter_user_name.clone(), after.user_id.clone()); + } + _ => unreachable!(), + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test() -> anyhow::Result<()> { + let user_store = InMemoryUserStore::default(); + let user1 = User { + user_id: "user_id1".to_owned(), + twitter_user_id: "twitter_user_id1".to_owned(), + twitter_user_name: "twitter_user_name1".to_owned(), + }; + user_store.store(None, user1.clone()).await?; + assert!(user_store + .find_by_twitter_user_id(&"twitter_user_id2".to_owned()) + .await? + .is_none()); + assert_eq!( + user_store + .find_by_twitter_user_id(&"twitter_user_id1".to_owned()) + .await?, + Some(user1.clone()) + ); + let user2 = User { + user_id: "user_id1".to_owned(), + twitter_user_id: "twitter_user_id2".to_owned(), + twitter_user_name: "twitter_user_name2".to_owned(), + }; + user_store.store(Some(user1), user2.clone()).await?; + assert_eq!( + user_store + .find_by_twitter_user_id(&"twitter_user_id2".to_owned()) + .await?, + Some(user2) + ); + Ok(()) + } +} diff --git a/twiq/crates/query_handler/src/lib.rs b/twiq/crates/query_handler/src/lib.rs index 22d12a38..8d55fbe4 100644 --- a/twiq/crates/query_handler/src/lib.rs +++ b/twiq/crates/query_handler/src/lib.rs @@ -1 +1,3 @@ +pub mod in_memory_user_store; pub mod user; +pub mod user_store; diff --git a/twiq/crates/query_handler/src/user.rs b/twiq/crates/query_handler/src/user.rs index 581a4a72..3e08632a 100644 --- a/twiq/crates/query_handler/src/user.rs +++ b/twiq/crates/query_handler/src/user.rs @@ -1,3 +1,4 @@ +#[derive(Clone, Debug, Eq, PartialEq)] pub struct User { pub user_id: String, pub twitter_user_id: String, diff --git a/twiq/crates/query_handler/src/user_store.rs b/twiq/crates/query_handler/src/user_store.rs new file mode 100644 index 00000000..e821016d --- /dev/null +++ b/twiq/crates/query_handler/src/user_store.rs @@ -0,0 +1,23 @@ +use async_trait::async_trait; + +use crate::user::User; + +#[derive(Debug, Eq, PartialEq, thiserror::Error)] +pub enum Error { + #[error("unknown {0}")] + Unknown(String), +} + +pub type Result = std::result::Result; + +#[async_trait] +pub trait UserStore { + async fn find_by_twitter_user_id(&self, twitter_user_id: &String) -> Result>; + async fn store(&self, before: Option, after: User) -> Result<()>; +} + +pub trait HasUserStore { + type UserStore: UserStore + Send + Sync; + + fn user_store(&self) -> &Self::UserStore; +}