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