Skip to main content

uqa_sql/ast/
acl_role_specification.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Object-privilege recipients preserve PUBLIC separately from named and session roles.
8
9use super::RoleSpecification;
10use serde::{Deserialize, Deserializer, Serialize};
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
13#[serde(tag = "kind", content = "role", rename_all = "snake_case")]
14pub enum AclRoleSpecification {
15    Public,
16    Role(RoleSpecification),
17}
18
19impl From<RoleSpecification> for AclRoleSpecification {
20    fn from(role: RoleSpecification) -> Self {
21        Self::Role(role)
22    }
23}
24
25impl From<String> for AclRoleSpecification {
26    fn from(name: String) -> Self {
27        Self::Role(RoleSpecification::Named(name))
28    }
29}
30
31impl From<&str> for AclRoleSpecification {
32    fn from(name: &str) -> Self {
33        Self::from(name.to_owned())
34    }
35}
36
37impl<'de> Deserialize<'de> for AclRoleSpecification {
38    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
39        #[derive(Deserialize)]
40        #[serde(
41            tag = "kind",
42            content = "role",
43            rename_all = "snake_case",
44            deny_unknown_fields
45        )]
46        enum Tagged {
47            Public,
48            Role(RoleSpecification),
49        }
50        #[derive(Deserialize)]
51        #[serde(untagged)]
52        enum Stored {
53            Tagged(Tagged),
54            Legacy(String),
55        }
56        Ok(match Stored::deserialize(deserializer)? {
57            Stored::Tagged(Tagged::Public) => Self::Public,
58            Stored::Tagged(Tagged::Role(role)) => Self::Role(role),
59            Stored::Legacy(name) => match name.as_str() {
60                "PUBLIC" => Self::Public,
61                "CURRENT_USER" => Self::Role(RoleSpecification::CurrentUser),
62                "SESSION_USER" => Self::Role(RoleSpecification::SessionUser),
63                _ => Self::Role(RoleSpecification::Named(name)),
64            },
65        })
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn quoted_role_names_public_and_session_keywords_remain_distinct() {
75        let specifications = [
76            AclRoleSpecification::Public,
77            AclRoleSpecification::from("PUBLIC"),
78            AclRoleSpecification::from("CURRENT_USER"),
79            AclRoleSpecification::from("SESSION_USER"),
80            AclRoleSpecification::from(RoleSpecification::CurrentUser),
81            AclRoleSpecification::from(RoleSpecification::SessionUser),
82        ];
83        let mut encodings = std::collections::BTreeSet::new();
84        for specification in specifications {
85            let json = serde_json::to_string(&specification).unwrap();
86            assert!(encodings.insert(json.clone()));
87            assert_eq!(
88                serde_json::from_str::<AclRoleSpecification>(&json).unwrap(),
89                specification
90            );
91        }
92    }
93
94    #[test]
95    fn legacy_object_grant_statements_keep_their_previous_keyword_meanings() {
96        for (name, expected) in [
97            ("PUBLIC", AclRoleSpecification::Public),
98            ("CURRENT_USER", RoleSpecification::CurrentUser.into()),
99            ("SESSION_USER", RoleSpecification::SessionUser.into()),
100            ("reader", AclRoleSpecification::from("reader")),
101        ] {
102            let json = serde_json::to_string(name).unwrap();
103            assert_eq!(
104                serde_json::from_str::<AclRoleSpecification>(&json).unwrap(),
105                expected
106            );
107        }
108        for json in [
109            r#"{"kind":"unknown","role":"PUBLIC"}"#,
110            r#"{"kind":"role"}"#,
111            r#"{"kind":"public","role":"reader"}"#,
112            r#"{"role":"PUBLIC"}"#,
113            r#"{"kind":"role","role":{"kind":"named","name":"PUBLIC","extra":true}}"#,
114            r#"{"kind":"role","role":{"kind":"current_user","name":"PUBLIC"}}"#,
115            "null",
116        ] {
117            assert!(
118                serde_json::from_str::<AclRoleSpecification>(json).is_err(),
119                "{json}"
120            );
121        }
122    }
123}