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            default,
143            skip_serializing_if = "Option::is_none",
144            deserialize_with = "cow_option_from_string"
145        )
146    )]
147    pub as_clause: Option<Cow<'static, str>>,
148
149    /// FOR clause (ALL/SELECT/INSERT/UPDATE/DELETE)
150    #[cfg_attr(
151        feature = "serde",
152        serde(
153            rename = "for",
154            default,
155            skip_serializing_if = "Option::is_none",
156            deserialize_with = "cow_option_from_string"
157        )
158    )]
159    pub for_clause: Option<Cow<'static, str>>,
160
161    /// TO roles
162    #[cfg_attr(
163        feature = "serde",
164        serde(
165            default,
166            skip_serializing_if = "Option::is_none",
167            deserialize_with = "cow_option_vec_from_strings"
168        )
169    )]
170    pub to: Option<Vec<Cow<'static, str>>>,
171
172    /// USING expression
173    #[cfg_attr(
174        feature = "serde",
175        serde(
176            default,
177            skip_serializing_if = "Option::is_none",
178            deserialize_with = "cow_option_from_string"
179        )
180    )]
181    pub using: Option<Cow<'static, str>>,
182
183    /// WITH CHECK expression
184    #[cfg_attr(
185        feature = "serde",
186        serde(
187            default,
188            skip_serializing_if = "Option::is_none",
189            deserialize_with = "cow_option_from_string"
190        )
191    )]
192    pub with_check: Option<Cow<'static, str>>,
193}
194
195impl Policy {
196    /// Create a new policy (runtime)
197    #[must_use]
198    pub fn new(
199        schema: impl Into<Cow<'static, str>>,
200        table: impl Into<Cow<'static, str>>,
201        name: impl Into<Cow<'static, str>>,
202    ) -> Self {
203        Self {
204            schema: schema.into(),
205            table: table.into(),
206            name: name.into(),
207            as_clause: None,
208            for_clause: None,
209            to: None,
210            using: None,
211            with_check: None,
212        }
213    }
214
215    /// Get the schema name
216    #[inline]
217    #[must_use]
218    pub fn schema(&self) -> &str {
219        &self.schema
220    }
221
222    /// Get the table name
223    #[inline]
224    #[must_use]
225    pub fn table(&self) -> &str {
226        &self.table
227    }
228
229    /// Get the policy name
230    #[inline]
231    #[must_use]
232    pub fn name(&self) -> &str {
233        &self.name
234    }
235}
236
237impl From<PolicyDef> for Policy {
238    fn from(def: PolicyDef) -> Self {
239        def.into_policy()
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_const_policy_def() {
249        const POLICY: PolicyDef = PolicyDef::new("public", "users", "users_policy")
250            .for_clause("SELECT")
251            .using("user_id = current_user_id()");
252
253        assert_eq!(POLICY.schema, "public");
254        assert_eq!(POLICY.table, "users");
255        assert_eq!(POLICY.name, "users_policy");
256    }
257
258    /// Optional fields are skipped when serializing; deserialization must
259    /// treat the missing keys as `None` instead of erroring.
260    #[cfg(feature = "serde")]
261    #[test]
262    fn test_serde_roundtrip_with_all_none_optionals() {
263        let policy = Policy::new("public", "users", "users_policy");
264        assert!(policy.as_clause.is_none());
265        assert!(policy.using.is_none());
266
267        let json = serde_json::to_string(&policy).expect("serialize");
268        let parsed: Policy = serde_json::from_str(&json).expect("deserialize");
269        assert_eq!(parsed, policy);
270    }
271
272    #[test]
273    fn test_policy_def_to_policy() {
274        const DEF: PolicyDef = PolicyDef::new("public", "users", "policy");
275        let policy = DEF.into_policy();
276        assert_eq!(policy.schema(), "public");
277        assert_eq!(policy.table(), "users");
278        assert_eq!(policy.name(), "policy");
279    }
280}