use std::collections::BTreeSet;
use crate::ast::Action;
use super::model::AccessOperation;
#[derive(Debug)]
pub enum AccessPolicyLoadError {
Read(std::io::Error),
Toml(toml::de::Error),
Json(serde_json::Error),
UnsupportedExtension(String),
}
impl std::fmt::Display for AccessPolicyLoadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Read(err) => write!(f, "failed to read access policy: {err}"),
Self::Toml(err) => write!(f, "failed to parse TOML access policy: {err}"),
Self::Json(err) => write!(f, "failed to parse JSON access policy: {err}"),
Self::UnsupportedExtension(extension) if extension.is_empty() => {
write!(f, "access policy file must use .toml or .json extension")
}
Self::UnsupportedExtension(extension) => {
write!(
f,
"unsupported access policy extension '.{extension}' (expected .toml or .json)"
)
}
}
}
}
impl std::error::Error for AccessPolicyLoadError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Read(err) => Some(err),
Self::Toml(err) => Some(err),
Self::Json(err) => Some(err),
Self::UnsupportedExtension(_) => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccessError {
pub table: String,
pub operation: Option<AccessOperation>,
pub kind: AccessErrorKind,
}
impl AccessError {
pub(super) fn new(
table: String,
operation: Option<AccessOperation>,
kind: AccessErrorKind,
) -> Self {
Self {
table,
operation,
kind,
}
}
}
impl std::fmt::Display for AccessError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.kind {
AccessErrorKind::NoPolicy => {
write!(f, "no access policy allows table '{}'", self.table)
}
AccessErrorKind::UnsupportedAction(action) => {
write!(f, "action {action:?} is not supported by access policy")
}
AccessErrorKind::OperationDenied => write!(
f,
"operation {:?} is denied on table '{}'",
self.operation, self.table
),
AccessErrorKind::MissingRole { required } => write!(
f,
"table '{}' requires one of roles {:?}",
self.table, required
),
AccessErrorKind::MissingScope { required } => {
write!(f, "table '{}' requires scopes {:?}", self.table, required)
}
AccessErrorKind::ColumnDenied { column } => write!(
f,
"column '{}' is denied for operation {:?} on table '{}'",
column, self.operation, self.table
),
AccessErrorKind::WildcardProjectionDenied => write!(
f,
"wildcard projection is denied by column policy on table '{}'",
self.table
),
AccessErrorKind::UnsupportedColumnExpression { context } => write!(
f,
"{} contains an expression that cannot be checked by column policy on table '{}'",
context, self.table
),
AccessErrorKind::ExplicitWriteColumnsRequired => write!(
f,
"operation {:?} on table '{}' requires explicit write columns",
self.operation, self.table
),
AccessErrorKind::JoinedTableColumnPolicyUnsupported => write!(
f,
"joined table '{}' has column policy that cannot be enforced in a flat join",
self.table
),
AccessErrorKind::SourceTableColumnPolicyUnsupported => write!(
f,
"source table '{}' has column policy that cannot be enforced without an explicit source query",
self.table
),
AccessErrorKind::AuxiliaryTableColumnPolicyUnsupported => write!(
f,
"auxiliary table '{}' has column policy that cannot be enforced in UPDATE FROM or DELETE USING",
self.table
),
AccessErrorKind::CteMutationUnsupported => {
write!(f, "CTE relation '{}' cannot be mutated", self.table)
}
AccessErrorKind::EmptyTable => write!(f, "command has no target table"),
}
}
}
impl std::error::Error for AccessError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AccessErrorKind {
NoPolicy,
UnsupportedAction(Action),
OperationDenied,
MissingRole {
required: BTreeSet<String>,
},
MissingScope {
required: BTreeSet<String>,
},
ColumnDenied {
column: String,
},
WildcardProjectionDenied,
UnsupportedColumnExpression {
context: &'static str,
},
ExplicitWriteColumnsRequired,
JoinedTableColumnPolicyUnsupported,
SourceTableColumnPolicyUnsupported,
AuxiliaryTableColumnPolicyUnsupported,
CteMutationUnsupported,
EmptyTable,
}