Skip to main content

drizzle_types/postgres/ddl/
policy.rs

1//! `PostgreSQL` Policy DDL types
2//!
3//! This module provides two complementary types:
4//! - [`PolicyDef`] - A const-friendly definition type for compile-time schema definitions
5//! - [`Policy`] - A runtime type for serde serialization/deserialization
6
7use crate::alloc_prelude::*;
8
9#[cfg(feature = "serde")]
10use crate::serde_helpers::{cow_from_string, cow_option_from_string, cow_option_vec_from_strings};
11
12// =============================================================================
13// Const-friendly Definition Type
14// =============================================================================
15
16/// Const-friendly policy definition for compile-time schema definitions.
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub struct PolicyDef {
19    /// Schema name
20    pub schema: &'static str,
21    /// Table name
22    pub table: &'static str,
23    /// Policy name
24    pub name: &'static str,
25    /// AS clause (PERMISSIVE/RESTRICTIVE)
26    pub as_clause: Option<&'static str>,
27    /// FOR clause (ALL/SELECT/INSERT/UPDATE/DELETE)
28    pub for_clause: Option<&'static str>,
29    /// TO roles (comma-separated)
30    pub to: Option<&'static [&'static str]>,
31    /// USING expression
32    pub using: Option<&'static str>,
33    /// WITH CHECK expression
34    pub with_check: Option<&'static str>,
35}
36
37impl PolicyDef {
38    /// Create a new policy definition
39    #[must_use]
40    pub const fn new(schema: &'static str, table: &'static str, name: &'static str) -> Self {
41        Self {
42            schema,
43            table,
44            name,
45            as_clause: None,
46            for_clause: None,
47            to: None,
48            using: None,
49            with_check: None,
50        }
51    }
52
53    /// Set AS clause
54    #[must_use]
55    pub const fn as_clause(self, clause: &'static str) -> Self {
56        Self {
57            as_clause: Some(clause),
58            ..self
59        }
60    }
61
62    /// Set FOR clause
63    #[must_use]
64    pub const fn for_clause(self, clause: &'static str) -> Self {
65        Self {
66            for_clause: Some(clause),
67            ..self
68        }
69    }
70
71    /// Set TO roles
72    #[must_use]
73    pub const fn to(self, roles: &'static [&'static str]) -> Self {
74        Self {
75            to: Some(roles),
76            ..self
77        }
78    }
79
80    /// Set USING expression
81    #[must_use]
82    pub const fn using(self, expr: &'static str) -> Self {
83        Self {
84            using: Some(expr),
85            ..self
86        }
87    }
88
89    /// Set WITH CHECK expression
90    #[must_use]
91    pub const fn with_check(self, expr: &'static str) -> Self {
92        Self {
93            with_check: Some(expr),
94            ..self
95        }
96    }
97
98    /// Convert to runtime [`Policy`] type
99    #[must_use]
100    pub fn into_policy(self) -> Policy {
101        Policy {
102            schema: Cow::Borrowed(self.schema),
103            table: Cow::Borrowed(self.table),
104            name: Cow::Borrowed(self.name),
105            as_clause: self.as_clause.map(Cow::Borrowed),
106            for_clause: self.for_clause.map(Cow::Borrowed),
107            to: self
108                .to
109                .map(|roles| roles.iter().copied().map(Cow::Borrowed).collect()),
110            using: self.using.map(Cow::Borrowed),
111            with_check: self.with_check.map(Cow::Borrowed),
112        }
113    }
114}
115
116// =============================================================================
117// Runtime Type for Serde
118// =============================================================================
119
120/// Runtime policy entity for serde serialization.
121#[derive(Clone, Debug, PartialEq, Eq)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
123#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
124pub struct Policy {
125    /// Schema name
126    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
127    pub schema: Cow<'static, str>,
128
129    /// Table name
130    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
131    pub table: Cow<'static, str>,
132
133    /// Policy name
134    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
135    pub name: Cow<'static, str>,
136
137    /// AS clause (PERMISSIVE/RESTRICTIVE)
138    #[cfg_attr(
139        feature = "serde",
140        serde(
141            rename = "as",
142            skip_serializing_if = "Option::is_none",
143            deserialize_with = "cow_option_from_string"
144        )
145    )]
146    pub as_clause: Option<Cow<'static, str>>,
147
148    /// FOR clause (ALL/SELECT/INSERT/UPDATE/DELETE)
149    #[cfg_attr(
150        feature = "serde",
151        serde(
152            rename = "for",
153            skip_serializing_if = "Option::is_none",
154            deserialize_with = "cow_option_from_string"
155        )
156    )]
157    pub for_clause: Option<Cow<'static, str>>,
158
159    /// TO roles
160    #[cfg_attr(
161        feature = "serde",
162        serde(
163            default,
164            skip_serializing_if = "Option::is_none",
165            deserialize_with = "cow_option_vec_from_strings"
166        )
167    )]
168    pub to: Option<Vec<Cow<'static, str>>>,
169
170    /// USING expression
171    #[cfg_attr(
172        feature = "serde",
173        serde(
174            skip_serializing_if = "Option::is_none",
175            deserialize_with = "cow_option_from_string"
176        )
177    )]
178    pub using: Option<Cow<'static, str>>,
179
180    /// WITH CHECK expression
181    #[cfg_attr(
182        feature = "serde",
183        serde(
184            skip_serializing_if = "Option::is_none",
185            deserialize_with = "cow_option_from_string"
186        )
187    )]
188    pub with_check: Option<Cow<'static, str>>,
189}
190
191impl Policy {
192    /// Create a new policy (runtime)
193    #[must_use]
194    pub fn new(
195        schema: impl Into<Cow<'static, str>>,
196        table: impl Into<Cow<'static, str>>,
197        name: impl Into<Cow<'static, str>>,
198    ) -> Self {
199        Self {
200            schema: schema.into(),
201            table: table.into(),
202            name: name.into(),
203            as_clause: None,
204            for_clause: None,
205            to: None,
206            using: None,
207            with_check: None,
208        }
209    }
210
211    /// Get the schema name
212    #[inline]
213    #[must_use]
214    pub fn schema(&self) -> &str {
215        &self.schema
216    }
217
218    /// Get the table name
219    #[inline]
220    #[must_use]
221    pub fn table(&self) -> &str {
222        &self.table
223    }
224
225    /// Get the policy name
226    #[inline]
227    #[must_use]
228    pub fn name(&self) -> &str {
229        &self.name
230    }
231}
232
233impl From<PolicyDef> for Policy {
234    fn from(def: PolicyDef) -> Self {
235        def.into_policy()
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn test_const_policy_def() {
245        const POLICY: PolicyDef = PolicyDef::new("public", "users", "users_policy")
246            .for_clause("SELECT")
247            .using("user_id = current_user_id()");
248
249        assert_eq!(POLICY.schema, "public");
250        assert_eq!(POLICY.table, "users");
251        assert_eq!(POLICY.name, "users_policy");
252    }
253
254    #[test]
255    fn test_policy_def_to_policy() {
256        const DEF: PolicyDef = PolicyDef::new("public", "users", "policy");
257        let policy = DEF.into_policy();
258        assert_eq!(policy.schema(), "public");
259        assert_eq!(policy.table(), "users");
260        assert_eq!(policy.name(), "policy");
261    }
262}