use std::{fmt, str::FromStr};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
#[derive(Debug, Clone, PartialEq, Eq, strum_macros::Display)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[non_exhaustive]
pub enum Privilege {
Ownership,
Usage,
Select,
Insert,
Update,
Delete,
CreateDatabase,
CreateNamespace,
CreateTable,
CreateMaterializedView,
CreateView,
CreateSecret,
CreateFunction,
Operate,
#[strum(to_string = "{0}")]
Unknown(UnknownPrivilege),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnknownPrivilege(String);
impl fmt::Display for UnknownPrivilege {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Privilege {
pub const KNOWN: [Self; 14] = [
Self::Ownership,
Self::Usage,
Self::Select,
Self::Insert,
Self::Update,
Self::Delete,
Self::CreateDatabase,
Self::CreateNamespace,
Self::CreateTable,
Self::CreateMaterializedView,
Self::CreateView,
Self::CreateSecret,
Self::CreateFunction,
Self::Operate,
];
pub fn valid_for_system(&self) -> bool {
matches!(
self,
Self::Operate | Self::CreateDatabase | Self::Usage | Self::Unknown(_)
)
}
pub fn valid_for_database(&self) -> bool {
matches!(
self,
Self::Ownership | Self::Usage | Self::CreateNamespace | Self::Unknown(_)
)
}
pub fn valid_for_namespace(&self) -> bool {
matches!(
self,
Self::Ownership
| Self::Usage
| Self::CreateNamespace
| Self::CreateTable
| Self::CreateMaterializedView
| Self::CreateView
| Self::CreateSecret
| Self::CreateFunction
| Self::Unknown(_)
)
}
pub fn valid_for_table(&self) -> bool {
matches!(
self,
Self::Ownership
| Self::Select
| Self::Insert
| Self::Update
| Self::Delete
| Self::Unknown(_)
)
}
pub fn valid_for_view(&self) -> bool {
matches!(self, Self::Ownership | Self::Select | Self::Unknown(_))
}
pub fn valid_for_secret(&self) -> bool {
matches!(
self,
Self::Ownership | Self::Usage | Self::Update | Self::Unknown(_)
)
}
pub fn valid_for_function(&self) -> bool {
matches!(self, Self::Ownership | Self::Usage | Self::Unknown(_))
}
pub fn is_write(&self) -> bool {
!matches!(self, Self::Usage | Self::Select | Self::Operate)
}
}
impl FromStr for Privilege {
type Err = String;
fn from_str(name: &str) -> Result<Self, Self::Err> {
if let Some(privilege) = Self::KNOWN.into_iter().find(|p| p.to_string() == name) {
return Ok(privilege);
}
if let Some(digits) = name.strip_prefix("PRIVILEGE")
&& !digits.is_empty()
&& digits.bytes().all(|b| b.is_ascii_digit())
{
return Ok(match digits.parse::<u64>() {
Ok(0) => Self::Ownership,
Ok(1) => Self::Usage,
Ok(2) => Self::Select,
Ok(3) => Self::Insert,
Ok(4) => Self::Update,
Ok(5) => Self::Delete,
Ok(6) => Self::CreateDatabase,
Ok(7) => Self::CreateNamespace,
Ok(8) => Self::CreateTable,
Ok(9) => Self::CreateMaterializedView,
Ok(10) => Self::CreateView,
Ok(11) => Self::CreateSecret,
Ok(12) => Self::CreateFunction,
Ok(13) => Self::Operate,
_ => Self::Unknown(UnknownPrivilege(name.to_owned())),
});
}
Err(format!("invalid privilege {name:?}"))
}
}
impl TryFrom<&Privilege> for u8 {
type Error = std::num::ParseIntError;
fn try_from(privilege: &Privilege) -> Result<Self, Self::Error> {
Ok(match privilege {
Privilege::Ownership => 0,
Privilege::Usage => 1,
Privilege::Select => 2,
Privilege::Insert => 3,
Privilege::Update => 4,
Privilege::Delete => 5,
Privilege::CreateDatabase => 6,
Privilege::CreateNamespace => 7,
Privilege::CreateTable => 8,
Privilege::CreateMaterializedView => 9,
Privilege::CreateView => 10,
Privilege::CreateSecret => 11,
Privilege::CreateFunction => 12,
Privilege::Operate => 13,
Privilege::Unknown(unknown) => unknown.0["PRIVILEGE".len()..].parse()?,
})
}
}
impl TryFrom<Privilege> for u8 {
type Error = std::num::ParseIntError;
fn try_from(privilege: Privilege) -> Result<Self, Self::Error> {
Self::try_from(&privilege)
}
}
impl Serialize for Privilege {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
impl<'de> Deserialize<'de> for Privilege {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(D::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::Privilege;
#[test]
fn display_matches_serde() {
for privilege in Privilege::KNOWN {
assert_eq!(
serde_json::to_string(&privilege).unwrap(),
format!("\"{privilege}\""),
);
}
}
#[test]
fn secret_and_function_creation_privileges() {
for (name, privilege) in [
("CREATE_SECRET", Privilege::CreateSecret),
("CREATE_FUNCTION", Privilege::CreateFunction),
] {
assert_eq!(name.parse::<Privilege>().unwrap(), privilege);
assert_eq!(privilege.to_string(), name);
assert_eq!(serde_json::to_value(&privilege).unwrap(), name);
assert_eq!(
serde_json::from_value::<Privilege>(serde_json::json!(name)).unwrap(),
privilege
);
assert!(privilege.valid_for_namespace());
assert!(!privilege.valid_for_system());
assert!(!privilege.valid_for_database());
assert!(!privilege.valid_for_table());
assert!(!privilege.valid_for_view());
assert!(privilege.is_write());
}
}
#[test]
fn numeric_aliases_resolve_to_known_privileges() {
for (number, privilege) in [
(0, Privilege::Ownership),
(1, Privilege::Usage),
(2, Privilege::Select),
(3, Privilege::Insert),
(4, Privilege::Update),
(5, Privilege::Delete),
(6, Privilege::CreateDatabase),
(7, Privilege::CreateNamespace),
(8, Privilege::CreateTable),
(9, Privilege::CreateMaterializedView),
(10, Privilege::CreateView),
(11, Privilege::CreateSecret),
(12, Privilege::CreateFunction),
(13, Privilege::Operate),
] {
assert_eq!(u8::try_from(&privilege).unwrap(), number);
assert_eq!(u8::try_from(privilege.clone()).unwrap(), number);
for name in [
format!("PRIVILEGE{number}"),
format!("PRIVILEGE{number:03}"),
] {
let parsed = name.parse::<Privilege>().unwrap();
assert_eq!(parsed, privilege, "{name}");
assert_eq!(
serde_json::from_value::<Privilege>(serde_json::json!(name)).unwrap(),
privilege
);
assert_eq!(
serde_json::to_value(&parsed).unwrap(),
privilege.to_string()
);
assert_eq!(
privilege.to_string().parse::<Privilege>().unwrap(),
privilege
);
}
}
}
#[test]
fn unknown_privileges_convert_to_u8() {
for number in 14..=u8::MAX {
for name in [
format!("PRIVILEGE{number}"),
format!("PRIVILEGE{number:03}"),
] {
let privilege: Privilege = name.parse().unwrap();
assert!(matches!(privilege, Privilege::Unknown(_)));
assert_eq!(u8::try_from(&privilege).unwrap(), number);
assert_eq!(u8::try_from(privilege).unwrap(), number);
}
}
}
#[test]
fn privileges_outside_u8_range_fail_conversion() {
for name in [
"PRIVILEGE256",
"PRIVILEGE0256",
"PRIVILEGE18446744073709551616",
] {
let privilege: Privilege = name.parse().unwrap();
assert_eq!(
u8::try_from(&privilege).unwrap_err().kind(),
&std::num::IntErrorKind::PosOverflow
);
assert_eq!(
u8::try_from(privilege).unwrap_err().kind(),
&std::num::IntErrorKind::PosOverflow
);
}
}
#[test]
fn future_privilege_names_round_trip() {
for name in [
"PRIVILEGE14",
"PRIVILEGE20",
"PRIVILEGE64",
"PRIVILEGE256",
"PRIVILEGE18446744073709551616",
"PRIVILEGE020",
] {
let privilege: Privilege = name.parse().unwrap();
assert!(matches!(privilege, Privilege::Unknown(_)));
assert_eq!(privilege.to_string(), name);
assert_eq!(serde_json::to_value(&privilege).unwrap(), name);
assert_eq!(
serde_json::from_value::<Privilege>(serde_json::json!(name)).unwrap(),
privilege
);
}
}
#[test]
fn rejects_invalid_privileges() {
for name in [
"",
"select",
" SELECT",
"SELECT ",
"PRIVILEGE",
"PRIVILEGE-1",
"PRIVILEGE+1",
"PRIVILEGE 1",
"PRIVILEGE1x",
"privilege12",
"UNKNOWN",
] {
assert!(name.parse::<Privilege>().is_err(), "{name}");
assert!(
serde_json::from_value::<Privilege>(serde_json::json!(name)).is_err(),
"{name}"
);
}
}
#[test]
fn unknown_privileges_are_valid_for_all_objects_and_treated_as_writes() {
for name in ["PRIVILEGE14", "PRIVILEGE20", "PRIVILEGE64", "PRIVILEGE256"] {
let privilege: Privilege = name.parse().unwrap();
assert!(privilege.valid_for_system());
assert!(privilege.valid_for_database());
assert!(privilege.valid_for_namespace());
assert!(privilege.valid_for_table());
assert!(privilege.valid_for_view());
assert!(privilege.valid_for_secret());
assert!(privilege.valid_for_function());
assert!(privilege.is_write());
}
assert!(!Privilege::Ownership.valid_for_system());
assert!(!Privilege::Select.valid_for_database());
}
#[test]
fn test_privilege_is_write() {
for privilege in Privilege::KNOWN {
assert_eq!(
privilege.is_write(),
matches!(
privilege,
Privilege::Ownership
| Privilege::Insert
| Privilege::Update
| Privilege::Delete
| Privilege::CreateDatabase
| Privilege::CreateNamespace
| Privilege::CreateTable
| Privilege::CreateMaterializedView
| Privilege::CreateView
| Privilege::CreateSecret
| Privilege::CreateFunction
),
"unexpected write classification for {privilege}"
);
}
}
#[test]
fn create_secret_and_create_function_are_namespace_scoped() {
for privilege in [Privilege::CreateSecret, Privilege::CreateFunction] {
assert!(privilege.valid_for_namespace());
assert!(!privilege.valid_for_system());
assert!(!privilege.valid_for_database());
assert!(!privilege.valid_for_table());
assert!(!privilege.valid_for_view());
assert!(!privilege.valid_for_secret());
assert!(!privilege.valid_for_function());
}
}
#[test]
fn secret_and_function_privileges() {
assert_eq!(
Privilege::KNOWN
.into_iter()
.filter(Privilege::valid_for_secret)
.collect::<Vec<_>>(),
vec![Privilege::Ownership, Privilege::Usage, Privilege::Update]
);
assert_eq!(
Privilege::KNOWN
.into_iter()
.filter(Privilege::valid_for_function)
.collect::<Vec<_>>(),
vec![Privilege::Ownership, Privilege::Usage]
);
}
}