use std::{fmt::Display, str::FromStr};
use snafu::Snafu;
use uuid::Uuid;
#[derive(Debug, Snafu)]
pub enum ParsingError {
#[snafu(display("Invalid access method: `{method}`"))]
InvalidAccessMethod { method: String },
#[snafu(display("String was not a PolicyUser casbin string: `{user}`"))]
PolicyUser { user: String },
#[snafu(display("String was not a PolicyInvite casbin string: `{invite}`"))]
PolicyInvite { invite: String },
#[snafu(display("String was not a PolicyInternalGroup casbin string: `{group}"))]
PolicyInternalGroup { group: String },
#[snafu(display("String was not a PolicyOPGroup casbin string: `{group}`"))]
PolicyOPGroup { group: String },
#[snafu(display("Invalid UUID: {source}"), context(false))]
Uuid {
#[snafu(source(from(uuid::Error, Box::new)))]
source: Box<uuid::Error>,
},
#[snafu(display("Custom: {message}"), whatever)]
Custom {
message: String,
#[snafu(source(from(Box<dyn std::error::Error + Send + Sync>, Some)))]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
}
pub trait IsSubject {}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct PolicyUser(pub(crate) uuid::Uuid);
impl IsSubject for PolicyUser {}
impl PolicyUser {
pub const fn nil() -> Self {
Self(Uuid::nil())
}
pub const fn from_u128(id: u128) -> Self {
Self(Uuid::from_u128(id))
}
pub fn generate() -> Self {
Self(Uuid::new_v4())
}
}
impl Display for PolicyUser {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<uuid::Uuid> for PolicyUser {
fn from(user: uuid::Uuid) -> Self {
PolicyUser(user)
}
}
impl FromStr for PolicyUser {
type Err = ParsingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with("user::") {
Ok(PolicyUser(uuid::Uuid::from_str(
s.trim_start_matches("user::"),
)?))
} else {
PolicyUserSnafu { user: s.to_owned() }.fail()
}
}
}
impl AsRef<uuid::Uuid> for PolicyUser {
fn as_ref(&self) -> &uuid::Uuid {
&self.0
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct PolicyInvite(pub(crate) uuid::Uuid);
impl IsSubject for PolicyInvite {}
impl PolicyInvite {
pub const fn nil() -> Self {
Self(Uuid::nil())
}
pub const fn from_u128(id: u128) -> Self {
Self(Uuid::from_u128(id))
}
pub fn generate() -> Self {
Self(Uuid::new_v4())
}
}
impl From<uuid::Uuid> for PolicyInvite {
fn from(invite: uuid::Uuid) -> Self {
PolicyInvite(invite)
}
}
impl FromStr for PolicyInvite {
type Err = ParsingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with("invite::") {
Ok(PolicyInvite(uuid::Uuid::from_str(
s.trim_start_matches("invite::"),
)?))
} else {
PolicyInviteSnafu {
invite: s.to_owned(),
}
.fail()
}
}
}
impl AsRef<uuid::Uuid> for PolicyInvite {
fn as_ref(&self) -> &uuid::Uuid {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PolicyRole(pub(crate) String);
impl IsSubject for PolicyRole {}
impl From<String> for PolicyRole {
fn from(group: String) -> Self {
PolicyRole(group)
}
}
impl From<&str> for PolicyRole {
fn from(group: &str) -> Self {
PolicyRole(group.to_string())
}
}
impl FromStr for PolicyRole {
type Err = ParsingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with("role::") {
Ok(PolicyRole(s.trim_start_matches("role::").to_string()))
} else {
PolicyInternalGroupSnafu {
group: s.to_owned(),
}
.fail()
}
}
}
impl AsRef<str> for PolicyRole {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PolicyGroup(pub(crate) String);
impl IsSubject for PolicyGroup {}
impl From<String> for PolicyGroup {
fn from(group: String) -> Self {
PolicyGroup(group)
}
}
impl From<&str> for PolicyGroup {
fn from(group: &str) -> Self {
PolicyGroup(group.to_string())
}
}
impl FromStr for PolicyGroup {
type Err = ParsingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.starts_with("group::") {
Ok(PolicyGroup(s.trim_start_matches("group::").to_string()))
} else {
PolicyOPGroupSnafu {
group: s.to_owned(),
}
.fail()
}
}
}
pub struct UserToRole(pub PolicyUser, pub PolicyRole);
pub struct UserToGroup(pub PolicyUser, pub PolicyGroup);
pub struct GroupToRole(pub PolicyGroup, pub PolicyRole);
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::subject::{ParsingError, PolicyInvite, PolicyUser};
#[test]
fn test_policy_invite_invalid_uuid() {
let raw_invite = "invite::00000000-0000-0000c0000-000000000000";
let parsing_result = PolicyInvite::from_str(raw_invite);
assert!(
matches!(parsing_result, Err(ParsingError::Uuid { source: _ }),),
"Expected Uuid error, Got: {:?}",
parsing_result,
);
}
#[test]
fn test_policy_user_invalid_uuid() {
let raw_invite = "user::00000000-0000-0000c0000-000000000000";
let parsing_result = PolicyUser::from_str(raw_invite);
assert!(
matches!(parsing_result, Err(ParsingError::Uuid { source: _ }),),
"Expected Uuid error, Got: {:?}",
parsing_result,
);
}
}