use std::fmt;
use crate::Request;
pub trait Identity: Send + Sync + 'static {
fn is_guest(&self) -> bool;
fn csrf_binding(&self) -> Option<&[u8]> {
None
}
fn set_cookie(&self) -> Option<&str> {
None
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Guest;
impl Identity for Guest {
#[inline]
fn is_guest(&self) -> bool {
true
}
}
pub trait Gate: Send + Sync + 'static {
type Identity: Identity;
type Rule: Send + Sync + 'static;
fn authenticate(&self, req: &Request<'_>) -> Result<Self::Identity, Denial>;
fn authorize(
&self,
who: &Self::Identity,
rule: &Self::Rule,
req: &Request<'_>,
) -> Result<(), Denial>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NoRule {}
#[derive(Clone, Copy, Debug, Default)]
pub struct Open;
impl Gate for Open {
type Identity = Guest;
type Rule = NoRule;
#[inline]
fn authenticate(&self, _req: &Request<'_>) -> Result<Guest, Denial> {
Ok(Guest)
}
fn authorize(&self, _who: &Guest, rule: &NoRule, _req: &Request<'_>) -> Result<(), Denial> {
match *rule {}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DenialKind {
SignIn,
Forbidden,
Hidden,
Status(u16),
}
#[derive(Clone, PartialEq, Eq)]
pub struct Denial {
kind: DenialKind,
code: &'static str,
cookie: Option<Box<str>>,
}
impl Denial {
#[must_use]
pub const fn sign_in(code: &'static str) -> Self {
Self {
kind: DenialKind::SignIn,
code,
cookie: None,
}
}
#[must_use]
pub const fn forbidden(code: &'static str) -> Self {
Self {
kind: DenialKind::Forbidden,
code,
cookie: None,
}
}
#[must_use]
pub const fn hidden(code: &'static str) -> Self {
Self {
kind: DenialKind::Hidden,
code,
cookie: None,
}
}
#[must_use]
pub const fn status(status: u16, code: &'static str) -> Self {
let kind = if matches!(status, 400..=499) {
DenialKind::Status(status)
} else {
DenialKind::Forbidden
};
Self {
kind,
code,
cookie: None,
}
}
#[must_use]
pub fn with_cookie(mut self, set_cookie: impl Into<Box<str>>) -> Self {
self.cookie = Some(set_cookie.into());
self
}
#[must_use]
pub fn cookie(&self) -> Option<&str> {
self.cookie.as_deref()
}
#[must_use]
pub const fn kind(&self) -> DenialKind {
self.kind
}
#[must_use]
pub const fn code(&self) -> &'static str {
self.code
}
#[must_use]
pub const fn status_code(&self) -> u16 {
match self.kind {
DenialKind::SignIn => 401,
DenialKind::Forbidden => 403,
DenialKind::Hidden => 404,
DenialKind::Status(s) => s,
}
}
}
impl fmt::Debug for Denial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Denial")
.field("kind", &self.kind)
.field("code", &self.code)
.field("cookie", &self.cookie.as_ref().map(|_| "<set>"))
.finish()
}
}
impl fmt::Display for Denial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "denied ({}): {}", self.status_code(), self.code)
}
}
impl std::error::Error for Denial {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_denial_statuses() {
assert_eq!(Denial::sign_in("a").status_code(), 401);
assert_eq!(Denial::forbidden("a").status_code(), 403);
assert_eq!(Denial::hidden("a").status_code(), 404);
assert_eq!(Denial::status(429, "a").status_code(), 429);
assert_eq!(Denial::status(200, "a").status_code(), 403);
assert_eq!(Denial::status(500, "a").status_code(), 403);
assert_eq!(Denial::forbidden("x.y").code(), "x.y");
}
}