1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
#![allow(clippy::std_instead_of_core)]
use core::fmt::Display;
use std::error::Error;
use async_trait::async_trait;
use twilight_model::{
channel::StageInstance,
id::{
marker::{
ChannelMarker, EmojiMarker, GenericMarker, GuildMarker, MessageMarker, RoleMarker,
StageMarker, UserMarker,
},
Id,
},
user::CurrentUser,
};
use crate::{
cache,
model::{
CachedActivity, CachedAttachment, CachedChannel, CachedEmbed, CachedEmbedField,
CachedEmoji, CachedGuild, CachedMember, CachedMessage, CachedPermissionOverwrite,
CachedPresence, CachedReaction, CachedRole, CachedSticker,
},
};
impl<E: Display + Send> From<E> for cache::Error<E> {
fn from(err: E) -> Self {
Self::Backend(err)
}
}
/// Provides methods to add, replace or delete data in the cache
///
/// This is for adding support for a backend, users of the cache itself only
/// need the methods in [`super::Cache`]
///
/// # Persistence
///
/// All of the data in the cache should be cleared every time the bot restarts
/// so that the cache can be rebuilt without the now-invalid data
///
/// # Uniqueness
///
/// Unless documented otherwise, only the main `id` field is unique, if there's
/// other fields that are also unique, they will be documented
///
/// None of the upsert methods should return an error on a conflict, unless
/// documented otherwise, they should delete the old value and insert
/// the new one (or replace all fields with their new values)
///
/// # This trait is not complete
///
/// You should expose the backend so that users can filter the results in the
/// query, for example they can do `SELECT * FROM users WHERE name = ?`
///
/// It's also advisable to implement your backend library's traits to
/// (de)serialize Discord models for the backend to streamline your codebase
///
/// Creating indexes for every ID field/column (for example, both `user_id` and
/// `guild_id` in `users`) will be a huge performance improvement
///
/// # Example
///
/// Though the example uses PostgresSQL, you can use this library with any SQL
/// or NoSQL backend
///
/// ```ignore
/// use sparkle_cache::backend::Backend;
/// use twilight_model::id::{
/// marker::{GuildMarker, UserMarker},
/// Id,
/// };
///
/// struct MyCache {
/// pub db: sql_library::Database, // Or add a getter method instead of making the field public
/// };
///
/// impl MyCache {
/// fn new() {
/// let db = sql_library::Database::connect("postgresql://localhost/discord");
/// db.query("CREATE UNIQUE INDEX channels_idx ON channels (channel_id);");
/// db.query("CREATE INDEX channels_guild_id_idx ON channels (guild_id);");
/// }
/// }
///
/// impl Backend for MyCache {
/// type Error = sqlx::Error;
///
/// async fn upsert_channel(&self, channel: CachedChannel) -> Result<(), Self::Error> {
/// sqlx::query!(
/// channel.id,
/// // Other fields here
/// "INSERT INTO channels (id, ...) VALUES ($1, ...)"
/// ).exec(&self.db)?;
/// Ok(())
/// }
/// // Implement other methods similarly
/// }
///
/// impl Cache for MyCache {
/// // Implement the methods here, usually using getter queries
/// }
/// ```
#[async_trait]
pub trait Backend {
/// The error type the backend returns, for example `sqlx::Error`
type Error: Error + Send + Sync + 'static;
/// Set or replace the current user information of the bot
async fn set_current_user(&self, current_user: CurrentUser) -> Result<(), Self::Error>;
/// Add or replace a channel in the cache
async fn upsert_channel(&self, channel: CachedChannel) -> Result<(), Self::Error>;
/// Remove a channel from the cache
async fn delete_channel(&self, channel_id: Id<ChannelMarker>) -> Result<(), Self::Error>;
/// Remove a guild's channels from the cache
///
/// This should be something like `DELETE FROM channels WHERE guild_id = ?`
async fn delete_guild_channels(&self, guild_id: Id<GuildMarker>) -> Result<(), Self::Error>;
/// Add a permission overwrite to the cache
///
/// None of the fields in this type is unique
async fn upsert_permission_overwrite(
&self,
permission_overwrite: CachedPermissionOverwrite,
) -> Result<(), Self::Error>;
/// Remove a channel's permission overwrites from the cache
///
/// This should be something like `DELETE FROM channel_overwrites WHERE
/// channel_id = ?`
async fn delete_channel_permission_overwrites(
&self,
channel_id: Id<ChannelMarker>,
) -> Result<(), Self::Error>;
/// Add or replace a message in the cache
async fn upsert_message(&self, message: CachedMessage) -> Result<(), Self::Error>;
/// Remove a message from the cache
async fn delete_message(&self, message_id: Id<MessageMarker>) -> Result<(), Self::Error>;
/// Add an embed to the cache
async fn upsert_embed(&self, embed: CachedEmbed) -> Result<(), Self::Error>;
/// Remove an embed from the cache
async fn delete_embed(&self, embed_id: Id<GenericMarker>) -> Result<(), Self::Error>;
/// Add an embed field to the cache
///
/// None of the fields in this type is unique
async fn upsert_embed_field(&self, embed_field: CachedEmbedField) -> Result<(), Self::Error>;
/// Remove an embed's fields from the cache
///
/// This should be something like `DELETE FROM embed_fields WHERE embed_id =
/// ?`
async fn delete_embed_fields(&self, embed_id: Id<GenericMarker>) -> Result<(), Self::Error>;
/// Get embeds of a message by its ID
///
/// This method is used internally in [`super::Cache::embeds`]
async fn select_message_embeds(
&self,
message_id: Id<MessageMarker>,
) -> Result<Vec<CachedEmbed>, Self::Error>;
/// Get fields of an embed by its ID
///
/// This method is used internally in [`super::Cache::embeds`]
async fn select_embed_fields(
&self,
embed_id: Id<GenericMarker>,
) -> Result<Vec<CachedEmbedField>, Self::Error>;
/// Add an attachment to the cache
async fn upsert_attachment(&self, attachment: CachedAttachment) -> Result<(), Self::Error>;
/// Remove a message's attachments from the cache
///
/// This should be something like `DELETE FROM attachments WHERE message_id
/// = ?`
async fn delete_message_attachments(
&self,
message_id: Id<MessageMarker>,
) -> Result<(), Self::Error>;
/// Add a reaction to the cache
///
/// Only the combination of message ID, user ID and emoji is unique, they're
/// not unique on their own
async fn upsert_reaction(&self, reaction: CachedReaction) -> Result<(), Self::Error>;
/// Remove a reaction from the cache
async fn delete_reaction(
&self,
message_id: Id<MessageMarker>,
user_id: Id<UserMarker>,
emoji: String,
) -> Result<(), Self::Error>;
/// Remove a message's reactions of the given emoji from the cache
///
/// This should be something like `DELETE FROM reactions WHERE message_id =
/// ? AND emoji = ?`
async fn delete_message_reactions_by_emoji(
&self,
message_id: Id<MessageMarker>,
emoji: String,
) -> Result<(), Self::Error>;
/// Remove a message's reactions from the cache
///
/// This should be something like `DELETE FROM reactions WHERE message_id =
/// ?`
async fn delete_message_reactions(
&self,
message_id: Id<MessageMarker>,
) -> Result<(), Self::Error>;
/// Add or replace a member in the cache
///
/// Only the combination of guild ID and user ID is unique, they're not
/// unique on their own
async fn upsert_member(&self, member: CachedMember) -> Result<(), Self::Error>;
/// Remove a member from the cache
async fn delete_member(
&self,
user_id: Id<UserMarker>,
guild_id: Id<GuildMarker>,
) -> Result<(), Self::Error>;
/// Remove a guild's members from the cache
///
/// This should be something like `DELETE FROM members WHERE guild_id = ?`
async fn delete_guild_members(&self, guild_id: Id<GuildMarker>) -> Result<(), Self::Error>;
/// Add or replace a presence in the cache
///
/// Only the combination of guild ID and user ID is unique, they're not
/// unique on their own
async fn upsert_presence(&self, presence: CachedPresence) -> Result<(), Self::Error>;
/// Remove a presence from the cache
async fn delete_presence(
&self,
guild_id: Id<GuildMarker>,
user_id: Id<UserMarker>,
) -> Result<(), Self::Error>;
/// Remove a guild's presences from the cache
///
/// This should be something like `DELETE FROM presences WHERE guild_id = ?`
async fn delete_guild_presences(&self, guild_id: Id<GuildMarker>) -> Result<(), Self::Error>;
/// Add an activity to the cache
///
/// None of the fields in this type is unique
async fn upsert_activity(&self, activity: CachedActivity) -> Result<(), Self::Error>;
/// Remove a user's activities from the cache
///
/// This should be something like `DELETE FROM activities WHERE guild_id = ?
/// AND user_id = ?`
async fn delete_user_activities(
&self,
guild_id: Id<GuildMarker>,
user_id: Id<UserMarker>,
) -> Result<(), Self::Error>;
/// Add or replace a guild in the cache
async fn upsert_guild(&self, guild: CachedGuild) -> Result<(), Self::Error>;
/// Remove a channel from the cache
async fn delete_guild(&self, guild_id: Id<GuildMarker>) -> Result<(), Self::Error>;
/// Add a role to the cache
///
/// The role ID is unique only if the role's user ID is `None`
async fn insert_role(&self, role: CachedRole) -> Result<(), Self::Error>;
/// Update roles in the cache
///
/// A separate method is necessary to update roles of users
///
/// When updating roles, make sure not to update the user ID field
///
/// This should be something like `UPDATE roles SET (...) = (...) WHERE id =
/// ?`
async fn update_roles(&self, role: CachedRole) -> Result<(), Self::Error>;
/// Remove a role from the cache
async fn delete_role(&self, role_id: Id<RoleMarker>) -> Result<(), Self::Error>;
/// Remove a guild's roles from the cache
///
/// This should be something like `DELETE FROM roles WHERE guild_id = ?`
async fn delete_guild_roles(&self, guild_id: Id<GuildMarker>) -> Result<(), Self::Error>;
/// Remove a member's roles from the cache
///
/// This should be something like `DELETE FROM roles WHERE guild_id = ? AND
/// user_id = ?`
async fn delete_member_roles(
&self,
guild_id: Id<GuildMarker>,
user_id: Id<UserMarker>,
) -> Result<(), Self::Error>;
/// Add or replace an emoji in the cache
async fn upsert_emoji(&self, emoji: CachedEmoji) -> Result<(), Self::Error>;
/// Remove an emoji from the cache
async fn delete_emoji(&self, emoji_id: Id<EmojiMarker>) -> Result<(), Self::Error>;
/// Remove a guild's emojis from the cache
///
/// This should be something like `DELETE FROM emojis WHERE guild_id = ?`
async fn delete_guild_emojis(&self, guild_id: Id<GuildMarker>) -> Result<(), Self::Error>;
/// Add or replace a sticker in the cache
///
/// The sticker ID is unique only if the sticker's message ID is `None`
///
/// When updating stickers, make sure not to update the message ID field
async fn upsert_sticker(&self, sticker: CachedSticker) -> Result<(), Self::Error>;
/// Remove a message's stickers from the cache
///
/// This should be something like `DELETE FROM stickers WHERE
/// message_id = ?`
async fn delete_message_stickers(
&self,
message_id: Id<MessageMarker>,
) -> Result<(), Self::Error>;
/// Remove a guild's stickers from the cache
///
/// This should be something like `DELETE FROM stickers WHERE guild_id = ?
/// AND message_id IS NULL`
async fn delete_guild_stickers(&self, guild_id: Id<GuildMarker>) -> Result<(), Self::Error>;
/// Add or replace a stage instance in the cache
async fn upsert_stage_instance(&self, stage: StageInstance) -> Result<(), Self::Error>;
/// Remove a stage instance from the cache
async fn delete_stage_instance(&self, stage_id: Id<StageMarker>) -> Result<(), Self::Error>;
/// Remove a guild's stage instance from the cache
///
/// This should be something like `DELETE FROM stage_instances WHERE
/// guild_id = ?`
async fn delete_guild_stage_instances(
&self,
guild_id: Id<GuildMarker>,
) -> Result<(), Self::Error>;
}