use axum::http::request::Parts;
use crate::core::{Filter, ModelSchema, SelectQuery, SqlValue, WhereExpr};
pub type QuerySetHookFn = fn(&Parts) -> Vec<Filter>;
pub struct AdminQuerySetHook {
pub table: &'static str,
pub hook: QuerySetHookFn,
}
inventory::collect!(AdminQuerySetHook);
#[must_use]
pub fn for_table(table: &str) -> Vec<&'static AdminQuerySetHook> {
inventory::iter::<AdminQuerySetHook>
.into_iter()
.filter(|h| h.table == table)
.collect()
}
pub(crate) struct RowScope(Vec<Filter>);
impl RowScope {
pub(crate) fn of(model: &'static ModelSchema, parts: &Parts) -> Self {
Self::with_liveness(model, parts, false)
}
pub(crate) fn trashed(model: &'static ModelSchema, parts: &Parts) -> Self {
Self::with_liveness(model, parts, true)
}
fn with_liveness(model: &'static ModelSchema, parts: &Parts, trashed: bool) -> Self {
Self(
for_table(model.table)
.iter()
.flat_map(|h| (h.hook)(parts))
.chain(crate::soft_delete::liveness_predicate(model, trashed))
.collect(),
)
}
pub(crate) fn filters(&self) -> &[Filter] {
&self.0
}
pub(crate) fn constrain(&self, mut where_clause: WhereExpr) -> WhereExpr {
for f in &self.0 {
where_clause.push_and(WhereExpr::Predicate(f.clone()));
}
where_clause
}
pub(crate) fn by_pk(
&self,
model: &'static ModelSchema,
pk_column: &'static str,
pk: SqlValue,
) -> SelectQuery {
let q = SelectQuery::by_pk(model, pk_column, pk);
SelectQuery {
where_clause: self.constrain(q.where_clause),
limit: None,
..q
}
}
pub(crate) fn by_pk_in(
&self,
model: &'static ModelSchema,
pk_column: &'static str,
pks: Vec<SqlValue>,
) -> SelectQuery {
let q = SelectQuery::by_pk_in(model, pk_column, pks);
SelectQuery {
where_clause: self.constrain(q.where_clause),
..q
}
}
}
#[macro_export]
macro_rules! register_admin_queryset {
($table:expr, $hook:expr $(,)?) => {
$crate::inventory::submit! {
$crate::admin::queryset_hooks::AdminQuerySetHook {
table: $table,
hook: {
const _HOOK: $crate::admin::queryset_hooks::QuerySetHookFn = $hook;
_HOOK
},
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn for_table_returns_empty_when_no_registrations() {
let v = for_table("nonexistent_table");
assert!(v.is_empty());
}
#[test]
fn fn_pointer_coercion_smoke_test() {
fn _h(_p: &Parts) -> Vec<Filter> {
Vec::new()
}
let _f: QuerySetHookFn = _h;
}
}