use std::{borrow::Cow, fmt, str::FromStr};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct LinkPermissions(u8);
impl LinkPermissions {
const OWNER: u8 = 0b001;
const READ: u8 = 0b010;
const WRITE: u8 = 0b100;
pub const fn new(bits: u8) -> Option<Self> {
if bits == 0
|| bits & !(Self::OWNER | Self::READ | Self::WRITE) != 0
|| bits & Self::OWNER != 0 && bits != Self::OWNER
{
None
} else {
Some(Self(bits))
}
}
pub const fn owner() -> Self {
Self(Self::OWNER)
}
pub const fn read() -> Self {
Self(Self::READ)
}
pub const fn write() -> Self {
Self(Self::WRITE)
}
pub const fn read_write() -> Self {
Self(Self::READ | Self::WRITE)
}
pub const fn bits(self) -> u8 {
self.0
}
pub const fn allows_owner(self) -> bool {
self.0 & Self::OWNER != 0
}
pub const fn allows_read(self) -> bool {
self.allows_owner() || self.0 & Self::READ != 0
}
pub const fn allows_write(self) -> bool {
self.allows_owner() || self.0 & Self::WRITE != 0
}
pub const fn as_str(self) -> &'static str {
const READ_WRITE: u8 = LinkPermissions::READ | LinkPermissions::WRITE;
match self.0 {
Self::OWNER => "o",
Self::READ => "r",
Self::WRITE => "w",
READ_WRITE => "rw",
_ => unreachable!(),
}
}
}
impl fmt::Display for LinkPermissions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for LinkPermissions {
type Err = PermissionsError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
if input.is_empty() {
return Err(PermissionsError::Empty);
}
let mut bits = 0;
for ch in input.chars() {
let bit = match ch {
'o' => LinkPermissions::OWNER,
'r' => LinkPermissions::READ,
'w' => LinkPermissions::WRITE,
other => return Err(PermissionsError::UnknownPermission(other)),
};
if bits & bit != 0 {
return Err(PermissionsError::DuplicatePermission(ch));
}
bits |= bit;
}
Self::new(bits).ok_or(PermissionsError::OwnerCannotBeCombined)
}
}
impl Serialize for LinkPermissions {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for LinkPermissions {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Cow::<str>::deserialize(deserializer)?
.parse()
.map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, thiserror::Error, Eq, PartialEq)]
pub enum PermissionsError {
#[error("permission string cannot be empty")]
Empty,
#[error("unknown stream permission {0:?}")]
UnknownPermission(char),
#[error("duplicate stream permission {0:?}")]
DuplicatePermission(char),
#[error("owner permission cannot be combined with read/write because it already includes them")]
OwnerCannotBeCombined,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn formats_every_permission_canonically() {
let cases = [
(LinkPermissions::owner(), "o"),
(LinkPermissions::read(), "r"),
(LinkPermissions::write(), "w"),
(LinkPermissions::read_write(), "rw"),
];
for (permissions, canonical) in cases {
assert_eq!(permissions.as_str(), canonical);
assert_eq!(permissions.to_string(), canonical);
}
}
#[test]
fn parses_data_permissions_in_any_order_and_formats_canonically() {
let permissions: LinkPermissions = "wr".parse().expect("valid permissions");
assert!(!permissions.allows_owner());
assert!(permissions.allows_read());
assert!(permissions.allows_write());
assert_eq!(permissions.to_string(), "rw");
}
#[test]
fn owner_implies_all_effective_permissions() {
let permissions: LinkPermissions = "o".parse().expect("valid permissions");
assert!(permissions.allows_owner());
assert!(permissions.allows_read());
assert!(permissions.allows_write());
assert_eq!(permissions.to_string(), "o");
}
#[test]
fn rejects_empty_unknown_duplicate_and_redundant_owner_permissions() {
assert_eq!("".parse::<LinkPermissions>(), Err(PermissionsError::Empty));
assert_eq!(
"rx".parse::<LinkPermissions>(),
Err(PermissionsError::UnknownPermission('x'))
);
assert_eq!(
"rr".parse::<LinkPermissions>(),
Err(PermissionsError::DuplicatePermission('r'))
);
assert_eq!(
"or".parse::<LinkPermissions>(),
Err(PermissionsError::OwnerCannotBeCombined)
);
assert_eq!(
"ow".parse::<LinkPermissions>(),
Err(PermissionsError::OwnerCannotBeCombined)
);
assert_eq!(
"orw".parse::<LinkPermissions>(),
Err(PermissionsError::OwnerCannotBeCombined)
);
}
#[test]
fn serde_uses_canonical_string_form() {
let permissions: LinkPermissions =
serde_json::from_str("\"wr\"").expect("valid permission JSON");
assert_eq!(permissions.to_string(), "rw");
assert_eq!(
serde_json::to_string(&permissions).expect("serialize permissions"),
"\"rw\""
);
}
}