#![deny(rust_2018_idioms, broken_intra_doc_links, unused, warnings)]
pub mod model;
mod builder;
mod config;
mod updates;
pub use self::{
builder::InMemoryCacheBuilder,
config::{Config, ResourceType},
updates::UpdateCache,
};
use self::model::*;
use dashmap::{mapref::entry::Entry, DashMap, DashSet};
use std::{
borrow::Cow,
collections::{BTreeMap, BTreeSet, HashSet},
hash::Hash,
sync::{Arc, Mutex},
};
use twilight_model::{
channel::{Group, GuildChannel, PrivateChannel},
gateway::presence::{Presence, UserOrId},
guild::{Emoji, Guild, Member, PartialMember, Role},
id::{ChannelId, EmojiId, GuildId, MessageId, RoleId, UserId},
user::{CurrentUser, User},
voice::VoiceState,
};
#[derive(Debug)]
struct GuildItem<T> {
data: Arc<T>,
guild_id: GuildId,
}
fn upsert_guild_item<K: Eq + Hash, V: PartialEq>(
map: &DashMap<K, GuildItem<V>>,
guild_id: GuildId,
k: K,
v: V,
) -> Arc<V> {
match map.entry(k) {
Entry::Occupied(e) if *e.get().data == v => Arc::clone(&e.get().data),
Entry::Occupied(mut e) => {
let v = Arc::new(v);
e.insert(GuildItem {
data: Arc::clone(&v),
guild_id,
});
v
}
Entry::Vacant(e) => Arc::clone(
&e.insert(GuildItem {
data: Arc::new(v),
guild_id,
})
.data,
),
}
}
fn upsert_item<K: Eq + Hash, V: PartialEq>(map: &DashMap<K, Arc<V>>, k: K, v: V) -> Arc<V> {
match map.entry(k) {
Entry::Occupied(e) if **e.get() == v => Arc::clone(e.get()),
Entry::Occupied(mut e) => {
let v = Arc::new(v);
e.insert(Arc::clone(&v));
v
}
Entry::Vacant(e) => {
let v = Arc::new(v);
e.insert(Arc::clone(&v));
v
}
}
}
#[derive(Debug, Default)]
struct InMemoryCacheRef {
config: Arc<Config>,
channels_guild: DashMap<ChannelId, GuildItem<GuildChannel>>,
channels_private: DashMap<ChannelId, Arc<PrivateChannel>>,
current_user: Mutex<Option<Arc<CurrentUser>>>,
emojis: DashMap<EmojiId, GuildItem<CachedEmoji>>,
groups: DashMap<ChannelId, Arc<Group>>,
guilds: DashMap<GuildId, Arc<CachedGuild>>,
guild_channels: DashMap<GuildId, HashSet<ChannelId>>,
guild_emojis: DashMap<GuildId, HashSet<EmojiId>>,
guild_members: DashMap<GuildId, HashSet<UserId>>,
guild_presences: DashMap<GuildId, HashSet<UserId>>,
guild_roles: DashMap<GuildId, HashSet<RoleId>>,
members: DashMap<(GuildId, UserId), Arc<CachedMember>>,
messages: DashMap<ChannelId, BTreeMap<MessageId, Arc<CachedMessage>>>,
presences: DashMap<(GuildId, UserId), Arc<CachedPresence>>,
roles: DashMap<RoleId, GuildItem<Role>>,
unavailable_guilds: DashSet<GuildId>,
users: DashMap<UserId, (Arc<User>, BTreeSet<GuildId>)>,
voice_state_channels: DashMap<ChannelId, HashSet<(GuildId, UserId)>>,
voice_state_guilds: DashMap<GuildId, HashSet<UserId>>,
voice_states: DashMap<(GuildId, UserId), Arc<VoiceState>>,
}
#[derive(Clone, Debug, Default)]
pub struct InMemoryCache(Arc<InMemoryCacheRef>);
impl InMemoryCache {
pub fn new() -> Self {
Self::default()
}
fn new_with_config(config: Config) -> Self {
Self(Arc::new(InMemoryCacheRef {
config: Arc::new(config),
..Default::default()
}))
}
pub fn builder() -> InMemoryCacheBuilder {
InMemoryCacheBuilder::new()
}
pub fn config(&self) -> Config {
(*self.0.config).clone()
}
pub fn update(&self, value: &impl UpdateCache) {
value.update(self);
}
pub fn guild_channel(&self, channel_id: ChannelId) -> Option<Arc<GuildChannel>> {
self.0
.channels_guild
.get(&channel_id)
.map(|x| Arc::clone(&x.data))
}
pub fn current_user(&self) -> Option<Arc<CurrentUser>> {
self.0
.current_user
.lock()
.expect("current user poisoned")
.clone()
}
pub fn emoji(&self, emoji_id: EmojiId) -> Option<Arc<CachedEmoji>> {
self.0.emojis.get(&emoji_id).map(|x| Arc::clone(&x.data))
}
pub fn group(&self, channel_id: ChannelId) -> Option<Arc<Group>> {
self.0
.groups
.get(&channel_id)
.map(|r| Arc::clone(r.value()))
}
pub fn guild(&self, guild_id: GuildId) -> Option<Arc<CachedGuild>> {
self.0.guilds.get(&guild_id).map(|r| Arc::clone(r.value()))
}
pub fn guild_channels(&self, guild_id: GuildId) -> Option<HashSet<ChannelId>> {
self.0
.guild_channels
.get(&guild_id)
.map(|r| r.value().clone())
}
pub fn guild_emojis(&self, guild_id: GuildId) -> Option<HashSet<EmojiId>> {
self.0
.guild_emojis
.get(&guild_id)
.map(|r| r.value().clone())
}
pub fn guild_members(&self, guild_id: GuildId) -> Option<HashSet<UserId>> {
self.0
.guild_members
.get(&guild_id)
.map(|r| r.value().clone())
}
pub fn guild_presences(&self, guild_id: GuildId) -> Option<HashSet<UserId>> {
self.0
.guild_presences
.get(&guild_id)
.map(|r| r.value().clone())
}
pub fn guild_roles(&self, guild_id: GuildId) -> Option<HashSet<RoleId>> {
self.0.guild_roles.get(&guild_id).map(|r| r.value().clone())
}
pub fn member(&self, guild_id: GuildId, user_id: UserId) -> Option<Arc<CachedMember>> {
self.0
.members
.get(&(guild_id, user_id))
.map(|r| Arc::clone(r.value()))
}
pub fn message(
&self,
channel_id: ChannelId,
message_id: MessageId,
) -> Option<Arc<CachedMessage>> {
let channel = self.0.messages.get(&channel_id)?;
channel.get(&message_id).cloned()
}
pub fn presence(&self, guild_id: GuildId, user_id: UserId) -> Option<Arc<CachedPresence>> {
self.0
.presences
.get(&(guild_id, user_id))
.map(|r| Arc::clone(r.value()))
}
pub fn private_channel(&self, channel_id: ChannelId) -> Option<Arc<PrivateChannel>> {
self.0
.channels_private
.get(&channel_id)
.map(|r| Arc::clone(r.value()))
}
pub fn role(&self, role_id: RoleId) -> Option<Arc<Role>> {
self.0
.roles
.get(&role_id)
.map(|role| Arc::clone(&role.data))
}
pub fn user(&self, user_id: UserId) -> Option<Arc<User>> {
self.0.users.get(&user_id).map(|r| Arc::clone(&r.0))
}
pub fn voice_channel_states(&self, channel_id: ChannelId) -> Option<Vec<Arc<VoiceState>>> {
let user_ids = self.0.voice_state_channels.get(&channel_id)?;
Some(
user_ids
.iter()
.filter_map(|key| self.0.voice_states.get(&key).map(|r| Arc::clone(r.value())))
.collect(),
)
}
pub fn voice_state(&self, user_id: UserId, guild_id: GuildId) -> Option<Arc<VoiceState>> {
self.0
.voice_states
.get(&(guild_id, user_id))
.map(|r| Arc::clone(r.value()))
}
pub fn clear(&self) {
self.0.channels_guild.clear();
self.0.channels_private.clear();
self.0
.current_user
.lock()
.expect("current user poisoned")
.take();
self.0.emojis.clear();
self.0.groups.clear();
self.0.guilds.clear();
self.0.guild_channels.clear();
self.0.guild_emojis.clear();
self.0.guild_members.clear();
self.0.guild_presences.clear();
self.0.guild_roles.clear();
self.0.members.clear();
self.0.messages.clear();
self.0.presences.clear();
self.0.roles.clear();
self.0.unavailable_guilds.clear();
self.0.users.clear();
self.0.voice_state_channels.clear();
self.0.voice_state_guilds.clear();
self.0.voice_states.clear();
}
fn cache_current_user(&self, mut current_user: CurrentUser) {
let mut user = self.0.current_user.lock().expect("current user poisoned");
if let Some(mut user) = user.as_mut() {
if let Some(user) = Arc::get_mut(&mut user) {
std::mem::swap(user, &mut current_user);
return;
}
}
*user = Some(Arc::new(current_user));
}
fn cache_guild_channels(
&self,
guild_id: GuildId,
guild_channels: impl IntoIterator<Item = GuildChannel>,
) {
for channel in guild_channels {
self.cache_guild_channel(guild_id, channel);
}
}
fn cache_guild_channel(
&self,
guild_id: GuildId,
mut channel: GuildChannel,
) -> Arc<GuildChannel> {
match channel {
GuildChannel::Category(ref mut c) => {
c.guild_id.replace(guild_id);
}
GuildChannel::Text(ref mut c) => {
c.guild_id.replace(guild_id);
}
GuildChannel::Voice(ref mut c) => {
c.guild_id.replace(guild_id);
}
}
let id = channel.id();
self.0
.guild_channels
.entry(guild_id)
.or_default()
.insert(id);
upsert_guild_item(&self.0.channels_guild, guild_id, id, channel)
}
fn cache_emoji(&self, guild_id: GuildId, emoji: Emoji) -> Arc<CachedEmoji> {
match self.0.emojis.get(&emoji.id) {
Some(e) if *e.data == emoji => return Arc::clone(&e.data),
Some(_) | None => {}
}
let user = match emoji.user {
Some(u) => Some(self.cache_user(Cow::Owned(u), Some(guild_id))),
None => None,
};
let cached = Arc::new(CachedEmoji {
id: emoji.id,
animated: emoji.animated,
name: emoji.name,
managed: emoji.managed,
require_colons: emoji.require_colons,
roles: emoji.roles,
user,
available: emoji.available,
});
self.0.emojis.insert(
cached.id,
GuildItem {
data: Arc::clone(&cached),
guild_id,
},
);
self.0
.guild_emojis
.entry(guild_id)
.or_default()
.insert(emoji.id);
cached
}
fn cache_emojis(&self, guild_id: GuildId, emojis: impl IntoIterator<Item = Emoji>) {
for emoji in emojis {
self.cache_emoji(guild_id, emoji);
}
}
fn cache_group(&self, group: Group) -> Arc<Group> {
upsert_item(&self.0.groups, group.id, group)
}
fn cache_guild(&self, guild: Guild) {
if self.wants(ResourceType::CHANNEL) {
self.0.guild_channels.insert(guild.id, HashSet::new());
self.cache_guild_channels(guild.id, guild.channels);
}
if self.wants(ResourceType::EMOJI) {
self.0.guild_emojis.insert(guild.id, HashSet::new());
self.cache_emojis(guild.id, guild.emojis);
}
if self.wants(ResourceType::MEMBER) {
self.0.guild_members.insert(guild.id, HashSet::new());
self.cache_members(guild.id, guild.members);
}
if self.wants(ResourceType::PRESENCE) {
self.0.guild_presences.insert(guild.id, HashSet::new());
self.cache_presences(guild.id, guild.presences);
}
if self.wants(ResourceType::ROLE) {
self.0.guild_roles.insert(guild.id, HashSet::new());
self.cache_roles(guild.id, guild.roles);
}
if self.wants(ResourceType::VOICE_STATE) {
self.0.voice_state_guilds.insert(guild.id, HashSet::new());
self.cache_voice_states(guild.voice_states);
}
let guild = CachedGuild {
id: guild.id,
afk_channel_id: guild.afk_channel_id,
afk_timeout: guild.afk_timeout,
application_id: guild.application_id,
banner: guild.banner,
default_message_notifications: guild.default_message_notifications,
description: guild.description,
discovery_splash: guild.discovery_splash,
explicit_content_filter: guild.explicit_content_filter,
features: guild.features,
icon: guild.icon,
joined_at: guild.joined_at,
large: guild.large,
lazy: guild.lazy,
max_members: guild.max_members,
max_presences: guild.max_presences,
member_count: guild.member_count,
mfa_level: guild.mfa_level,
name: guild.name,
owner: guild.owner,
owner_id: guild.owner_id,
permissions: guild.permissions,
preferred_locale: guild.preferred_locale,
premium_subscription_count: guild.premium_subscription_count,
premium_tier: guild.premium_tier,
region: guild.region,
rules_channel_id: guild.rules_channel_id,
splash: guild.splash,
system_channel_id: guild.system_channel_id,
system_channel_flags: guild.system_channel_flags,
unavailable: guild.unavailable,
verification_level: guild.verification_level,
vanity_url_code: guild.vanity_url_code,
widget_channel_id: guild.widget_channel_id,
widget_enabled: guild.widget_enabled,
};
self.0.unavailable_guilds.remove(&guild.id);
self.0.guilds.insert(guild.id, Arc::new(guild));
}
fn cache_member(&self, guild_id: GuildId, member: Member) -> Arc<CachedMember> {
let member_id = member.user.id;
let id = (guild_id, member_id);
match self.0.members.get(&id) {
Some(m) if **m == member => return Arc::clone(&m),
Some(_) | None => {}
}
let user = self.cache_user(Cow::Owned(member.user), Some(guild_id));
let cached = Arc::new(CachedMember {
deaf: member.deaf,
guild_id,
joined_at: member.joined_at,
mute: member.mute,
nick: member.nick,
pending: member.pending,
premium_since: member.premium_since,
roles: member.roles,
user,
});
self.0.members.insert(id, Arc::clone(&cached));
self.0
.guild_members
.entry(guild_id)
.or_default()
.insert(member_id);
cached
}
fn cache_borrowed_partial_member(
&self,
guild_id: GuildId,
member: &PartialMember,
user: Arc<User>,
) -> Arc<CachedMember> {
let id = (guild_id, user.id);
match self.0.members.get(&id) {
Some(m) if **m == member => return Arc::clone(&m),
Some(_) | None => {}
}
self.0
.guild_members
.entry(guild_id)
.or_default()
.insert(user.id);
let cached = Arc::new(CachedMember {
deaf: member.deaf,
guild_id,
joined_at: member.joined_at.to_owned(),
mute: member.mute,
nick: member.nick.to_owned(),
pending: false,
premium_since: None,
roles: member.roles.to_owned(),
user,
});
self.0.members.insert(id, Arc::clone(&cached));
cached
}
fn cache_members(&self, guild_id: GuildId, members: impl IntoIterator<Item = Member>) {
for member in members {
self.cache_member(guild_id, member);
}
}
fn cache_presences(&self, guild_id: GuildId, presences: impl IntoIterator<Item = Presence>) {
for presence in presences {
self.cache_presence(guild_id, presence);
}
}
fn cache_presence(&self, guild_id: GuildId, presence: Presence) -> Arc<CachedPresence> {
let k = (guild_id, presence_user_id(&presence));
match self.0.presences.get(&k) {
Some(p) if **p == presence => return Arc::clone(&p),
Some(_) | None => {}
}
let cached = Arc::new(CachedPresence::from(&presence));
self.0.presences.insert(k, Arc::clone(&cached));
cached
}
fn cache_private_channel(&self, private_channel: PrivateChannel) -> Arc<PrivateChannel> {
let id = private_channel.id;
match self.0.channels_private.get(&id) {
Some(c) if **c == private_channel => Arc::clone(&c),
Some(_) | None => {
let v = Arc::new(private_channel);
self.0.channels_private.insert(id, Arc::clone(&v));
v
}
}
}
fn cache_roles(&self, guild_id: GuildId, roles: impl IntoIterator<Item = Role>) {
for role in roles {
self.cache_role(guild_id, role);
}
}
fn cache_role(&self, guild_id: GuildId, role: Role) -> Arc<Role> {
self.0
.guild_roles
.entry(guild_id)
.or_default()
.insert(role.id);
upsert_guild_item(&self.0.roles, guild_id, role.id, role)
}
fn cache_user(&self, user: Cow<'_, User>, guild_id: Option<GuildId>) -> Arc<User> {
match self.0.users.get_mut(&user.id) {
Some(mut u) if *u.0 == *user => {
if let Some(guild_id) = guild_id {
u.1.insert(guild_id);
}
return Arc::clone(&u.value().0);
}
Some(_) | None => {}
}
let user = Arc::new(user.into_owned());
if let Some(guild_id) = guild_id {
let mut guild_id_set = BTreeSet::new();
guild_id_set.insert(guild_id);
self.0
.users
.insert(user.id, (Arc::clone(&user), guild_id_set));
}
user
}
fn cache_voice_states(&self, voice_states: impl IntoIterator<Item = VoiceState>) {
for voice_state in voice_states {
self.cache_voice_state(voice_state);
}
}
fn cache_voice_state(&self, vs: VoiceState) -> Option<Arc<VoiceState>> {
let guild_id = match vs.guild_id {
Some(id) => id,
None => return None,
};
let user_id = vs.user_id;
if let Some(voice_state) = self.0.voice_states.get(&(guild_id, user_id)) {
if let Some(channel_id) = voice_state.channel_id {
let remove_channel_mapping = self
.0
.voice_state_channels
.get_mut(&channel_id)
.map(|mut channel_voice_states| {
channel_voice_states.remove(&(guild_id, user_id));
channel_voice_states.is_empty()
})
.unwrap_or_default();
if remove_channel_mapping {
self.0.voice_state_channels.remove(&channel_id);
}
}
}
if vs.channel_id.is_none() {
{
let remove_guild = self
.0
.voice_state_guilds
.get_mut(&guild_id)
.map(|mut guild_users| {
guild_users.remove(&user_id);
guild_users.is_empty()
})
.unwrap_or_default();
if remove_guild {
self.0.voice_state_guilds.remove(&guild_id);
}
}
let (_, state) = self.0.voice_states.remove(&(guild_id, user_id))?;
return Some(state);
}
let state = Arc::new(vs);
self.0
.voice_states
.insert((guild_id, user_id), Arc::clone(&state));
self.0
.voice_state_guilds
.entry(guild_id)
.or_default()
.insert(user_id);
if let Some(channel_id) = state.channel_id {
self.0
.voice_state_channels
.entry(channel_id)
.or_default()
.insert((guild_id, user_id));
}
Some(state)
}
fn delete_group(&self, channel_id: ChannelId) -> Option<Arc<Group>> {
self.0.groups.remove(&channel_id).map(|(_, v)| v)
}
fn unavailable_guild(&self, guild_id: GuildId) {
self.0.unavailable_guilds.insert(guild_id);
self.0.guilds.remove(&guild_id);
}
fn delete_guild_channel(&self, channel_id: ChannelId) -> Option<Arc<GuildChannel>> {
let GuildItem { data, guild_id } = self.0.channels_guild.remove(&channel_id)?.1;
if let Some(mut guild_channels) = self.0.guild_channels.get_mut(&guild_id) {
guild_channels.remove(&channel_id);
}
Some(data)
}
fn delete_role(&self, role_id: RoleId) -> Option<Arc<Role>> {
let role = self.0.roles.remove(&role_id).map(|(_, v)| v)?;
if let Some(mut roles) = self.0.guild_roles.get_mut(&role.guild_id) {
roles.remove(&role_id);
}
Some(role.data)
}
fn wants(&self, resource_type: ResourceType) -> bool {
self.0.config.resource_types().contains(resource_type)
}
}
fn presence_user_id(presence: &Presence) -> UserId {
match presence.user {
UserOrId::User(ref u) => u.id,
UserOrId::UserId { id } => id,
}
}
#[cfg(test)]
mod tests {
use crate::InMemoryCache;
use std::borrow::Cow;
use twilight_model::{
channel::{ChannelType, GuildChannel, TextChannel},
gateway::payload::{MemberRemove, RoleDelete},
guild::{
DefaultMessageNotificationLevel, Emoji, ExplicitContentFilter, Guild, Member, MfaLevel,
Permissions, PremiumTier, Role, SystemChannelFlags, VerificationLevel,
},
id::{ChannelId, EmojiId, GuildId, RoleId, UserId},
user::{CurrentUser, User},
voice::VoiceState,
};
fn current_user(id: u64) -> CurrentUser {
CurrentUser {
avatar: None,
bot: true,
discriminator: "9876".to_owned(),
email: None,
id: UserId(id),
mfa_enabled: true,
name: "test".to_owned(),
verified: Some(true),
premium_type: None,
public_flags: None,
flags: None,
locale: None,
}
}
fn emoji(id: EmojiId, user: Option<User>) -> Emoji {
Emoji {
animated: false,
available: true,
id,
managed: false,
name: "test".to_owned(),
require_colons: true,
roles: Vec::new(),
user,
}
}
fn member(id: UserId, guild_id: GuildId) -> Member {
Member {
deaf: false,
guild_id,
hoisted_role: None,
joined_at: None,
mute: false,
nick: None,
pending: false,
premium_since: None,
roles: Vec::new(),
user: user(id),
}
}
fn role(id: RoleId) -> Role {
Role {
color: 0,
hoist: false,
id,
managed: false,
mentionable: false,
name: "test".to_owned(),
permissions: Permissions::empty(),
position: 0,
tags: None,
}
}
fn user(id: UserId) -> User {
User {
avatar: None,
bot: false,
discriminator: "0001".to_owned(),
email: None,
flags: None,
id,
locale: None,
mfa_enabled: None,
name: "user".to_owned(),
premium_type: None,
public_flags: None,
system: None,
verified: None,
}
}
fn voice_state(
guild_id: GuildId,
channel_id: Option<ChannelId>,
user_id: UserId,
) -> VoiceState {
VoiceState {
channel_id,
deaf: false,
guild_id: Some(guild_id),
member: None,
mute: true,
self_deaf: false,
self_mute: true,
self_stream: false,
session_id: "a".to_owned(),
suppress: false,
token: None,
user_id,
}
}
#[test]
fn test_current_user_retrieval() {
let cache = InMemoryCache::new();
assert!(cache.current_user().is_none());
cache.cache_current_user(current_user(1));
assert!(cache.current_user().is_some());
}
#[test]
fn test_guild_create_channels_have_guild_ids() {
let mut channels = Vec::new();
channels.push(GuildChannel::Text(TextChannel {
id: ChannelId(111),
guild_id: None,
kind: ChannelType::GuildText,
last_message_id: None,
last_pin_timestamp: None,
name: "guild channel with no guild id".to_owned(),
nsfw: true,
permission_overwrites: Vec::new(),
parent_id: None,
position: 1,
rate_limit_per_user: None,
topic: None,
}));
let guild = Guild {
id: GuildId(123),
afk_channel_id: None,
afk_timeout: 300,
application_id: None,
banner: None,
channels,
default_message_notifications: DefaultMessageNotificationLevel::Mentions,
description: None,
discovery_splash: None,
emojis: Vec::new(),
explicit_content_filter: ExplicitContentFilter::AllMembers,
features: vec![],
icon: None,
joined_at: Some("".to_owned()),
large: false,
lazy: Some(true),
max_members: Some(50),
max_presences: Some(100),
member_count: Some(25),
members: Vec::new(),
mfa_level: MfaLevel::Elevated,
name: "this is a guild".to_owned(),
owner: Some(false),
owner_id: UserId(456),
permissions: Some(Permissions::SEND_MESSAGES),
preferred_locale: "en-GB".to_owned(),
premium_subscription_count: Some(0),
premium_tier: PremiumTier::None,
presences: Vec::new(),
region: "us-east".to_owned(),
roles: Vec::new(),
splash: None,
system_channel_id: None,
system_channel_flags: SystemChannelFlags::SUPPRESS_JOIN_NOTIFICATIONS,
rules_channel_id: None,
unavailable: false,
verification_level: VerificationLevel::VeryHigh,
voice_states: Vec::new(),
vanity_url_code: None,
widget_channel_id: None,
widget_enabled: None,
max_video_channel_users: None,
approximate_member_count: None,
approximate_presence_count: None,
};
let cache = InMemoryCache::new();
cache.cache_guild(guild);
let channel = cache.guild_channel(ChannelId(111)).unwrap();
match *channel {
GuildChannel::Text(ref c) => {
assert_eq!(Some(GuildId(123)), c.guild_id);
}
_ => assert!(false, "{:?}", channel),
}
}
#[test]
fn test_syntax_update() {
let cache = InMemoryCache::new();
cache.update(&RoleDelete {
guild_id: GuildId(0),
role_id: RoleId(1),
});
}
#[test]
fn test_cache_user_guild_state() {
let user_id = UserId(2);
let cache = InMemoryCache::new();
cache.cache_user(Cow::Owned(user(user_id)), Some(GuildId(1)));
{
let user = cache.0.users.get(&user_id).unwrap();
assert!(user.1.contains(&GuildId(1)));
assert_eq!(1, user.1.len());
}
cache.cache_user(Cow::Owned(user(user_id)), Some(GuildId(3)));
{
let user = cache.0.users.get(&user_id).unwrap();
assert!(user.1.contains(&GuildId(3)));
assert_eq!(2, user.1.len());
}
cache.update(&MemberRemove {
guild_id: GuildId(3),
user: user(user_id),
});
{
let user = cache.0.users.get(&user_id).unwrap();
assert!(!user.1.contains(&GuildId(3)));
assert_eq!(1, user.1.len());
}
cache.update(&MemberRemove {
guild_id: GuildId(1),
user: user(user_id),
});
assert!(!cache.0.users.contains_key(&user_id));
}
#[test]
fn test_voice_state_inserts_and_removes() {
let cache = InMemoryCache::new();
{
let (guild_id, channel_id, user_id) = (GuildId(1), ChannelId(11), UserId(1));
cache.cache_voice_state(voice_state(guild_id, Some(channel_id), user_id));
assert!(cache.0.voice_states.contains_key(&(guild_id, user_id)));
assert_eq!(1, cache.0.voice_states.len());
assert!(cache.0.voice_state_channels.contains_key(&channel_id));
assert_eq!(1, cache.0.voice_state_channels.len());
assert!(cache.0.voice_state_guilds.contains_key(&guild_id));
assert_eq!(1, cache.0.voice_state_guilds.len());
}
{
let (guild_id, channel_id, user_id) = (GuildId(2), ChannelId(21), UserId(2));
cache.cache_voice_state(voice_state(guild_id, Some(channel_id), user_id));
assert!(cache.0.voice_states.contains_key(&(guild_id, user_id)));
assert_eq!(2, cache.0.voice_states.len());
assert!(cache.0.voice_state_channels.contains_key(&channel_id));
assert_eq!(2, cache.0.voice_state_channels.len());
assert!(cache.0.voice_state_guilds.contains_key(&guild_id));
assert_eq!(2, cache.0.voice_state_guilds.len());
}
{
let (guild_id, channel_id, user_id) = (GuildId(1), ChannelId(12), UserId(3));
cache.cache_voice_state(voice_state(guild_id, Some(channel_id), user_id));
assert!(cache.0.voice_states.contains_key(&(guild_id, user_id)));
assert_eq!(3, cache.0.voice_states.len());
assert!(cache.0.voice_state_channels.contains_key(&channel_id));
assert_eq!(3, cache.0.voice_state_channels.len());
assert!(cache.0.voice_state_guilds.contains_key(&guild_id));
assert_eq!(2, cache.0.voice_state_guilds.len());
}
{
let (guild_id, channel_id, user_id) = (GuildId(1), ChannelId(11), UserId(3));
cache.cache_voice_state(voice_state(guild_id, Some(channel_id), user_id));
assert!(cache.0.voice_states.contains_key(&(guild_id, user_id)));
assert_eq!(3, cache.0.voice_states.len());
assert!(cache.0.voice_state_channels.contains_key(&channel_id));
assert_eq!(2, cache.0.voice_state_channels.len());
assert!(cache.0.voice_state_guilds.contains_key(&guild_id));
assert_eq!(2, cache.0.voice_state_guilds.len());
}
{
let (guild_id, channel_id, user_id) = (GuildId(1), ChannelId(11), UserId(3));
cache.cache_voice_state(voice_state(guild_id, None, user_id));
assert!(!cache.0.voice_states.contains_key(&(guild_id, user_id)));
assert_eq!(2, cache.0.voice_states.len());
assert!(cache.0.voice_state_channels.contains_key(&channel_id));
assert!(cache.0.voice_state_guilds.contains_key(&guild_id));
assert_eq!(2, cache.0.voice_state_guilds.len());
}
{
let (guild_id, channel_id, user_id) = (GuildId(2), ChannelId(21), UserId(2));
cache.cache_voice_state(voice_state(guild_id, None, user_id));
assert!(!cache.0.voice_states.contains_key(&(guild_id, user_id)));
assert_eq!(1, cache.0.voice_states.len());
assert!(!cache.0.voice_state_channels.contains_key(&channel_id));
assert_eq!(1, cache.0.voice_state_channels.len());
assert!(!cache.0.voice_state_guilds.contains_key(&guild_id));
assert_eq!(1, cache.0.voice_state_guilds.len());
}
{
let (guild_id, _channel_id, user_id) = (GuildId(1), ChannelId(11), UserId(1));
cache.cache_voice_state(voice_state(guild_id, None, user_id));
assert!(cache.0.voice_states.is_empty());
assert!(cache.0.voice_state_channels.is_empty());
assert!(cache.0.voice_state_guilds.is_empty());
}
}
#[test]
fn test_voice_states() {
let cache = InMemoryCache::new();
cache.cache_voice_state(voice_state(GuildId(1), Some(ChannelId(2)), UserId(3)));
cache.cache_voice_state(voice_state(GuildId(1), Some(ChannelId(2)), UserId(4)));
assert_eq!(2, cache.voice_channel_states(ChannelId(2)).unwrap().len());
assert!(cache.voice_channel_states(ChannelId(0)).is_none());
}
#[test]
fn test_cache_role() {
let cache = InMemoryCache::new();
{
let guild_1_role_ids = (1..=10).map(RoleId).collect::<Vec<_>>();
let guild_1_roles = guild_1_role_ids
.iter()
.copied()
.map(role)
.collect::<Vec<_>>();
for role in guild_1_roles.clone() {
cache.cache_role(GuildId(1), role);
}
let cached_roles = cache.guild_roles(GuildId(1)).unwrap();
assert_eq!(cached_roles.len(), guild_1_role_ids.len());
assert!(guild_1_role_ids.iter().all(|id| cached_roles.contains(id)));
assert!(guild_1_roles
.into_iter()
.all(|role| *cache.role(role.id).expect("Role missing from cache") == role))
}
{
let guild_2_role_ids = (101..=110).map(RoleId).collect::<Vec<_>>();
let guild_2_roles = guild_2_role_ids
.iter()
.copied()
.map(role)
.collect::<Vec<_>>();
cache.cache_roles(GuildId(2), guild_2_roles.clone());
let cached_roles = cache.guild_roles(GuildId(2)).unwrap();
assert_eq!(cached_roles.len(), guild_2_role_ids.len());
assert!(guild_2_role_ids.iter().all(|id| cached_roles.contains(id)));
assert!(guild_2_roles
.into_iter()
.all(|role| *cache.role(role.id).expect("Role missing from cache") == role))
}
}
#[test]
fn test_cache_guild_member() {
let cache = InMemoryCache::new();
{
let guild_1_user_ids = (1..=10).map(UserId).collect::<Vec<_>>();
let guild_1_members = guild_1_user_ids
.iter()
.copied()
.map(|id| member(id, GuildId(1)))
.collect::<Vec<_>>();
for member in guild_1_members {
cache.cache_member(GuildId(1), member);
}
let cached_roles = cache.guild_members(GuildId(1)).unwrap();
assert_eq!(cached_roles.len(), guild_1_user_ids.len());
assert!(guild_1_user_ids.iter().all(|id| cached_roles.contains(id)));
assert!(guild_1_user_ids
.iter()
.all(|id| cache.member(GuildId(1), *id).is_some()));
assert!(guild_1_user_ids.iter().all(|id| cache.user(*id).is_some()));
}
{
let guild_2_user_ids = (1..=10).map(UserId).collect::<Vec<_>>();
let guild_2_members = guild_2_user_ids
.iter()
.copied()
.map(|id| member(id, GuildId(2)))
.collect::<Vec<_>>();
cache.cache_members(GuildId(2), guild_2_members);
let cached_roles = cache.guild_members(GuildId(1)).unwrap();
assert_eq!(cached_roles.len(), guild_2_user_ids.len());
assert!(guild_2_user_ids.iter().all(|id| cached_roles.contains(id)));
assert!(guild_2_user_ids
.iter()
.copied()
.all(|id| cache.member(GuildId(1), id).is_some()));
assert!(guild_2_user_ids.iter().all(|id| cache.user(*id).is_some()));
}
}
#[test]
fn test_cache_emoji() {
let cache = InMemoryCache::new();
fn user_mod(id: EmojiId) -> Option<User> {
if id.0 % 2 == 0 {
Some(user(UserId(1)))
} else {
None
}
}
{
let guild_1_emoji_ids = (1..=10).map(EmojiId).collect::<Vec<_>>();
let guild_1_emoji = guild_1_emoji_ids
.iter()
.copied()
.map(|id| emoji(id, user_mod(id)))
.collect::<Vec<_>>();
for emoji in guild_1_emoji {
cache.cache_emoji(GuildId(1), emoji);
}
for id in guild_1_emoji_ids.iter().cloned() {
let global_emoji = cache.emoji(id);
assert!(global_emoji.is_some());
}
let guild_emojis = cache.guild_emojis(GuildId(1));
assert!(guild_emojis.is_some());
let guild_emojis = guild_emojis.unwrap();
assert_eq!(guild_1_emoji_ids.len(), guild_emojis.len());
assert!(guild_1_emoji_ids.iter().all(|id| guild_emojis.contains(id)));
}
{
let guild_2_emoji_ids = (11..=20).map(EmojiId).collect::<Vec<_>>();
let guild_2_emojis = guild_2_emoji_ids
.iter()
.copied()
.map(|id| emoji(id, user_mod(id)))
.collect::<Vec<_>>();
cache.cache_emojis(GuildId(2), guild_2_emojis);
for id in guild_2_emoji_ids.iter().cloned() {
let global_emoji = cache.emoji(id);
assert!(global_emoji.is_some());
}
let guild_emojis = cache.guild_emojis(GuildId(2));
assert!(guild_emojis.is_some());
let guild_emojis = guild_emojis.unwrap();
assert_eq!(guild_2_emoji_ids.len(), guild_emojis.len());
assert!(guild_2_emoji_ids.iter().all(|id| guild_emojis.contains(id)));
}
}
#[test]
fn test_clear() {
let cache = InMemoryCache::new();
cache.cache_emoji(GuildId(1), emoji(EmojiId(3), None));
cache.cache_member(GuildId(2), member(UserId(4), GuildId(2)));
cache.clear();
assert!(cache.0.emojis.is_empty());
assert!(cache.0.members.is_empty());
}
}