1use crate::response::{IntoResponse, Response, ResponseBody, StatusCode};
36use serde::{Serialize, Serializer};
37use std::collections::HashMap;
38use std::sync::atomic::{AtomicBool, Ordering};
39
40static DEBUG_MODE_ENABLED: AtomicBool = AtomicBool::new(false);
47
48pub fn enable_debug_mode() {
54 DEBUG_MODE_ENABLED.store(true, Ordering::SeqCst);
55}
56
57pub fn disable_debug_mode() {
59 DEBUG_MODE_ENABLED.store(false, Ordering::SeqCst);
60}
61
62#[must_use]
64pub fn is_debug_mode_enabled() -> bool {
65 DEBUG_MODE_ENABLED.load(Ordering::SeqCst)
66}
67
68#[derive(Debug, Clone)]
86pub struct DebugConfig {
87 pub enabled: bool,
89 pub debug_header: Option<String>,
91 pub debug_token: Option<String>,
93 pub allow_unauthenticated: bool,
95}
96
97impl Default for DebugConfig {
98 fn default() -> Self {
99 Self {
100 enabled: false,
101 debug_header: None,
102 debug_token: None,
103 allow_unauthenticated: false,
104 }
105 }
106}
107
108impl DebugConfig {
109 #[must_use]
111 pub fn new() -> Self {
112 Self::default()
113 }
114
115 #[must_use]
117 pub fn enable(mut self) -> Self {
118 self.enabled = true;
119 self
120 }
121
122 #[must_use]
127 pub fn with_debug_header(
128 mut self,
129 header_name: impl Into<String>,
130 token: impl Into<String>,
131 ) -> Self {
132 self.debug_header = Some(header_name.into());
133 self.debug_token = Some(token.into());
134 self
135 }
136
137 #[must_use]
144 pub fn allow_unauthenticated(mut self) -> Self {
145 self.allow_unauthenticated = true;
146 self
147 }
148
149 pub fn is_authorized(&self, request_headers: &[(String, Vec<u8>)]) -> bool {
160 if !self.enabled {
161 return false;
162 }
163
164 if self.allow_unauthenticated {
165 return true;
166 }
167
168 if let (Some(header_name), Some(expected_token)) = (&self.debug_header, &self.debug_token) {
170 for (name, value) in request_headers {
171 if name.eq_ignore_ascii_case(header_name) {
172 if let Ok(token) = std::str::from_utf8(value) {
173 return constant_time_str_eq(token, expected_token);
175 }
176 }
177 }
178 }
179
180 false
181 }
182}
183
184fn constant_time_str_eq(a: &str, b: &str) -> bool {
188 crate::password::constant_time_eq(a.as_bytes(), b.as_bytes())
189}
190
191#[derive(Debug, Clone, Default, Serialize)]
221pub struct DebugInfo {
222 #[serde(skip_serializing_if = "Option::is_none")]
224 pub source_file: Option<String>,
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub source_line: Option<u32>,
228 #[serde(skip_serializing_if = "Option::is_none")]
230 pub function_name: Option<String>,
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub route_pattern: Option<String>,
234 #[serde(skip_serializing_if = "Option::is_none")]
236 pub handler_name: Option<String>,
237 #[serde(skip_serializing_if = "HashMap::is_empty")]
239 pub extra: HashMap<String, String>,
240}
241
242impl DebugInfo {
243 #[must_use]
245 pub fn new() -> Self {
246 Self::default()
247 }
248
249 #[must_use]
251 pub fn with_source_location(
252 mut self,
253 file: impl Into<String>,
254 line: u32,
255 function: impl Into<String>,
256 ) -> Self {
257 self.source_file = Some(file.into());
258 self.source_line = Some(line);
259 self.function_name = Some(function.into());
260 self
261 }
262
263 #[must_use]
265 pub fn with_route_pattern(mut self, pattern: impl Into<String>) -> Self {
266 self.route_pattern = Some(pattern.into());
267 self
268 }
269
270 #[must_use]
272 pub fn with_handler_name(mut self, name: impl Into<String>) -> Self {
273 self.handler_name = Some(name.into());
274 self
275 }
276
277 #[must_use]
279 pub fn with_extra(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
280 self.extra.insert(key.into(), value.into());
281 self
282 }
283
284 #[must_use]
286 pub fn is_empty(&self) -> bool {
287 self.source_file.is_none()
288 && self.source_line.is_none()
289 && self.function_name.is_none()
290 && self.route_pattern.is_none()
291 && self.handler_name.is_none()
292 && self.extra.is_empty()
293 }
294}
295
296#[macro_export]
308macro_rules! debug_location {
309 () => {
310 $crate::error::DebugInfo::new().with_source_location(
311 file!(),
312 line!(),
313 module_path!(),
315 )
316 };
317 ($func_name:expr) => {
318 $crate::error::DebugInfo::new().with_source_location(file!(), line!(), $func_name)
319 };
320}
321
322#[derive(Debug, Clone, PartialEq, Eq)]
345pub enum LocItem {
346 Field(String),
348 Index(usize),
350}
351
352impl LocItem {
353 #[must_use]
355 pub fn field(name: impl Into<String>) -> Self {
356 Self::Field(name.into())
357 }
358
359 #[must_use]
361 pub fn index(idx: usize) -> Self {
362 Self::Index(idx)
363 }
364
365 #[must_use]
367 pub fn as_str(&self) -> Option<&str> {
368 match self {
369 Self::Field(s) => Some(s),
370 Self::Index(_) => None,
371 }
372 }
373
374 #[must_use]
376 pub fn as_index(&self) -> Option<usize> {
377 match self {
378 Self::Field(_) => None,
379 Self::Index(i) => Some(*i),
380 }
381 }
382}
383
384impl From<&str> for LocItem {
385 fn from(s: &str) -> Self {
386 Self::Field(s.to_owned())
387 }
388}
389
390impl From<String> for LocItem {
391 fn from(s: String) -> Self {
392 Self::Field(s)
393 }
394}
395
396impl From<usize> for LocItem {
397 fn from(i: usize) -> Self {
398 Self::Index(i)
399 }
400}
401
402impl Serialize for LocItem {
403 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
404 where
405 S: Serializer,
406 {
407 match self {
408 Self::Field(s) => serializer.serialize_str(s),
409 Self::Index(i) => serializer.serialize_u64(*i as u64),
410 }
411 }
412}
413
414pub mod loc {
420 use super::LocItem;
421
422 #[must_use]
424 pub fn path(param: &str) -> Vec<LocItem> {
425 vec![LocItem::field("path"), LocItem::field(param)]
426 }
427
428 #[must_use]
430 pub fn query(param: &str) -> Vec<LocItem> {
431 vec![LocItem::field("query"), LocItem::field(param)]
432 }
433
434 #[must_use]
436 pub fn header(name: &str) -> Vec<LocItem> {
437 vec![LocItem::field("header"), LocItem::field(name)]
438 }
439
440 #[must_use]
442 pub fn cookie(name: &str) -> Vec<LocItem> {
443 vec![LocItem::field("cookie"), LocItem::field(name)]
444 }
445
446 #[must_use]
448 pub fn body() -> Vec<LocItem> {
449 vec![LocItem::field("body")]
450 }
451
452 #[must_use]
454 pub fn body_field(field: &str) -> Vec<LocItem> {
455 vec![LocItem::field("body"), LocItem::field(field)]
456 }
457
458 #[must_use]
460 pub fn body_path(fields: &[&str]) -> Vec<LocItem> {
461 let mut loc = vec![LocItem::field("body")];
462 for field in fields {
463 loc.push(LocItem::field(*field));
464 }
465 loc
466 }
467
468 #[must_use]
470 pub fn body_indexed(field: &str, idx: usize) -> Vec<LocItem> {
471 vec![
472 LocItem::field("body"),
473 LocItem::field(field),
474 LocItem::index(idx),
475 ]
476 }
477
478 #[must_use]
480 pub fn response() -> Vec<LocItem> {
481 vec![LocItem::field("response")]
482 }
483
484 #[must_use]
486 pub fn response_field(field: &str) -> Vec<LocItem> {
487 vec![LocItem::field("response"), LocItem::field(field)]
488 }
489
490 #[must_use]
492 pub fn response_path(fields: &[&str]) -> Vec<LocItem> {
493 let mut loc = vec![LocItem::field("response")];
494 for field in fields {
495 loc.push(LocItem::field(*field));
496 }
497 loc
498 }
499}
500
501pub mod error_types {
507 pub const MISSING: &str = "missing";
509 pub const STRING_TOO_SHORT: &str = "string_too_short";
511 pub const STRING_TOO_LONG: &str = "string_too_long";
513 pub const STRING_TYPE: &str = "string_type";
515 pub const INT_TYPE: &str = "int_type";
517 pub const FLOAT_TYPE: &str = "float_type";
519 pub const BOOL_TYPE: &str = "bool_type";
521 pub const GREATER_THAN_EQUAL: &str = "greater_than_equal";
523 pub const LESS_THAN_EQUAL: &str = "less_than_equal";
525 pub const STRING_PATTERN_MISMATCH: &str = "string_pattern_mismatch";
527 pub const VALUE_ERROR: &str = "value_error";
529 pub const URL_TYPE: &str = "url_type";
531 pub const UUID_TYPE: &str = "uuid_type";
533 pub const JSON_INVALID: &str = "json_invalid";
535 pub const JSON_TYPE: &str = "json_type";
537 pub const TOO_SHORT: &str = "too_short";
539 pub const TOO_LONG: &str = "too_long";
541 pub const ENUM: &str = "enum";
543 pub const EXTRA_FORBIDDEN: &str = "extra_forbidden";
545
546 pub const SERIALIZATION_ERROR: &str = "serialization_error";
549 pub const MODEL_VALIDATION_ERROR: &str = "model_validation_error";
551}
552
553#[derive(Debug)]
583pub struct HttpError {
584 pub status: StatusCode,
586 pub detail: Option<String>,
588 pub headers: Vec<(String, Vec<u8>)>,
590 pub debug_info: Option<DebugInfo>,
592}
593
594impl HttpError {
595 #[must_use]
597 pub fn new(status: StatusCode) -> Self {
598 Self {
599 status,
600 detail: None,
601 headers: Vec::new(),
602 debug_info: None,
603 }
604 }
605
606 #[must_use]
608 pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
609 self.detail = Some(detail.into());
610 self
611 }
612
613 #[must_use]
615 pub fn with_header(mut self, name: impl Into<String>, value: impl Into<Vec<u8>>) -> Self {
616 self.headers.push((name.into(), value.into()));
617 self
618 }
619
620 #[must_use]
637 pub fn with_debug_info(mut self, debug_info: DebugInfo) -> Self {
638 self.debug_info = Some(debug_info);
639 self
640 }
641
642 #[must_use]
649 pub fn with_debug_location(self, function_name: impl Into<String>) -> Self {
650 self.with_debug_info(DebugInfo::new().with_source_location(
651 std::any::type_name::<Self>(),
652 0, function_name,
654 ))
655 }
656
657 #[must_use]
659 pub fn bad_request() -> Self {
660 Self::new(StatusCode::BAD_REQUEST)
661 }
662
663 #[must_use]
665 pub fn unauthorized() -> Self {
666 Self::new(StatusCode::UNAUTHORIZED)
667 }
668
669 #[must_use]
671 pub fn forbidden() -> Self {
672 Self::new(StatusCode::FORBIDDEN)
673 }
674
675 #[must_use]
677 pub fn not_found() -> Self {
678 Self::new(StatusCode::NOT_FOUND)
679 }
680
681 #[must_use]
683 pub fn internal() -> Self {
684 Self::new(StatusCode::INTERNAL_SERVER_ERROR)
685 }
686
687 #[must_use]
689 pub fn payload_too_large() -> Self {
690 Self::new(StatusCode::PAYLOAD_TOO_LARGE)
691 }
692
693 #[must_use]
695 pub fn unsupported_media_type() -> Self {
696 Self::new(StatusCode::UNSUPPORTED_MEDIA_TYPE)
697 }
698}
699
700impl IntoResponse for HttpError {
701 fn into_response(self) -> Response {
702 let detail = self
703 .detail
704 .as_deref()
705 .unwrap_or_else(|| self.status.canonical_reason());
706
707 let body = if is_debug_mode_enabled() {
709 if let Some(ref debug_info) = self.debug_info {
710 #[derive(Serialize)]
711 struct ErrorBodyWithDebug<'a> {
712 detail: &'a str,
713 debug: &'a DebugInfo,
714 }
715 serde_json::to_vec(&ErrorBodyWithDebug {
716 detail,
717 debug: debug_info,
718 })
719 .unwrap_or_default()
720 } else {
721 #[derive(Serialize)]
722 struct ErrorBody<'a> {
723 detail: &'a str,
724 }
725 serde_json::to_vec(&ErrorBody { detail }).unwrap_or_default()
726 }
727 } else {
728 #[derive(Serialize)]
729 struct ErrorBody<'a> {
730 detail: &'a str,
731 }
732 serde_json::to_vec(&ErrorBody { detail }).unwrap_or_default()
733 };
734
735 let mut response = Response::with_status(self.status)
736 .header("content-type", b"application/json".to_vec())
737 .body(ResponseBody::Bytes(body));
738
739 for (name, value) in self.headers {
740 response = response.header(name, value);
741 }
742
743 response
744 }
745}
746
747impl std::fmt::Display for HttpError {
748 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
749 write!(f, "{}", self.status.canonical_reason())?;
750 if let Some(ref detail) = self.detail {
751 write!(f, ": {detail}")?;
752 }
753 Ok(())
754 }
755}
756
757impl std::error::Error for HttpError {}
758
759#[derive(Debug, Clone, Serialize)]
785pub struct ValidationError {
786 #[serde(rename = "type")]
788 pub error_type: &'static str,
789
790 pub loc: Vec<LocItem>,
797
798 pub msg: String,
800
801 #[serde(skip_serializing_if = "Option::is_none")]
805 pub input: Option<serde_json::Value>,
806
807 #[serde(skip_serializing_if = "Option::is_none")]
814 pub ctx: Option<HashMap<String, serde_json::Value>>,
815}
816
817impl ValidationError {
818 #[must_use]
820 pub fn new(error_type: &'static str, loc: Vec<LocItem>) -> Self {
821 Self {
822 error_type,
823 loc,
824 msg: Self::default_message(error_type),
825 input: None,
826 ctx: None,
827 }
828 }
829
830 #[must_use]
832 pub fn missing(loc: Vec<LocItem>) -> Self {
833 Self::new(error_types::MISSING, loc)
834 }
835
836 #[must_use]
838 pub fn string_too_short(loc: Vec<LocItem>, min_length: usize) -> Self {
839 Self::new(error_types::STRING_TOO_SHORT, loc)
840 .with_msg(format!(
841 "String should have at least {min_length} character{}",
842 if min_length == 1 { "" } else { "s" }
843 ))
844 .with_ctx_value("min_length", serde_json::json!(min_length))
845 }
846
847 #[must_use]
849 pub fn string_too_long(loc: Vec<LocItem>, max_length: usize) -> Self {
850 Self::new(error_types::STRING_TOO_LONG, loc)
851 .with_msg(format!(
852 "String should have at most {max_length} character{}",
853 if max_length == 1 { "" } else { "s" }
854 ))
855 .with_ctx_value("max_length", serde_json::json!(max_length))
856 }
857
858 #[must_use]
860 pub fn type_error(loc: Vec<LocItem>, expected_type: &'static str) -> Self {
861 let error_type = match expected_type {
862 "string" => error_types::STRING_TYPE,
863 "int" | "integer" => error_types::INT_TYPE,
864 "float" | "number" => error_types::FLOAT_TYPE,
865 "bool" | "boolean" => error_types::BOOL_TYPE,
866 _ => error_types::VALUE_ERROR,
867 };
868 Self::new(error_type, loc).with_msg(format!("Input should be a valid {expected_type}"))
869 }
870
871 #[must_use]
873 pub fn json_invalid(loc: Vec<LocItem>, message: impl Into<String>) -> Self {
874 Self::new(error_types::JSON_INVALID, loc).with_msg(message)
875 }
876
877 #[must_use]
879 pub fn with_msg(mut self, msg: impl Into<String>) -> Self {
880 self.msg = msg.into();
881 self
882 }
883
884 #[must_use]
886 pub fn with_input(mut self, input: serde_json::Value) -> Self {
887 self.input = Some(input);
888 self
889 }
890
891 #[must_use]
893 pub fn with_ctx_value(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
894 self.ctx
895 .get_or_insert_with(HashMap::new)
896 .insert(key.into(), value);
897 self
898 }
899
900 #[must_use]
902 pub fn with_ctx(mut self, ctx: HashMap<String, serde_json::Value>) -> Self {
903 self.ctx = Some(ctx);
904 self
905 }
906
907 #[must_use]
909 pub fn with_loc_prefix(mut self, prefix: Vec<LocItem>) -> Self {
910 let mut new_loc = prefix;
911 new_loc.extend(self.loc);
912 self.loc = new_loc;
913 self
914 }
915
916 #[must_use]
918 pub fn with_loc_suffix(mut self, item: impl Into<LocItem>) -> Self {
919 self.loc.push(item.into());
920 self
921 }
922
923 #[must_use]
925 pub fn greater_than_equal<T: std::fmt::Display>(loc: Vec<LocItem>, min: T) -> Self {
926 let min_str = min.to_string();
927 Self::new(error_types::GREATER_THAN_EQUAL, loc)
928 .with_msg(format!(
929 "Input should be greater than or equal to {min_str}"
930 ))
931 .with_ctx_value("ge", serde_json::json!(min_str))
932 }
933
934 #[must_use]
936 pub fn less_than_equal<T: std::fmt::Display>(loc: Vec<LocItem>, max: T) -> Self {
937 let max_str = max.to_string();
938 Self::new(error_types::LESS_THAN_EQUAL, loc)
939 .with_msg(format!("Input should be less than or equal to {max_str}"))
940 .with_ctx_value("le", serde_json::json!(max_str))
941 }
942
943 #[must_use]
945 pub fn pattern_mismatch(loc: Vec<LocItem>, pattern: &str) -> Self {
946 Self::new(error_types::STRING_PATTERN_MISMATCH, loc)
947 .with_msg(format!("String should match pattern '{pattern}'"))
948 .with_ctx_value("pattern", serde_json::json!(pattern))
949 }
950
951 #[must_use]
953 pub fn invalid_email(loc: Vec<LocItem>) -> Self {
954 Self::new(error_types::VALUE_ERROR, loc).with_msg("Value is not a valid email address")
955 }
956
957 #[must_use]
959 pub fn invalid_url(loc: Vec<LocItem>) -> Self {
960 Self::new(error_types::URL_TYPE, loc).with_msg("Input should be a valid URL")
961 }
962
963 #[must_use]
965 pub fn invalid_uuid(loc: Vec<LocItem>) -> Self {
966 Self::new(error_types::UUID_TYPE, loc).with_msg("Input should be a valid UUID")
967 }
968
969 #[must_use]
971 pub fn value_error(loc: Vec<LocItem>, msg: impl Into<String>) -> Self {
972 Self::new(error_types::VALUE_ERROR, loc).with_msg(msg)
973 }
974
975 fn default_message(error_type: &str) -> String {
977 match error_type {
978 error_types::MISSING => "Field required".to_owned(),
979 error_types::STRING_TOO_SHORT => "String too short".to_owned(),
980 error_types::STRING_TOO_LONG => "String too long".to_owned(),
981 error_types::STRING_TYPE => "Input should be a valid string".to_owned(),
982 error_types::INT_TYPE => "Input should be a valid integer".to_owned(),
983 error_types::FLOAT_TYPE => "Input should be a valid number".to_owned(),
984 error_types::BOOL_TYPE => "Input should be a valid boolean".to_owned(),
985 error_types::JSON_INVALID => "Invalid JSON".to_owned(),
986 error_types::VALUE_ERROR => "Value error".to_owned(),
987 _ => "Validation error".to_owned(),
988 }
989 }
990}
991
992#[derive(Debug, Clone, Default)]
1021pub struct ValidationErrors {
1022 pub errors: Vec<ValidationError>,
1024 pub body: Option<serde_json::Value>,
1026 pub debug_info: Option<DebugInfo>,
1028}
1029
1030impl ValidationErrors {
1031 #[must_use]
1033 pub fn new() -> Self {
1034 Self {
1035 errors: Vec::new(),
1036 body: None,
1037 debug_info: None,
1038 }
1039 }
1040
1041 #[must_use]
1043 pub fn single(error: ValidationError) -> Self {
1044 Self {
1045 errors: vec![error],
1046 body: None,
1047 debug_info: None,
1048 }
1049 }
1050
1051 #[must_use]
1053 pub fn from_errors(errors: Vec<ValidationError>) -> Self {
1054 Self {
1055 errors,
1056 body: None,
1057 debug_info: None,
1058 }
1059 }
1060
1061 pub fn push(&mut self, error: ValidationError) {
1063 self.errors.push(error);
1064 }
1065
1066 pub fn extend(&mut self, errors: impl IntoIterator<Item = ValidationError>) {
1068 self.errors.extend(errors);
1069 }
1070
1071 #[must_use]
1073 pub fn with_body(mut self, body: serde_json::Value) -> Self {
1074 self.body = Some(body);
1075 self
1076 }
1077
1078 #[must_use]
1083 pub fn with_debug_info(mut self, debug_info: DebugInfo) -> Self {
1084 self.debug_info = Some(debug_info);
1085 self
1086 }
1087
1088 #[must_use]
1090 pub fn is_empty(&self) -> bool {
1091 self.errors.is_empty()
1092 }
1093
1094 #[must_use]
1096 pub fn len(&self) -> usize {
1097 self.errors.len()
1098 }
1099
1100 pub fn iter(&self) -> impl Iterator<Item = &ValidationError> {
1102 self.errors.iter()
1103 }
1104
1105 #[must_use]
1107 pub fn to_json(&self) -> String {
1108 #[derive(Serialize)]
1109 struct Body<'a> {
1110 detail: &'a [ValidationError],
1111 }
1112
1113 serde_json::to_string(&Body {
1114 detail: &self.errors,
1115 })
1116 .unwrap_or_else(|_| r#"{"detail":[]}"#.to_owned())
1117 }
1118
1119 #[must_use]
1121 pub fn to_json_bytes(&self) -> Vec<u8> {
1122 #[derive(Serialize)]
1123 struct Body<'a> {
1124 detail: &'a [ValidationError],
1125 }
1126
1127 serde_json::to_vec(&Body {
1128 detail: &self.errors,
1129 })
1130 .unwrap_or_else(|_| b"{\"detail\":[]}".to_vec())
1131 }
1132
1133 pub fn merge(&mut self, other: ValidationErrors) {
1135 self.errors.extend(other.errors);
1136 if self.body.is_none() {
1137 self.body = other.body;
1138 }
1139 if self.debug_info.is_none() {
1140 self.debug_info = other.debug_info;
1141 }
1142 }
1143
1144 #[must_use]
1146 pub fn with_loc_prefix(mut self, prefix: Vec<LocItem>) -> Self {
1147 for error in &mut self.errors {
1148 let mut new_loc = prefix.clone();
1149 new_loc.extend(std::mem::take(&mut error.loc));
1150 error.loc = new_loc;
1151 }
1152 self
1153 }
1154}
1155
1156impl IntoIterator for ValidationErrors {
1157 type Item = ValidationError;
1158 type IntoIter = std::vec::IntoIter<ValidationError>;
1159
1160 fn into_iter(self) -> Self::IntoIter {
1161 self.errors.into_iter()
1162 }
1163}
1164
1165impl<'a> IntoIterator for &'a ValidationErrors {
1166 type Item = &'a ValidationError;
1167 type IntoIter = std::slice::Iter<'a, ValidationError>;
1168
1169 fn into_iter(self) -> Self::IntoIter {
1170 self.errors.iter()
1171 }
1172}
1173
1174impl Extend<ValidationError> for ValidationErrors {
1175 fn extend<T: IntoIterator<Item = ValidationError>>(&mut self, iter: T) {
1176 self.errors.extend(iter);
1177 }
1178}
1179
1180impl FromIterator<ValidationError> for ValidationErrors {
1181 fn from_iter<T: IntoIterator<Item = ValidationError>>(iter: T) -> Self {
1182 Self::from_errors(iter.into_iter().collect())
1183 }
1184}
1185
1186impl std::fmt::Display for ValidationErrors {
1187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1188 write!(f, "{} validation error", self.errors.len())?;
1189 if self.errors.len() != 1 {
1190 write!(f, "s")?;
1191 }
1192 Ok(())
1193 }
1194}
1195
1196impl std::error::Error for ValidationErrors {}
1197
1198impl IntoResponse for ValidationErrors {
1199 fn into_response(self) -> Response {
1200 let body = if is_debug_mode_enabled() {
1202 if let Some(ref debug_info) = self.debug_info {
1203 #[derive(Serialize)]
1204 struct BodyWithDebug<'a> {
1205 detail: &'a [ValidationError],
1206 debug: &'a DebugInfo,
1207 }
1208 serde_json::to_vec(&BodyWithDebug {
1209 detail: &self.errors,
1210 debug: debug_info,
1211 })
1212 .unwrap_or_else(|_| b"{\"detail\":[]}".to_vec())
1213 } else {
1214 self.to_json_bytes()
1215 }
1216 } else {
1217 self.to_json_bytes()
1218 };
1219
1220 Response::with_status(StatusCode::UNPROCESSABLE_ENTITY)
1221 .header("content-type", b"application/json".to_vec())
1222 .body(ResponseBody::Bytes(body))
1223 }
1224}
1225
1226#[derive(Debug, Clone, Default)]
1265pub struct ResponseValidationError {
1266 pub errors: Vec<ValidationError>,
1268 pub response_content: Option<serde_json::Value>,
1271 pub summary: Option<String>,
1273 pub debug_info: Option<DebugInfo>,
1275}
1276
1277impl ResponseValidationError {
1278 #[must_use]
1280 pub fn new() -> Self {
1281 Self::default()
1282 }
1283
1284 #[must_use]
1288 pub fn serialization_failed(message: impl Into<String>) -> Self {
1289 let msg = message.into();
1290 Self {
1291 errors: vec![
1292 ValidationError::new(
1293 error_types::SERIALIZATION_ERROR,
1294 vec![LocItem::field("response")],
1295 )
1296 .with_msg(&msg),
1297 ],
1298 response_content: None,
1299 summary: Some(msg),
1300 debug_info: None,
1301 }
1302 }
1303
1304 #[must_use]
1308 pub fn model_validation_failed(message: impl Into<String>) -> Self {
1309 let msg = message.into();
1310 Self {
1311 errors: vec![
1312 ValidationError::new(
1313 error_types::MODEL_VALIDATION_ERROR,
1314 vec![LocItem::field("response")],
1315 )
1316 .with_msg(&msg),
1317 ],
1318 response_content: None,
1319 summary: Some(msg),
1320 debug_info: None,
1321 }
1322 }
1323
1324 #[must_use]
1326 pub fn with_error(mut self, error: ValidationError) -> Self {
1327 self.errors.push(error);
1328 self
1329 }
1330
1331 #[must_use]
1333 pub fn with_errors(mut self, errors: impl IntoIterator<Item = ValidationError>) -> Self {
1334 self.errors.extend(errors);
1335 self
1336 }
1337
1338 #[must_use]
1342 pub fn with_response_content(mut self, content: serde_json::Value) -> Self {
1343 self.response_content = Some(content);
1344 self
1345 }
1346
1347 #[must_use]
1349 pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
1350 self.summary = Some(summary.into());
1351 self
1352 }
1353
1354 #[must_use]
1359 pub fn with_debug_info(mut self, debug_info: DebugInfo) -> Self {
1360 self.debug_info = Some(debug_info);
1361 self
1362 }
1363
1364 #[must_use]
1366 pub fn is_empty(&self) -> bool {
1367 self.errors.is_empty()
1368 }
1369
1370 #[must_use]
1372 pub fn len(&self) -> usize {
1373 self.errors.len()
1374 }
1375
1376 pub fn iter(&self) -> impl Iterator<Item = &ValidationError> {
1378 self.errors.iter()
1379 }
1380
1381 #[must_use]
1386 pub fn to_log_string(&self) -> String {
1387 let mut parts = Vec::new();
1388
1389 if let Some(ref summary) = self.summary {
1390 parts.push(format!("Summary: {}", summary));
1391 }
1392
1393 parts.push(format!("Errors ({}): ", self.errors.len()));
1394 for (i, error) in self.errors.iter().enumerate() {
1395 let loc_str: Vec<String> = error
1396 .loc
1397 .iter()
1398 .map(|item| match item {
1399 LocItem::Field(s) => s.clone(),
1400 LocItem::Index(i) => i.to_string(),
1401 })
1402 .collect();
1403 parts.push(format!(
1404 " [{}] {} at [{}]: {}",
1405 i + 1,
1406 error.error_type,
1407 loc_str.join("."),
1408 error.msg
1409 ));
1410 }
1411
1412 if let Some(ref content) = self.response_content {
1413 let content_str = serde_json::to_string(content).unwrap_or_default();
1415 let truncated = if content_str.len() > 500 {
1416 let mut end = 500;
1418 while end > 0 && !content_str.is_char_boundary(end) {
1419 end -= 1;
1420 }
1421 format!("{}...(truncated)", &content_str[..end])
1422 } else {
1423 content_str
1424 };
1425 parts.push(format!("Response content: {}", truncated));
1426 }
1427
1428 parts.join("\n")
1429 }
1430}
1431
1432impl std::fmt::Display for ResponseValidationError {
1433 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1434 write!(f, "Response validation failed")?;
1435 if let Some(ref summary) = self.summary {
1436 write!(f, ": {}", summary)?;
1437 }
1438 Ok(())
1439 }
1440}
1441
1442impl std::error::Error for ResponseValidationError {}
1443
1444impl IntoResponse for ResponseValidationError {
1445 fn into_response(self) -> Response {
1446 let body = if is_debug_mode_enabled() {
1455 #[derive(Serialize)]
1457 struct DebugBody<'a> {
1458 error: &'static str,
1459 detail: &'static str,
1460 #[serde(skip_serializing_if = "Option::is_none")]
1461 debug: Option<DebugResponseInfo<'a>>,
1462 }
1463
1464 #[derive(Serialize)]
1465 struct DebugResponseInfo<'a> {
1466 #[serde(skip_serializing_if = "Option::is_none")]
1467 summary: Option<&'a str>,
1468 errors: &'a [ValidationError],
1469 #[serde(skip_serializing_if = "Option::is_none")]
1470 response_content: &'a Option<serde_json::Value>,
1471 #[serde(skip_serializing_if = "Option::is_none")]
1472 source: Option<&'a DebugInfo>,
1473 }
1474
1475 let debug_info = DebugResponseInfo {
1476 summary: self.summary.as_deref(),
1477 errors: &self.errors,
1478 response_content: &self.response_content,
1479 source: self.debug_info.as_ref(),
1480 };
1481
1482 serde_json::to_vec(&DebugBody {
1483 error: "internal_server_error",
1484 detail: "Response validation failed",
1485 debug: Some(debug_info),
1486 })
1487 .unwrap_or_else(|_| {
1488 b"{\"error\":\"internal_server_error\",\"detail\":\"Internal Server Error\"}"
1489 .to_vec()
1490 })
1491 } else {
1492 b"{\"error\":\"internal_server_error\",\"detail\":\"Internal Server Error\"}".to_vec()
1494 };
1495
1496 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
1497 .header("content-type", b"application/json".to_vec())
1498 .body(ResponseBody::Bytes(body))
1499 }
1500}
1501
1502#[cfg(test)]
1507mod tests {
1508 use super::*;
1509 use serde_json::json;
1510 use serial_test::serial;
1511
1512 #[test]
1517 fn loc_item_field_creation() {
1518 let item = LocItem::field("email");
1519 assert_eq!(item.as_str(), Some("email"));
1520 assert_eq!(item.as_index(), None);
1521 }
1522
1523 #[test]
1524 fn loc_item_index_creation() {
1525 let item = LocItem::index(42);
1526 assert_eq!(item.as_str(), None);
1527 assert_eq!(item.as_index(), Some(42));
1528 }
1529
1530 #[test]
1531 fn loc_item_from_str() {
1532 let item: LocItem = "name".into();
1533 assert_eq!(item, LocItem::Field("name".to_owned()));
1534 }
1535
1536 #[test]
1537 fn loc_item_from_string() {
1538 let item: LocItem = String::from("age").into();
1539 assert_eq!(item, LocItem::Field("age".to_owned()));
1540 }
1541
1542 #[test]
1543 fn loc_item_from_usize() {
1544 let item: LocItem = 5usize.into();
1545 assert_eq!(item, LocItem::Index(5));
1546 }
1547
1548 #[test]
1549 fn loc_item_serialize_field() {
1550 let item = LocItem::field("email");
1551 let json = serde_json::to_string(&item).unwrap();
1552 assert_eq!(json, "\"email\"");
1553 }
1554
1555 #[test]
1556 fn loc_item_serialize_index() {
1557 let item = LocItem::index(3);
1558 let json = serde_json::to_string(&item).unwrap();
1559 assert_eq!(json, "3");
1560 }
1561
1562 #[test]
1567 fn loc_path_creates_correct_location() {
1568 let loc = loc::path("user_id");
1569 assert_eq!(loc.len(), 2);
1570 assert_eq!(loc[0].as_str(), Some("path"));
1571 assert_eq!(loc[1].as_str(), Some("user_id"));
1572 }
1573
1574 #[test]
1575 fn loc_query_creates_correct_location() {
1576 let loc = loc::query("q");
1577 assert_eq!(loc.len(), 2);
1578 assert_eq!(loc[0].as_str(), Some("query"));
1579 assert_eq!(loc[1].as_str(), Some("q"));
1580 }
1581
1582 #[test]
1583 fn loc_header_creates_correct_location() {
1584 let loc = loc::header("Authorization");
1585 assert_eq!(loc.len(), 2);
1586 assert_eq!(loc[0].as_str(), Some("header"));
1587 assert_eq!(loc[1].as_str(), Some("Authorization"));
1588 }
1589
1590 #[test]
1591 fn loc_cookie_creates_correct_location() {
1592 let loc = loc::cookie("session_id");
1593 assert_eq!(loc.len(), 2);
1594 assert_eq!(loc[0].as_str(), Some("cookie"));
1595 assert_eq!(loc[1].as_str(), Some("session_id"));
1596 }
1597
1598 #[test]
1599 fn loc_body_creates_root_location() {
1600 let loc = loc::body();
1601 assert_eq!(loc.len(), 1);
1602 assert_eq!(loc[0].as_str(), Some("body"));
1603 }
1604
1605 #[test]
1606 fn loc_body_field_creates_correct_location() {
1607 let loc = loc::body_field("email");
1608 assert_eq!(loc.len(), 2);
1609 assert_eq!(loc[0].as_str(), Some("body"));
1610 assert_eq!(loc[1].as_str(), Some("email"));
1611 }
1612
1613 #[test]
1614 fn loc_body_path_creates_nested_location() {
1615 let loc = loc::body_path(&["user", "profile", "name"]);
1616 assert_eq!(loc.len(), 4);
1617 assert_eq!(loc[0].as_str(), Some("body"));
1618 assert_eq!(loc[1].as_str(), Some("user"));
1619 assert_eq!(loc[2].as_str(), Some("profile"));
1620 assert_eq!(loc[3].as_str(), Some("name"));
1621 }
1622
1623 #[test]
1624 fn loc_body_indexed_creates_array_location() {
1625 let loc = loc::body_indexed("items", 0);
1626 assert_eq!(loc.len(), 3);
1627 assert_eq!(loc[0].as_str(), Some("body"));
1628 assert_eq!(loc[1].as_str(), Some("items"));
1629 assert_eq!(loc[2].as_index(), Some(0));
1630 }
1631
1632 #[test]
1637 fn validation_error_new_with_default_message() {
1638 let error = ValidationError::new(error_types::MISSING, loc::query("q"));
1639 assert_eq!(error.error_type, "missing");
1640 assert_eq!(error.msg, "Field required");
1641 assert!(error.input.is_none());
1642 assert!(error.ctx.is_none());
1643 }
1644
1645 #[test]
1646 fn validation_error_missing() {
1647 let error = ValidationError::missing(loc::query("page"));
1648 assert_eq!(error.error_type, "missing");
1649 assert_eq!(error.msg, "Field required");
1650 }
1651
1652 #[test]
1653 fn validation_error_string_too_short() {
1654 let error = ValidationError::string_too_short(loc::body_field("name"), 3);
1655 assert_eq!(error.error_type, "string_too_short");
1656 assert!(error.msg.contains("3"));
1657 assert!(error.ctx.is_some());
1658 let ctx = error.ctx.unwrap();
1659 assert_eq!(ctx.get("min_length"), Some(&json!(3)));
1660 }
1661
1662 #[test]
1663 fn validation_error_string_too_long() {
1664 let error = ValidationError::string_too_long(loc::body_field("bio"), 500);
1665 assert_eq!(error.error_type, "string_too_long");
1666 assert!(error.msg.contains("500"));
1667 assert!(error.ctx.is_some());
1668 let ctx = error.ctx.unwrap();
1669 assert_eq!(ctx.get("max_length"), Some(&json!(500)));
1670 }
1671
1672 #[test]
1673 fn validation_error_type_error_int() {
1674 let error = ValidationError::type_error(loc::query("count"), "integer");
1675 assert_eq!(error.error_type, "int_type");
1676 assert!(error.msg.contains("integer"));
1677 }
1678
1679 #[test]
1680 fn validation_error_type_error_string() {
1681 let error = ValidationError::type_error(loc::body_field("name"), "string");
1682 assert_eq!(error.error_type, "string_type");
1683 assert!(error.msg.contains("string"));
1684 }
1685
1686 #[test]
1687 fn validation_error_json_invalid() {
1688 let error = ValidationError::json_invalid(loc::body(), "unexpected end of input");
1689 assert_eq!(error.error_type, "json_invalid");
1690 assert_eq!(error.msg, "unexpected end of input");
1691 }
1692
1693 #[test]
1694 fn validation_error_with_input() {
1695 let error = ValidationError::missing(loc::query("q")).with_input(json!(null));
1696 assert_eq!(error.input, Some(json!(null)));
1697 }
1698
1699 #[test]
1700 fn validation_error_with_ctx_value() {
1701 let error = ValidationError::new(error_types::GREATER_THAN_EQUAL, loc::body_field("age"))
1702 .with_ctx_value("ge", json!(0));
1703 assert!(error.ctx.is_some());
1704 assert_eq!(error.ctx.unwrap().get("ge"), Some(&json!(0)));
1705 }
1706
1707 #[test]
1708 fn validation_error_with_multiple_ctx_values() {
1709 let error = ValidationError::new(
1710 error_types::STRING_PATTERN_MISMATCH,
1711 loc::body_field("email"),
1712 )
1713 .with_ctx_value("pattern", json!("^.+@.+$"))
1714 .with_ctx_value("expected", json!("email format"));
1715 let ctx = error.ctx.unwrap();
1716 assert_eq!(ctx.len(), 2);
1717 assert_eq!(ctx.get("pattern"), Some(&json!("^.+@.+$")));
1718 assert_eq!(ctx.get("expected"), Some(&json!("email format")));
1719 }
1720
1721 #[test]
1722 fn validation_error_with_loc_prefix() {
1723 let error = ValidationError::missing(vec![LocItem::field("email")])
1724 .with_loc_prefix(vec![LocItem::field("body"), LocItem::field("user")]);
1725 assert_eq!(error.loc.len(), 3);
1726 assert_eq!(error.loc[0].as_str(), Some("body"));
1727 assert_eq!(error.loc[1].as_str(), Some("user"));
1728 assert_eq!(error.loc[2].as_str(), Some("email"));
1729 }
1730
1731 #[test]
1732 fn validation_error_with_loc_suffix() {
1733 let error = ValidationError::missing(loc::body())
1734 .with_loc_suffix("items")
1735 .with_loc_suffix(0usize)
1736 .with_loc_suffix("name");
1737 assert_eq!(error.loc.len(), 4);
1738 assert_eq!(error.loc[0].as_str(), Some("body"));
1739 assert_eq!(error.loc[1].as_str(), Some("items"));
1740 assert_eq!(error.loc[2].as_index(), Some(0));
1741 assert_eq!(error.loc[3].as_str(), Some("name"));
1742 }
1743
1744 #[test]
1749 fn validation_error_serializes_to_fastapi_format() {
1750 let error = ValidationError::missing(loc::query("q"));
1751 let json = serde_json::to_value(&error).unwrap();
1752
1753 assert_eq!(json["type"], "missing");
1754 assert_eq!(json["loc"], json!(["query", "q"]));
1755 assert_eq!(json["msg"], "Field required");
1756 assert!(json.get("input").is_none()); assert!(json.get("ctx").is_none());
1758 }
1759
1760 #[test]
1761 fn validation_error_serializes_with_array_index() {
1762 let error = ValidationError::missing(vec![
1763 LocItem::field("body"),
1764 LocItem::field("items"),
1765 LocItem::index(2),
1766 LocItem::field("name"),
1767 ]);
1768 let json = serde_json::to_value(&error).unwrap();
1769
1770 assert_eq!(json["loc"], json!(["body", "items", 2, "name"]));
1771 }
1772
1773 #[test]
1774 fn validation_error_serializes_with_input_and_ctx() {
1775 let error =
1776 ValidationError::string_too_short(loc::body_field("name"), 3).with_input(json!("ab"));
1777 let json = serde_json::to_value(&error).unwrap();
1778
1779 assert_eq!(json["input"], "ab");
1780 assert_eq!(json["ctx"]["min_length"], 3);
1781 }
1782
1783 #[test]
1788 fn validation_errors_new_is_empty() {
1789 let errors = ValidationErrors::new();
1790 assert!(errors.is_empty());
1791 assert_eq!(errors.len(), 0);
1792 }
1793
1794 #[test]
1795 fn validation_errors_single() {
1796 let errors = ValidationErrors::single(ValidationError::missing(loc::query("q")));
1797 assert!(!errors.is_empty());
1798 assert_eq!(errors.len(), 1);
1799 }
1800
1801 #[test]
1802 fn validation_errors_push() {
1803 let mut errors = ValidationErrors::new();
1804 errors.push(ValidationError::missing(loc::query("q")));
1805 errors.push(ValidationError::missing(loc::query("page")));
1806 assert_eq!(errors.len(), 2);
1807 }
1808
1809 #[test]
1810 fn validation_errors_extend() {
1811 let mut errors = ValidationErrors::new();
1812 errors.extend(vec![
1813 ValidationError::missing(loc::query("q")),
1814 ValidationError::missing(loc::query("page")),
1815 ]);
1816 assert_eq!(errors.len(), 2);
1817 }
1818
1819 #[test]
1820 fn validation_errors_from_errors() {
1821 let errors = ValidationErrors::from_errors(vec![
1822 ValidationError::missing(loc::query("q")),
1823 ValidationError::string_too_short(loc::body_field("name"), 1),
1824 ]);
1825 assert_eq!(errors.len(), 2);
1826 }
1827
1828 #[test]
1829 fn validation_errors_with_body() {
1830 let body = json!({"name": ""});
1831 let errors = ValidationErrors::single(ValidationError::string_too_short(
1832 loc::body_field("name"),
1833 1,
1834 ))
1835 .with_body(body.clone());
1836
1837 assert_eq!(errors.body, Some(body));
1838 }
1839
1840 #[test]
1841 fn validation_errors_merge() {
1842 let mut errors1 = ValidationErrors::single(ValidationError::missing(loc::query("q")));
1843 let errors2 = ValidationErrors::single(ValidationError::missing(loc::query("page")));
1844
1845 errors1.merge(errors2);
1846 assert_eq!(errors1.len(), 2);
1847 }
1848
1849 #[test]
1850 fn validation_errors_with_loc_prefix() {
1851 let errors = ValidationErrors::from_errors(vec![
1852 ValidationError::missing(vec![LocItem::field("name")]),
1853 ValidationError::missing(vec![LocItem::field("email")]),
1854 ])
1855 .with_loc_prefix(vec![LocItem::field("body"), LocItem::field("user")]);
1856
1857 for error in &errors {
1858 assert_eq!(error.loc[0].as_str(), Some("body"));
1859 assert_eq!(error.loc[1].as_str(), Some("user"));
1860 }
1861 }
1862
1863 #[test]
1864 fn validation_errors_iter() {
1865 let errors = ValidationErrors::from_errors(vec![
1866 ValidationError::missing(loc::query("q")),
1867 ValidationError::missing(loc::query("page")),
1868 ]);
1869
1870 let count = errors.iter().count();
1871 assert_eq!(count, 2);
1872 }
1873
1874 #[test]
1875 fn validation_errors_into_iter() {
1876 let errors = ValidationErrors::from_errors(vec![
1877 ValidationError::missing(loc::query("q")),
1878 ValidationError::missing(loc::query("page")),
1879 ]);
1880
1881 let collected: Vec<_> = errors.into_iter().collect();
1882 assert_eq!(collected.len(), 2);
1883 }
1884
1885 #[test]
1886 fn validation_errors_from_iterator() {
1887 let errors: ValidationErrors = vec![
1888 ValidationError::missing(loc::query("q")),
1889 ValidationError::missing(loc::query("page")),
1890 ]
1891 .into_iter()
1892 .collect();
1893
1894 assert_eq!(errors.len(), 2);
1895 }
1896
1897 #[test]
1902 fn validation_errors_to_json() {
1903 let errors = ValidationErrors::single(
1904 ValidationError::missing(loc::query("q")).with_input(json!(null)),
1905 );
1906 let json = errors.to_json();
1907
1908 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1909 assert!(parsed["detail"].is_array());
1910 assert_eq!(parsed["detail"][0]["type"], "missing");
1911 assert_eq!(parsed["detail"][0]["loc"], json!(["query", "q"]));
1912 }
1913
1914 #[test]
1915 fn validation_errors_to_json_bytes() {
1916 let errors = ValidationErrors::single(ValidationError::missing(loc::query("q")));
1917 let bytes = errors.to_json_bytes();
1918 let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
1919
1920 assert!(json["detail"].is_array());
1921 }
1922
1923 #[test]
1924 fn validation_errors_fastapi_format_match() {
1925 let errors = ValidationErrors::from_errors(vec![
1927 ValidationError::missing(loc::query("q")),
1928 ValidationError::string_too_short(loc::body_field("name"), 3).with_input(json!("ab")),
1929 ]);
1930
1931 let json = errors.to_json();
1932 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1933
1934 assert_eq!(parsed["detail"][0]["type"], "missing");
1936 assert_eq!(parsed["detail"][0]["loc"], json!(["query", "q"]));
1937 assert_eq!(parsed["detail"][0]["msg"], "Field required");
1938
1939 assert_eq!(parsed["detail"][1]["type"], "string_too_short");
1941 assert_eq!(parsed["detail"][1]["loc"], json!(["body", "name"]));
1942 assert_eq!(parsed["detail"][1]["input"], "ab");
1943 assert_eq!(parsed["detail"][1]["ctx"]["min_length"], 3);
1944 }
1945
1946 #[test]
1947 fn validation_errors_nested_array_location() {
1948 let error = ValidationError::missing(vec![
1950 LocItem::field("body"),
1951 LocItem::field("items"),
1952 LocItem::index(0),
1953 LocItem::field("name"),
1954 ]);
1955 let errors = ValidationErrors::single(error);
1956 let json = errors.to_json();
1957 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1958
1959 assert_eq!(
1960 parsed["detail"][0]["loc"],
1961 json!(["body", "items", 0, "name"])
1962 );
1963 }
1964
1965 #[test]
1970 fn validation_errors_into_response() {
1971 let errors = ValidationErrors::single(ValidationError::missing(loc::query("q")));
1972 let response = errors.into_response();
1973
1974 assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
1975
1976 let content_type = response
1978 .headers()
1979 .iter()
1980 .find(|(name, _): &&(String, Vec<u8>)| name.eq_ignore_ascii_case("content-type"))
1981 .map(|(_, v)| v.as_slice());
1982 assert_eq!(content_type, Some(b"application/json".as_slice()));
1983 }
1984
1985 #[test]
1990 fn error_types_match_pydantic() {
1991 assert_eq!(error_types::MISSING, "missing");
1993 assert_eq!(error_types::STRING_TOO_SHORT, "string_too_short");
1994 assert_eq!(error_types::STRING_TOO_LONG, "string_too_long");
1995 assert_eq!(error_types::STRING_TYPE, "string_type");
1996 assert_eq!(error_types::INT_TYPE, "int_type");
1997 assert_eq!(error_types::FLOAT_TYPE, "float_type");
1998 assert_eq!(error_types::BOOL_TYPE, "bool_type");
1999 assert_eq!(error_types::JSON_INVALID, "json_invalid");
2000 assert_eq!(error_types::VALUE_ERROR, "value_error");
2001 }
2002
2003 #[test]
2008 fn http_error_new_with_status() {
2009 let error = HttpError::new(StatusCode::NOT_FOUND);
2010 assert_eq!(error.status, StatusCode::NOT_FOUND);
2011 assert!(error.detail.is_none());
2012 assert!(error.headers.is_empty());
2013 }
2014
2015 #[test]
2016 fn http_error_bad_request() {
2017 let error = HttpError::bad_request();
2018 assert_eq!(error.status, StatusCode::BAD_REQUEST);
2019 assert_eq!(error.status.as_u16(), 400);
2020 }
2021
2022 #[test]
2023 fn http_error_unauthorized() {
2024 let error = HttpError::unauthorized();
2025 assert_eq!(error.status, StatusCode::UNAUTHORIZED);
2026 assert_eq!(error.status.as_u16(), 401);
2027 }
2028
2029 #[test]
2030 fn http_error_forbidden() {
2031 let error = HttpError::forbidden();
2032 assert_eq!(error.status, StatusCode::FORBIDDEN);
2033 assert_eq!(error.status.as_u16(), 403);
2034 }
2035
2036 #[test]
2037 fn http_error_not_found() {
2038 let error = HttpError::not_found();
2039 assert_eq!(error.status, StatusCode::NOT_FOUND);
2040 assert_eq!(error.status.as_u16(), 404);
2041 }
2042
2043 #[test]
2044 fn http_error_internal() {
2045 let error = HttpError::internal();
2046 assert_eq!(error.status, StatusCode::INTERNAL_SERVER_ERROR);
2047 assert_eq!(error.status.as_u16(), 500);
2048 }
2049
2050 #[test]
2051 fn http_error_payload_too_large() {
2052 let error = HttpError::payload_too_large();
2053 assert_eq!(error.status, StatusCode::PAYLOAD_TOO_LARGE);
2054 assert_eq!(error.status.as_u16(), 413);
2055 }
2056
2057 #[test]
2058 fn http_error_unsupported_media_type() {
2059 let error = HttpError::unsupported_media_type();
2060 assert_eq!(error.status, StatusCode::UNSUPPORTED_MEDIA_TYPE);
2061 assert_eq!(error.status.as_u16(), 415);
2062 }
2063
2064 #[test]
2065 fn http_error_with_detail() {
2066 let error = HttpError::not_found().with_detail("User not found");
2067 assert_eq!(error.detail, Some("User not found".to_owned()));
2068 }
2069
2070 #[test]
2071 fn http_error_with_detail_owned_string() {
2072 let detail = String::from("Resource missing");
2073 let error = HttpError::not_found().with_detail(detail);
2074 assert_eq!(error.detail, Some("Resource missing".to_owned()));
2075 }
2076
2077 #[test]
2078 fn http_error_with_header() {
2079 let error = HttpError::unauthorized()
2080 .with_header("WWW-Authenticate", b"Bearer realm=\"api\"".to_vec());
2081 assert_eq!(error.headers.len(), 1);
2082 assert_eq!(error.headers[0].0, "WWW-Authenticate");
2083 assert_eq!(error.headers[0].1, b"Bearer realm=\"api\"".to_vec());
2084 }
2085
2086 #[test]
2087 fn http_error_with_multiple_headers() {
2088 let error = HttpError::bad_request()
2089 .with_header("X-Error-Code", b"E001".to_vec())
2090 .with_header("X-Error-Context", b"validation".to_vec())
2091 .with_header("Retry-After", b"60".to_vec());
2092 assert_eq!(error.headers.len(), 3);
2093 }
2094
2095 #[test]
2096 fn http_error_with_detail_and_headers() {
2097 let error = HttpError::unauthorized()
2098 .with_detail("Invalid or expired token")
2099 .with_header("WWW-Authenticate", b"Bearer".to_vec())
2100 .with_header("X-Token-Expired", b"true".to_vec());
2101
2102 assert_eq!(error.detail, Some("Invalid or expired token".to_owned()));
2103 assert_eq!(error.headers.len(), 2);
2104 }
2105
2106 #[test]
2107 fn http_error_display_without_detail() {
2108 let error = HttpError::not_found();
2109 let display = format!("{}", error);
2110 assert_eq!(display, "Not Found");
2111 }
2112
2113 #[test]
2114 fn http_error_display_with_detail() {
2115 let error = HttpError::not_found().with_detail("User 123 not found");
2116 let display = format!("{}", error);
2117 assert_eq!(display, "Not Found: User 123 not found");
2118 }
2119
2120 #[test]
2121 fn http_error_is_error_trait() {
2122 let error: Box<dyn std::error::Error> = Box::new(HttpError::internal());
2123 assert!(error.to_string().contains("Internal Server Error"));
2125 }
2126
2127 #[test]
2128 fn http_error_into_response_status() {
2129 let error = HttpError::forbidden();
2130 let response = error.into_response();
2131 assert_eq!(response.status(), StatusCode::FORBIDDEN);
2132 }
2133
2134 #[test]
2135 fn http_error_into_response_json_content_type() {
2136 let error = HttpError::bad_request();
2137 let response = error.into_response();
2138
2139 let content_type = response
2140 .headers()
2141 .iter()
2142 .find(|(name, _)| name.eq_ignore_ascii_case("content-type"))
2143 .map(|(_, v)| v.as_slice());
2144 assert_eq!(content_type, Some(b"application/json".as_slice()));
2145 }
2146
2147 #[test]
2148 fn http_error_into_response_json_body_format() {
2149 let error = HttpError::not_found().with_detail("Resource not found");
2150 let response = error.into_response();
2151
2152 let body = match response.body_ref() {
2154 ResponseBody::Bytes(b) => b.clone(),
2155 _ => panic!("Expected bytes body"),
2156 };
2157
2158 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2159 assert_eq!(parsed["detail"], "Resource not found");
2160 }
2161
2162 #[test]
2163 fn http_error_into_response_default_detail() {
2164 let error = HttpError::not_found();
2166 let response = error.into_response();
2167
2168 let body = match response.body_ref() {
2169 ResponseBody::Bytes(b) => b.clone(),
2170 _ => panic!("Expected bytes body"),
2171 };
2172
2173 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2174 assert_eq!(parsed["detail"], "Not Found");
2175 }
2176
2177 #[test]
2178 fn http_error_into_response_with_custom_headers() {
2179 let error = HttpError::unauthorized()
2180 .with_detail("Token expired")
2181 .with_header("WWW-Authenticate", b"Bearer realm=\"api\"".to_vec());
2182
2183 let response = error.into_response();
2184
2185 let www_auth = response
2187 .headers()
2188 .iter()
2189 .find(|(name, _)| name.eq_ignore_ascii_case("www-authenticate"))
2190 .map(|(_, v)| v.as_slice());
2191 assert_eq!(www_auth, Some(b"Bearer realm=\"api\"".as_slice()));
2192 }
2193
2194 #[test]
2195 fn http_error_into_response_multiple_custom_headers() {
2196 let error = HttpError::bad_request()
2197 .with_header("X-Error-Code", b"VALIDATION_FAILED".to_vec())
2198 .with_header("X-Request-Id", b"abc-123".to_vec());
2199
2200 let response = error.into_response();
2201
2202 let headers: Vec<_> = response.headers().iter().collect();
2203
2204 assert!(headers.len() >= 3);
2206
2207 let error_code = headers
2208 .iter()
2209 .find(|(name, _)| name.eq_ignore_ascii_case("x-error-code"))
2210 .map(|(_, v)| v.as_slice());
2211 assert_eq!(error_code, Some(b"VALIDATION_FAILED".as_slice()));
2212
2213 let request_id = headers
2214 .iter()
2215 .find(|(name, _)| name.eq_ignore_ascii_case("x-request-id"))
2216 .map(|(_, v)| v.as_slice());
2217 assert_eq!(request_id, Some(b"abc-123".as_slice()));
2218 }
2219
2220 #[test]
2221 fn http_error_response_body_is_valid_json() {
2222 let errors = vec![
2224 HttpError::bad_request(),
2225 HttpError::unauthorized(),
2226 HttpError::forbidden(),
2227 HttpError::not_found(),
2228 HttpError::internal(),
2229 HttpError::payload_too_large(),
2230 HttpError::unsupported_media_type(),
2231 ];
2232
2233 for error in errors {
2234 let status = error.status;
2235 let response = error.into_response();
2236 let body = match response.body_ref() {
2237 ResponseBody::Bytes(b) => b.clone(),
2238 _ => panic!("Expected bytes body"),
2239 };
2240
2241 let parsed: Result<serde_json::Value, _> = serde_json::from_slice(&body);
2243 assert!(
2244 parsed.is_ok(),
2245 "Failed to parse JSON for status {}: {:?}",
2246 status.as_u16(),
2247 String::from_utf8_lossy(&body)
2248 );
2249
2250 let json = parsed.unwrap();
2252 assert!(
2253 json.get("detail").is_some(),
2254 "Missing detail field for status {}",
2255 status.as_u16()
2256 );
2257 }
2258 }
2259
2260 #[test]
2261 fn http_error_fastapi_compatible_format() {
2262 let error = HttpError::forbidden().with_detail("Insufficient permissions");
2265 let response = error.into_response();
2266
2267 let body = match response.body_ref() {
2268 ResponseBody::Bytes(b) => b.clone(),
2269 _ => panic!("Expected bytes body"),
2270 };
2271
2272 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
2273
2274 let obj = json.as_object().unwrap();
2276 assert_eq!(obj.len(), 1, "Expected only 'detail' field");
2277 assert_eq!(json["detail"], "Insufficient permissions");
2278 }
2279
2280 #[test]
2281 fn http_error_chained_builder_pattern() {
2282 let error = HttpError::new(StatusCode::TOO_MANY_REQUESTS)
2284 .with_detail("Rate limit exceeded")
2285 .with_header("Retry-After", b"60".to_vec())
2286 .with_header("X-RateLimit-Remaining", b"0".to_vec());
2287
2288 assert_eq!(error.status, StatusCode::TOO_MANY_REQUESTS);
2289 assert_eq!(error.detail, Some("Rate limit exceeded".to_owned()));
2290 assert_eq!(error.headers.len(), 2);
2291 }
2292
2293 #[test]
2298 fn error_types_all_constants_defined() {
2299 assert!(!error_types::MISSING.is_empty());
2301 assert!(!error_types::STRING_TOO_SHORT.is_empty());
2302 assert!(!error_types::STRING_TOO_LONG.is_empty());
2303 assert!(!error_types::STRING_TYPE.is_empty());
2304 assert!(!error_types::INT_TYPE.is_empty());
2305 assert!(!error_types::FLOAT_TYPE.is_empty());
2306 assert!(!error_types::BOOL_TYPE.is_empty());
2307 assert!(!error_types::GREATER_THAN_EQUAL.is_empty());
2308 assert!(!error_types::LESS_THAN_EQUAL.is_empty());
2309 assert!(!error_types::STRING_PATTERN_MISMATCH.is_empty());
2310 assert!(!error_types::VALUE_ERROR.is_empty());
2311 assert!(!error_types::URL_TYPE.is_empty());
2312 assert!(!error_types::UUID_TYPE.is_empty());
2313 assert!(!error_types::JSON_INVALID.is_empty());
2314 assert!(!error_types::JSON_TYPE.is_empty());
2315 assert!(!error_types::TOO_SHORT.is_empty());
2316 assert!(!error_types::TOO_LONG.is_empty());
2317 assert!(!error_types::ENUM.is_empty());
2318 assert!(!error_types::EXTRA_FORBIDDEN.is_empty());
2319 }
2320
2321 #[test]
2322 fn error_types_numeric_range_constants() {
2323 assert_eq!(error_types::GREATER_THAN_EQUAL, "greater_than_equal");
2325 assert_eq!(error_types::LESS_THAN_EQUAL, "less_than_equal");
2326 }
2327
2328 #[test]
2329 fn error_types_collection_constants() {
2330 assert_eq!(error_types::TOO_SHORT, "too_short");
2332 assert_eq!(error_types::TOO_LONG, "too_long");
2333 }
2334
2335 #[test]
2340 fn validation_error_empty_location() {
2341 let error = ValidationError::new(error_types::VALUE_ERROR, vec![]);
2342 assert!(error.loc.is_empty());
2343
2344 let json = serde_json::to_value(&error).unwrap();
2345 assert_eq!(json["loc"], json!([]));
2346 }
2347
2348 #[test]
2349 fn validation_error_deeply_nested_location() {
2350 let error = ValidationError::missing(vec![
2352 LocItem::field("body"),
2353 LocItem::field("data"),
2354 LocItem::field("users"),
2355 LocItem::index(0),
2356 LocItem::field("profile"),
2357 LocItem::field("settings"),
2358 LocItem::index(5),
2359 LocItem::field("value"),
2360 ]);
2361
2362 let json = serde_json::to_value(&error).unwrap();
2363 assert_eq!(
2364 json["loc"],
2365 json!([
2366 "body", "data", "users", 0, "profile", "settings", 5, "value"
2367 ])
2368 );
2369 }
2370
2371 #[test]
2372 fn validation_errors_empty_to_json() {
2373 let errors = ValidationErrors::new();
2374 let json = errors.to_json();
2375 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
2376
2377 assert_eq!(parsed["detail"], json!([]));
2378 }
2379
2380 #[test]
2381 fn validation_errors_many_errors() {
2382 let mut errors = ValidationErrors::new();
2384 for i in 0..100 {
2385 errors.push(ValidationError::missing(loc::query(&format!("param{}", i))));
2386 }
2387
2388 assert_eq!(errors.len(), 100);
2389
2390 let json = errors.to_json();
2391 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
2392 assert_eq!(parsed["detail"].as_array().unwrap().len(), 100);
2393 }
2394
2395 #[test]
2396 fn validation_error_special_characters_in_field_name() {
2397 let error = ValidationError::missing(vec![
2399 LocItem::field("body"),
2400 LocItem::field("user-name"),
2401 LocItem::field("email@domain"),
2402 ]);
2403
2404 let json = serde_json::to_value(&error).unwrap();
2405 assert_eq!(json["loc"], json!(["body", "user-name", "email@domain"]));
2406 }
2407
2408 #[test]
2409 fn validation_error_unicode_in_message() {
2410 let error = ValidationError::new(error_types::VALUE_ERROR, loc::body_field("name"))
2411 .with_msg("名前が無効です");
2412
2413 let json = serde_json::to_value(&error).unwrap();
2414 assert_eq!(json["msg"], "名前が無効です");
2415 }
2416
2417 #[test]
2418 fn validation_error_large_input_value() {
2419 let large_string = "x".repeat(10000);
2421 let error = ValidationError::string_too_long(loc::body_field("bio"), 500)
2422 .with_input(json!(large_string));
2423
2424 let json = serde_json::to_value(&error).unwrap();
2425 assert_eq!(json["input"].as_str().unwrap().len(), 10000);
2426 }
2427
2428 #[test]
2429 fn http_error_empty_detail() {
2430 let error = HttpError::bad_request().with_detail("");
2432 assert_eq!(error.detail, Some(String::new()));
2433
2434 let response = error.into_response();
2435 let body = match response.body_ref() {
2436 ResponseBody::Bytes(b) => b.clone(),
2437 _ => panic!("Expected bytes body"),
2438 };
2439
2440 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2441 assert_eq!(parsed["detail"], "");
2443 }
2444
2445 #[test]
2446 fn http_error_binary_header_value() {
2447 let error = HttpError::bad_request().with_header("X-Binary", vec![0x00, 0xFF, 0x80]);
2449
2450 assert_eq!(error.headers[0].1, vec![0x00, 0xFF, 0x80]);
2451 }
2452
2453 #[test]
2458 #[serial]
2459 fn debug_mode_default_disabled() {
2460 disable_debug_mode();
2462 assert!(!is_debug_mode_enabled());
2463 }
2464
2465 #[test]
2466 #[serial]
2467 fn debug_mode_can_be_enabled_and_disabled() {
2468 disable_debug_mode();
2470 assert!(!is_debug_mode_enabled());
2471
2472 enable_debug_mode();
2474 assert!(is_debug_mode_enabled());
2475
2476 disable_debug_mode();
2478 assert!(!is_debug_mode_enabled());
2479 }
2480
2481 #[test]
2482 fn debug_config_default() {
2483 let config = DebugConfig::default();
2484 assert!(!config.enabled);
2485 assert!(config.debug_header.is_none());
2486 assert!(config.debug_token.is_none());
2487 assert!(!config.allow_unauthenticated);
2488 }
2489
2490 #[test]
2491 fn debug_config_builder() {
2492 let config = DebugConfig::new()
2493 .enable()
2494 .with_debug_header("X-Debug-Token", "secret123");
2495
2496 assert!(config.enabled);
2497 assert_eq!(config.debug_header, Some("X-Debug-Token".to_owned()));
2498 assert_eq!(config.debug_token, Some("secret123".to_owned()));
2499 assert!(!config.allow_unauthenticated);
2500 }
2501
2502 #[test]
2503 fn debug_config_allow_unauthenticated() {
2504 let config = DebugConfig::new().enable().allow_unauthenticated();
2505
2506 assert!(config.enabled);
2507 assert!(config.allow_unauthenticated);
2508 }
2509
2510 #[test]
2511 fn debug_config_is_authorized_when_disabled() {
2512 let config = DebugConfig::new();
2513 let headers: Vec<(String, Vec<u8>)> = vec![];
2514
2515 assert!(!config.is_authorized(&headers));
2517 }
2518
2519 #[test]
2520 fn debug_config_is_authorized_unauthenticated() {
2521 let config = DebugConfig::new().enable().allow_unauthenticated();
2522 let headers: Vec<(String, Vec<u8>)> = vec![];
2523
2524 assert!(config.is_authorized(&headers));
2526 }
2527
2528 #[test]
2529 fn debug_config_is_authorized_with_valid_token() {
2530 let config = DebugConfig::new()
2531 .enable()
2532 .with_debug_header("X-Debug-Token", "my-secret");
2533
2534 let headers = vec![("X-Debug-Token".to_owned(), b"my-secret".to_vec())];
2535
2536 assert!(config.is_authorized(&headers));
2537 }
2538
2539 #[test]
2540 fn debug_config_is_authorized_with_invalid_token() {
2541 let config = DebugConfig::new()
2542 .enable()
2543 .with_debug_header("X-Debug-Token", "my-secret");
2544
2545 let headers = vec![("X-Debug-Token".to_owned(), b"wrong-secret".to_vec())];
2546
2547 assert!(!config.is_authorized(&headers));
2548 }
2549
2550 #[test]
2551 fn debug_config_is_authorized_missing_header() {
2552 let config = DebugConfig::new()
2553 .enable()
2554 .with_debug_header("X-Debug-Token", "my-secret");
2555
2556 let headers: Vec<(String, Vec<u8>)> = vec![];
2557
2558 assert!(!config.is_authorized(&headers));
2559 }
2560
2561 #[test]
2562 fn debug_config_header_case_insensitive() {
2563 let config = DebugConfig::new()
2564 .enable()
2565 .with_debug_header("X-Debug-Token", "my-secret");
2566
2567 let headers = vec![("x-debug-token".to_owned(), b"my-secret".to_vec())];
2568
2569 assert!(config.is_authorized(&headers));
2570 }
2571
2572 #[test]
2573 fn debug_info_new() {
2574 let info = DebugInfo::new();
2575 assert!(info.is_empty());
2576 assert!(info.source_file.is_none());
2577 assert!(info.source_line.is_none());
2578 assert!(info.function_name.is_none());
2579 assert!(info.route_pattern.is_none());
2580 assert!(info.handler_name.is_none());
2581 assert!(info.extra.is_empty());
2582 }
2583
2584 #[test]
2585 fn debug_info_with_source_location() {
2586 let info = DebugInfo::new().with_source_location("src/handlers/user.rs", 42, "get_user");
2587
2588 assert!(!info.is_empty());
2589 assert_eq!(info.source_file, Some("src/handlers/user.rs".to_owned()));
2590 assert_eq!(info.source_line, Some(42));
2591 assert_eq!(info.function_name, Some("get_user".to_owned()));
2592 }
2593
2594 #[test]
2595 fn debug_info_with_route_pattern() {
2596 let info = DebugInfo::new().with_route_pattern("/users/{id}");
2597
2598 assert!(!info.is_empty());
2599 assert_eq!(info.route_pattern, Some("/users/{id}".to_owned()));
2600 }
2601
2602 #[test]
2603 fn debug_info_with_handler_name() {
2604 let info = DebugInfo::new().with_handler_name("UserController::get");
2605
2606 assert!(!info.is_empty());
2607 assert_eq!(info.handler_name, Some("UserController::get".to_owned()));
2608 }
2609
2610 #[test]
2611 fn debug_info_with_extra() {
2612 let info = DebugInfo::new()
2613 .with_extra("user_id", "abc123")
2614 .with_extra("request_id", "req-456");
2615
2616 assert!(!info.is_empty());
2617 assert_eq!(info.extra.get("user_id"), Some(&"abc123".to_owned()));
2618 assert_eq!(info.extra.get("request_id"), Some(&"req-456".to_owned()));
2619 }
2620
2621 #[test]
2622 fn debug_info_full_builder() {
2623 let info = DebugInfo::new()
2624 .with_source_location("src/api/users.rs", 100, "create_user")
2625 .with_route_pattern("/api/users")
2626 .with_handler_name("UsersHandler::create")
2627 .with_extra("method", "POST");
2628
2629 assert!(!info.is_empty());
2630 assert_eq!(info.source_file, Some("src/api/users.rs".to_owned()));
2631 assert_eq!(info.source_line, Some(100));
2632 assert_eq!(info.function_name, Some("create_user".to_owned()));
2633 assert_eq!(info.route_pattern, Some("/api/users".to_owned()));
2634 assert_eq!(info.handler_name, Some("UsersHandler::create".to_owned()));
2635 assert_eq!(info.extra.get("method"), Some(&"POST".to_owned()));
2636 }
2637
2638 #[test]
2639 fn debug_info_serialization() {
2640 let info = DebugInfo::new()
2641 .with_source_location("src/test.rs", 42, "test_fn")
2642 .with_route_pattern("/test");
2643
2644 let json = serde_json::to_value(&info).unwrap();
2645
2646 assert_eq!(json["source_file"], "src/test.rs");
2647 assert_eq!(json["source_line"], 42);
2648 assert_eq!(json["function_name"], "test_fn");
2649 assert_eq!(json["route_pattern"], "/test");
2650 assert!(json.get("handler_name").is_none());
2652 assert!(json.get("extra").is_none());
2653 }
2654
2655 #[test]
2656 fn debug_info_serialization_skip_none() {
2657 let info = DebugInfo::new().with_route_pattern("/test");
2658
2659 let json = serde_json::to_value(&info).unwrap();
2660
2661 assert_eq!(json["route_pattern"], "/test");
2663 assert!(json.get("source_file").is_none());
2664 assert!(json.get("source_line").is_none());
2665 assert!(json.get("function_name").is_none());
2666 }
2667
2668 #[test]
2669 fn http_error_with_debug_info() {
2670 let debug = DebugInfo::new()
2671 .with_source_location("src/handlers.rs", 50, "handle_request")
2672 .with_route_pattern("/api/test");
2673
2674 let error = HttpError::not_found()
2675 .with_detail("Resource not found")
2676 .with_debug_info(debug);
2677
2678 assert!(error.debug_info.is_some());
2679 let info = error.debug_info.unwrap();
2680 assert_eq!(info.source_file, Some("src/handlers.rs".to_owned()));
2681 assert_eq!(info.source_line, Some(50));
2682 }
2683
2684 #[test]
2685 #[serial]
2686 fn http_error_response_without_debug_mode() {
2687 disable_debug_mode();
2688
2689 let error = HttpError::not_found()
2690 .with_detail("User not found")
2691 .with_debug_info(
2692 DebugInfo::new()
2693 .with_source_location("src/test.rs", 42, "test")
2694 .with_route_pattern("/users/{id}"),
2695 );
2696
2697 let response = error.into_response();
2698 let body = match response.body_ref() {
2699 ResponseBody::Bytes(b) => b.clone(),
2700 _ => panic!("Expected bytes body"),
2701 };
2702
2703 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2704
2705 assert_eq!(parsed["detail"], "User not found");
2707 assert!(parsed.get("debug").is_none());
2708 }
2709
2710 #[test]
2711 #[serial]
2712 fn http_error_response_with_debug_mode() {
2713 enable_debug_mode();
2715
2716 let error = HttpError::not_found()
2717 .with_detail("User not found")
2718 .with_debug_info(
2719 DebugInfo::new()
2720 .with_source_location("src/test.rs", 42, "test")
2721 .with_route_pattern("/users/{id}"),
2722 );
2723
2724 let response = error.into_response();
2725 let body = match response.body_ref() {
2726 ResponseBody::Bytes(b) => b.clone(),
2727 _ => panic!("Expected bytes body"),
2728 };
2729
2730 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2731
2732 assert_eq!(parsed["detail"], "User not found");
2734 assert!(parsed.get("debug").is_some());
2735 assert_eq!(parsed["debug"]["source_file"], "src/test.rs");
2736 assert_eq!(parsed["debug"]["source_line"], 42);
2737 assert_eq!(parsed["debug"]["function_name"], "test");
2738 assert_eq!(parsed["debug"]["route_pattern"], "/users/{id}");
2739
2740 disable_debug_mode();
2742 }
2743
2744 #[test]
2745 #[serial]
2746 fn http_error_response_with_debug_mode_no_debug_info() {
2747 enable_debug_mode();
2749
2750 let error = HttpError::not_found().with_detail("User not found");
2751
2752 let response = error.into_response();
2753 let body = match response.body_ref() {
2754 ResponseBody::Bytes(b) => b.clone(),
2755 _ => panic!("Expected bytes body"),
2756 };
2757
2758 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2759
2760 assert_eq!(parsed["detail"], "User not found");
2762 assert!(parsed.get("debug").is_none());
2763
2764 disable_debug_mode();
2766 }
2767
2768 #[test]
2769 fn validation_errors_with_debug_info() {
2770 let errors = ValidationErrors::single(ValidationError::missing(loc::query("q")))
2771 .with_debug_info(
2772 DebugInfo::new()
2773 .with_source_location("src/extractors.rs", 100, "extract_query")
2774 .with_handler_name("SearchHandler::search"),
2775 );
2776
2777 assert!(errors.debug_info.is_some());
2778 }
2779
2780 #[test]
2781 #[serial]
2782 fn validation_errors_response_without_debug_mode() {
2783 disable_debug_mode();
2784
2785 let errors = ValidationErrors::single(ValidationError::missing(loc::query("q")))
2786 .with_debug_info(DebugInfo::new().with_source_location("src/test.rs", 42, "test"));
2787
2788 let response = errors.into_response();
2789 let body = match response.body_ref() {
2790 ResponseBody::Bytes(b) => b.clone(),
2791 _ => panic!("Expected bytes body"),
2792 };
2793
2794 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2795
2796 assert!(parsed["detail"].is_array());
2798 assert!(parsed.get("debug").is_none());
2799 }
2800
2801 #[test]
2802 #[serial]
2803 fn validation_errors_response_with_debug_mode() {
2804 enable_debug_mode();
2805
2806 let errors = ValidationErrors::single(ValidationError::missing(loc::query("q")))
2807 .with_debug_info(
2808 DebugInfo::new()
2809 .with_source_location("src/test.rs", 42, "test")
2810 .with_route_pattern("/search"),
2811 );
2812
2813 let response = errors.into_response();
2814 let body = match response.body_ref() {
2815 ResponseBody::Bytes(b) => b.clone(),
2816 _ => panic!("Expected bytes body"),
2817 };
2818
2819 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2820
2821 assert!(parsed["detail"].is_array());
2823 assert!(parsed.get("debug").is_some());
2824 assert_eq!(parsed["debug"]["source_file"], "src/test.rs");
2825 assert_eq!(parsed["debug"]["route_pattern"], "/search");
2826
2827 disable_debug_mode();
2829 }
2830
2831 #[test]
2832 fn validation_errors_merge_preserves_debug_info() {
2833 let mut errors1 = ValidationErrors::single(ValidationError::missing(loc::query("q")))
2834 .with_debug_info(DebugInfo::new().with_source_location("src/a.rs", 1, "a"));
2835
2836 let errors2 = ValidationErrors::single(ValidationError::missing(loc::query("page")))
2837 .with_debug_info(DebugInfo::new().with_source_location("src/b.rs", 2, "b"));
2838
2839 errors1.merge(errors2);
2840
2841 assert!(errors1.debug_info.is_some());
2843 assert_eq!(
2844 errors1.debug_info.as_ref().unwrap().source_file,
2845 Some("src/a.rs".to_owned())
2846 );
2847 }
2848
2849 #[test]
2850 fn validation_errors_merge_takes_other_debug_info_if_none() {
2851 let mut errors1 = ValidationErrors::single(ValidationError::missing(loc::query("q")));
2852
2853 let errors2 = ValidationErrors::single(ValidationError::missing(loc::query("page")))
2854 .with_debug_info(DebugInfo::new().with_source_location("src/b.rs", 2, "b"));
2855
2856 errors1.merge(errors2);
2857
2858 assert!(errors1.debug_info.is_some());
2860 assert_eq!(
2861 errors1.debug_info.as_ref().unwrap().source_file,
2862 Some("src/b.rs".to_owned())
2863 );
2864 }
2865
2866 #[test]
2871 fn response_validation_error_new_is_empty() {
2872 let error = ResponseValidationError::new();
2873 assert!(error.is_empty());
2874 assert_eq!(error.len(), 0);
2875 assert!(error.response_content.is_none());
2876 assert!(error.summary.is_none());
2877 }
2878
2879 #[test]
2880 fn response_validation_error_serialization_failed() {
2881 let error = ResponseValidationError::serialization_failed("failed to serialize DateTime");
2882 assert_eq!(error.len(), 1);
2883 assert!(error.summary.is_some());
2884 assert_eq!(
2885 error.summary.as_deref(),
2886 Some("failed to serialize DateTime")
2887 );
2888 assert_eq!(error.errors[0].error_type, error_types::SERIALIZATION_ERROR);
2889 }
2890
2891 #[test]
2892 fn response_validation_error_model_validation_failed() {
2893 let error = ResponseValidationError::model_validation_failed("missing required field 'id'");
2894 assert_eq!(error.len(), 1);
2895 assert!(error.summary.is_some());
2896 assert_eq!(
2897 error.errors[0].error_type,
2898 error_types::MODEL_VALIDATION_ERROR
2899 );
2900 }
2901
2902 #[test]
2903 fn response_validation_error_with_error() {
2904 let error = ResponseValidationError::new()
2905 .with_error(ValidationError::missing(loc::response_field("user_id")));
2906 assert_eq!(error.len(), 1);
2907 assert_eq!(error.errors[0].loc.len(), 2);
2908 }
2909
2910 #[test]
2911 fn response_validation_error_with_errors() {
2912 let error = ResponseValidationError::new().with_errors(vec![
2913 ValidationError::missing(loc::response_field("id")),
2914 ValidationError::missing(loc::response_field("name")),
2915 ]);
2916 assert_eq!(error.len(), 2);
2917 }
2918
2919 #[test]
2920 fn response_validation_error_with_response_content() {
2921 let content = json!({"name": "Alice", "age": 30});
2922 let error = ResponseValidationError::serialization_failed("test")
2923 .with_response_content(content.clone());
2924 assert!(error.response_content.is_some());
2925 assert_eq!(error.response_content.as_ref().unwrap()["name"], "Alice");
2926 }
2927
2928 #[test]
2929 fn response_validation_error_with_summary() {
2930 let error = ResponseValidationError::new().with_summary("Custom summary");
2931 assert_eq!(error.summary.as_deref(), Some("Custom summary"));
2932 }
2933
2934 #[test]
2935 fn response_validation_error_with_debug_info() {
2936 let error = ResponseValidationError::serialization_failed("test")
2937 .with_debug_info(DebugInfo::new().with_source_location("handler.rs", 42, "get_user"));
2938 assert!(error.debug_info.is_some());
2939 }
2940
2941 #[test]
2942 fn response_validation_error_display() {
2943 let error = ResponseValidationError::new();
2944 assert_eq!(format!("{}", error), "Response validation failed");
2945
2946 let error = ResponseValidationError::new().with_summary("missing field");
2947 assert_eq!(
2948 format!("{}", error),
2949 "Response validation failed: missing field"
2950 );
2951 }
2952
2953 #[test]
2954 #[serial]
2955 fn response_validation_error_into_response_production_mode() {
2956 disable_debug_mode();
2958
2959 let error = ResponseValidationError::serialization_failed("some internal error")
2960 .with_response_content(json!({"secret": "data"}));
2961
2962 let response = error.into_response();
2963 assert_eq!(response.status().as_u16(), 500);
2964
2965 let content_type = response
2967 .headers()
2968 .iter()
2969 .find(|(name, _)| name == "content-type")
2970 .map(|(_, value)| String::from_utf8_lossy(value).to_string());
2971 assert_eq!(content_type, Some("application/json".to_string()));
2972
2973 if let crate::response::ResponseBody::Bytes(bytes) = response.body_ref() {
2975 let body: serde_json::Value = serde_json::from_slice(bytes).unwrap();
2976 assert_eq!(body["error"], "internal_server_error");
2977 assert_eq!(body["detail"], "Internal Server Error");
2978 assert!(body.get("debug").is_none());
2980 } else {
2981 panic!("Expected Bytes body");
2982 }
2983 }
2984
2985 #[test]
2986 #[serial]
2987 fn response_validation_error_into_response_debug_mode() {
2988 enable_debug_mode();
2990
2991 let error = ResponseValidationError::serialization_failed("DateTime serialize failed")
2992 .with_response_content(json!({"created_at": "invalid-date"}))
2993 .with_debug_info(DebugInfo::new().with_source_location("handler.rs", 100, "get_user"));
2994
2995 let response = error.into_response();
2996 assert_eq!(response.status().as_u16(), 500);
2997
2998 if let crate::response::ResponseBody::Bytes(bytes) = response.body_ref() {
3000 let body: serde_json::Value = serde_json::from_slice(bytes).unwrap();
3001 assert_eq!(body["error"], "internal_server_error");
3002 assert!(body.get("debug").is_some());
3004 let debug = &body["debug"];
3005 assert_eq!(debug["summary"], "DateTime serialize failed");
3006 assert!(debug.get("errors").is_some());
3007 assert!(debug.get("response_content").is_some());
3008 } else {
3009 panic!("Expected Bytes body");
3010 }
3011
3012 disable_debug_mode();
3014 }
3015
3016 #[test]
3017 fn response_validation_error_to_log_string() {
3018 let error = ResponseValidationError::serialization_failed("test error")
3019 .with_error(ValidationError::missing(loc::response_field("id")))
3020 .with_response_content(json!({"name": "Alice"}));
3021
3022 let log = error.to_log_string();
3023 assert!(log.contains("Summary: test error"));
3024 assert!(log.contains("serialization_error"));
3025 assert!(log.contains("Response content:"));
3026 assert!(log.contains("Alice"));
3027 }
3028
3029 #[test]
3030 fn response_validation_error_to_log_string_truncates_large_content() {
3031 let large_string = "x".repeat(1000);
3033 let error = ResponseValidationError::serialization_failed("test")
3034 .with_response_content(json!({"data": large_string}));
3035
3036 let log = error.to_log_string();
3037 assert!(log.contains("(truncated)"));
3038 }
3039
3040 #[test]
3041 fn response_validation_error_iter() {
3042 let error = ResponseValidationError::new()
3043 .with_error(ValidationError::missing(loc::response_field("a")))
3044 .with_error(ValidationError::missing(loc::response_field("b")));
3045
3046 let locs: Vec<_> = error.iter().map(|e| e.loc.clone()).collect();
3047 assert_eq!(locs.len(), 2);
3048 }
3049
3050 #[test]
3051 fn loc_response_helper() {
3052 let loc = loc::response();
3053 assert_eq!(loc.len(), 1);
3054 assert!(matches!(&loc[0], LocItem::Field(s) if s == "response"));
3055 }
3056
3057 #[test]
3058 fn loc_response_field_helper() {
3059 let loc = loc::response_field("user_id");
3060 assert_eq!(loc.len(), 2);
3061 assert!(matches!(&loc[0], LocItem::Field(s) if s == "response"));
3062 assert!(matches!(&loc[1], LocItem::Field(s) if s == "user_id"));
3063 }
3064
3065 #[test]
3066 fn loc_response_path_helper() {
3067 let loc = loc::response_path(&["user", "profile", "name"]);
3068 assert_eq!(loc.len(), 4);
3069 assert!(matches!(&loc[0], LocItem::Field(s) if s == "response"));
3070 assert!(matches!(&loc[1], LocItem::Field(s) if s == "user"));
3071 assert!(matches!(&loc[2], LocItem::Field(s) if s == "profile"));
3072 assert!(matches!(&loc[3], LocItem::Field(s) if s == "name"));
3073 }
3074}