use crate::ir::grant::GrantTarget;
use crate::ir::policy::Policy;
use crate::ir::table::Table;
pub fn run_on_table(t: &mut Table) {
for p in &mut t.policies {
normalize_roles(p);
}
t.policies
.sort_by(|a, b| a.name.as_str().cmp(b.name.as_str()));
}
fn normalize_roles(p: &mut Policy) {
if p.roles.contains(&GrantTarget::Public) {
p.roles = vec![GrantTarget::Public];
return;
}
p.roles.sort();
p.roles.dedup();
}
#[cfg(test)]
mod tests {
use super::*;
use crate::identifier::{Identifier, QualifiedName};
use crate::ir::grant::GrantTarget;
use crate::ir::policy::{Policy, PolicyCommand};
use crate::ir::table::Table;
fn id(s: &str) -> Identifier {
Identifier::from_unquoted(s).unwrap()
}
fn qn(schema: &str, name: &str) -> QualifiedName {
QualifiedName::new(id(schema), id(name))
}
fn policy(name: &str, roles: Vec<GrantTarget>) -> Policy {
Policy {
name: id(name),
permissive: true,
command: PolicyCommand::All,
roles,
using: None,
with_check: None,
}
}
fn empty_table(qname: QualifiedName) -> Table {
Table {
qname,
columns: vec![],
constraints: vec![],
partition_by: None,
partition_of: None,
comment: None,
owner: None,
grants: vec![],
rls_enabled: false,
rls_forced: false,
policies: vec![],
storage: crate::ir::reloptions::TableStorageOptions::default(),
access_method: None,
tablespace: None,
}
}
#[test]
fn sorts_policies_by_name() {
let mut t = empty_table(qn("app", "users"));
t.policies = vec![
policy("zebra", vec![]),
policy("alpha", vec![]),
policy("middle", vec![]),
];
run_on_table(&mut t);
let names: Vec<_> = t
.policies
.iter()
.map(|p| p.name.as_str().to_owned())
.collect();
assert_eq!(names, vec!["alpha", "middle", "zebra"]);
}
#[test]
fn sorts_roles_without_public() {
let mut t = empty_table(qn("app", "users"));
t.policies = vec![policy(
"p",
vec![
GrantTarget::Role(id("zelda")),
GrantTarget::Role(id("alice")),
],
)];
run_on_table(&mut t);
assert_eq!(t.policies[0].roles[0], GrantTarget::Role(id("alice")));
assert_eq!(t.policies[0].roles[1], GrantTarget::Role(id("zelda")));
}
#[test]
fn dedupes_duplicate_roles_without_public() {
let mut t = empty_table(qn("app", "users"));
t.policies = vec![policy(
"p",
vec![
GrantTarget::Role(id("alice")),
GrantTarget::Role(id("alice")),
GrantTarget::Role(id("bob")),
],
)];
run_on_table(&mut t);
assert_eq!(t.policies[0].roles.len(), 2);
}
#[test]
fn public_plus_named_role_collapses_to_public_only() {
let mut t = empty_table(qn("app", "users"));
t.policies = vec![policy(
"p",
vec![GrantTarget::Public, GrantTarget::Role(id("app_owner"))],
)];
run_on_table(&mut t);
assert_eq!(
t.policies[0].roles,
vec![GrantTarget::Public],
"expected [Public] but got {:?}",
t.policies[0].roles
);
}
#[test]
fn public_alone_is_unchanged() {
let mut t = empty_table(qn("app", "users"));
t.policies = vec![policy("p", vec![GrantTarget::Public])];
run_on_table(&mut t);
assert_eq!(t.policies[0].roles, vec![GrantTarget::Public]);
}
#[test]
fn named_roles_without_public_are_unchanged() {
let mut t = empty_table(qn("app", "users"));
t.policies = vec![policy(
"p",
vec![GrantTarget::Role(id("alice")), GrantTarget::Role(id("bob"))],
)];
run_on_table(&mut t);
assert_eq!(t.policies[0].roles.len(), 2);
assert!(!t.policies[0].roles.contains(&GrantTarget::Public));
}
#[test]
fn run_is_idempotent() {
let mut t = empty_table(qn("app", "users"));
t.policies = vec![
policy("b", vec![GrantTarget::Role(id("alice"))]),
policy("a", vec![GrantTarget::Public]),
];
run_on_table(&mut t);
let snap1 = format!("{t:?}");
run_on_table(&mut t);
let snap2 = format!("{t:?}");
assert_eq!(snap1, snap2);
}
}