use rust_webx_core::error::Result;
use rust_webx_core::http::IHttpContext;
use rust_webx_core::routing::IEndpoint;
use serde_json;
use std::sync::Arc;
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),
}
}
}
#[allow(clippy::type_complexity)]
pub struct StubEndpoint {
pub method: &'static str,
pub path: &'static str,
pub handler_type: &'static str,
pub dispatch_fn: Option<
fn(
Vec<u8>,
std::collections::HashMap<String, String>,
std::collections::HashMap<String, String>,
Option<Box<dyn rust_webx_core::auth::IClaims>>,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = Result<rust_webx_core::route::scan::ResponseData>> + Send>,
>,
>,
pub auth_required_role: &'static str,
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 {
let body_bytes = ctx.request().body_bytes().await.unwrap_or_default();
let route_params = ctx.request().route_params().clone();
let query_params = ctx.request().query().clone();
let claims = ctx.claims().map(|c| c.clone_box());
if !self.auth_required_role.is_empty() {
match ctx.claims() {
None => {
write_problem(ctx, 401, "Authentication required").await;
return Ok(());
}
Some(claims) => {
if self.auth_required_role != "authenticated"
&& !claims
.roles()
.iter()
.any(|r| r.as_str() == self.auth_required_role)
{
let mut ext = HashMap::new();
ext.insert(
"required_role".into(),
serde_json::Value::String(self.auth_required_role.to_string()),
);
write_forbidden(
ctx,
"Forbidden: insufficient permissions",
ext,
)
.await;
return Ok(());
}
}
}
}
if !self.auth_required_role.is_empty() {
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(body_bytes, route_params, query_params, claims).await?;
ctx.response_mut().set_status(response.status);
ctx.response_mut()
.set_header("content-type", &response.content_type);
ctx.response_mut().write_bytes(response.body).await?;
return Ok(());
}
ctx.response_mut().set_status(200);
ctx.response_mut()
.write_text(&format!(
"Matched route: {} {} (handler: {})",
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
}
}