use nom::{ AsChar as _, IResult, Parser, branch::alt, bytes::complete::{is_not, tag, take_till}, character::char, combinator::{opt, value}, error::ParseError, multi::{many0, many1}, sequence::delimited, }; use serde::{Deserialize, Serialize}; use sqlx::{ Decode, Postgres, error::BoxDynError, postgres::{PgHasArrayType, PgTypeInfo, PgValueRef}, }; use strum::IntoEnumIterator as _; /// 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" 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. #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct PgAclItem { pub grantee: String, pub privileges: Vec, pub grantor: String, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] pub struct PgPrivilege { pub grant_option: bool, pub privilege: PgPrivilegeType, } #[derive( Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, strum::Display, strum::EnumIter, strum::EnumString, )] pub enum PgPrivilegeType { #[serde(rename = "r")] #[strum(to_string = "r")] Select, #[serde(rename = "a")] #[strum(to_string = "a")] Insert, #[serde(rename = "w")] #[strum(to_string = "w")] Update, #[serde(rename = "d")] #[strum(to_string = "d")] Delete, #[serde(rename = "D")] #[strum(to_string = "D")] Truncate, #[serde(rename = "x")] #[strum(to_string = "x")] References, #[serde(rename = "t")] #[strum(to_string = "t")] Trigger, #[serde(rename = "C")] #[strum(to_string = "C")] Create, #[serde(rename = "c")] #[strum(to_string = "c")] Connect, #[serde(rename = "T")] #[strum(to_string = "T")] Temporary, #[serde(rename = "X")] #[strum(to_string = "X")] Execute, #[serde(rename = "U")] #[strum(to_string = "U")] Usage, #[serde(rename = "s")] #[strum(to_string = "s")] Set, #[serde(rename = "A")] #[strum(to_string = "A")] AlterSystem, #[serde(rename = "m")] #[strum(to_string = "m")] Maintain, } impl<'a> Decode<'a, Postgres> for PgAclItem { fn decode(value: PgValueRef<'a>) -> Result { let acl_item_str = <&str as Decode>::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 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, E> { many0(parse_privilege).parse(input) } fn parse_privilege<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, PgPrivilege, E> { // [`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)?; let (remainder, parsed_grant_option) = opt(char('*')).parse(remainder)?; Ok(( remainder, PgPrivilege { grant_option: parsed_grant_option.is_some(), privilege: *priv_type, }, )) }