anycms_core/frameworks/
actix.rs1#[cfg(feature = "actix")]
2use crate::result::{ApiResult, ErrorCode};
3#[cfg(feature = "actix")]
4use actix_web::Error;
5#[cfg(feature = "actix")]
6use actix_web::HttpMessage;
7#[cfg(feature = "actix")]
8use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform, forward_ready};
9#[cfg(feature = "actix")]
10use actix_web::{HttpResponse, Responder};
11#[cfg(feature = "actix")]
12use futures_util::future::LocalBoxFuture;
13#[cfg(feature = "actix")]
14use serde::Serialize;
15#[cfg(feature = "actix")]
16use std::rc::Rc;
17
18#[cfg(feature = "actix")]
19fn error_code_to_status(code: ErrorCode) -> actix_web::http::StatusCode {
20 match code {
21 ErrorCode::Success => actix_web::http::StatusCode::OK,
22 ErrorCode::BadRequest => actix_web::http::StatusCode::BAD_REQUEST,
23 ErrorCode::Unauthorized => actix_web::http::StatusCode::UNAUTHORIZED,
24 ErrorCode::Forbidden => actix_web::http::StatusCode::FORBIDDEN,
25 ErrorCode::NotFound => actix_web::http::StatusCode::NOT_FOUND,
26 ErrorCode::Conflict => actix_web::http::StatusCode::CONFLICT,
27 ErrorCode::ValidationError => actix_web::http::StatusCode::UNPROCESSABLE_ENTITY,
28 ErrorCode::InternalError => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
29 }
30}
31
32#[cfg(feature = "actix")]
41#[derive(Clone)]
42pub struct ApiTraceId(pub String);
43
44#[cfg(feature = "actix")]
50#[derive(Clone)]
51pub struct ApiResultMiddlewareConfig {
52 pub trace_id_headers: Vec<String>,
56
57 pub inject_timestamp: bool,
61}
62
63#[cfg(feature = "actix")]
64impl Default for ApiResultMiddlewareConfig {
65 fn default() -> Self {
66 Self {
67 trace_id_headers: vec!["X-Request-ID".to_string(), "X-Trace-ID".to_string()],
68 inject_timestamp: false,
69 }
70 }
71}
72
73#[cfg(feature = "actix")]
99pub struct ApiResultLayer {
100 config: ApiResultMiddlewareConfig,
101}
102
103#[cfg(feature = "actix")]
104impl ApiResultLayer {
105 pub fn new(config: ApiResultMiddlewareConfig) -> Self {
107 Self { config }
108 }
109}
110
111#[cfg(feature = "actix")]
112impl Default for ApiResultLayer {
113 fn default() -> Self {
114 Self::new(ApiResultMiddlewareConfig::default())
115 }
116}
117
118#[cfg(feature = "actix")]
119impl<S, B> Transform<S, ServiceRequest> for ApiResultLayer
120where
121 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
122 S::Future: 'static,
123 B: 'static,
124{
125 type Response = ServiceResponse<B>;
126 type Error = Error;
127 type InitError = ();
128 type Transform = ApiResultMiddleware<S>;
129 type Future = std::future::Ready<Result<Self::Transform, Self::InitError>>;
130
131 fn new_transform(&self, service: S) -> Self::Future {
132 std::future::ready(Ok(ApiResultMiddleware {
133 service: Rc::new(service),
134 config: self.config.clone(),
135 }))
136 }
137}
138
139#[cfg(feature = "actix")]
145pub struct ApiResultMiddleware<S> {
146 service: Rc<S>,
147 config: ApiResultMiddlewareConfig,
148}
149
150#[cfg(feature = "actix")]
151impl<S, B> Service<ServiceRequest> for ApiResultMiddleware<S>
152where
153 S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
154 S::Future: 'static,
155 B: 'static,
156{
157 type Response = ServiceResponse<B>;
158 type Error = Error;
159 type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
160
161 forward_ready!(service);
162
163 fn call(&self, req: ServiceRequest) -> Self::Future {
164 let service = Rc::clone(&self.service);
165
166 let trace_id = self
168 .config
169 .trace_id_headers
170 .iter()
171 .find_map(|h| {
172 req.headers()
173 .get(h.as_str())
174 .and_then(|v| v.to_str().ok())
175 .map(|s| s.to_string())
176 })
177 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
178
179 req.extensions_mut().insert(ApiTraceId(trace_id));
181
182 Box::pin(async move {
183 let res = service.call(req).await?;
184 Ok(res)
185 })
186 }
187}
188
189#[cfg(feature = "actix")]
198impl<T: Serialize> Responder for ApiResult<T> {
199 type Body = actix_web::body::BoxBody;
200
201 fn respond_to(self, req: &actix_web::HttpRequest) -> actix_web::HttpResponse<Self::Body> {
202 let mut result = self;
203
204 if result.trace_id.is_none()
206 && let Some(tid) = req.extensions().get::<ApiTraceId>()
207 {
208 result = result.with_trace_id(&tid.0);
209 }
210
211 let status = result
212 .code
213 .and_then(ErrorCode::from_i32)
214 .map(error_code_to_status)
215 .unwrap_or_else(|| {
216 if result.success {
217 actix_web::http::StatusCode::OK
218 } else {
219 actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
220 }
221 });
222
223 HttpResponse::build(status).json(result)
224 }
225}