Skip to content

Commit

Permalink
twiq: Add InMemoryUserStore
Browse files Browse the repository at this point in the history
  • Loading branch information
bouzuya committed Oct 14, 2022
1 parent 8e0018e commit c44f7cd
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 4 deletions.
13 changes: 9 additions & 4 deletions twiq/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions twiq/crates/query_handler/Cargo.toml
Expand Up @@ -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"] }
106 changes: 106 additions & 0 deletions 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<Mutex<HashMap<UserId, User>>>,
twitter_user_ids: Arc<Mutex<HashMap<TwitterUserId, UserId>>>,
twitter_user_names: Arc<Mutex<HashMap<TwitterUserName, UserId>>>,
}

#[async_trait]
impl UserStore for InMemoryUserStore {
#[instrument]
async fn find_by_twitter_user_id(
&self,
twitter_user_id: &TwitterUserId,
) -> Result<Option<User>> {
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<User>, 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(())
}
}
2 changes: 2 additions & 0 deletions twiq/crates/query_handler/src/lib.rs
@@ -1 +1,3 @@
pub mod in_memory_user_store;
pub mod user;
pub mod user_store;
1 change: 1 addition & 0 deletions 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,
Expand Down
23 changes: 23 additions & 0 deletions 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<T, E = Error> = std::result::Result<T, E>;

#[async_trait]
pub trait UserStore {
async fn find_by_twitter_user_id(&self, twitter_user_id: &String) -> Result<Option<User>>;
async fn store(&self, before: Option<User>, after: User) -> Result<()>;
}

pub trait HasUserStore {
type UserStore: UserStore + Send + Sync;

fn user_store(&self) -> &Self::UserStore;
}

0 comments on commit c44f7cd

Please sign in to comment.