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
119 ctx.response_mut().set_status(response.status);
120 if !response.content_type.is_empty() {
121 ctx.response_mut()
122 .set_header("content-type", &response.content_type);
123 }
124 for (key, value) in &response.headers {
125 ctx.response_mut().set_header(key, value);
126 }
127 ctx.response_mut().write_body(response.body).await?;
128 return Ok(());
129 }
130
131 write_problem(
133 ctx,
134 501,
135 &format!(
136 "Route handler not implemented: {} {} (request type: {})",
137 self.method, self.path, self.handler_type
138 ),
139 )
140 .await;
141 Ok(())
142 }
143}
144
145#[allow(clippy::type_complexity)]
147pub struct ControllerEndpoint {
148 handler: Box<
149 dyn Fn(
150 &mut dyn IHttpContext,
151 )
152 -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + '_>>
153 + Send
154 + Sync,
155 >,
156}
157
158#[async_trait::async_trait]
159impl IEndpoint for ControllerEndpoint {
160 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
161 (self.handler)(ctx).await
162 }
163}
164
165impl ControllerEndpoint {
166 pub fn new<F>(f: F) -> Self
167 where
168 F: for<'a> Fn(
169 &'a mut dyn IHttpContext,
170 ) -> std::pin::Pin<
171 Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>,
172 > + Send
173 + Sync
174 + 'static,
175 {
176 Self {
177 handler: Box::new(f),
178 }
179 }
180}
181
182pub struct StaticJsonEndpoint {
188 pub body: Vec<u8>,
189 pub content_type: &'static str,
190}
191
192impl StaticJsonEndpoint {
193 pub fn new(body: Vec<u8>) -> Self {
195 Self {
196 body,
197 content_type: "application/json",
198 }
199 }
200}
201
202#[async_trait::async_trait]
203impl IEndpoint for StaticJsonEndpoint {
204 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
205 ctx.response_mut().set_status(200);
206 ctx.response_mut()
207 .set_header("content-type", self.content_type);
208 ctx.response_mut().write_bytes(self.body.clone()).await
209 }
210}
211
212pub struct StaticHtmlEndpoint {
216 pub body: &'static str,
217}
218
219#[async_trait::async_trait]
220impl IEndpoint for StaticHtmlEndpoint {
221 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
222 ctx.response_mut().set_status(200);
223 ctx.response_mut()
224 .set_header("content-type", "text/html; charset=utf-8");
225 ctx.response_mut()
226 .write_bytes(self.body.as_bytes().to_vec())
227 .await
228 }
229}