pgevolve_core/ir/
policy.rs1use serde::{Deserialize, Serialize};
8
9use crate::identifier::Identifier;
10use crate::ir::default_expr::NormalizedExpr;
11use crate::ir::grant::GrantTarget;
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
15pub struct Policy {
16 pub name: Identifier,
18 pub permissive: bool,
20 pub command: PolicyCommand,
22 pub roles: Vec<GrantTarget>,
26 pub using: Option<NormalizedExpr>,
28 pub with_check: Option<NormalizedExpr>,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
35#[serde(rename_all = "snake_case")]
36pub enum PolicyCommand {
37 All,
39 Select,
41 Insert,
43 Update,
45 Delete,
47}
48
49impl PolicyCommand {
50 #[must_use]
52 pub const fn sql_keyword(self) -> &'static str {
53 match self {
54 Self::All => "ALL",
55 Self::Select => "SELECT",
56 Self::Insert => "INSERT",
57 Self::Update => "UPDATE",
58 Self::Delete => "DELETE",
59 }
60 }
61
62 #[must_use]
64 pub fn from_pg_text(s: &str) -> Option<Self> {
65 match s {
66 "ALL" => Some(Self::All),
67 "SELECT" => Some(Self::Select),
68 "INSERT" => Some(Self::Insert),
69 "UPDATE" => Some(Self::Update),
70 "DELETE" => Some(Self::Delete),
71 _ => None,
72 }
73 }
74
75 #[must_use]
78 pub const fn allows_with_check(self) -> bool {
79 matches!(self, Self::All | Self::Insert | Self::Update)
80 }
81
82 #[must_use]
85 pub const fn allows_using(self) -> bool {
86 matches!(self, Self::All | Self::Select | Self::Update | Self::Delete)
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 fn id(s: &str) -> Identifier {
95 Identifier::from_unquoted(s).unwrap()
96 }
97
98 #[test]
99 fn pg_text_roundtrips() {
100 for cmd in [
101 PolicyCommand::All,
102 PolicyCommand::Select,
103 PolicyCommand::Insert,
104 PolicyCommand::Update,
105 PolicyCommand::Delete,
106 ] {
107 assert_eq!(PolicyCommand::from_pg_text(cmd.sql_keyword()), Some(cmd));
108 }
109 }
110
111 #[test]
112 fn from_pg_text_rejects_unknown() {
113 assert_eq!(PolicyCommand::from_pg_text("BOGUS"), None);
114 }
115
116 #[test]
117 fn select_and_delete_reject_with_check() {
118 assert!(!PolicyCommand::Select.allows_with_check());
119 assert!(!PolicyCommand::Delete.allows_with_check());
120 assert!(PolicyCommand::All.allows_with_check());
121 assert!(PolicyCommand::Insert.allows_with_check());
122 assert!(PolicyCommand::Update.allows_with_check());
123 }
124
125 #[test]
126 fn insert_rejects_using() {
127 assert!(!PolicyCommand::Insert.allows_using());
128 assert!(PolicyCommand::Select.allows_using());
129 assert!(PolicyCommand::Delete.allows_using());
130 assert!(PolicyCommand::Update.allows_using());
131 assert!(PolicyCommand::All.allows_using());
132 }
133
134 #[test]
135 fn policy_sort_by_name() {
136 let a = Policy {
137 name: id("alpha"),
138 permissive: true,
139 command: PolicyCommand::All,
140 roles: vec![GrantTarget::Public],
141 using: None,
142 with_check: None,
143 };
144 let b = Policy {
145 name: id("beta"),
146 permissive: true,
147 command: PolicyCommand::All,
148 roles: vec![GrantTarget::Public],
149 using: None,
150 with_check: None,
151 };
152 let mut policies = vec![b.clone(), a.clone()];
153 policies.sort();
154 assert_eq!(policies, vec![a, b]);
155 }
156}