1use actix_web::{error::ResponseError, web, FromRequest, HttpRequest, HttpResponse};
107use domainstack::ValidationError;
108use futures::future::{ready, Ready};
109use std::marker::PhantomData;
110
111pub struct DomainJson<T, Dto = ()> {
112 pub domain: T,
113 _dto: PhantomData<Dto>,
114}
115
116impl<T, Dto> DomainJson<T, Dto> {
117 pub fn new(domain: T) -> Self {
118 Self {
119 domain,
120 _dto: PhantomData,
121 }
122 }
123}
124
125pub struct ErrorResponse(pub error_envelope::Error);
126
127impl From<error_envelope::Error> for ErrorResponse {
128 fn from(err: error_envelope::Error) -> Self {
129 ErrorResponse(err)
130 }
131}
132
133impl From<ValidationError> for ErrorResponse {
134 fn from(err: ValidationError) -> Self {
135 use domainstack_envelope::IntoEnvelopeError;
136 ErrorResponse(err.into_envelope_error())
137 }
138}
139
140impl<T, Dto> FromRequest for DomainJson<T, Dto>
141where
142 Dto: serde::de::DeserializeOwned,
143 T: TryFrom<Dto, Error = ValidationError>,
144{
145 type Error = ErrorResponse;
146 type Future = Ready<Result<Self, Self::Error>>;
147
148 fn from_request(req: &HttpRequest, payload: &mut actix_web::dev::Payload) -> Self::Future {
149 let json_fut = web::Json::<Dto>::from_request(req, payload);
150
151 ready(match futures::executor::block_on(json_fut) {
158 Ok(web::Json(dto)) => domainstack_http::into_domain(dto)
159 .map(DomainJson::new)
160 .map_err(ErrorResponse),
161 Err(e) => Err(ErrorResponse(error_envelope::Error::bad_request(format!(
162 "Invalid JSON: {}",
163 e
164 )))),
165 })
166 }
167}
168
169impl ResponseError for ErrorResponse {
170 fn status_code(&self) -> actix_web::http::StatusCode {
171 actix_web::http::StatusCode::from_u16(self.0.status)
172 .unwrap_or(actix_web::http::StatusCode::INTERNAL_SERVER_ERROR)
173 }
174
175 fn error_response(&self) -> HttpResponse {
176 HttpResponse::build(self.status_code()).json(&self.0)
177 }
178}
179
180impl std::fmt::Debug for ErrorResponse {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 write!(f, "ErrorResponse({:?})", self.0)
183 }
184}
185
186impl std::fmt::Display for ErrorResponse {
187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188 write!(f, "{}", self.0.message)
189 }
190}
191
192pub struct ValidatedJson<Dto>(pub Dto);
193
194impl<Dto> FromRequest for ValidatedJson<Dto>
195where
196 Dto: serde::de::DeserializeOwned + domainstack::Validate,
197{
198 type Error = ErrorResponse;
199 type Future = Ready<Result<Self, Self::Error>>;
200
201 fn from_request(req: &HttpRequest, payload: &mut actix_web::dev::Payload) -> Self::Future {
202 let json_fut = web::Json::<Dto>::from_request(req, payload);
203
204 ready(match futures::executor::block_on(json_fut) {
207 Ok(web::Json(dto)) => dto.validate().map(|_| ValidatedJson(dto)).map_err(|e| {
208 use domainstack_envelope::IntoEnvelopeError;
209 ErrorResponse(e.into_envelope_error())
210 }),
211 Err(e) => Err(ErrorResponse(error_envelope::Error::bad_request(format!(
212 "Invalid JSON: {}",
213 e
214 )))),
215 })
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use actix_web::{test, web, App};
223 use domainstack::{prelude::*, Validate};
224
225 #[derive(Debug, Clone, serde::Deserialize)]
228 struct UserDto {
229 name: String,
230 age: u8,
231 }
232
233 #[derive(Debug, serde::Serialize)]
234 struct User {
235 name: String,
236 age: u8,
237 }
238
239 impl TryFrom<UserDto> for User {
240 type Error = ValidationError;
241
242 fn try_from(dto: UserDto) -> Result<Self, Self::Error> {
243 let mut err = ValidationError::new();
244
245 let name_rule = rules::min_len(2).and(rules::max_len(50));
246 if let Err(e) = validate("name", dto.name.as_str(), &name_rule) {
247 err.extend(e);
248 }
249
250 let age_rule = rules::range(18, 120);
251 if let Err(e) = validate("age", &dto.age, &age_rule) {
252 err.extend(e);
253 }
254
255 if !err.is_empty() {
256 return Err(err);
257 }
258
259 Ok(Self {
260 name: dto.name,
261 age: dto.age,
262 })
263 }
264 }
265
266 async fn create_user(
267 DomainJson { domain: user, .. }: DomainJson<User, UserDto>,
268 ) -> web::Json<User> {
269 web::Json(user)
270 }
271
272 type UserJson = DomainJson<User, UserDto>;
273
274 async fn create_user_with_alias(UserJson { domain: user, .. }: UserJson) -> web::Json<User> {
275 web::Json(user)
276 }
277
278 async fn create_user_result_style(
279 UserJson { domain: user, .. }: UserJson,
280 ) -> Result<web::Json<User>, ErrorResponse> {
281 Ok(web::Json(user))
282 }
283
284 #[actix_rt::test]
285 async fn test_domain_json_valid() {
286 let app = test::init_service(App::new().route("/", web::post().to(create_user))).await;
287
288 let req = test::TestRequest::post()
289 .uri("/")
290 .set_json(serde_json::json!({"name": "Alice", "age": 30}))
291 .to_request();
292
293 let resp = test::call_service(&app, req).await;
294 assert_eq!(resp.status(), 200);
295 }
296
297 #[actix_rt::test]
298 async fn test_domain_json_invalid() {
299 let app = test::init_service(App::new().route("/", web::post().to(create_user))).await;
300
301 let req = test::TestRequest::post()
302 .uri("/")
303 .set_json(serde_json::json!({"name": "A", "age": 200}))
304 .to_request();
305
306 let resp = test::call_service(&app, req).await;
307 assert_eq!(resp.status(), 400);
308
309 let body: serde_json::Value = test::read_body_json(resp).await;
310 assert!(body["details"].is_object());
311 assert_eq!(
312 body["message"].as_str().unwrap(),
313 "Validation failed with 2 errors"
314 );
315
316 let details = body["details"].as_object().unwrap();
317 let fields = details["fields"].as_object().unwrap();
318
319 assert!(fields.contains_key("name"));
320 assert!(fields.contains_key("age"));
321 }
322
323 #[actix_rt::test]
324 async fn test_domain_json_malformed_json() {
325 let app = test::init_service(App::new().route("/", web::post().to(create_user))).await;
326
327 let req = test::TestRequest::post()
328 .uri("/")
329 .set_payload("{invalid json")
330 .insert_header(("content-type", "application/json"))
331 .to_request();
332
333 let resp = test::call_service(&app, req).await;
334 assert_eq!(resp.status(), 400);
335 }
336
337 #[actix_rt::test]
338 async fn test_domain_json_missing_fields() {
339 let app = test::init_service(App::new().route("/", web::post().to(create_user))).await;
340
341 let req = test::TestRequest::post()
342 .uri("/")
343 .set_json(serde_json::json!({"name": "Alice"}))
344 .to_request();
345
346 let resp = test::call_service(&app, req).await;
347 assert_eq!(resp.status(), 400);
348 }
349
350 #[actix_rt::test]
351 async fn test_type_alias_pattern() {
352 let app =
353 test::init_service(App::new().route("/", web::post().to(create_user_with_alias))).await;
354
355 let req = test::TestRequest::post()
356 .uri("/")
357 .set_json(serde_json::json!({"name": "Alice", "age": 30}))
358 .to_request();
359
360 let resp = test::call_service(&app, req).await;
361 assert_eq!(resp.status(), 200);
362 }
363
364 #[actix_rt::test]
365 async fn test_result_style_handler() {
366 let app =
367 test::init_service(App::new().route("/", web::post().to(create_user_result_style)))
368 .await;
369
370 let req = test::TestRequest::post()
371 .uri("/")
372 .set_json(serde_json::json!({"name": "Alice", "age": 30}))
373 .to_request();
374
375 let resp = test::call_service(&app, req).await;
376 assert_eq!(resp.status(), 200);
377 }
378
379 #[derive(Debug, Clone, Validate, serde::Deserialize, serde::Serialize)]
381 struct ValidatedUserDto {
382 #[validate(length(min = 2, max = 50))]
383 name: String,
384
385 #[validate(range(min = 18, max = 120))]
386 age: u8,
387 }
388
389 async fn accept_validated_dto(
390 ValidatedJson(dto): ValidatedJson<ValidatedUserDto>,
391 ) -> web::Json<ValidatedUserDto> {
392 web::Json(dto)
393 }
394
395 #[actix_rt::test]
396 async fn test_validated_json_valid() {
397 let app =
398 test::init_service(App::new().route("/", web::post().to(accept_validated_dto))).await;
399
400 let req = test::TestRequest::post()
401 .uri("/")
402 .set_json(serde_json::json!({"name": "Alice", "age": 30}))
403 .to_request();
404
405 let resp = test::call_service(&app, req).await;
406 assert_eq!(resp.status(), 200);
407
408 let body: ValidatedUserDto = test::read_body_json(resp).await;
409 assert_eq!(body.name, "Alice");
410 assert_eq!(body.age, 30);
411 }
412
413 #[actix_rt::test]
414 async fn test_validated_json_invalid() {
415 let app =
416 test::init_service(App::new().route("/", web::post().to(accept_validated_dto))).await;
417
418 let req = test::TestRequest::post()
419 .uri("/")
420 .set_json(serde_json::json!({"name": "A", "age": 200}))
421 .to_request();
422
423 let resp = test::call_service(&app, req).await;
424 assert_eq!(resp.status(), 400);
425
426 let body: serde_json::Value = test::read_body_json(resp).await;
427 assert_eq!(
428 body["message"].as_str().unwrap(),
429 "Validation failed with 2 errors"
430 );
431
432 let details = body["details"].as_object().unwrap();
433 let fields = details["fields"].as_object().unwrap();
434
435 assert!(fields.contains_key("name"));
436 assert!(fields.contains_key("age"));
437 }
438
439 #[actix_rt::test]
440 async fn test_validated_json_malformed_json() {
441 let app =
442 test::init_service(App::new().route("/", web::post().to(accept_validated_dto))).await;
443
444 let req = test::TestRequest::post()
445 .uri("/")
446 .set_payload("{invalid json")
447 .insert_header(("content-type", "application/json"))
448 .to_request();
449
450 let resp = test::call_service(&app, req).await;
451 assert_eq!(resp.status(), 400);
452 }
453
454 #[actix_rt::test]
455 async fn test_error_response_debug() {
456 let err = ErrorResponse(error_envelope::Error::bad_request("Test error"));
457 let debug_str = format!("{:?}", err);
458 assert!(debug_str.contains("ErrorResponse"));
459 }
460
461 #[actix_rt::test]
462 async fn test_error_response_display() {
463 let err = ErrorResponse(error_envelope::Error::bad_request("Custom message"));
464 let display_str = format!("{}", err);
465 assert_eq!(display_str, "Custom message");
466 }
467
468 #[actix_rt::test]
469 async fn test_error_response_status_code() {
470 use actix_web::ResponseError;
471
472 let err = ErrorResponse(error_envelope::Error::bad_request("Bad request"));
473 assert_eq!(err.status_code().as_u16(), 400);
474
475 let mut custom_err = error_envelope::Error::bad_request("Custom");
476 custom_err.status = 422;
477 let err = ErrorResponse(custom_err);
478 assert_eq!(err.status_code().as_u16(), 422);
479 }
480}