use topcoat::context::Cx;
#[derive(Debug)]
pub enum Ability<'a, M> {
ViewAny,
View(&'a M),
Create,
Update(&'a M),
DeleteAny,
Delete(&'a M),
}
impl<M> Clone for Ability<'_, M> {
fn clone(&self) -> Self {
*self
}
}
impl<M> Copy for Ability<'_, M> {}
impl<'a, M> Ability<'a, M> {
pub fn record(self) -> Option<&'a M> {
match self {
Self::View(record) | Self::Update(record) | Self::Delete(record) => Some(record),
Self::ViewAny | Self::Create | Self::DeleteAny => None,
}
}
pub fn is_read(self) -> bool {
matches!(self, Self::ViewAny | Self::View(_))
}
}
pub trait Policy<M>: Send + Sync + 'static {
fn allows(&self, cx: &Cx, ability: Ability<'_, M>) -> bool;
}
macro_rules! combinators {
($($ty:ident $(<$($param:ident),+>)?),+ $(,)?) => {$(
impl$(<$($param),+>)? $ty$(<$($param),+>)? {
pub fn and<P>(self, other: P) -> And<Self, P> {
And(self, other)
}
pub fn or<P>(self, other: P) -> Or<Self, P> {
Or(self, other)
}
}
)+};
}
combinators!(Allow, Deny, ReadOnly, When<F>, And<A, B>, Or<A, B>);
impl<M, F> Policy<M> for F
where
F: Fn(&Cx, Ability<'_, M>) -> bool + Send + Sync + 'static,
{
fn allows(&self, cx: &Cx, ability: Ability<'_, M>) -> bool {
self(cx, ability)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Allow;
impl<M> Policy<M> for Allow {
fn allows(&self, _cx: &Cx, _ability: Ability<'_, M>) -> bool {
true
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Deny;
impl<M> Policy<M> for Deny {
fn allows(&self, _cx: &Cx, _ability: Ability<'_, M>) -> bool {
false
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ReadOnly;
impl<M> Policy<M> for ReadOnly {
fn allows(&self, _cx: &Cx, ability: Ability<'_, M>) -> bool {
ability.is_read()
}
}
pub fn when<F>(predicate: F) -> When<F>
where
F: Fn(&Cx) -> bool + Send + Sync + 'static,
{
When(predicate)
}
#[derive(Debug, Clone, Copy)]
pub struct When<F>(F);
impl<M, F> Policy<M> for When<F>
where
F: Fn(&Cx) -> bool + Send + Sync + 'static,
{
fn allows(&self, cx: &Cx, _ability: Ability<'_, M>) -> bool {
(self.0)(cx)
}
}
#[derive(Debug, Clone, Copy)]
pub struct And<A, B>(A, B);
impl<M, A: Policy<M>, B: Policy<M>> Policy<M> for And<A, B> {
fn allows(&self, cx: &Cx, ability: Ability<'_, M>) -> bool {
self.0.allows(cx, ability) && self.1.allows(cx, ability)
}
}
#[derive(Debug, Clone, Copy)]
pub struct Or<A, B>(A, B);
impl<M, A: Policy<M>, B: Policy<M>> Policy<M> for Or<A, B> {
fn allows(&self, cx: &Cx, ability: Ability<'_, M>) -> bool {
self.0.allows(cx, ability) || self.1.allows(cx, ability)
}
}
#[cfg(test)]
mod tests;