use serde_json;
use std::sync::Arc;
use webx_core::error::Result;
use webx_core::http::IHttpContext;
use webx_core::routing::IEndpoint;
use crate::authz::AuthorizerSet;
use crate::problem_response::{write_forbidden, write_problem};
use std::collections::HashMap;
#[allow(clippy::type_complexity)]
pub struct RequestEndpoint {
handler: Box<
dyn Fn(
&mut dyn IHttpContext,
)
-> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + '_>>
+ Send
+ Sync,
>,
}
#[async_trait::async_trait]
impl IEndpoint for RequestEndpoint {
async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
(self.handler)(ctx).await
}
}
impl RequestEndpoint {
pub fn new<F>(f: F) -> Self
where
F: for<'a> Fn(
&'a mut dyn IHttpContext,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>,
> + Send
+ Sync
+ 'static,
{
Self {
handler: Box::new(f),
}
}
}
pub struct StubEndpoint {
pub method: &'static str,
pub path: &'static str,
pub handler_type: &'static str,
pub dispatch_fn: Option<webx_core::route::scan::RouteDispatchFn>,
pub authorization: &'static [webx_core::route::scan::AuthorizeRule],
pub authorizers: Option<Arc<AuthorizerSet>>,
}
#[async_trait::async_trait]
impl IEndpoint for StubEndpoint {
async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
if let Some(dispatch) = self.dispatch_fn {
if !self.authorization.is_empty() {
let denied = match ctx.claims() {
None => {
return Err(webx_core::error::Error::Unauthorized(
"authentication required".to_string(),
));
}
Some(claims) => self
.authorization
.iter()
.find(|rule| !rule.matches(claims.roles(), claims.permissions()))
.map(|rule| (rule.roles, rule.permissions)),
};
if let Some((roles, permissions)) = denied {
let mut ext = HashMap::new();
ext.insert("required_roles".into(), serde_json::json!(roles));
ext.insert(
"required_permissions".into(),
serde_json::json!(permissions),
);
write_forbidden(ctx, "Forbidden: insufficient permissions", ext).await;
return Ok(());
}
if let Some(ref authorizers) = self.authorizers {
if !authorizers.is_empty() {
if let Some(claims) = ctx.claims() {
let resource_key = ctx.request().route_pattern().unwrap_or(self.path);
let method = ctx.request().method();
authorizers.authorize(claims, resource_key, method).await?;
}
}
}
}
let response = dispatch(&mut *ctx).await?;
response.execute(&mut *ctx).await?;
return Ok(());
}
write_problem(
ctx,
501,
&format!(
"Route handler not implemented: {} {} (request type: {})",
self.method, self.path, self.handler_type
),
)
.await;
Ok(())
}
}
#[allow(clippy::type_complexity)]
pub struct ControllerEndpoint {
handler: Box<
dyn Fn(
&mut dyn IHttpContext,
)
-> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + '_>>
+ Send
+ Sync,
>,
}
#[async_trait::async_trait]
impl IEndpoint for ControllerEndpoint {
async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
(self.handler)(ctx).await
}
}
impl ControllerEndpoint {
pub fn new<F>(f: F) -> Self
where
F: for<'a> Fn(
&'a mut dyn IHttpContext,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>,
> + Send
+ Sync
+ 'static,
{
Self {
handler: Box::new(f),
}
}
}
pub struct StaticJsonEndpoint {
pub body: Vec<u8>,
pub content_type: &'static str,
}
impl StaticJsonEndpoint {
pub fn new(body: Vec<u8>) -> Self {
Self {
body,
content_type: "application/json",
}
}
}
#[async_trait::async_trait]
impl IEndpoint for StaticJsonEndpoint {
async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
ctx.response_mut().set_status(200);
ctx.response_mut()
.set_header("content-type", self.content_type);
ctx.response_mut().write_bytes(self.body.clone()).await
}
}
pub struct StaticHtmlEndpoint {
pub body: &'static str,
}
#[async_trait::async_trait]
impl IEndpoint for StaticHtmlEndpoint {
async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
ctx.response_mut().set_status(200);
ctx.response_mut()
.set_header("content-type", "text/html; charset=utf-8");
ctx.response_mut()
.write_bytes(self.body.as_bytes().to_vec())
.await
}
}