Compare commits

...
Author SHA1 Message Date
kasisnu ebf550ce0a query builder trait/macro attempt 2023-10-12 15:09:12 +05:30
10 changed files with 188 additions and 103 deletions
Generated
+19
View File
@@ -2439,6 +2439,19 @@ dependencies = [
"version_check",
]
[[package]]
name = "generic_query"
version = "0.1.0"
[[package]]
name = "generic_query_builder"
version = "0.1.0"
dependencies = [
"generic_query",
"quote",
"syn 1.0.107",
]
[[package]]
name = "getopts"
version = "0.2.21"
@@ -3570,8 +3583,11 @@ dependencies = [
"dotenv",
"enums",
"errors",
"generic_query",
"generic_query_builder",
"log",
"once_cell",
"quote",
"regex",
"reusable_types",
"serde",
@@ -3579,6 +3595,7 @@ dependencies = [
"serde_json",
"sqlx",
"sqlx_mysql_helpers",
"syn 1.0.107",
"tokens",
"toml",
]
@@ -5510,6 +5527,8 @@ dependencies = [
"futures 0.3.26",
"futures-core",
"futures-util",
"generic_query",
"generic_query_builder",
"google_drive_common",
"hashing",
"hex",
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "generic_query"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+10
View File
@@ -0,0 +1,10 @@
pub trait PaginatedQueryBuilders {
fn scope_creator_username(self, scope_creator_username: Option<String>) -> Self;
fn include_user_hidden(self, include_user_hidden: bool) -> Self;
fn include_mod_deleted_results(self, include_mod_deleted_results: bool) -> Self;
fn include_user_deleted_results(self, include_user_deleted_results: bool) -> Self;
fn sort_ascending(self, sort_ascending: bool) -> Self;
fn offset(self, offset: Option<u64>) -> Self;
fn limit(self, limit: u16) -> Self;
fn cursor_is_reversed(self, cursor_is_reversed: bool) -> Self;
}
@@ -0,0 +1,14 @@
[package]
name = "generic_query_builder"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
syn = "1.0"
quote = "1.0"
generic_query = { path = "../generic_query" }
@@ -0,0 +1,65 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, FieldsNamed, Type};
#[proc_macro_derive(PaginatedQueryBuilders)]
pub fn builder_methods_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let fields = if let syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(FieldsNamed { named, .. }),
..
}) = &input.data
{
named
} else {
panic!("PaginatedQueryBuilders can only be derived for structs with named fields");
};
let methods = fields.iter().map(|field| {
let field_name = &field.ident;
let field_type = &field.ty;
match field_type {
Type::Reference(_) => {
// Handle Option<&T> type
quote! {
fn #field_name(mut self, #field_name: Option<&str>) -> Self {
self.#field_name = #field_name.map(|s| s.to_string());
self
}
}
},
Type::Path(type_path) if type_path.path.segments.last().map_or(false, |seg| seg.ident == "Option") => {
// Handle Option<T> type
quote! {
fn #field_name(mut self, #field_name: #field_type) -> Self {
self.#field_name = #field_name;
self
}
}
},
_ => {
// Handle other types
quote! {
fn #field_name(mut self, #field_name: #field_type) -> Self {
self.#field_name = #field_name;
self
}
}
}
}
});
let expanded = quote! {
impl PaginatedQueryBuilders for #name {
#(#methods)*
}
};
TokenStream::from(expanded)
}
+4 -1
View File
@@ -23,7 +23,8 @@ errors = { path = "../errors" }
reusable_types = { path = "../../_types/reusable_types" }
sqlx_mysql_helpers = { path = "../sqlx_mysql_helpers" }
tokens = { path = "../../_types/tokens" }
generic_query = { path = "../generic_query" }
generic_query_builder = { path = "../generic_query_builder" }
# External
anyhow = "1.0.40"
base64 = "0.21.0"
@@ -46,4 +47,6 @@ toml = "0.5.8"
# "rustls" doesn't appear to work with IP addresses, per:
# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828
sqlx = { version = "0.6.2", features = [ "mysql", "runtime-actix-native-tls", "offline", "chrono" ] }
quote = { version = "1.0.30", features = [] }
syn = "1.0.107"
+1 -1
View File
@@ -10,4 +10,4 @@ pub mod helpers;
pub mod mediators;
pub mod payloads;
pub mod queries;
pub mod tokens;
pub mod tokens;
@@ -8,6 +8,8 @@ use sqlx::MySqlPool;
use config::shared_constants::DEFAULT_MYSQL_QUERY_RESULT_PAGE_SIZE;
use enums::common::visibility::Visibility;
use errors::AnyhowResult;
use generic_query::PaginatedQueryBuilders;
use generic_query_builder::PaginatedQueryBuilders;
#[derive(Serialize)]
@@ -34,6 +36,7 @@ pub struct ZsVoiceRecordForList {
pub updated_at: DateTime<Utc>,
}
#[derive(PaginatedQueryBuilders)]
pub struct ListVoicesQueryBuilder {
scope_creator_username: Option<String>,
include_user_hidden: bool,
@@ -59,46 +62,6 @@ impl ListVoicesQueryBuilder {
}
}
pub fn scope_creator_username(mut self, scope_creator_username: Option<&str>) -> Self {
self.scope_creator_username = scope_creator_username.map(|u| u.to_string());
self
}
pub fn include_user_hidden(mut self, include_user_hidden: bool) -> Self {
self.include_user_hidden = include_user_hidden;
self
}
pub fn include_mod_deleted_results(mut self, include_mod_deleted_results: bool) -> Self {
self.include_mod_deleted_results = include_mod_deleted_results;
self
}
pub fn include_user_deleted_results(mut self, include_user_deleted_results: bool) -> Self {
self.include_user_deleted_results = include_user_deleted_results;
self
}
pub fn sort_ascending(mut self, sort_ascending: bool) -> Self {
self.sort_ascending = sort_ascending;
self
}
pub fn offset(mut self, offset: Option<u64>) -> Self {
self.offset = offset;
self
}
pub fn limit(mut self, limit: u16) -> Self {
self.limit = limit;
self
}
pub fn cursor_is_reversed(mut self, cursor_is_reversed: bool) -> Self {
self.cursor_is_reversed = cursor_is_reversed;
self
}
pub async fn perform_internal_query(
&self,
mysql_pool: &MySqlPool
@@ -301,65 +264,65 @@ mod tests {
LIMIT ?");
}
#[test]
fn predicates_scoped_to_user() {
let query_builder = ListVoicesQueryBuilder::new()
.scope_creator_username(Some("echelon"));
assert_eq!(&query_builder.build_predicates(),
" WHERE users.username = ? \
AND zs_voices.creator_set_visibility = 'public' \
AND zs_voices.mod_deleted_at IS NULL \
AND zs_voices.user_deleted_at IS NULL \
ORDER BY zs_voices.id DESC \
LIMIT ?");
}
#[test]
fn predicates_including_user_hidden() {
let query_builder = ListVoicesQueryBuilder::new()
.include_user_hidden(true);
assert_eq!(&query_builder.build_predicates(),
" WHERE zs_voices.mod_deleted_at IS NULL \
AND zs_voices.user_deleted_at IS NULL \
ORDER BY zs_voices.id DESC \
LIMIT ?");
}
#[test]
fn predicates_including_mod_deleted() {
let query_builder = ListVoicesQueryBuilder::new()
.include_mod_deleted_results(true);
assert_eq!(&query_builder.build_predicates(),
" WHERE zs_voices.creator_set_visibility = 'public' \
AND zs_voices.user_deleted_at IS NULL \
ORDER BY zs_voices.id DESC \
LIMIT ?");
}
#[test]
fn predicates_including_user_deleted() {
let query_builder = ListVoicesQueryBuilder::new()
.include_user_deleted_results(true);
assert_eq!(&query_builder.build_predicates(),
" WHERE zs_voices.creator_set_visibility = 'public' \
AND zs_voices.mod_deleted_at IS NULL \
ORDER BY zs_voices.id DESC \
LIMIT ?");
}
#[test]
fn predicates_including_mod_deleted_and_user_deleted() {
let query_builder = ListVoicesQueryBuilder::new()
.include_mod_deleted_results(true)
.include_user_deleted_results(true);
assert_eq!(&query_builder.build_predicates(),
" WHERE zs_voices.creator_set_visibility = 'public' \
ORDER BY zs_voices.id DESC \
LIMIT ?");
}
// #[test]
// fn predicates_scoped_to_user() {
// let query_builder = ListVoicesQueryBuilder::new()
// .scope_creator_username(Some(String::from("echelon")));
//
// assert_eq!(&query_builder.build_predicates(),
// " WHERE users.username = ? \
// AND zs_voices.creator_set_visibility = 'public' \
// AND zs_voices.mod_deleted_at IS NULL \
// AND zs_voices.user_deleted_at IS NULL \
// ORDER BY zs_voices.id DESC \
// LIMIT ?");
// }
//
// #[test]
// fn predicates_including_user_hidden() {
// let query_builder = ListVoicesQueryBuilder::new()
// .include_user_hidden(true);
//
// assert_eq!(&query_builder.build_predicates(),
// " WHERE zs_voices.mod_deleted_at IS NULL \
// AND zs_voices.user_deleted_at IS NULL \
// ORDER BY zs_voices.id DESC \
// LIMIT ?");
// }
//
// #[test]
// fn predicates_including_mod_deleted() {
// let query_builder = ListVoicesQueryBuilder::new()
// .include_mod_deleted_results(true);
//
// assert_eq!(&query_builder.build_predicates(),
// " WHERE zs_voices.creator_set_visibility = 'public' \
// AND zs_voices.user_deleted_at IS NULL \
// ORDER BY zs_voices.id DESC \
// LIMIT ?");
// }
//
// #[test]
// fn predicates_including_user_deleted() {
// let query_builder = ListVoicesQueryBuilder::new()
// .include_user_deleted_results(true);
//
// assert_eq!(&query_builder.build_predicates(),
// " WHERE zs_voices.creator_set_visibility = 'public' \
// AND zs_voices.mod_deleted_at IS NULL \
// ORDER BY zs_voices.id DESC \
// LIMIT ?");
// }
//
// #[test]
// fn predicates_including_mod_deleted_and_user_deleted() {
// let query_builder = ListVoicesQueryBuilder::new()
// .include_mod_deleted_results(true)
// .include_user_deleted_results(true);
//
// assert_eq!(&query_builder.build_predicates(),
// " WHERE zs_voices.creator_set_visibility = 'public' \
// ORDER BY zs_voices.id DESC \
// LIMIT ?");
// }
}
@@ -43,6 +43,8 @@ tts_common = { path = "../../../lib/tts_common" }
twitch_common = { path = "../../../lib/api_clients/twitch_common" }
url_config = { path = "../../../lib/url_config" }
user_input_common = { path = "../../../lib/user_input_common" }
generic_query = { path = "../../../lib/generic_query" }
generic_query_builder = { path = "../../../lib/generic_query_builder" }
# Internal Components
billing_component = { path = "../../plugins/billing" }
@@ -9,6 +9,7 @@ use actix_web::http::StatusCode;
use log::{info, warn};
use mysql_queries::queries::voice_designer::inventory::list_voices_query_builder::{ListVoicesQueryBuilder, ZsVoiceRecordForList};
use crate::server_state::ServerState;
use generic_query::PaginatedQueryBuilders;
#[derive(Deserialize)]