use cratestack_core::CratestackContext;
use cratestack_policy::{context_has_role, context_in_tenant};
use crate::{PolicyLiteral, ReadPredicate, sqlx};
use super::policy_relation::push_relation_policy_query;
use super::values::{auth_value_to_sql, push_bind_value, value_matches_auth_literal};
pub(super) fn push_policy_predicate(
query: &mut sqlx::QueryBuilder<sqlx::Postgres>,
predicate: ReadPredicate,
ctx: &CratestackContext,
) {
match predicate {
ReadPredicate::AuthNotNull => {
query.push(if ctx.is_authenticated() {
"TRUE"
} else {
"FALSE"
});
}
ReadPredicate::AuthIsNull => {
query.push(if ctx.is_authenticated() {
"FALSE"
} else {
"TRUE"
});
}
ReadPredicate::AuthIsSystem => {
query.push(if ctx.is_system() { "TRUE" } else { "FALSE" });
}
ReadPredicate::HasRole { role } => {
query.push(if context_has_role(ctx, role) {
"TRUE"
} else {
"FALSE"
});
}
ReadPredicate::InTenant { tenant_id } => {
query.push(if context_in_tenant(ctx, tenant_id) {
"TRUE"
} else {
"FALSE"
});
}
ReadPredicate::AuthFieldEqLiteral { auth_field, value } => {
query.push(
if ctx
.auth_field(auth_field)
.is_some_and(|candidate| value_matches_auth_literal(candidate, value))
{
"TRUE"
} else {
"FALSE"
},
);
}
ReadPredicate::AuthFieldNeLiteral { auth_field, value } => {
query.push(
if ctx
.auth_field(auth_field)
.is_some_and(|candidate| !value_matches_auth_literal(candidate, value))
{
"TRUE"
} else {
"FALSE"
},
);
}
ReadPredicate::FieldIsTrue { column } => {
query.push(column).push(" = TRUE");
}
ReadPredicate::FieldEqLiteral { column, value } => {
query.push(column).push(" = ");
push_policy_literal(query, value);
}
ReadPredicate::FieldNeLiteral { column, value } => {
query.push(column).push(" != ");
push_policy_literal(query, value);
}
ReadPredicate::FieldInLiterals { column, values } => {
push_in_list(query, column, values, false);
}
ReadPredicate::FieldNotInLiterals { column, values } => {
push_in_list(query, column, values, true);
}
ReadPredicate::FieldEqAuth { column, auth_field } => {
if let Some(value) = auth_value_to_sql(ctx, auth_field) {
query.push(column).push(" = ");
push_bind_value(query, &value);
} else {
query.push("FALSE");
}
}
ReadPredicate::FieldNeAuth { column, auth_field } => {
if let Some(value) = auth_value_to_sql(ctx, auth_field) {
query.push(column).push(" != ");
push_bind_value(query, &value);
} else {
query.push("FALSE");
}
}
ReadPredicate::Relation {
quantifier,
parent_table,
parent_column,
related_table,
related_column,
expr,
} => push_relation_policy_query(
query,
quantifier,
parent_table,
parent_column,
related_table,
related_column,
expr,
ctx,
),
}
}
fn push_in_list(
query: &mut sqlx::QueryBuilder<sqlx::Postgres>,
column: &str,
values: &[PolicyLiteral],
negate: bool,
) {
if values.is_empty() {
query.push(if negate { "TRUE" } else { "FALSE" });
return;
}
query.push(column);
query.push(if negate { " NOT IN (" } else { " IN (" });
for (slot, literal) in values.iter().enumerate() {
if slot > 0 {
query.push(", ");
}
push_policy_literal(query, *literal);
}
query.push(")");
}
fn push_policy_literal(query: &mut sqlx::QueryBuilder<sqlx::Postgres>, literal: PolicyLiteral) {
match literal {
PolicyLiteral::Bool(value) => query.push_bind(value),
PolicyLiteral::Int(value) => query.push_bind(value),
PolicyLiteral::String(value) => query.push_bind(value.to_owned()),
};
}