An idiomatic pattern for Condition / Filters / Query Builders for API?
⚓ Rust 📅 2025-12-04 👤 surdeus 👁️ 1I have the below rust code:
pub struct IdFilter {
pub eq: Option<uuid::Uuid>,
pub contained_in: Option<Vec<uuid::Uuid>>,
}
pub struct NullableIdFilter {
pub eq: Option<uuid::Uuid>,
pub contained_in: Option<Vec<uuid::Uuid>>,
pub is_null: Option<bool>,
}
pub struct NullableDateTimeFilter {
pub gt: Option<DateTime<Utc>>,
pub gte: Option<DateTime<Utc>>,
pub lt: Option<DateTime<Utc>>,
pub lte: Option<DateTime<Utc>>,
pub is_null: Option<bool>,
}
pub struct NullableStringFilter {
pub eq: Option<String>,
pub similar_to: Option<String>,
pub starts_with: Option<String>,
pub ends_with: Option<String>,
pub contained_in: Option<Vec<String>>,
pub is_null: Option<bool>,
}
pub struct PlayerQueryInput {
pub filters: Option<Box<PlayerFilters>>,
pub paging: Option<Paging>,
pub sort_by: Option<Vec<PlayerSortBy>>,
}
pub struct PlayerFilters {
pub and: Vec<Self>,
pub or: Vec<Self>,
pub not: Option<Box<Self>>,
pub id: Option<IdFilter>,
pub created_time: Option<DateTimeFilter>,
pub creator: Option<NullableIdFilter>,
pub modified_time: Option<NullableDateTimeFilter>,
pub modifier: Option<NullableIdFilter>,
pub status: Option<PlayerStatus>,
pub jersey_number: Option<NumberFilter<i64>>,
pub start_date: Option<DateTimeFilter>,
pub end_date: Option<DateTimeFilter>,
pub is_part_time: Option<BoolFilter>,
pub weekend_game: Option<BoolFilter>,
pub team_reference: Option<NullableStringFilter>,
pub salary: Option<DecimalFilter>,
pub bonus: Option<DecimalFilter>,
pub tax: Option<DecimalFilter>,
pub team_id: Option<IdFilter>,
pub contract_id: Option<IdFilter>,
pub position_id: Option<IdFilter>,
pub equipment_id: Option<NullableIdFilter>,
pub vehicle_id: Option<NullableIdFilter>,
pub tool_id: Option<NullableIdFilter>,
pub permit_id: Option<NullableIdFilter>,
pub material_id: Option<NullableIdFilter>,
pub extra_feature_id: Option<NullableIdFilter>,
pub invoice_id: Option<NullableIdFilter>,
pub transfer_list_id: Option<NullableIdFilter>,
// Here all the "with_" and "query_" fields: these are all Player's related objects (entities)
pub with_equipment: Option<Box<EquipmentFilters>>,
pub with_permit: Option<Box<PermitFilters>>,
pub with_vehicle: Option<Box<VehicleFilters>>,
pub with_container: Option<Box<ContainerFilters>>,
pub with_transfer_list: Option<Box<PlayerTransferListFilters>>,
pub with_material: Option<Box<MaterialFilters>>,
pub with_extra_feature: Option<Box<ExtraFeatureFilters>>,
pub with_position: Option<Box<PositionFilters>>,
pub with_expenses: Option<Box<PlayerExpenseQueryInput>>,
pub query_expenses_with: Option<Box<PlayerExpenseQueryInput>>,
pub with_routes: Option<Box<PlayerRouteQueryInput>>,
pub query_routes_with: Option<Box<PlayerRouteQueryInput>>,
pub with_team: Option<Box<TeamFilters>>,
pub with_documents: Option<Box<PlayerDocumentQueryInput>>,
pub with_letters: Option<Box<PlayerLetterQueryInput>>,
pub query_letters_with: Option<Box<PlayerLetterQueryInput>>,
pub with_bonuses: Option<Box<PlayerBonusQueryInput>>,
pub query_bonuses_with: Option<Box<PlayerBonusQueryInput>>,
pub with_free_items: Option<Box<PlayerFreeItemQueryInput>>,
pub query_free_items_with: Option<Box<PlayerFreeItemQueryInput>>,
pub with_skill: Option<Box<PlayerSkillQueryInput>>,
pub query_skill_with: Option<Box<PlayerSkillQueryInput>>,
pub with_invoice: Option<Box<InvoiceFilters>>,
pub with_contract: Option<Box<ContractFilters>>,
// this is for Player's related objects "skills", not a native field (is not a bool on the Player struct)
pub has_skill: Option<bool>,
}
As you can imagine this can be a very big struct: this for player is just an example, real ones can be huge: a lot of KB!
So far this pattern worked well because using graphql with async-graphql the generated graphql schema was fine and I could use a very convenient and typed query like this:
const filters = {
status: PlayerStatus.SOME,
team_reference: { eq: "some" },
and: [{filters}, {filters}],
or: [{filters}, {filters}],
// and even:
and: [
{
is_part_time: { is_null: true },
or: [{filters}]
}
]
}
as you can see is very useful.
But now for a new project I'm dropping graphql and I would like to find another way of creating filters, something less huge. I'm using classic REST APIs. But this is not related to specific technique I use, I think.
A StackOverflow's user suggested this:
use chrono::{DateTime, Utc};
enum SingleFilter {
Id (u32),
CreatedAt (DateTime<Utc>),
CreatedBy (u32),
Position (i16),
}
fn main() {
let mut player_filters = vec![];
player_filters.push (SingleFilter::CreatedBy(42));
println!("{player_filters:?}")
}
But what about AND, OR, NOT and other aggregation operators?
What about fields like with_invoice or query_skill_with?
I need to serialize and deserialize automatically from frontend.
What do you think about?
Is there an idomatic way of doing this?
1 post - 1 participant
🏷️ Rust_feed