use cratestack_core::{CratestackContext, CratestackError};
use super::push_action_policy_query;
use crate::{ModelDescriptor, cratestack_error_from_sqlx, sqlx};
pub(crate) async fn probe_current_version<'e, E, M, PK>(
executor: E,
descriptor: &'static ModelDescriptor<M, PK>,
id: PK,
version_col: &'static str,
ctx: &CratestackContext,
) -> Result<Option<i64>, CratestackError>
where
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
{
let mut query = sqlx::QueryBuilder::<sqlx::Postgres>::new("SELECT ");
query.push(version_col);
query.push(" FROM ").push(descriptor.table_name);
query
.push(" WHERE ")
.push(descriptor.primary_key)
.push(" = ");
query.push_bind(id);
query.push(" AND ");
push_action_policy_query(
&mut query,
descriptor.read_allow_policies,
descriptor.read_deny_policies,
ctx,
);
let row: Option<(i64,)> = query
.build_query_as::<(i64,)>()
.fetch_optional(executor)
.await
.map_err(cratestack_error_from_sqlx)?;
Ok(row.map(|(v,)| v))
}
pub(crate) async fn no_row_error<'e, E, M, PK>(
executor: E,
descriptor: &'static ModelDescriptor<M, PK>,
id: PK,
ctx: &CratestackContext,
if_match: Option<i64>,
action: &str,
) -> CratestackError
where
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
{
if let (Some(version_col), Some(expected)) = (descriptor.version_column, if_match) {
match probe_current_version(executor, descriptor, id, version_col, ctx).await {
Err(error) => return error,
Ok(Some(current)) if current != expected => {
return CratestackError::PreconditionFailed(format!(
"version mismatch: expected {expected}, found {current}",
));
}
Ok(_) => {}
}
}
CratestackError::Forbidden(format!("{action} policy denied this operation"))
}