#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
#![forbid(missing_docs)]
#![allow(clippy::needless_return)]
use std::collections::HashMap;
#[cfg(feature = "dot-separator")]
const DEFAULT_SEPARATOR: &str = ".";
#[cfg(feature = "colon-separator")]
const DEFAULT_SEPARATOR: &str = ":";
#[cfg(not(any(feature = "dot-separator", feature = "colon-separator")))]
compile_error!(
"No default separator chosen! Please add a coresponding feature to the dotperms crate."
);
#[cfg(test)]
mod tests;
mod std_trait_impl;
#[allow(unused_imports)]
pub use crate::std_trait_impl::*;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PermNode<'a> {
inter: Vec<&'a str>,
divider: &'a str,
context: HashMap<String, String>,
}
impl<'a> PermNode<'a> {
pub fn new() -> Self {
return PermNode {
inter: Vec::new(),
context: HashMap::new(),
divider: DEFAULT_SEPARATOR,
};
}
pub fn new_with_divider(divider: &'a str) -> Self {
return PermNode {
inter: Vec::new(),
context: HashMap::new(),
divider,
};
}
pub fn push(&mut self, part: &'a str) {
if part == "*" && !self.inter.is_empty() {
return;
}
if part.is_empty() {
return;
}
self.inter.push(part);
}
pub fn pop(&mut self) -> Option<&'a str> {
self.inter.pop()
}
pub fn set(&mut self, perm_node_str: &'a str, divider: &str) {
self.inter.extend(PermNode::after_parse_logic(
perm_node_str.split(divider).collect(),
));
}
pub fn from_str(value: &'a str, divider: &'a str) -> Self {
PermNode {
inter: PermNode::after_parse_logic(value.split(divider).collect()),
context: HashMap::new(),
divider,
}
}
fn after_parse_logic(input: Vec<&'a str>) -> Vec<&'a str> {
if input
.iter()
.any(|el| el.chars().count() > 1 && el.contains("*"))
{
return Vec::new();
}
for i in 0..input.len() {
if i == 0 && input[i] == "*" {
return vec!["*"];
}
if input[i] == "*" || input[i].is_empty() {
let mut new = Vec::with_capacity(i);
new.extend_from_slice(&input[..i]);
return new;
}
}
return input;
}
pub fn allows(&self, requirement: &PermNode) -> bool {
if self.inter.is_empty() {
return false;
}
let context_allowed: bool = 'c: {
for (context_item_key, context_item_value) in self.context.iter() {
let val = requirement.context.get(context_item_key);
if val.is_none() || val.is_some_and(|it| it != context_item_value) {
break 'c false;
}
}
break 'c true;
};
let perm_allowed: bool = 'c: {
let our_part = &self.inter;
let their_part = &requirement.inter;
if their_part.is_empty() {
return false;
}
if our_part.len() == 1 && our_part[0] == "*" {
return true;
}
if their_part.starts_with(our_part) {
break 'c true;
}
break 'c false;
};
return perm_allowed && context_allowed;
}
pub fn set_ctx(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.context.insert(key.into(), value.into());
self
}
pub fn get_ctx(&self, key: &str) -> Option<impl AsRef<str>> {
return self.context.get(key);
}
pub fn ctx_iter(&self) -> impl Iterator<Item = (impl AsRef<str>, impl AsRef<str>)> {
return self.context.iter();
}
pub fn node_iter(&self) -> impl ExactSizeIterator<Item = impl AsRef<str>> {
return self.inter.iter();
}
pub fn node_len(&self) -> usize {
return self.inter.len();
}
}
pub fn check_requirement<'a>(
requirements: impl Iterator<Item = &'a PermNode<'a>>,
permissions: impl Iterator<Item = &'a PermNode<'a>> + Clone,
) -> bool {
for requirement in requirements {
let mut at_least_one_allow = false;
for perm in permissions.clone() {
if perm.allows(requirement) {
at_least_one_allow = true;
}
}
if !at_least_one_allow {
return false;
}
}
return true;
}
#[macro_export]
macro_rules! pn {
($val:expr) => {
PermNode::from($val)
};
}