2025-05-26 22:08:21 -07:00
|
|
|
use nom::{
|
2025-07-08 16:54:51 -07:00
|
|
|
AsChar as _, IResult, Parser,
|
2025-05-26 22:08:21 -07:00
|
|
|
branch::alt,
|
|
|
|
|
bytes::complete::{is_not, tag, take_till},
|
|
|
|
|
character::char,
|
|
|
|
|
combinator::{opt, value},
|
|
|
|
|
error::ParseError,
|
|
|
|
|
multi::{many0, many1},
|
|
|
|
|
sequence::delimited,
|
|
|
|
|
};
|
2025-10-22 00:43:53 -07:00
|
|
|
use serde::{Deserialize, Serialize};
|
2025-05-26 22:08:21 -07:00
|
|
|
use sqlx::{
|
2025-07-08 16:54:51 -07:00
|
|
|
Decode, Postgres,
|
2025-05-26 22:08:21 -07:00
|
|
|
error::BoxDynError,
|
|
|
|
|
postgres::{PgHasArrayType, PgTypeInfo, PgValueRef},
|
|
|
|
|
};
|
2025-10-22 00:43:53 -07:00
|
|
|
use strum::IntoEnumIterator as _;
|
2025-05-26 22:08:21 -07:00
|
|
|
|
|
|
|
|
/// This type will automatically decode Postgres "aclitem" values, provided that
|
|
|
|
|
/// the query is cast to a TEXT type and selected with type annotations. For
|
|
|
|
|
/// example:
|
|
|
|
|
/// ```sql
|
|
|
|
|
/// select datacl::text[] as "datacl: Vec<PgAclItem>" from pg_database;
|
|
|
|
|
/// ```
|
|
|
|
|
/// The TEXT cast is necessary because the aclitem type itself is incompatible
|
|
|
|
|
/// with binary value format, which makes it incompatible with SQLx.
|
2025-10-22 00:43:53 -07:00
|
|
|
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
2025-05-26 22:08:21 -07:00
|
|
|
pub struct PgAclItem {
|
|
|
|
|
pub grantee: String,
|
|
|
|
|
pub privileges: Vec<PgPrivilege>,
|
|
|
|
|
pub grantor: String,
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-22 00:43:53 -07:00
|
|
|
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
|
2025-05-26 22:08:21 -07:00
|
|
|
pub struct PgPrivilege {
|
|
|
|
|
pub grant_option: bool,
|
|
|
|
|
pub privilege: PgPrivilegeType,
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-22 00:43:53 -07:00
|
|
|
#[derive(
|
|
|
|
|
Clone,
|
|
|
|
|
Copy,
|
|
|
|
|
Debug,
|
|
|
|
|
Deserialize,
|
|
|
|
|
Eq,
|
|
|
|
|
Hash,
|
|
|
|
|
PartialEq,
|
|
|
|
|
Serialize,
|
|
|
|
|
strum::Display,
|
|
|
|
|
strum::EnumIter,
|
|
|
|
|
strum::EnumString,
|
|
|
|
|
)]
|
2025-05-26 22:08:21 -07:00
|
|
|
pub enum PgPrivilegeType {
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "r")]
|
|
|
|
|
#[strum(to_string = "r")]
|
2025-05-26 22:08:21 -07:00
|
|
|
Select,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "a")]
|
|
|
|
|
#[strum(to_string = "a")]
|
2025-05-26 22:08:21 -07:00
|
|
|
Insert,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "w")]
|
|
|
|
|
#[strum(to_string = "w")]
|
2025-05-26 22:08:21 -07:00
|
|
|
Update,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "d")]
|
|
|
|
|
#[strum(to_string = "d")]
|
2025-05-26 22:08:21 -07:00
|
|
|
Delete,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "D")]
|
|
|
|
|
#[strum(to_string = "D")]
|
2025-05-26 22:08:21 -07:00
|
|
|
Truncate,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "x")]
|
|
|
|
|
#[strum(to_string = "x")]
|
2025-05-26 22:08:21 -07:00
|
|
|
References,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "t")]
|
|
|
|
|
#[strum(to_string = "t")]
|
2025-05-26 22:08:21 -07:00
|
|
|
Trigger,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "C")]
|
|
|
|
|
#[strum(to_string = "C")]
|
2025-05-26 22:08:21 -07:00
|
|
|
Create,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "c")]
|
|
|
|
|
#[strum(to_string = "c")]
|
2025-05-26 22:08:21 -07:00
|
|
|
Connect,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "T")]
|
|
|
|
|
#[strum(to_string = "T")]
|
2025-05-26 22:08:21 -07:00
|
|
|
Temporary,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "X")]
|
|
|
|
|
#[strum(to_string = "X")]
|
2025-05-26 22:08:21 -07:00
|
|
|
Execute,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "U")]
|
|
|
|
|
#[strum(to_string = "U")]
|
2025-05-26 22:08:21 -07:00
|
|
|
Usage,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "s")]
|
|
|
|
|
#[strum(to_string = "s")]
|
2025-05-26 22:08:21 -07:00
|
|
|
Set,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "A")]
|
|
|
|
|
#[strum(to_string = "A")]
|
2025-05-26 22:08:21 -07:00
|
|
|
AlterSystem,
|
2025-10-22 00:43:53 -07:00
|
|
|
#[serde(rename = "m")]
|
|
|
|
|
#[strum(to_string = "m")]
|
2025-05-26 22:08:21 -07:00
|
|
|
Maintain,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Decode<'a, Postgres> for PgAclItem {
|
|
|
|
|
fn decode(value: PgValueRef<'a>) -> Result<Self, BoxDynError> {
|
|
|
|
|
let acl_item_str = <&str as Decode<Postgres>>::decode(value)?;
|
|
|
|
|
let (remainder, acl_item) = parse_acl_item::<(_, nom::error::ErrorKind)>(acl_item_str)
|
|
|
|
|
.map_err(|err| err.to_owned())?;
|
|
|
|
|
assert_eq!(remainder, "");
|
|
|
|
|
Ok(acl_item)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl sqlx::Type<Postgres> for PgAclItem {
|
|
|
|
|
fn type_info() -> PgTypeInfo {
|
|
|
|
|
PgTypeInfo::with_name("aclitem")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PgHasArrayType for PgAclItem {
|
|
|
|
|
fn array_type_info() -> PgTypeInfo {
|
|
|
|
|
PgTypeInfo::array_of("aclitem")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_acl_item<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, PgAclItem, E> {
|
|
|
|
|
let (remainder, grantee) = parse_identifier(input)?;
|
|
|
|
|
let (remainder, _) = char('=').parse(remainder)?;
|
|
|
|
|
let (remainder, privileges) = parse_privileges(remainder)?;
|
|
|
|
|
let (remainder, _) = char('/').parse(remainder)?;
|
|
|
|
|
let (remainder, grantor) = parse_identifier(remainder)?;
|
|
|
|
|
Ok((
|
|
|
|
|
remainder,
|
|
|
|
|
PgAclItem {
|
|
|
|
|
grantee,
|
|
|
|
|
privileges,
|
|
|
|
|
grantor,
|
|
|
|
|
},
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_identifier<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, String, E> {
|
|
|
|
|
alt((
|
|
|
|
|
delimited(char('"'), parse_escaped_identifier, char('"')),
|
|
|
|
|
parse_plain_identifier,
|
|
|
|
|
))
|
|
|
|
|
.parse(input)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// WARNING: This works correctly only for identifiers read from aclitem
|
|
|
|
|
/// strings, as they are quote-escaped whenever the identifier contains
|
|
|
|
|
/// characters other than alphanumerics and underscores.
|
|
|
|
|
fn parse_plain_identifier<'a, E: ParseError<&'a str>>(
|
|
|
|
|
input: &'a str,
|
|
|
|
|
) -> IResult<&'a str, String, E> {
|
|
|
|
|
take_till(|c: char| !c.is_alphanum() && c != '_')
|
|
|
|
|
.parse(input)
|
|
|
|
|
.map(|(remainder, parsed)| (remainder, parsed.to_owned()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_escaped_identifier<'a, E: ParseError<&'a str>>(
|
|
|
|
|
input: &'a str,
|
|
|
|
|
) -> IResult<&'a str, String, E> {
|
|
|
|
|
let (remainder, parsed) = many1(alt((value("\"", tag("\"\"")), is_not("\"")))).parse(input)?;
|
|
|
|
|
Ok((remainder, parsed.join("")))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_privileges<'a, E: ParseError<&'a str>>(
|
|
|
|
|
input: &'a str,
|
|
|
|
|
) -> IResult<&'a str, Vec<PgPrivilege>, E> {
|
|
|
|
|
many0(parse_privilege).parse(input)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_privilege<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, PgPrivilege, E> {
|
2025-10-22 00:43:53 -07:00
|
|
|
// [`tag`] does not take owned [`String`]s, so we must first store those in
|
|
|
|
|
// a [`Vec`] whose items can be referenced durably throughout this function.
|
|
|
|
|
let branches_owned: Vec<_> = PgPrivilegeType::iter()
|
|
|
|
|
.map(|priv_type| (priv_type, priv_type.to_string()))
|
|
|
|
|
.collect();
|
|
|
|
|
// [`alt`] can take a slice of branches as its `List` argument, but it must
|
|
|
|
|
// be mutable for some reason.
|
|
|
|
|
let mut branches: Vec<_> = branches_owned
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(branch_value, branch_tag_owned)| value(branch_value, tag(branch_tag_owned.as_str())))
|
|
|
|
|
.collect();
|
|
|
|
|
let (remainder, priv_type) = alt(branches.as_mut_slice()).parse(input)?;
|
2025-05-26 22:08:21 -07:00
|
|
|
let (remainder, parsed_grant_option) = opt(char('*')).parse(remainder)?;
|
|
|
|
|
Ok((
|
|
|
|
|
remainder,
|
|
|
|
|
PgPrivilege {
|
|
|
|
|
grant_option: parsed_grant_option.is_some(),
|
2025-10-22 00:43:53 -07:00
|
|
|
privilege: *priv_type,
|
2025-05-26 22:08:21 -07:00
|
|
|
},
|
|
|
|
|
))
|
|
|
|
|
}
|