use serde::{Deserialize, Serialize};
use crate::identifier::Identifier;
use crate::ir::default_expr::NormalizedExpr;
use crate::ir::grant::GrantTarget;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
pub struct Policy {
pub name: Identifier,
pub permissive: bool,
pub command: PolicyCommand,
pub roles: Vec<GrantTarget>,
pub using: Option<NormalizedExpr>,
pub with_check: Option<NormalizedExpr>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PolicyCommand {
All,
Select,
Insert,
Update,
Delete,
}
impl PolicyCommand {
#[must_use]
pub const fn sql_keyword(self) -> &'static str {
match self {
Self::All => "ALL",
Self::Select => "SELECT",
Self::Insert => "INSERT",
Self::Update => "UPDATE",
Self::Delete => "DELETE",
}
}
#[must_use]
pub fn from_pg_text(s: &str) -> Option<Self> {
match s {
"ALL" => Some(Self::All),
"SELECT" => Some(Self::Select),
"INSERT" => Some(Self::Insert),
"UPDATE" => Some(Self::Update),
"DELETE" => Some(Self::Delete),
_ => None,
}
}
#[must_use]
pub const fn allows_with_check(self) -> bool {
matches!(self, Self::All | Self::Insert | Self::Update)
}
#[must_use]
pub const fn allows_using(self) -> bool {
matches!(self, Self::All | Self::Select | Self::Update | Self::Delete)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn id(s: &str) -> Identifier {
Identifier::from_unquoted(s).unwrap()
}
#[test]
fn pg_text_roundtrips() {
for cmd in [
PolicyCommand::All,
PolicyCommand::Select,
PolicyCommand::Insert,
PolicyCommand::Update,
PolicyCommand::Delete,
] {
assert_eq!(PolicyCommand::from_pg_text(cmd.sql_keyword()), Some(cmd));
}
}
#[test]
fn from_pg_text_rejects_unknown() {
assert_eq!(PolicyCommand::from_pg_text("BOGUS"), None);
}
#[test]
fn select_and_delete_reject_with_check() {
assert!(!PolicyCommand::Select.allows_with_check());
assert!(!PolicyCommand::Delete.allows_with_check());
assert!(PolicyCommand::All.allows_with_check());
assert!(PolicyCommand::Insert.allows_with_check());
assert!(PolicyCommand::Update.allows_with_check());
}
#[test]
fn insert_rejects_using() {
assert!(!PolicyCommand::Insert.allows_using());
assert!(PolicyCommand::Select.allows_using());
assert!(PolicyCommand::Delete.allows_using());
assert!(PolicyCommand::Update.allows_using());
assert!(PolicyCommand::All.allows_using());
}
#[test]
fn policy_sort_by_name() {
let a = Policy {
name: id("alpha"),
permissive: true,
command: PolicyCommand::All,
roles: vec![GrantTarget::Public],
using: None,
with_check: None,
};
let b = Policy {
name: id("beta"),
permissive: true,
command: PolicyCommand::All,
roles: vec![GrantTarget::Public],
using: None,
with_check: None,
};
let mut policies = vec![b.clone(), a.clone()];
policies.sort();
assert_eq!(policies, vec![a, b]);
}
}