Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discord Frontend: Migrate to New Queries #2509

Merged
merged 3 commits into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions discord-frontend/Cargo.lock

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

Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,9 @@ use hartex_discord_utils::interaction::embed_response;
use hartex_discord_utils::interaction::ephemeral_error_response;
use hartex_discord_utils::localizable::Localizable;
use hartex_discord_utils::markdown::MarkdownStyle;
use hartex_discord_utils::postgres::PostgresErrorExt;
use hartex_localization_core::Localizer;
use miette::IntoDiagnostic;
use regex::Regex;
use tokio_postgres::error::SqlState;

lazy_static::lazy_static! {
/// The regex for looking for a Discord emoji in the command input.
Expand Down Expand Up @@ -109,7 +107,7 @@ pub async fn execute(
let result = CachedEmojiRepository.get(emoji_id).await;
let emoji = match result {
Ok(emoji) => emoji,
Err(CacheError::Postgres(postgres_error)) if postgres_error.is(SqlState::NO_DATA) => {
Err(CacheError::Database(_)) /*if postgres_error.is(SqlState::NO_DATA)*/ => {
interaction_client
.create_response(
interaction.id,
Expand Down
2 changes: 2 additions & 0 deletions discord-frontend/hartex-discord-entitycache-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ rust-version = "1.86.0"
hartex_discord_core = { path = "../hartex-discord-core", features = ["discord-model"] }
hartex_discord_entitycache_macros = { path = "../hartex-discord-entitycache-macros", optional = true, default-features = false, features = ["discord_model_git"] }

hartex_database_queries = { path = "../../database/hartex-database-queries" }

bb8 = "0.9.0"
tokio-postgres = "0.7.12"

Expand Down
21 changes: 6 additions & 15 deletions discord-frontend/hartex-discord-entitycache-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,41 +28,32 @@ use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;

use bb8::RunError;
use tokio_postgres::Error as PostgresError;
use hartex_database_queries::result::Error as DatabaseError;

/// A cache error..
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub enum CacheError {
Bb8(RunError<PostgresError>),
/// Error related to environment variables.
Env(VarError),
/// A postgres error occurred.
Postgres(PostgresError),
Database(DatabaseError),
}

impl Display for CacheError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Bb8(error) => writeln!(f, "bb8 postgres error: {error}"),
Self::Env(error) => writeln!(f, "env error: {error}"),
Self::Postgres(error) => writeln!(f, "postgres error: {error}"),
Self::Database(error) => writeln!(f, "database error: {error}"),
}
}
}

impl Error for CacheError {}

impl From<RunError<PostgresError>> for CacheError {
fn from(error: RunError<PostgresError>) -> Self {
Self::Bb8(error)
}
}

impl From<PostgresError> for CacheError {
fn from(error: PostgresError) -> Self {
Self::Postgres(error)
impl From<DatabaseError> for CacheError {
fn from(error: DatabaseError) -> Self {
Self::Database(error)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use hartex_discord_entitycache_core::entity;
/// An emoji entity.
#[entity(
from = "twilight_model::guild::Emoji",
assume = ["CachedEmojiSelectByGuildId", "CachedEmojiSelectById"],
assume = ["NightlyCachedEmojis"],
id = ["id"],
include = [
"animated",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use hartex_discord_entitycache_core::entity;
#[allow(clippy::module_name_repetitions)]
#[entity(
from = "twilight_model::guild::Guild",
assume = ["CachedGuildSelectById"],
assume = ["NightlyCachedGuilds"],
id = ["id"],
include = [
"default_message_notifications",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use hartex_discord_entitycache_core::entity;
#[allow(clippy::module_name_repetitions)]
#[entity(
from = "twilight_model::guild::Member",
assume = ["CachedMemberSelectByGuildId", "CachedMemberSelectByUserIdAndGuildId"],
assume = ["NightlyCachedMembers"],
id = ["guild_id", "user_id"],
include = ["flags", "joined_at", "nick", "roles"],
extra = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use hartex_discord_entitycache_core::entity;
#[allow(clippy::module_name_repetitions)]
#[entity(
from = "twilight_model::guild::Role",
assume = ["CachedRoleSelectByGuildId", "CachedRoleSelectByIdAndGuildId"],
assume = ["NightlyCachedRoles"],
id = ["guild_id", "id"],
include = ["color", "flags", "hoist", "icon", "managed", "mentionable", "position"],
extra = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use hartex_discord_entitycache_core::entity;
#[allow(clippy::module_name_repetitions)]
#[entity(
from = "twilight_model::user::User",
assume = ["CachedUserSelectById"],
assume = ["NightlyCachedUsers"],
id = ["id"],
include = [
"avatar",
Expand Down
66 changes: 36 additions & 30 deletions discord-frontend/hartex-discord-entitycache-macros/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,26 +368,23 @@ pub fn implement_entity(input: &EntityMacroInput, item_struct: &ItemStruct) -> O
};
let ret_type = syn::parse_str::<Type>(hashmap.get(&*entity).unwrap()).unwrap();

let query_function_name = make_query_function_name(first, &element.as_value.value());
let (query_module_name, query_struct_name) =
make_query_function_name(first, &element.as_value.value());
// FIXME: bad assumption of always calling .to_string() here (mostly just that should suffice, but...)
let mut full_query_function_call = quote! {
let data = hartex_database_queries::discord_frontend::queries::#query_function_name::#query_function_name().bind(client, &#param_name.to_string())
let data = hartex_database_queries::queries::discord_frontend::#query_module_name::#query_struct_name::bind(#param_name.to_string()).executor().await?
};

let function = match &*element.unique_or_multiple.to_string() {
"multiple" => {
let ident = Ident::new(&pluralize(first, 2, false), Span::call_site());

full_query_function_call.append_all(quote! {
.all().await?;
.many().await?;
});

quote! {
pub async fn #ident(&self, #param_decl) -> hartex_discord_entitycache_core::error::CacheResult<Vec<#ret_type>> {
let pinned = std::pin::Pin::static_ref(&hartex_discord_utils::DATABASE_POOL).await;
let pooled = pinned.get().await?;
let client = pooled.client();

#full_query_function_call

Ok(data.into_iter().map(|thing| #ret_type::from(thing)).collect())
Expand All @@ -403,10 +400,6 @@ pub fn implement_entity(input: &EntityMacroInput, item_struct: &ItemStruct) -> O

quote! {
pub async fn #ident(&self, #param_decl) -> hartex_discord_entitycache_core::error::CacheResult<#ret_type> {
let pinned = std::pin::Pin::static_ref(&hartex_discord_utils::DATABASE_POOL).await;
let pooled = pinned.get().await?;
let client = pooled.client();

#full_query_function_call

Ok(#ret_type::from(data))
Expand All @@ -423,10 +416,9 @@ pub fn implement_entity(input: &EntityMacroInput, item_struct: &ItemStruct) -> O
if input.extra_fields_array.elements.is_empty() {
let extra = assumed_extra_impls
.map(|str| {
let ident_snake = Ident::new(&str.to_case(Case::Snake), Span::call_site());
let ident_pascal = Ident::new(&str.to_case(Case::Pascal), Span::call_site());
let full_ident =
quote! {hartex_database_queries::discord_frontend::queries::#ident_snake::#ident_pascal};
quote! {hartex_database_queries::tables::discord_frontend::#ident_pascal};

quote! {
impl From<#full_ident> for #item_struct_name {
Expand Down Expand Up @@ -484,10 +476,9 @@ pub fn implement_entity(input: &EntityMacroInput, item_struct: &ItemStruct) -> O
.multiunzip();
let extra = assumed_extra_impls
.map(|str| {
let ident_snake = Ident::new(&str.to_case(Case::Snake), Span::call_site());
let ident_pascal = Ident::new(&str.to_case(Case::Pascal), Span::call_site());
let full_ident =
quote! {hartex_database_queries::discord_frontend::queries::#ident_snake::#ident_pascal};
quote! {hartex_database_queries::tables::discord_frontend::#ident_pascal};

quote! {
impl From<#full_ident> for #item_struct_name {
Expand Down Expand Up @@ -568,7 +559,7 @@ fn make_field_decl_and_assignments(
return (
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: model.#field_name.parse().unwrap()},
quote! {#field_name: model.#field_name().parse().unwrap()},
);
}

Expand All @@ -582,85 +573,100 @@ fn make_field_decl_and_assignments(
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: #field_type::from(model.#field_name as u8)},
quote! {#field_name: #field_type::from(model.#field_name() as u8)},
)
} else if field_type.is("Id") {
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: std::str::FromStr::from_str(&model.#field_name).unwrap()},
quote! {#field_name: std::str::FromStr::from_str(model.#field_name()).unwrap()},
)
} else if field_type.is("MemberFlags") {
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: twilight_model::guild::MemberFlags::from_bits(model.#field_name as u64).unwrap()},
quote! {#field_name: twilight_model::guild::MemberFlags::from_bits(model.#field_name() as u64).unwrap()},
)
} else if field_type.is("RoleFlags") {
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: twilight_model::guild::RoleFlags::from_bits(model.#field_name as u64).unwrap()},
quote! {#field_name: twilight_model::guild::RoleFlags::from_bits(model.#field_name() as u64).unwrap()},
)
} else if field_type.is_option_of("ImageHash") {
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: model.#field_name.as_deref().map(|str| std::str::FromStr::from_str(str).unwrap())},
quote! {#field_name: model.#field_name().map(|str| std::str::FromStr::from_str(str).unwrap())},
)
} else if field_type.is_option_of("Timestamp") {
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: model.#field_name.map(|timestamp| twilight_model::util::Timestamp::from_secs(timestamp.unix_timestamp()).unwrap())},
quote! {#field_name: model.#field_name().map(|timestamp| twilight_model::util::Timestamp::from_secs(timestamp.timestamp()).unwrap())},
)
} else if field_type.is_option_of("String") {
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: model.#field_name().map(String::from)},
)
} else if field_type.is("i64") {
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: model.#field_name as i64},
quote! {#field_name: model.#field_name() as i64},
)
} else if field_type.is("u32") {
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: model.#field_name as u32},
quote! {#field_name: model.#field_name() as u32},
)
} else if field_type.is("String") {
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: model.#field_name().to_string()},
)
} else if field_type.is_option_of("u64") {
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: model.#field_name.map(|i| i as u64)},
quote! {#field_name: model.#field_name().map(|i| i as u64)},
)
} else if field_type.is_vec_of("GuildFeature") {
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: model.#field_name.iter().cloned().map(From::from).collect()},
quote! {#field_name: model.#field_name().iter().cloned().map(From::from).collect()},
)
} else if field_type.is_vec_of("Id") {
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: model.#field_name.iter().map(|str| std::str::FromStr::from_str(str).unwrap()).collect()},
quote! {#field_name: model.#field_name().iter().map(|str| std::str::FromStr::from_str(str).unwrap()).collect()},
)
} else {
(
quote! {pub #field_name: #field_type},
quote! {#field_name: model.#field_name},
quote! {#field_name: model.#field_name},
quote! {#field_name: model.#field_name()},
)
}
}

// FIXME: may need to generalize for multiple fields
/// Construct an identifier containing the database query function name.
fn make_query_function_name(target_entity: &str, by_field: &str) -> Ident {
fn make_query_function_name(target_entity: &str, by_field: &str) -> (Ident, Ident) {
let name = format!(
"cached_{}_select_by_{}",
target_entity.to_lowercase(),
by_field.to_lowercase()
);

Ident::new(&name, Span::call_site())
(
Ident::new(&name, Span::call_site()),
Ident::new(name.to_case(Case::Pascal).as_str(), Span::call_site()),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ hartex_discord_entitycache_entities = { path = "../hartex-discord-entitycache-en

hartex_discord_utils = { path = "../../rust-utilities/hartex-discord-utils" }

chrono = "0.4.39"
serde_scan = "0.4.1"
time = "0.3.37"
tokio-postgres = "0.7.12"
Expand Down
Loading
Loading