rust_webx_host/
endpoint.rs1use rust_webx_core::error::Result;
4use rust_webx_core::http::IHttpContext;
5use rust_webx_core::routing::IEndpoint;
6use serde_json;
7use std::sync::Arc;
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
50#[allow(clippy::type_complexity)]
57pub struct StubEndpoint {
58 pub method: &'static str,
59 pub path: &'static str,
60 pub handler_type: &'static str,
61 pub dispatch_fn: Option<
62 fn(
63 Vec<u8>,
64 std::collections::HashMap<String, String>,
65 std::collections::HashMap<String, String>,
66 Option<Box<dyn rust_webx_core::auth::IClaims>>,
67 ) -> std::pin::Pin<
68 Box<dyn std::future::Future<Output = Result<rust_webx_core::route::scan::ResponseData>> + Send>,
69 >,
70 >,
71 pub auth_required_role: &'static str,
72 pub authorizers: Option<Arc<AuthorizerSet>>,
76}
77
78#[async_trait::async_trait]
79impl IEndpoint for StubEndpoint {
80 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
81 if let Some(dispatch) = self.dispatch_fn {
82 let body_bytes = ctx.request().body_bytes().await.unwrap_or_default();
83 let route_params = ctx.request().route_params().clone();
84 let query_params = ctx.request().query().clone();
85
86 let claims = ctx.claims().map(|c| c.clone_box());
89
90 if !self.auth_required_role.is_empty() {
92 match ctx.claims() {
93 None => {
94 write_problem(ctx, 401, "Authentication required").await;
95 return Ok(());
96 }
97 Some(claims) => {
98 if self.auth_required_role != "authenticated"
99 && !claims
100 .roles()
101 .iter()
102 .any(|r| r.as_str() == self.auth_required_role)
103 {
104 let mut ext = HashMap::new();
105 ext.insert(
106 "required_role".into(),
107 serde_json::Value::String(self.auth_required_role.to_string()),
108 );
109 write_forbidden(
110 ctx,
111 "Forbidden: insufficient permissions",
112 ext,
113 )
114 .await;
115 return Ok(());
116 }
117 }
118 }
119 }
120
121 if !self.auth_required_role.is_empty() {
123 if let Some(ref authorizers) = self.authorizers {
124 if !authorizers.is_empty() {
125 if let Some(claims) = ctx.claims() {
126 let resource_key = ctx.request().route_pattern().unwrap_or(self.path);
127 let method = ctx.request().method();
128 authorizers.authorize(claims, resource_key, method).await?;
129 }
130 }
131 }
132 }
133
134 let response = dispatch(body_bytes, route_params, query_params, claims).await?;
135
136 ctx.response_mut().set_status(response.status);
137 ctx.response_mut()
138 .set_header("content-type", &response.content_type);
139 ctx.response_mut().write_bytes(response.body).await?;
140 return Ok(());
141 }
142
143 ctx.response_mut().set_status(200);
145 ctx.response_mut()
146 .write_text(&format!(
147 "Matched route: {} {} (handler: {})",
148 self.method, self.path, self.handler_type
149 ))
150 .await?;
151 Ok(())
152 }
153}
154
155#[allow(clippy::type_complexity)]
157pub struct ControllerEndpoint {
158 handler: Box<
159 dyn Fn(
160 &mut dyn IHttpContext,
161 )
162 -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + '_>>
163 + Send
164 + Sync,
165 >,
166}
167
168#[async_trait::async_trait]
169impl IEndpoint for ControllerEndpoint {
170 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
171 (self.handler)(ctx).await
172 }
173}
174
175impl ControllerEndpoint {
176 pub fn new<F>(f: F) -> Self
177 where
178 F: for<'a> Fn(
179 &'a mut dyn IHttpContext,
180 ) -> std::pin::Pin<
181 Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>,
182 > + Send
183 + Sync
184 + 'static,
185 {
186 Self {
187 handler: Box::new(f),
188 }
189 }
190}
191
192pub struct StaticJsonEndpoint {
198 pub body: Vec<u8>,
199 pub content_type: &'static str,
200}
201
202impl StaticJsonEndpoint {
203 pub fn new(body: Vec<u8>) -> Self {
205 Self {
206 body,
207 content_type: "application/json",
208 }
209 }
210}
211
212#[async_trait::async_trait]
213impl IEndpoint for StaticJsonEndpoint {
214 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
215 ctx.response_mut().set_status(200);
216 ctx.response_mut()
217 .set_header("content-type", self.content_type);
218 ctx.response_mut().write_bytes(self.body.clone()).await
219 }
220}
221
222pub struct StaticHtmlEndpoint {
226 pub body: &'static str,
227}
228
229#[async_trait::async_trait]
230impl IEndpoint for StaticHtmlEndpoint {
231 async fn handle(&self, ctx: &mut dyn IHttpContext) -> Result<()> {
232 ctx.response_mut().set_status(200);
233 ctx.response_mut()
234 .set_header("content-type", "text/html; charset=utf-8");
235 ctx.response_mut()
236 .write_bytes(self.body.as_bytes().to_vec())
237 .await
238 }
239}