use serde::{Deserialize, Serialize};
use crate::identifier::Identifier;
use crate::ir::eq::DiffMacro;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DiffMacro)]
pub struct Role {
pub name: Identifier,
#[diff(nested)]
pub attributes: RoleAttributes,
#[diff(via_debug)]
pub member_of: Vec<Identifier>,
#[diff(via_debug)]
pub comment: Option<String>,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DiffMacro)]
pub struct RoleAttributes {
pub superuser: bool,
pub createdb: bool,
pub createrole: bool,
pub inherit: bool,
pub login: bool,
pub replication: bool,
pub bypass_rls: bool,
#[diff(via_debug)]
pub connection_limit: Option<i64>,
#[diff(via_debug)]
pub valid_until: Option<String>,
}
impl Default for RoleAttributes {
fn default() -> Self {
Self {
superuser: false,
createdb: false,
createrole: false,
inherit: true, login: false,
replication: false,
bypass_rls: false,
connection_limit: None,
valid_until: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::eq::Diff;
fn id(s: &str) -> Identifier {
Identifier::from_unquoted(s).unwrap()
}
fn base() -> Role {
Role {
name: id("app_user"),
attributes: RoleAttributes::default(),
member_of: vec![],
comment: None,
}
}
#[test]
fn equal_roles_have_no_diff() {
assert!(base().canonical_eq(&base()));
}
#[test]
fn login_change_diffs() {
let mut b = base();
b.attributes.login = true;
assert!(base().diff(&b).iter().any(|x| x.path == "attributes.login"));
}
#[test]
fn connection_limit_change_diffs() {
let mut b = base();
b.attributes.connection_limit = Some(10);
assert!(
base()
.diff(&b)
.iter()
.any(|x| x.path == "attributes.connection_limit")
);
}
#[test]
fn valid_until_change_diffs() {
let mut b = base();
b.attributes.valid_until = Some("2030-01-01T00:00:00Z".into());
assert!(
base()
.diff(&b)
.iter()
.any(|x| x.path == "attributes.valid_until")
);
}
#[test]
fn attributes_diff_does_not_emit_coarse_path() {
let mut b = base();
b.attributes.superuser = true;
assert!(
!base().diff(&b).iter().any(|x| x.path == "attributes"),
"coarse 'attributes' path must not appear; use per-field paths"
);
}
#[test]
fn membership_change_diffs() {
let mut b = base();
b.member_of.push(id("readers"));
assert!(base().diff(&b).iter().any(|x| x.path == "member_of"));
}
#[test]
fn comment_change_diffs() {
let mut b = base();
b.comment = Some("the app".into());
assert!(base().diff(&b).iter().any(|x| x.path == "comment"));
}
#[test]
fn default_attributes_match_postgres_defaults() {
let a = RoleAttributes::default();
assert!(a.inherit, "PG default for INHERIT is true");
assert!(!a.superuser);
assert!(!a.login);
assert_eq!(a.connection_limit, None);
}
}