1use serde_json;
4use std::sync::Arc;
5use webx_core::error::Result;
6use webx_core::http::IHttpContext;
7use webx_core::routing::IEndpoint;
8
9use crate::authz::AuthorizerSet;
10use crate::problem_response::{write_forbidden, write_problem};
11use std::collections::HashMap;
12
13#[allow(clippy::type_complexity)]
15pub struct RequestEndpoint {
16 handler: Box<
17 dyn Fn(
18 &mut dyn IHttpContext,
19 )
20 -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + '_>>
21 + Send
22 + Sync,
23 >,
24}
25
26#[async_trait::async_trait]
27impl IEndpoint for RequestEndpoint {
28 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
29 (self.handler)(ctx).await
30 }
31}
32
33impl RequestEndpoint {
34 pub fn new<F>(f: F) -> Self
35 where
36 F: for<'a> Fn(
37 &'a mut dyn IHttpContext,
38 ) -> std::pin::Pin<
39 Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>,
40 > + Send
41 + Sync
42 + 'static,
43 {
44 Self {
45 handler: Box::new(f),
46 }
47 }
48}
49
50pub struct StubEndpoint {
57 pub method: &'static str,
58 pub path: &'static str,
59 pub handler_type: &'static str,
60 pub dispatch_fn: Option<webx_core::route::scan::RouteDispatchFn>,
61 pub authorization: &'static [webx_core::route::scan::AuthorizeRule],
64 pub authorizers: Option<Arc<AuthorizerSet>>,
68}
69
70#[async_trait::async_trait]
71impl IEndpoint for StubEndpoint {
72 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
73 if let Some(dispatch) = self.dispatch_fn {
74 if !self.authorization.is_empty() {
76 let denied = match ctx.claims() {
77 None => {
78 return Err(webx_core::error::Error::Unauthorized(
84 "authentication required".to_string(),
85 ));
86 }
87 Some(claims) => self
88 .authorization
89 .iter()
90 .find(|rule| !rule.matches(claims.roles(), claims.permissions()))
91 .map(|rule| (rule.roles, rule.permissions)),
92 };
93
94 if let Some((roles, permissions)) = denied {
95 let mut ext = HashMap::new();
96 ext.insert("required_roles".into(), serde_json::json!(roles));
97 ext.insert(
98 "required_permissions".into(),
99 serde_json::json!(permissions),
100 );
101 write_forbidden(ctx, "Forbidden: insufficient permissions", ext).await;
102 return Ok(());
103 }
104
105 if let Some(ref authorizers) = self.authorizers {
107 if !authorizers.is_empty() {
108 if let Some(claims) = ctx.claims() {
109 let resource_key = ctx.request().route_pattern().unwrap_or(self.path);
110 let method = ctx.request().method();
111 authorizers.authorize(claims, resource_key, method).await?;
112 }
113 }
114 }
115 }
116
117 let response = dispatch(&mut *ctx).await?;
118 response.execute(&mut *ctx).await?;
119 return Ok(());
120 }
121
122 write_problem(
124 ctx,
125 501,
126 &format!(
127 "Route handler not implemented: {} {} (request type: {})",
128 self.method, self.path, self.handler_type
129 ),
130 )
131 .await;
132 Ok(())
133 }
134}
135
136#[allow(clippy::type_complexity)]
138pub struct ControllerEndpoint {
139 handler: Box<
140 dyn Fn(
141 &mut dyn IHttpContext,
142 )
143 -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + '_>>
144 + Send
145 + Sync,
146 >,
147}
148
149#[async_trait::async_trait]
150impl IEndpoint for ControllerEndpoint {
151 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
152 (self.handler)(ctx).await
153 }
154}
155
156impl ControllerEndpoint {
157 pub fn new<F>(f: F) -> Self
158 where
159 F: for<'a> Fn(
160 &'a mut dyn IHttpContext,
161 ) -> std::pin::Pin<
162 Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>,
163 > + Send
164 + Sync
165 + 'static,
166 {
167 Self {
168 handler: Box::new(f),
169 }
170 }
171}
172
173pub struct StaticJsonEndpoint {
179 pub body: Vec<u8>,
180 pub content_type: &'static str,
181}
182
183impl StaticJsonEndpoint {
184 pub fn new(body: Vec<u8>) -> Self {
186 Self {
187 body,
188 content_type: "application/json",
189 }
190 }
191}
192
193#[async_trait::async_trait]
194impl IEndpoint for StaticJsonEndpoint {
195 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
196 ctx.response_mut().set_status(200);
197 ctx.response_mut()
198 .set_header("content-type", self.content_type);
199 ctx.response_mut().write_bytes(self.body.clone()).await
200 }
201}
202
203pub struct StaticHtmlEndpoint {
207 pub body: &'static str,
208}
209
210#[async_trait::async_trait]
211impl IEndpoint for StaticHtmlEndpoint {
212 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
213 ctx.response_mut().set_status(200);
214 ctx.response_mut()
215 .set_header("content-type", "text/html; charset=utf-8");
216 ctx.response_mut()
217 .write_bytes(self.body.as_bytes().to_vec())
218 .await
219 }
220}