use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::{
fmt,
path::{Path, PathBuf},
str::FromStr,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(transparent)]
pub struct FsPermissions {
pub permissions: Vec<PathPermission>,
}
impl FsPermissions {
pub fn new(permissions: impl IntoIterator<Item = PathPermission>) -> Self {
Self { permissions: permissions.into_iter().collect() }
}
pub fn is_path_allowed(&self, path: &Path, kind: FsAccessKind) -> bool {
self.find_permission(path).map(|perm| perm.is_granted(kind)).unwrap_or_default()
}
pub fn find_permission(&self, path: &Path) -> Option<FsAccessPermission> {
self.permissions.iter().find(|perm| path.starts_with(&perm.path)).map(|perm| perm.access)
}
pub fn join_all(&mut self, root: impl AsRef<Path>) {
let root = root.as_ref();
self.permissions.iter_mut().for_each(|perm| {
perm.path = root.join(&perm.path);
})
}
pub fn joined(mut self, root: impl AsRef<Path>) -> Self {
self.join_all(root);
self
}
pub fn remove(&mut self, path: impl AsRef<Path>) {
let path = path.as_ref();
self.permissions.retain(|permission| permission.path != path)
}
pub fn is_empty(&self) -> bool {
self.permissions.is_empty()
}
pub fn len(&self) -> usize {
self.permissions.len()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct PathPermission {
pub access: FsAccessPermission,
pub path: PathBuf,
}
impl PathPermission {
pub fn new(path: impl Into<PathBuf>, access: FsAccessPermission) -> Self {
Self { path: path.into(), access }
}
pub fn read(path: impl Into<PathBuf>) -> Self {
Self::new(path, FsAccessPermission::Read)
}
pub fn read_write(path: impl Into<PathBuf>) -> Self {
Self::new(path, FsAccessPermission::ReadWrite)
}
pub fn write(path: impl Into<PathBuf>) -> Self {
Self::new(path, FsAccessPermission::Write)
}
pub fn none(path: impl Into<PathBuf>) -> Self {
Self::new(path, FsAccessPermission::None)
}
pub fn is_granted(&self, kind: FsAccessKind) -> bool {
self.access.is_granted(kind)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum FsAccessKind {
Read,
Write,
}
impl fmt::Display for FsAccessKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FsAccessKind::Read => f.write_str("read"),
FsAccessKind::Write => f.write_str("write"),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum FsAccessPermission {
#[default]
None,
ReadWrite,
Read,
Write,
}
impl FsAccessPermission {
pub fn is_granted(&self, kind: FsAccessKind) -> bool {
match (self, kind) {
(FsAccessPermission::ReadWrite, _) => true,
(FsAccessPermission::None, _) => false,
(FsAccessPermission::Read, FsAccessKind::Read) => true,
(FsAccessPermission::Write, FsAccessKind::Write) => true,
_ => false,
}
}
}
impl FromStr for FsAccessPermission {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"true" | "read-write" | "readwrite" => Ok(FsAccessPermission::ReadWrite),
"false" | "none" => Ok(FsAccessPermission::None),
"read" => Ok(FsAccessPermission::Read),
"write" => Ok(FsAccessPermission::Write),
_ => Err(format!("Unknown variant {s}")),
}
}
}
impl fmt::Display for FsAccessPermission {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FsAccessPermission::ReadWrite => f.write_str("read-write"),
FsAccessPermission::None => f.write_str("none"),
FsAccessPermission::Read => f.write_str("read"),
FsAccessPermission::Write => f.write_str("write"),
}
}
}
impl Serialize for FsAccessPermission {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
FsAccessPermission::ReadWrite => serializer.serialize_bool(true),
FsAccessPermission::None => serializer.serialize_bool(false),
FsAccessPermission::Read => serializer.serialize_str("read"),
FsAccessPermission::Write => serializer.serialize_str("write"),
}
}
}
impl<'de> Deserialize<'de> for FsAccessPermission {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum Status {
Bool(bool),
String(String),
}
match Status::deserialize(deserializer)? {
Status::Bool(enabled) => {
let status =
if enabled { FsAccessPermission::ReadWrite } else { FsAccessPermission::None };
Ok(status)
}
Status::String(val) => val.parse().map_err(serde::de::Error::custom),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_parse_permission() {
assert_eq!(FsAccessPermission::ReadWrite, "true".parse().unwrap());
assert_eq!(FsAccessPermission::ReadWrite, "readwrite".parse().unwrap());
assert_eq!(FsAccessPermission::ReadWrite, "read-write".parse().unwrap());
assert_eq!(FsAccessPermission::None, "false".parse().unwrap());
assert_eq!(FsAccessPermission::None, "none".parse().unwrap());
assert_eq!(FsAccessPermission::Read, "read".parse().unwrap());
assert_eq!(FsAccessPermission::Write, "write".parse().unwrap());
}
}