uqa_sql/catalog/roles/
tuple.rs1use super::{
10 identity::{RoleBinding, RoleSubject},
11 RoleDefinition,
12};
13use crate::SQLError;
14use std::collections::BTreeMap;
15
16#[derive(Clone, Debug)]
17pub struct RoleTuple {
18 pub role: RoleBinding,
19 pub revision: u64,
20}
21
22impl RoleTuple {
23 pub fn bind(definition: &RoleDefinition) -> Result<Self, SQLError> {
24 if definition.revision == 0 {
25 return Err(SQLError::Internal(
26 "role definition has no tuple revision".into(),
27 ));
28 }
29 Ok(Self {
30 role: RoleBinding::from_definition(definition)?,
31 revision: definition.revision,
32 })
33 }
34
35 pub fn revalidate<'a>(
36 &self,
37 roles: &'a BTreeMap<String, RoleDefinition>,
38 ) -> Result<&'a RoleDefinition, SQLError> {
39 let current = self
40 .role
41 .role_definition(roles)
42 .ok_or_else(|| SQLError::Routine {
43 sqlstate: "XX000".into(),
44 message: "tuple concurrently deleted".into(),
45 })?;
46 if current.revision != self.revision {
47 return Err(SQLError::Routine {
48 sqlstate: "XX000".into(),
49 message: "tuple concurrently updated".into(),
50 });
51 }
52 Ok(current)
53 }
54}
55
56pub fn validate_revisions(roles: &BTreeMap<String, RoleDefinition>) -> Result<(), String> {
57 for (name, role) in roles {
58 if role.revision == 0 {
59 return Err(format!("persisted role `{name}` has no tuple revision"));
60 }
61 }
62 Ok(())
63}
64
65pub fn validate_revision_changes(
66 before: &BTreeMap<String, RoleDefinition>,
67 after: &BTreeMap<String, RoleDefinition>,
68) -> Result<(), SQLError> {
69 validate_revisions(before).map_err(SQLError::Internal)?;
70 validate_revisions(after).map_err(SQLError::Internal)?;
71 for (name, role) in after {
72 let previous = before
73 .get(name)
74 .filter(|previous| previous.identity() == role.identity())
75 .or_else(|| {
76 before
77 .values()
78 .find(|previous| previous.identity() == role.identity())
79 });
80 let valid = previous.map_or(role.revision == 1, |previous| {
81 previous == role || previous.revision.checked_add(1) == Some(role.revision)
82 });
83 if !valid {
84 return Err(SQLError::Internal(format!(
85 "role `{name}` did not advance its tuple revision"
86 )));
87 }
88 }
89 Ok(())
90}
91
92#[cfg(test)]
93mod tests;