use crate::builder::AbilityBuilder;
use crate::error::Forbidden;
use crate::rule::{Effect, Rule};
use crate::subject::{IntoTarget, Target};
pub struct Ability<A, S, C> {
claims: C,
rules: Vec<Rule<A, S, C>>,
}
impl<A, S, C> Ability<A, S, C> {
pub(crate) fn new(claims: C, rules: Vec<Rule<A, S, C>>) -> Self {
Self { claims, rules }
}
pub fn builder(claims: C) -> AbilityBuilder<A, S, C> {
AbilityBuilder::new(claims)
}
pub fn claims(&self) -> &C {
&self.claims
}
pub fn can<'t, T, M>(&self, action: A, target: T) -> bool
where
A: PartialEq,
S: PartialEq,
T: IntoTarget<'t, S, M>,
{
self.evaluate(&action, target.into_target())
}
pub fn cannot<'t, T, M>(&self, action: A, target: T) -> bool
where
A: PartialEq,
S: PartialEq,
T: IntoTarget<'t, S, M>,
{
!self.can(action, target)
}
pub fn authorize<'t, T, M>(&self, action: A, target: T) -> Result<(), Forbidden>
where
A: PartialEq,
S: PartialEq,
T: IntoTarget<'t, S, M>,
{
if self.can(action, target) {
Ok(())
} else {
Err(Forbidden)
}
}
fn evaluate(&self, action: &A, target: Target<'_, S>) -> bool
where
A: PartialEq,
S: PartialEq,
{
for rule in self.rules.iter().rev() {
if &rule.action != action || rule.subject != target.subject {
continue;
}
match &rule.condition {
None => return rule.effect == Effect::Allow,
Some(predicate) => {
if let Some(resource) = target.resource
&& predicate(&self.claims, resource)
{
return rule.effect == Effect::Allow;
}
}
}
}
false
}
}