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 write_problem(ctx, 401, "Authentication required").await;
79 return Ok(());
80 }
81 Some(claims) => self
82 .authorization
83 .iter()
84 .find(|rule| !rule.matches(claims.roles(), claims.permissions()))
85 .map(|rule| (rule.roles, rule.permissions)),
86 };
87
88 if let Some((roles, permissions)) = denied {
89 let mut ext = HashMap::new();
90 ext.insert("required_roles".into(), serde_json::json!(roles));
91 ext.insert(
92 "required_permissions".into(),
93 serde_json::json!(permissions),
94 );
95 write_forbidden(ctx, "Forbidden: insufficient permissions", ext).await;
96 return Ok(());
97 }
98
99 if let Some(ref authorizers) = self.authorizers {
101 if !authorizers.is_empty() {
102 if let Some(claims) = ctx.claims() {
103 let resource_key = ctx.request().route_pattern().unwrap_or(self.path);
104 let method = ctx.request().method();
105 authorizers.authorize(claims, resource_key, method).await?;
106 }
107 }
108 }
109 }
110
111 let response = dispatch(&mut *ctx).await?;
112
113 ctx.response_mut().set_status(response.status);
114 if !response.content_type.is_empty() {
115 ctx.response_mut()
116 .set_header("content-type", &response.content_type);
117 }
118 for (key, value) in &response.headers {
119 ctx.response_mut().set_header(key, value);
120 }
121 ctx.response_mut().write_body(response.body).await?;
122 return Ok(());
123 }
124
125 write_problem(
127 ctx,
128 501,
129 &format!(
130 "Route handler not implemented: {} {} (request type: {})",
131 self.method, self.path, self.handler_type
132 ),
133 )
134 .await;
135 Ok(())
136 }
137}
138
139#[allow(clippy::type_complexity)]
141pub struct ControllerEndpoint {
142 handler: Box<
143 dyn Fn(
144 &mut dyn IHttpContext,
145 )
146 -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + '_>>
147 + Send
148 + Sync,
149 >,
150}
151
152#[async_trait::async_trait]
153impl IEndpoint for ControllerEndpoint {
154 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
155 (self.handler)(ctx).await
156 }
157}
158
159impl ControllerEndpoint {
160 pub fn new<F>(f: F) -> Self
161 where
162 F: for<'a> Fn(
163 &'a mut dyn IHttpContext,
164 ) -> std::pin::Pin<
165 Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>,
166 > + Send
167 + Sync
168 + 'static,
169 {
170 Self {
171 handler: Box::new(f),
172 }
173 }
174}
175
176pub struct StaticJsonEndpoint {
182 pub body: Vec<u8>,
183 pub content_type: &'static str,
184}
185
186impl StaticJsonEndpoint {
187 pub fn new(body: Vec<u8>) -> Self {
189 Self {
190 body,
191 content_type: "application/json",
192 }
193 }
194}
195
196#[async_trait::async_trait]
197impl IEndpoint for StaticJsonEndpoint {
198 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
199 ctx.response_mut().set_status(200);
200 ctx.response_mut()
201 .set_header("content-type", self.content_type);
202 ctx.response_mut().write_bytes(self.body.clone()).await
203 }
204}
205
206pub struct StaticHtmlEndpoint {
210 pub body: &'static str,
211}
212
213#[async_trait::async_trait]
214impl IEndpoint for StaticHtmlEndpoint {
215 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
216 ctx.response_mut().set_status(200);
217 ctx.response_mut()
218 .set_header("content-type", "text/html; charset=utf-8");
219 ctx.response_mut()
220 .write_bytes(self.body.as_bytes().to_vec())
221 .await
222 }
223}