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 {
189 let a_bytes = a.as_bytes();
190 let b_bytes = b.as_bytes();
191
192 if a_bytes.len() != b_bytes.len() {
193 return false;
194 }
195
196 let diff = a_bytes
197 .iter()
198 .zip(b_bytes.iter())
199 .fold(0u8, |acc, (x, y)| acc | (x ^ y));
200
201 diff == 0
202}
203
204#[derive(Debug, Clone, Default, Serialize)]
234pub struct DebugInfo {
235 #[serde(skip_serializing_if = "Option::is_none")]
237 pub source_file: Option<String>,
238 #[serde(skip_serializing_if = "Option::is_none")]
240 pub source_line: Option<u32>,
241 #[serde(skip_serializing_if = "Option::is_none")]
243 pub function_name: Option<String>,
244 #[serde(skip_serializing_if = "Option::is_none")]
246 pub route_pattern: Option<String>,
247 #[serde(skip_serializing_if = "Option::is_none")]
249 pub handler_name: Option<String>,
250 #[serde(skip_serializing_if = "HashMap::is_empty")]
252 pub extra: HashMap<String, String>,
253}
254
255impl DebugInfo {
256 #[must_use]
258 pub fn new() -> Self {
259 Self::default()
260 }
261
262 #[must_use]
264 pub fn with_source_location(
265 mut self,
266 file: impl Into<String>,
267 line: u32,
268 function: impl Into<String>,
269 ) -> Self {
270 self.source_file = Some(file.into());
271 self.source_line = Some(line);
272 self.function_name = Some(function.into());
273 self
274 }
275
276 #[must_use]
278 pub fn with_route_pattern(mut self, pattern: impl Into<String>) -> Self {
279 self.route_pattern = Some(pattern.into());
280 self
281 }
282
283 #[must_use]
285 pub fn with_handler_name(mut self, name: impl Into<String>) -> Self {
286 self.handler_name = Some(name.into());
287 self
288 }
289
290 #[must_use]
292 pub fn with_extra(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
293 self.extra.insert(key.into(), value.into());
294 self
295 }
296
297 #[must_use]
299 pub fn is_empty(&self) -> bool {
300 self.source_file.is_none()
301 && self.source_line.is_none()
302 && self.function_name.is_none()
303 && self.route_pattern.is_none()
304 && self.handler_name.is_none()
305 && self.extra.is_empty()
306 }
307}
308
309#[macro_export]
321macro_rules! debug_location {
322 () => {
323 $crate::error::DebugInfo::new().with_source_location(
324 file!(),
325 line!(),
326 module_path!(),
328 )
329 };
330 ($func_name:expr) => {
331 $crate::error::DebugInfo::new().with_source_location(file!(), line!(), $func_name)
332 };
333}
334
335#[derive(Debug, Clone, PartialEq, Eq)]
358pub enum LocItem {
359 Field(String),
361 Index(usize),
363}
364
365impl LocItem {
366 #[must_use]
368 pub fn field(name: impl Into<String>) -> Self {
369 Self::Field(name.into())
370 }
371
372 #[must_use]
374 pub fn index(idx: usize) -> Self {
375 Self::Index(idx)
376 }
377
378 #[must_use]
380 pub fn as_str(&self) -> Option<&str> {
381 match self {
382 Self::Field(s) => Some(s),
383 Self::Index(_) => None,
384 }
385 }
386
387 #[must_use]
389 pub fn as_index(&self) -> Option<usize> {
390 match self {
391 Self::Field(_) => None,
392 Self::Index(i) => Some(*i),
393 }
394 }
395}
396
397impl From<&str> for LocItem {
398 fn from(s: &str) -> Self {
399 Self::Field(s.to_owned())
400 }
401}
402
403impl From<String> for LocItem {
404 fn from(s: String) -> Self {
405 Self::Field(s)
406 }
407}
408
409impl From<usize> for LocItem {
410 fn from(i: usize) -> Self {
411 Self::Index(i)
412 }
413}
414
415impl Serialize for LocItem {
416 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
417 where
418 S: Serializer,
419 {
420 match self {
421 Self::Field(s) => serializer.serialize_str(s),
422 Self::Index(i) => serializer.serialize_u64(*i as u64),
423 }
424 }
425}
426
427pub mod loc {
433 use super::LocItem;
434
435 #[must_use]
437 pub fn path(param: &str) -> Vec<LocItem> {
438 vec![LocItem::field("path"), LocItem::field(param)]
439 }
440
441 #[must_use]
443 pub fn query(param: &str) -> Vec<LocItem> {
444 vec![LocItem::field("query"), LocItem::field(param)]
445 }
446
447 #[must_use]
449 pub fn header(name: &str) -> Vec<LocItem> {
450 vec![LocItem::field("header"), LocItem::field(name)]
451 }
452
453 #[must_use]
455 pub fn cookie(name: &str) -> Vec<LocItem> {
456 vec![LocItem::field("cookie"), LocItem::field(name)]
457 }
458
459 #[must_use]
461 pub fn body() -> Vec<LocItem> {
462 vec![LocItem::field("body")]
463 }
464
465 #[must_use]
467 pub fn body_field(field: &str) -> Vec<LocItem> {
468 vec![LocItem::field("body"), LocItem::field(field)]
469 }
470
471 #[must_use]
473 pub fn body_path(fields: &[&str]) -> Vec<LocItem> {
474 let mut loc = vec![LocItem::field("body")];
475 for field in fields {
476 loc.push(LocItem::field(*field));
477 }
478 loc
479 }
480
481 #[must_use]
483 pub fn body_indexed(field: &str, idx: usize) -> Vec<LocItem> {
484 vec![
485 LocItem::field("body"),
486 LocItem::field(field),
487 LocItem::index(idx),
488 ]
489 }
490
491 #[must_use]
493 pub fn response() -> Vec<LocItem> {
494 vec![LocItem::field("response")]
495 }
496
497 #[must_use]
499 pub fn response_field(field: &str) -> Vec<LocItem> {
500 vec![LocItem::field("response"), LocItem::field(field)]
501 }
502
503 #[must_use]
505 pub fn response_path(fields: &[&str]) -> Vec<LocItem> {
506 let mut loc = vec![LocItem::field("response")];
507 for field in fields {
508 loc.push(LocItem::field(*field));
509 }
510 loc
511 }
512}
513
514pub mod error_types {
520 pub const MISSING: &str = "missing";
522 pub const STRING_TOO_SHORT: &str = "string_too_short";
524 pub const STRING_TOO_LONG: &str = "string_too_long";
526 pub const STRING_TYPE: &str = "string_type";
528 pub const INT_TYPE: &str = "int_type";
530 pub const FLOAT_TYPE: &str = "float_type";
532 pub const BOOL_TYPE: &str = "bool_type";
534 pub const GREATER_THAN_EQUAL: &str = "greater_than_equal";
536 pub const LESS_THAN_EQUAL: &str = "less_than_equal";
538 pub const STRING_PATTERN_MISMATCH: &str = "string_pattern_mismatch";
540 pub const VALUE_ERROR: &str = "value_error";
542 pub const URL_TYPE: &str = "url_type";
544 pub const UUID_TYPE: &str = "uuid_type";
546 pub const JSON_INVALID: &str = "json_invalid";
548 pub const JSON_TYPE: &str = "json_type";
550 pub const TOO_SHORT: &str = "too_short";
552 pub const TOO_LONG: &str = "too_long";
554 pub const ENUM: &str = "enum";
556 pub const EXTRA_FORBIDDEN: &str = "extra_forbidden";
558
559 pub const SERIALIZATION_ERROR: &str = "serialization_error";
562 pub const MODEL_VALIDATION_ERROR: &str = "model_validation_error";
564}
565
566#[derive(Debug)]
596pub struct HttpError {
597 pub status: StatusCode,
599 pub detail: Option<String>,
601 pub headers: Vec<(String, Vec<u8>)>,
603 pub debug_info: Option<DebugInfo>,
605}
606
607impl HttpError {
608 #[must_use]
610 pub fn new(status: StatusCode) -> Self {
611 Self {
612 status,
613 detail: None,
614 headers: Vec::new(),
615 debug_info: None,
616 }
617 }
618
619 #[must_use]
621 pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
622 self.detail = Some(detail.into());
623 self
624 }
625
626 #[must_use]
628 pub fn with_header(mut self, name: impl Into<String>, value: impl Into<Vec<u8>>) -> Self {
629 self.headers.push((name.into(), value.into()));
630 self
631 }
632
633 #[must_use]
650 pub fn with_debug_info(mut self, debug_info: DebugInfo) -> Self {
651 self.debug_info = Some(debug_info);
652 self
653 }
654
655 #[must_use]
662 pub fn with_debug_location(self, function_name: impl Into<String>) -> Self {
663 self.with_debug_info(DebugInfo::new().with_source_location(
664 std::any::type_name::<Self>(),
665 0, function_name,
667 ))
668 }
669
670 #[must_use]
672 pub fn bad_request() -> Self {
673 Self::new(StatusCode::BAD_REQUEST)
674 }
675
676 #[must_use]
678 pub fn unauthorized() -> Self {
679 Self::new(StatusCode::UNAUTHORIZED)
680 }
681
682 #[must_use]
684 pub fn forbidden() -> Self {
685 Self::new(StatusCode::FORBIDDEN)
686 }
687
688 #[must_use]
690 pub fn not_found() -> Self {
691 Self::new(StatusCode::NOT_FOUND)
692 }
693
694 #[must_use]
696 pub fn internal() -> Self {
697 Self::new(StatusCode::INTERNAL_SERVER_ERROR)
698 }
699
700 #[must_use]
702 pub fn payload_too_large() -> Self {
703 Self::new(StatusCode::PAYLOAD_TOO_LARGE)
704 }
705
706 #[must_use]
708 pub fn unsupported_media_type() -> Self {
709 Self::new(StatusCode::UNSUPPORTED_MEDIA_TYPE)
710 }
711}
712
713impl IntoResponse for HttpError {
714 fn into_response(self) -> Response {
715 let detail = self
716 .detail
717 .as_deref()
718 .unwrap_or_else(|| self.status.canonical_reason());
719
720 let body = if is_debug_mode_enabled() {
722 if let Some(ref debug_info) = self.debug_info {
723 #[derive(Serialize)]
724 struct ErrorBodyWithDebug<'a> {
725 detail: &'a str,
726 debug: &'a DebugInfo,
727 }
728 serde_json::to_vec(&ErrorBodyWithDebug {
729 detail,
730 debug: debug_info,
731 })
732 .unwrap_or_default()
733 } else {
734 #[derive(Serialize)]
735 struct ErrorBody<'a> {
736 detail: &'a str,
737 }
738 serde_json::to_vec(&ErrorBody { detail }).unwrap_or_default()
739 }
740 } else {
741 #[derive(Serialize)]
742 struct ErrorBody<'a> {
743 detail: &'a str,
744 }
745 serde_json::to_vec(&ErrorBody { detail }).unwrap_or_default()
746 };
747
748 let mut response = Response::with_status(self.status)
749 .header("content-type", b"application/json".to_vec())
750 .body(ResponseBody::Bytes(body));
751
752 for (name, value) in self.headers {
753 response = response.header(name, value);
754 }
755
756 response
757 }
758}
759
760impl std::fmt::Display for HttpError {
761 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
762 write!(f, "{}", self.status.canonical_reason())?;
763 if let Some(ref detail) = self.detail {
764 write!(f, ": {detail}")?;
765 }
766 Ok(())
767 }
768}
769
770impl std::error::Error for HttpError {}
771
772#[derive(Debug, Clone, Serialize)]
798pub struct ValidationError {
799 #[serde(rename = "type")]
801 pub error_type: &'static str,
802
803 pub loc: Vec<LocItem>,
810
811 pub msg: String,
813
814 #[serde(skip_serializing_if = "Option::is_none")]
818 pub input: Option<serde_json::Value>,
819
820 #[serde(skip_serializing_if = "Option::is_none")]
827 pub ctx: Option<HashMap<String, serde_json::Value>>,
828}
829
830impl ValidationError {
831 #[must_use]
833 pub fn new(error_type: &'static str, loc: Vec<LocItem>) -> Self {
834 Self {
835 error_type,
836 loc,
837 msg: Self::default_message(error_type),
838 input: None,
839 ctx: None,
840 }
841 }
842
843 #[must_use]
845 pub fn missing(loc: Vec<LocItem>) -> Self {
846 Self::new(error_types::MISSING, loc)
847 }
848
849 #[must_use]
851 pub fn string_too_short(loc: Vec<LocItem>, min_length: usize) -> Self {
852 Self::new(error_types::STRING_TOO_SHORT, loc)
853 .with_msg(format!(
854 "String should have at least {min_length} character{}",
855 if min_length == 1 { "" } else { "s" }
856 ))
857 .with_ctx_value("min_length", serde_json::json!(min_length))
858 }
859
860 #[must_use]
862 pub fn string_too_long(loc: Vec<LocItem>, max_length: usize) -> Self {
863 Self::new(error_types::STRING_TOO_LONG, loc)
864 .with_msg(format!(
865 "String should have at most {max_length} character{}",
866 if max_length == 1 { "" } else { "s" }
867 ))
868 .with_ctx_value("max_length", serde_json::json!(max_length))
869 }
870
871 #[must_use]
873 pub fn type_error(loc: Vec<LocItem>, expected_type: &'static str) -> Self {
874 let error_type = match expected_type {
875 "string" => error_types::STRING_TYPE,
876 "int" | "integer" => error_types::INT_TYPE,
877 "float" | "number" => error_types::FLOAT_TYPE,
878 "bool" | "boolean" => error_types::BOOL_TYPE,
879 _ => error_types::VALUE_ERROR,
880 };
881 Self::new(error_type, loc).with_msg(format!("Input should be a valid {expected_type}"))
882 }
883
884 #[must_use]
886 pub fn json_invalid(loc: Vec<LocItem>, message: impl Into<String>) -> Self {
887 Self::new(error_types::JSON_INVALID, loc).with_msg(message)
888 }
889
890 #[must_use]
892 pub fn with_msg(mut self, msg: impl Into<String>) -> Self {
893 self.msg = msg.into();
894 self
895 }
896
897 #[must_use]
899 pub fn with_input(mut self, input: serde_json::Value) -> Self {
900 self.input = Some(input);
901 self
902 }
903
904 #[must_use]
906 pub fn with_ctx_value(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
907 self.ctx
908 .get_or_insert_with(HashMap::new)
909 .insert(key.into(), value);
910 self
911 }
912
913 #[must_use]
915 pub fn with_ctx(mut self, ctx: HashMap<String, serde_json::Value>) -> Self {
916 self.ctx = Some(ctx);
917 self
918 }
919
920 #[must_use]
922 pub fn with_loc_prefix(mut self, prefix: Vec<LocItem>) -> Self {
923 let mut new_loc = prefix;
924 new_loc.extend(self.loc);
925 self.loc = new_loc;
926 self
927 }
928
929 #[must_use]
931 pub fn with_loc_suffix(mut self, item: impl Into<LocItem>) -> Self {
932 self.loc.push(item.into());
933 self
934 }
935
936 fn default_message(error_type: &str) -> String {
938 match error_type {
939 error_types::MISSING => "Field required".to_owned(),
940 error_types::STRING_TOO_SHORT => "String too short".to_owned(),
941 error_types::STRING_TOO_LONG => "String too long".to_owned(),
942 error_types::STRING_TYPE => "Input should be a valid string".to_owned(),
943 error_types::INT_TYPE => "Input should be a valid integer".to_owned(),
944 error_types::FLOAT_TYPE => "Input should be a valid number".to_owned(),
945 error_types::BOOL_TYPE => "Input should be a valid boolean".to_owned(),
946 error_types::JSON_INVALID => "Invalid JSON".to_owned(),
947 error_types::VALUE_ERROR => "Value error".to_owned(),
948 _ => "Validation error".to_owned(),
949 }
950 }
951}
952
953#[derive(Debug, Clone, Default)]
982pub struct ValidationErrors {
983 pub errors: Vec<ValidationError>,
985 pub body: Option<serde_json::Value>,
987 pub debug_info: Option<DebugInfo>,
989}
990
991impl ValidationErrors {
992 #[must_use]
994 pub fn new() -> Self {
995 Self {
996 errors: Vec::new(),
997 body: None,
998 debug_info: None,
999 }
1000 }
1001
1002 #[must_use]
1004 pub fn single(error: ValidationError) -> Self {
1005 Self {
1006 errors: vec![error],
1007 body: None,
1008 debug_info: None,
1009 }
1010 }
1011
1012 #[must_use]
1014 pub fn from_errors(errors: Vec<ValidationError>) -> Self {
1015 Self {
1016 errors,
1017 body: None,
1018 debug_info: None,
1019 }
1020 }
1021
1022 pub fn push(&mut self, error: ValidationError) {
1024 self.errors.push(error);
1025 }
1026
1027 pub fn extend(&mut self, errors: impl IntoIterator<Item = ValidationError>) {
1029 self.errors.extend(errors);
1030 }
1031
1032 #[must_use]
1034 pub fn with_body(mut self, body: serde_json::Value) -> Self {
1035 self.body = Some(body);
1036 self
1037 }
1038
1039 #[must_use]
1044 pub fn with_debug_info(mut self, debug_info: DebugInfo) -> Self {
1045 self.debug_info = Some(debug_info);
1046 self
1047 }
1048
1049 #[must_use]
1051 pub fn is_empty(&self) -> bool {
1052 self.errors.is_empty()
1053 }
1054
1055 #[must_use]
1057 pub fn len(&self) -> usize {
1058 self.errors.len()
1059 }
1060
1061 pub fn iter(&self) -> impl Iterator<Item = &ValidationError> {
1063 self.errors.iter()
1064 }
1065
1066 #[must_use]
1068 pub fn to_json(&self) -> String {
1069 #[derive(Serialize)]
1070 struct Body<'a> {
1071 detail: &'a [ValidationError],
1072 }
1073
1074 serde_json::to_string(&Body {
1075 detail: &self.errors,
1076 })
1077 .unwrap_or_else(|_| r#"{"detail":[]}"#.to_owned())
1078 }
1079
1080 #[must_use]
1082 pub fn to_json_bytes(&self) -> Vec<u8> {
1083 #[derive(Serialize)]
1084 struct Body<'a> {
1085 detail: &'a [ValidationError],
1086 }
1087
1088 serde_json::to_vec(&Body {
1089 detail: &self.errors,
1090 })
1091 .unwrap_or_else(|_| b"{\"detail\":[]}".to_vec())
1092 }
1093
1094 pub fn merge(&mut self, other: ValidationErrors) {
1096 self.errors.extend(other.errors);
1097 if self.body.is_none() {
1098 self.body = other.body;
1099 }
1100 if self.debug_info.is_none() {
1101 self.debug_info = other.debug_info;
1102 }
1103 }
1104
1105 #[must_use]
1107 pub fn with_loc_prefix(mut self, prefix: Vec<LocItem>) -> Self {
1108 for error in &mut self.errors {
1109 let mut new_loc = prefix.clone();
1110 new_loc.extend(std::mem::take(&mut error.loc));
1111 error.loc = new_loc;
1112 }
1113 self
1114 }
1115}
1116
1117impl IntoIterator for ValidationErrors {
1118 type Item = ValidationError;
1119 type IntoIter = std::vec::IntoIter<ValidationError>;
1120
1121 fn into_iter(self) -> Self::IntoIter {
1122 self.errors.into_iter()
1123 }
1124}
1125
1126impl<'a> IntoIterator for &'a ValidationErrors {
1127 type Item = &'a ValidationError;
1128 type IntoIter = std::slice::Iter<'a, ValidationError>;
1129
1130 fn into_iter(self) -> Self::IntoIter {
1131 self.errors.iter()
1132 }
1133}
1134
1135impl Extend<ValidationError> for ValidationErrors {
1136 fn extend<T: IntoIterator<Item = ValidationError>>(&mut self, iter: T) {
1137 self.errors.extend(iter);
1138 }
1139}
1140
1141impl FromIterator<ValidationError> for ValidationErrors {
1142 fn from_iter<T: IntoIterator<Item = ValidationError>>(iter: T) -> Self {
1143 Self::from_errors(iter.into_iter().collect())
1144 }
1145}
1146
1147impl std::fmt::Display for ValidationErrors {
1148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1149 write!(f, "{} validation error", self.errors.len())?;
1150 if self.errors.len() != 1 {
1151 write!(f, "s")?;
1152 }
1153 Ok(())
1154 }
1155}
1156
1157impl std::error::Error for ValidationErrors {}
1158
1159impl IntoResponse for ValidationErrors {
1160 fn into_response(self) -> Response {
1161 let body = if is_debug_mode_enabled() {
1163 if let Some(ref debug_info) = self.debug_info {
1164 #[derive(Serialize)]
1165 struct BodyWithDebug<'a> {
1166 detail: &'a [ValidationError],
1167 debug: &'a DebugInfo,
1168 }
1169 serde_json::to_vec(&BodyWithDebug {
1170 detail: &self.errors,
1171 debug: debug_info,
1172 })
1173 .unwrap_or_else(|_| b"{\"detail\":[]}".to_vec())
1174 } else {
1175 self.to_json_bytes()
1176 }
1177 } else {
1178 self.to_json_bytes()
1179 };
1180
1181 Response::with_status(StatusCode::UNPROCESSABLE_ENTITY)
1182 .header("content-type", b"application/json".to_vec())
1183 .body(ResponseBody::Bytes(body))
1184 }
1185}
1186
1187#[derive(Debug, Clone, Default)]
1226pub struct ResponseValidationError {
1227 pub errors: Vec<ValidationError>,
1229 pub response_content: Option<serde_json::Value>,
1232 pub summary: Option<String>,
1234 pub debug_info: Option<DebugInfo>,
1236}
1237
1238impl ResponseValidationError {
1239 #[must_use]
1241 pub fn new() -> Self {
1242 Self::default()
1243 }
1244
1245 #[must_use]
1249 pub fn serialization_failed(message: impl Into<String>) -> Self {
1250 let msg = message.into();
1251 Self {
1252 errors: vec![
1253 ValidationError::new(
1254 error_types::SERIALIZATION_ERROR,
1255 vec![LocItem::field("response")],
1256 )
1257 .with_msg(&msg),
1258 ],
1259 response_content: None,
1260 summary: Some(msg),
1261 debug_info: None,
1262 }
1263 }
1264
1265 #[must_use]
1269 pub fn model_validation_failed(message: impl Into<String>) -> Self {
1270 let msg = message.into();
1271 Self {
1272 errors: vec![
1273 ValidationError::new(
1274 error_types::MODEL_VALIDATION_ERROR,
1275 vec![LocItem::field("response")],
1276 )
1277 .with_msg(&msg),
1278 ],
1279 response_content: None,
1280 summary: Some(msg),
1281 debug_info: None,
1282 }
1283 }
1284
1285 #[must_use]
1287 pub fn with_error(mut self, error: ValidationError) -> Self {
1288 self.errors.push(error);
1289 self
1290 }
1291
1292 #[must_use]
1294 pub fn with_errors(mut self, errors: impl IntoIterator<Item = ValidationError>) -> Self {
1295 self.errors.extend(errors);
1296 self
1297 }
1298
1299 #[must_use]
1303 pub fn with_response_content(mut self, content: serde_json::Value) -> Self {
1304 self.response_content = Some(content);
1305 self
1306 }
1307
1308 #[must_use]
1310 pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
1311 self.summary = Some(summary.into());
1312 self
1313 }
1314
1315 #[must_use]
1320 pub fn with_debug_info(mut self, debug_info: DebugInfo) -> Self {
1321 self.debug_info = Some(debug_info);
1322 self
1323 }
1324
1325 #[must_use]
1327 pub fn is_empty(&self) -> bool {
1328 self.errors.is_empty()
1329 }
1330
1331 #[must_use]
1333 pub fn len(&self) -> usize {
1334 self.errors.len()
1335 }
1336
1337 pub fn iter(&self) -> impl Iterator<Item = &ValidationError> {
1339 self.errors.iter()
1340 }
1341
1342 #[must_use]
1347 pub fn to_log_string(&self) -> String {
1348 let mut parts = Vec::new();
1349
1350 if let Some(ref summary) = self.summary {
1351 parts.push(format!("Summary: {}", summary));
1352 }
1353
1354 parts.push(format!("Errors ({}): ", self.errors.len()));
1355 for (i, error) in self.errors.iter().enumerate() {
1356 let loc_str: Vec<String> = error
1357 .loc
1358 .iter()
1359 .map(|item| match item {
1360 LocItem::Field(s) => s.clone(),
1361 LocItem::Index(i) => i.to_string(),
1362 })
1363 .collect();
1364 parts.push(format!(
1365 " [{}] {} at [{}]: {}",
1366 i + 1,
1367 error.error_type,
1368 loc_str.join("."),
1369 error.msg
1370 ));
1371 }
1372
1373 if let Some(ref content) = self.response_content {
1374 let content_str = serde_json::to_string(content).unwrap_or_default();
1376 let truncated = if content_str.len() > 500 {
1377 let mut end = 500;
1379 while end > 0 && !content_str.is_char_boundary(end) {
1380 end -= 1;
1381 }
1382 format!("{}...(truncated)", &content_str[..end])
1383 } else {
1384 content_str
1385 };
1386 parts.push(format!("Response content: {}", truncated));
1387 }
1388
1389 parts.join("\n")
1390 }
1391}
1392
1393impl std::fmt::Display for ResponseValidationError {
1394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1395 write!(f, "Response validation failed")?;
1396 if let Some(ref summary) = self.summary {
1397 write!(f, ": {}", summary)?;
1398 }
1399 Ok(())
1400 }
1401}
1402
1403impl std::error::Error for ResponseValidationError {}
1404
1405impl IntoResponse for ResponseValidationError {
1406 fn into_response(self) -> Response {
1407 let body = if is_debug_mode_enabled() {
1413 #[derive(Serialize)]
1415 struct DebugBody<'a> {
1416 error: &'static str,
1417 detail: &'static str,
1418 #[serde(skip_serializing_if = "Option::is_none")]
1419 debug: Option<DebugResponseInfo<'a>>,
1420 }
1421
1422 #[derive(Serialize)]
1423 struct DebugResponseInfo<'a> {
1424 #[serde(skip_serializing_if = "Option::is_none")]
1425 summary: Option<&'a str>,
1426 errors: &'a [ValidationError],
1427 #[serde(skip_serializing_if = "Option::is_none")]
1428 response_content: &'a Option<serde_json::Value>,
1429 #[serde(skip_serializing_if = "Option::is_none")]
1430 source: Option<&'a DebugInfo>,
1431 }
1432
1433 let debug_info = DebugResponseInfo {
1434 summary: self.summary.as_deref(),
1435 errors: &self.errors,
1436 response_content: &self.response_content,
1437 source: self.debug_info.as_ref(),
1438 };
1439
1440 serde_json::to_vec(&DebugBody {
1441 error: "internal_server_error",
1442 detail: "Response validation failed",
1443 debug: Some(debug_info),
1444 })
1445 .unwrap_or_else(|_| {
1446 b"{\"error\":\"internal_server_error\",\"detail\":\"Internal Server Error\"}"
1447 .to_vec()
1448 })
1449 } else {
1450 b"{\"error\":\"internal_server_error\",\"detail\":\"Internal Server Error\"}".to_vec()
1452 };
1453
1454 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
1455 .header("content-type", b"application/json".to_vec())
1456 .body(ResponseBody::Bytes(body))
1457 }
1458}
1459
1460#[cfg(test)]
1465mod tests {
1466 use super::*;
1467 use serde_json::json;
1468 use serial_test::serial;
1469
1470 #[test]
1475 fn loc_item_field_creation() {
1476 let item = LocItem::field("email");
1477 assert_eq!(item.as_str(), Some("email"));
1478 assert_eq!(item.as_index(), None);
1479 }
1480
1481 #[test]
1482 fn loc_item_index_creation() {
1483 let item = LocItem::index(42);
1484 assert_eq!(item.as_str(), None);
1485 assert_eq!(item.as_index(), Some(42));
1486 }
1487
1488 #[test]
1489 fn loc_item_from_str() {
1490 let item: LocItem = "name".into();
1491 assert_eq!(item, LocItem::Field("name".to_owned()));
1492 }
1493
1494 #[test]
1495 fn loc_item_from_string() {
1496 let item: LocItem = String::from("age").into();
1497 assert_eq!(item, LocItem::Field("age".to_owned()));
1498 }
1499
1500 #[test]
1501 fn loc_item_from_usize() {
1502 let item: LocItem = 5usize.into();
1503 assert_eq!(item, LocItem::Index(5));
1504 }
1505
1506 #[test]
1507 fn loc_item_serialize_field() {
1508 let item = LocItem::field("email");
1509 let json = serde_json::to_string(&item).unwrap();
1510 assert_eq!(json, "\"email\"");
1511 }
1512
1513 #[test]
1514 fn loc_item_serialize_index() {
1515 let item = LocItem::index(3);
1516 let json = serde_json::to_string(&item).unwrap();
1517 assert_eq!(json, "3");
1518 }
1519
1520 #[test]
1525 fn loc_path_creates_correct_location() {
1526 let loc = loc::path("user_id");
1527 assert_eq!(loc.len(), 2);
1528 assert_eq!(loc[0].as_str(), Some("path"));
1529 assert_eq!(loc[1].as_str(), Some("user_id"));
1530 }
1531
1532 #[test]
1533 fn loc_query_creates_correct_location() {
1534 let loc = loc::query("q");
1535 assert_eq!(loc.len(), 2);
1536 assert_eq!(loc[0].as_str(), Some("query"));
1537 assert_eq!(loc[1].as_str(), Some("q"));
1538 }
1539
1540 #[test]
1541 fn loc_header_creates_correct_location() {
1542 let loc = loc::header("Authorization");
1543 assert_eq!(loc.len(), 2);
1544 assert_eq!(loc[0].as_str(), Some("header"));
1545 assert_eq!(loc[1].as_str(), Some("Authorization"));
1546 }
1547
1548 #[test]
1549 fn loc_cookie_creates_correct_location() {
1550 let loc = loc::cookie("session_id");
1551 assert_eq!(loc.len(), 2);
1552 assert_eq!(loc[0].as_str(), Some("cookie"));
1553 assert_eq!(loc[1].as_str(), Some("session_id"));
1554 }
1555
1556 #[test]
1557 fn loc_body_creates_root_location() {
1558 let loc = loc::body();
1559 assert_eq!(loc.len(), 1);
1560 assert_eq!(loc[0].as_str(), Some("body"));
1561 }
1562
1563 #[test]
1564 fn loc_body_field_creates_correct_location() {
1565 let loc = loc::body_field("email");
1566 assert_eq!(loc.len(), 2);
1567 assert_eq!(loc[0].as_str(), Some("body"));
1568 assert_eq!(loc[1].as_str(), Some("email"));
1569 }
1570
1571 #[test]
1572 fn loc_body_path_creates_nested_location() {
1573 let loc = loc::body_path(&["user", "profile", "name"]);
1574 assert_eq!(loc.len(), 4);
1575 assert_eq!(loc[0].as_str(), Some("body"));
1576 assert_eq!(loc[1].as_str(), Some("user"));
1577 assert_eq!(loc[2].as_str(), Some("profile"));
1578 assert_eq!(loc[3].as_str(), Some("name"));
1579 }
1580
1581 #[test]
1582 fn loc_body_indexed_creates_array_location() {
1583 let loc = loc::body_indexed("items", 0);
1584 assert_eq!(loc.len(), 3);
1585 assert_eq!(loc[0].as_str(), Some("body"));
1586 assert_eq!(loc[1].as_str(), Some("items"));
1587 assert_eq!(loc[2].as_index(), Some(0));
1588 }
1589
1590 #[test]
1595 fn validation_error_new_with_default_message() {
1596 let error = ValidationError::new(error_types::MISSING, loc::query("q"));
1597 assert_eq!(error.error_type, "missing");
1598 assert_eq!(error.msg, "Field required");
1599 assert!(error.input.is_none());
1600 assert!(error.ctx.is_none());
1601 }
1602
1603 #[test]
1604 fn validation_error_missing() {
1605 let error = ValidationError::missing(loc::query("page"));
1606 assert_eq!(error.error_type, "missing");
1607 assert_eq!(error.msg, "Field required");
1608 }
1609
1610 #[test]
1611 fn validation_error_string_too_short() {
1612 let error = ValidationError::string_too_short(loc::body_field("name"), 3);
1613 assert_eq!(error.error_type, "string_too_short");
1614 assert!(error.msg.contains("3"));
1615 assert!(error.ctx.is_some());
1616 let ctx = error.ctx.unwrap();
1617 assert_eq!(ctx.get("min_length"), Some(&json!(3)));
1618 }
1619
1620 #[test]
1621 fn validation_error_string_too_long() {
1622 let error = ValidationError::string_too_long(loc::body_field("bio"), 500);
1623 assert_eq!(error.error_type, "string_too_long");
1624 assert!(error.msg.contains("500"));
1625 assert!(error.ctx.is_some());
1626 let ctx = error.ctx.unwrap();
1627 assert_eq!(ctx.get("max_length"), Some(&json!(500)));
1628 }
1629
1630 #[test]
1631 fn validation_error_type_error_int() {
1632 let error = ValidationError::type_error(loc::query("count"), "integer");
1633 assert_eq!(error.error_type, "int_type");
1634 assert!(error.msg.contains("integer"));
1635 }
1636
1637 #[test]
1638 fn validation_error_type_error_string() {
1639 let error = ValidationError::type_error(loc::body_field("name"), "string");
1640 assert_eq!(error.error_type, "string_type");
1641 assert!(error.msg.contains("string"));
1642 }
1643
1644 #[test]
1645 fn validation_error_json_invalid() {
1646 let error = ValidationError::json_invalid(loc::body(), "unexpected end of input");
1647 assert_eq!(error.error_type, "json_invalid");
1648 assert_eq!(error.msg, "unexpected end of input");
1649 }
1650
1651 #[test]
1652 fn validation_error_with_input() {
1653 let error = ValidationError::missing(loc::query("q")).with_input(json!(null));
1654 assert_eq!(error.input, Some(json!(null)));
1655 }
1656
1657 #[test]
1658 fn validation_error_with_ctx_value() {
1659 let error = ValidationError::new(error_types::GREATER_THAN_EQUAL, loc::body_field("age"))
1660 .with_ctx_value("ge", json!(0));
1661 assert!(error.ctx.is_some());
1662 assert_eq!(error.ctx.unwrap().get("ge"), Some(&json!(0)));
1663 }
1664
1665 #[test]
1666 fn validation_error_with_multiple_ctx_values() {
1667 let error = ValidationError::new(
1668 error_types::STRING_PATTERN_MISMATCH,
1669 loc::body_field("email"),
1670 )
1671 .with_ctx_value("pattern", json!("^.+@.+$"))
1672 .with_ctx_value("expected", json!("email format"));
1673 let ctx = error.ctx.unwrap();
1674 assert_eq!(ctx.len(), 2);
1675 assert_eq!(ctx.get("pattern"), Some(&json!("^.+@.+$")));
1676 assert_eq!(ctx.get("expected"), Some(&json!("email format")));
1677 }
1678
1679 #[test]
1680 fn validation_error_with_loc_prefix() {
1681 let error = ValidationError::missing(vec![LocItem::field("email")])
1682 .with_loc_prefix(vec![LocItem::field("body"), LocItem::field("user")]);
1683 assert_eq!(error.loc.len(), 3);
1684 assert_eq!(error.loc[0].as_str(), Some("body"));
1685 assert_eq!(error.loc[1].as_str(), Some("user"));
1686 assert_eq!(error.loc[2].as_str(), Some("email"));
1687 }
1688
1689 #[test]
1690 fn validation_error_with_loc_suffix() {
1691 let error = ValidationError::missing(loc::body())
1692 .with_loc_suffix("items")
1693 .with_loc_suffix(0usize)
1694 .with_loc_suffix("name");
1695 assert_eq!(error.loc.len(), 4);
1696 assert_eq!(error.loc[0].as_str(), Some("body"));
1697 assert_eq!(error.loc[1].as_str(), Some("items"));
1698 assert_eq!(error.loc[2].as_index(), Some(0));
1699 assert_eq!(error.loc[3].as_str(), Some("name"));
1700 }
1701
1702 #[test]
1707 fn validation_error_serializes_to_fastapi_format() {
1708 let error = ValidationError::missing(loc::query("q"));
1709 let json = serde_json::to_value(&error).unwrap();
1710
1711 assert_eq!(json["type"], "missing");
1712 assert_eq!(json["loc"], json!(["query", "q"]));
1713 assert_eq!(json["msg"], "Field required");
1714 assert!(json.get("input").is_none()); assert!(json.get("ctx").is_none());
1716 }
1717
1718 #[test]
1719 fn validation_error_serializes_with_array_index() {
1720 let error = ValidationError::missing(vec![
1721 LocItem::field("body"),
1722 LocItem::field("items"),
1723 LocItem::index(2),
1724 LocItem::field("name"),
1725 ]);
1726 let json = serde_json::to_value(&error).unwrap();
1727
1728 assert_eq!(json["loc"], json!(["body", "items", 2, "name"]));
1729 }
1730
1731 #[test]
1732 fn validation_error_serializes_with_input_and_ctx() {
1733 let error =
1734 ValidationError::string_too_short(loc::body_field("name"), 3).with_input(json!("ab"));
1735 let json = serde_json::to_value(&error).unwrap();
1736
1737 assert_eq!(json["input"], "ab");
1738 assert_eq!(json["ctx"]["min_length"], 3);
1739 }
1740
1741 #[test]
1746 fn validation_errors_new_is_empty() {
1747 let errors = ValidationErrors::new();
1748 assert!(errors.is_empty());
1749 assert_eq!(errors.len(), 0);
1750 }
1751
1752 #[test]
1753 fn validation_errors_single() {
1754 let errors = ValidationErrors::single(ValidationError::missing(loc::query("q")));
1755 assert!(!errors.is_empty());
1756 assert_eq!(errors.len(), 1);
1757 }
1758
1759 #[test]
1760 fn validation_errors_push() {
1761 let mut errors = ValidationErrors::new();
1762 errors.push(ValidationError::missing(loc::query("q")));
1763 errors.push(ValidationError::missing(loc::query("page")));
1764 assert_eq!(errors.len(), 2);
1765 }
1766
1767 #[test]
1768 fn validation_errors_extend() {
1769 let mut errors = ValidationErrors::new();
1770 errors.extend(vec![
1771 ValidationError::missing(loc::query("q")),
1772 ValidationError::missing(loc::query("page")),
1773 ]);
1774 assert_eq!(errors.len(), 2);
1775 }
1776
1777 #[test]
1778 fn validation_errors_from_errors() {
1779 let errors = ValidationErrors::from_errors(vec![
1780 ValidationError::missing(loc::query("q")),
1781 ValidationError::string_too_short(loc::body_field("name"), 1),
1782 ]);
1783 assert_eq!(errors.len(), 2);
1784 }
1785
1786 #[test]
1787 fn validation_errors_with_body() {
1788 let body = json!({"name": ""});
1789 let errors = ValidationErrors::single(ValidationError::string_too_short(
1790 loc::body_field("name"),
1791 1,
1792 ))
1793 .with_body(body.clone());
1794
1795 assert_eq!(errors.body, Some(body));
1796 }
1797
1798 #[test]
1799 fn validation_errors_merge() {
1800 let mut errors1 = ValidationErrors::single(ValidationError::missing(loc::query("q")));
1801 let errors2 = ValidationErrors::single(ValidationError::missing(loc::query("page")));
1802
1803 errors1.merge(errors2);
1804 assert_eq!(errors1.len(), 2);
1805 }
1806
1807 #[test]
1808 fn validation_errors_with_loc_prefix() {
1809 let errors = ValidationErrors::from_errors(vec![
1810 ValidationError::missing(vec![LocItem::field("name")]),
1811 ValidationError::missing(vec![LocItem::field("email")]),
1812 ])
1813 .with_loc_prefix(vec![LocItem::field("body"), LocItem::field("user")]);
1814
1815 for error in &errors {
1816 assert_eq!(error.loc[0].as_str(), Some("body"));
1817 assert_eq!(error.loc[1].as_str(), Some("user"));
1818 }
1819 }
1820
1821 #[test]
1822 fn validation_errors_iter() {
1823 let errors = ValidationErrors::from_errors(vec![
1824 ValidationError::missing(loc::query("q")),
1825 ValidationError::missing(loc::query("page")),
1826 ]);
1827
1828 let count = errors.iter().count();
1829 assert_eq!(count, 2);
1830 }
1831
1832 #[test]
1833 fn validation_errors_into_iter() {
1834 let errors = ValidationErrors::from_errors(vec![
1835 ValidationError::missing(loc::query("q")),
1836 ValidationError::missing(loc::query("page")),
1837 ]);
1838
1839 let collected: Vec<_> = errors.into_iter().collect();
1840 assert_eq!(collected.len(), 2);
1841 }
1842
1843 #[test]
1844 fn validation_errors_from_iterator() {
1845 let errors: ValidationErrors = vec![
1846 ValidationError::missing(loc::query("q")),
1847 ValidationError::missing(loc::query("page")),
1848 ]
1849 .into_iter()
1850 .collect();
1851
1852 assert_eq!(errors.len(), 2);
1853 }
1854
1855 #[test]
1860 fn validation_errors_to_json() {
1861 let errors = ValidationErrors::single(
1862 ValidationError::missing(loc::query("q")).with_input(json!(null)),
1863 );
1864 let json = errors.to_json();
1865
1866 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1867 assert!(parsed["detail"].is_array());
1868 assert_eq!(parsed["detail"][0]["type"], "missing");
1869 assert_eq!(parsed["detail"][0]["loc"], json!(["query", "q"]));
1870 }
1871
1872 #[test]
1873 fn validation_errors_to_json_bytes() {
1874 let errors = ValidationErrors::single(ValidationError::missing(loc::query("q")));
1875 let bytes = errors.to_json_bytes();
1876 let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
1877
1878 assert!(json["detail"].is_array());
1879 }
1880
1881 #[test]
1882 fn validation_errors_fastapi_format_match() {
1883 let errors = ValidationErrors::from_errors(vec![
1885 ValidationError::missing(loc::query("q")),
1886 ValidationError::string_too_short(loc::body_field("name"), 3).with_input(json!("ab")),
1887 ]);
1888
1889 let json = errors.to_json();
1890 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1891
1892 assert_eq!(parsed["detail"][0]["type"], "missing");
1894 assert_eq!(parsed["detail"][0]["loc"], json!(["query", "q"]));
1895 assert_eq!(parsed["detail"][0]["msg"], "Field required");
1896
1897 assert_eq!(parsed["detail"][1]["type"], "string_too_short");
1899 assert_eq!(parsed["detail"][1]["loc"], json!(["body", "name"]));
1900 assert_eq!(parsed["detail"][1]["input"], "ab");
1901 assert_eq!(parsed["detail"][1]["ctx"]["min_length"], 3);
1902 }
1903
1904 #[test]
1905 fn validation_errors_nested_array_location() {
1906 let error = ValidationError::missing(vec![
1908 LocItem::field("body"),
1909 LocItem::field("items"),
1910 LocItem::index(0),
1911 LocItem::field("name"),
1912 ]);
1913 let errors = ValidationErrors::single(error);
1914 let json = errors.to_json();
1915 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1916
1917 assert_eq!(
1918 parsed["detail"][0]["loc"],
1919 json!(["body", "items", 0, "name"])
1920 );
1921 }
1922
1923 #[test]
1928 fn validation_errors_into_response() {
1929 let errors = ValidationErrors::single(ValidationError::missing(loc::query("q")));
1930 let response = errors.into_response();
1931
1932 assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
1933
1934 let content_type = response
1936 .headers()
1937 .iter()
1938 .find(|(name, _): &&(String, Vec<u8>)| name.eq_ignore_ascii_case("content-type"))
1939 .map(|(_, v)| v.as_slice());
1940 assert_eq!(content_type, Some(b"application/json".as_slice()));
1941 }
1942
1943 #[test]
1948 fn error_types_match_pydantic() {
1949 assert_eq!(error_types::MISSING, "missing");
1951 assert_eq!(error_types::STRING_TOO_SHORT, "string_too_short");
1952 assert_eq!(error_types::STRING_TOO_LONG, "string_too_long");
1953 assert_eq!(error_types::STRING_TYPE, "string_type");
1954 assert_eq!(error_types::INT_TYPE, "int_type");
1955 assert_eq!(error_types::FLOAT_TYPE, "float_type");
1956 assert_eq!(error_types::BOOL_TYPE, "bool_type");
1957 assert_eq!(error_types::JSON_INVALID, "json_invalid");
1958 assert_eq!(error_types::VALUE_ERROR, "value_error");
1959 }
1960
1961 #[test]
1966 fn http_error_new_with_status() {
1967 let error = HttpError::new(StatusCode::NOT_FOUND);
1968 assert_eq!(error.status, StatusCode::NOT_FOUND);
1969 assert!(error.detail.is_none());
1970 assert!(error.headers.is_empty());
1971 }
1972
1973 #[test]
1974 fn http_error_bad_request() {
1975 let error = HttpError::bad_request();
1976 assert_eq!(error.status, StatusCode::BAD_REQUEST);
1977 assert_eq!(error.status.as_u16(), 400);
1978 }
1979
1980 #[test]
1981 fn http_error_unauthorized() {
1982 let error = HttpError::unauthorized();
1983 assert_eq!(error.status, StatusCode::UNAUTHORIZED);
1984 assert_eq!(error.status.as_u16(), 401);
1985 }
1986
1987 #[test]
1988 fn http_error_forbidden() {
1989 let error = HttpError::forbidden();
1990 assert_eq!(error.status, StatusCode::FORBIDDEN);
1991 assert_eq!(error.status.as_u16(), 403);
1992 }
1993
1994 #[test]
1995 fn http_error_not_found() {
1996 let error = HttpError::not_found();
1997 assert_eq!(error.status, StatusCode::NOT_FOUND);
1998 assert_eq!(error.status.as_u16(), 404);
1999 }
2000
2001 #[test]
2002 fn http_error_internal() {
2003 let error = HttpError::internal();
2004 assert_eq!(error.status, StatusCode::INTERNAL_SERVER_ERROR);
2005 assert_eq!(error.status.as_u16(), 500);
2006 }
2007
2008 #[test]
2009 fn http_error_payload_too_large() {
2010 let error = HttpError::payload_too_large();
2011 assert_eq!(error.status, StatusCode::PAYLOAD_TOO_LARGE);
2012 assert_eq!(error.status.as_u16(), 413);
2013 }
2014
2015 #[test]
2016 fn http_error_unsupported_media_type() {
2017 let error = HttpError::unsupported_media_type();
2018 assert_eq!(error.status, StatusCode::UNSUPPORTED_MEDIA_TYPE);
2019 assert_eq!(error.status.as_u16(), 415);
2020 }
2021
2022 #[test]
2023 fn http_error_with_detail() {
2024 let error = HttpError::not_found().with_detail("User not found");
2025 assert_eq!(error.detail, Some("User not found".to_owned()));
2026 }
2027
2028 #[test]
2029 fn http_error_with_detail_owned_string() {
2030 let detail = String::from("Resource missing");
2031 let error = HttpError::not_found().with_detail(detail);
2032 assert_eq!(error.detail, Some("Resource missing".to_owned()));
2033 }
2034
2035 #[test]
2036 fn http_error_with_header() {
2037 let error = HttpError::unauthorized()
2038 .with_header("WWW-Authenticate", b"Bearer realm=\"api\"".to_vec());
2039 assert_eq!(error.headers.len(), 1);
2040 assert_eq!(error.headers[0].0, "WWW-Authenticate");
2041 assert_eq!(error.headers[0].1, b"Bearer realm=\"api\"".to_vec());
2042 }
2043
2044 #[test]
2045 fn http_error_with_multiple_headers() {
2046 let error = HttpError::bad_request()
2047 .with_header("X-Error-Code", b"E001".to_vec())
2048 .with_header("X-Error-Context", b"validation".to_vec())
2049 .with_header("Retry-After", b"60".to_vec());
2050 assert_eq!(error.headers.len(), 3);
2051 }
2052
2053 #[test]
2054 fn http_error_with_detail_and_headers() {
2055 let error = HttpError::unauthorized()
2056 .with_detail("Invalid or expired token")
2057 .with_header("WWW-Authenticate", b"Bearer".to_vec())
2058 .with_header("X-Token-Expired", b"true".to_vec());
2059
2060 assert_eq!(error.detail, Some("Invalid or expired token".to_owned()));
2061 assert_eq!(error.headers.len(), 2);
2062 }
2063
2064 #[test]
2065 fn http_error_display_without_detail() {
2066 let error = HttpError::not_found();
2067 let display = format!("{}", error);
2068 assert_eq!(display, "Not Found");
2069 }
2070
2071 #[test]
2072 fn http_error_display_with_detail() {
2073 let error = HttpError::not_found().with_detail("User 123 not found");
2074 let display = format!("{}", error);
2075 assert_eq!(display, "Not Found: User 123 not found");
2076 }
2077
2078 #[test]
2079 fn http_error_is_error_trait() {
2080 let error: Box<dyn std::error::Error> = Box::new(HttpError::internal());
2081 assert!(error.to_string().contains("Internal Server Error"));
2083 }
2084
2085 #[test]
2086 fn http_error_into_response_status() {
2087 let error = HttpError::forbidden();
2088 let response = error.into_response();
2089 assert_eq!(response.status(), StatusCode::FORBIDDEN);
2090 }
2091
2092 #[test]
2093 fn http_error_into_response_json_content_type() {
2094 let error = HttpError::bad_request();
2095 let response = error.into_response();
2096
2097 let content_type = response
2098 .headers()
2099 .iter()
2100 .find(|(name, _)| name.eq_ignore_ascii_case("content-type"))
2101 .map(|(_, v)| v.as_slice());
2102 assert_eq!(content_type, Some(b"application/json".as_slice()));
2103 }
2104
2105 #[test]
2106 fn http_error_into_response_json_body_format() {
2107 let error = HttpError::not_found().with_detail("Resource not found");
2108 let response = error.into_response();
2109
2110 let body = match response.body_ref() {
2112 ResponseBody::Bytes(b) => b.clone(),
2113 _ => panic!("Expected bytes body"),
2114 };
2115
2116 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2117 assert_eq!(parsed["detail"], "Resource not found");
2118 }
2119
2120 #[test]
2121 fn http_error_into_response_default_detail() {
2122 let error = HttpError::not_found();
2124 let response = error.into_response();
2125
2126 let body = match response.body_ref() {
2127 ResponseBody::Bytes(b) => b.clone(),
2128 _ => panic!("Expected bytes body"),
2129 };
2130
2131 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2132 assert_eq!(parsed["detail"], "Not Found");
2133 }
2134
2135 #[test]
2136 fn http_error_into_response_with_custom_headers() {
2137 let error = HttpError::unauthorized()
2138 .with_detail("Token expired")
2139 .with_header("WWW-Authenticate", b"Bearer realm=\"api\"".to_vec());
2140
2141 let response = error.into_response();
2142
2143 let www_auth = response
2145 .headers()
2146 .iter()
2147 .find(|(name, _)| name.eq_ignore_ascii_case("www-authenticate"))
2148 .map(|(_, v)| v.as_slice());
2149 assert_eq!(www_auth, Some(b"Bearer realm=\"api\"".as_slice()));
2150 }
2151
2152 #[test]
2153 fn http_error_into_response_multiple_custom_headers() {
2154 let error = HttpError::bad_request()
2155 .with_header("X-Error-Code", b"VALIDATION_FAILED".to_vec())
2156 .with_header("X-Request-Id", b"abc-123".to_vec());
2157
2158 let response = error.into_response();
2159
2160 let headers: Vec<_> = response.headers().iter().collect();
2161
2162 assert!(headers.len() >= 3);
2164
2165 let error_code = headers
2166 .iter()
2167 .find(|(name, _)| name.eq_ignore_ascii_case("x-error-code"))
2168 .map(|(_, v)| v.as_slice());
2169 assert_eq!(error_code, Some(b"VALIDATION_FAILED".as_slice()));
2170
2171 let request_id = headers
2172 .iter()
2173 .find(|(name, _)| name.eq_ignore_ascii_case("x-request-id"))
2174 .map(|(_, v)| v.as_slice());
2175 assert_eq!(request_id, Some(b"abc-123".as_slice()));
2176 }
2177
2178 #[test]
2179 fn http_error_response_body_is_valid_json() {
2180 let errors = vec![
2182 HttpError::bad_request(),
2183 HttpError::unauthorized(),
2184 HttpError::forbidden(),
2185 HttpError::not_found(),
2186 HttpError::internal(),
2187 HttpError::payload_too_large(),
2188 HttpError::unsupported_media_type(),
2189 ];
2190
2191 for error in errors {
2192 let status = error.status;
2193 let response = error.into_response();
2194 let body = match response.body_ref() {
2195 ResponseBody::Bytes(b) => b.clone(),
2196 _ => panic!("Expected bytes body"),
2197 };
2198
2199 let parsed: Result<serde_json::Value, _> = serde_json::from_slice(&body);
2201 assert!(
2202 parsed.is_ok(),
2203 "Failed to parse JSON for status {}: {:?}",
2204 status.as_u16(),
2205 String::from_utf8_lossy(&body)
2206 );
2207
2208 let json = parsed.unwrap();
2210 assert!(
2211 json.get("detail").is_some(),
2212 "Missing detail field for status {}",
2213 status.as_u16()
2214 );
2215 }
2216 }
2217
2218 #[test]
2219 fn http_error_fastapi_compatible_format() {
2220 let error = HttpError::forbidden().with_detail("Insufficient permissions");
2223 let response = error.into_response();
2224
2225 let body = match response.body_ref() {
2226 ResponseBody::Bytes(b) => b.clone(),
2227 _ => panic!("Expected bytes body"),
2228 };
2229
2230 let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
2231
2232 let obj = json.as_object().unwrap();
2234 assert_eq!(obj.len(), 1, "Expected only 'detail' field");
2235 assert_eq!(json["detail"], "Insufficient permissions");
2236 }
2237
2238 #[test]
2239 fn http_error_chained_builder_pattern() {
2240 let error = HttpError::new(StatusCode::TOO_MANY_REQUESTS)
2242 .with_detail("Rate limit exceeded")
2243 .with_header("Retry-After", b"60".to_vec())
2244 .with_header("X-RateLimit-Remaining", b"0".to_vec());
2245
2246 assert_eq!(error.status, StatusCode::TOO_MANY_REQUESTS);
2247 assert_eq!(error.detail, Some("Rate limit exceeded".to_owned()));
2248 assert_eq!(error.headers.len(), 2);
2249 }
2250
2251 #[test]
2256 fn error_types_all_constants_defined() {
2257 assert!(!error_types::MISSING.is_empty());
2259 assert!(!error_types::STRING_TOO_SHORT.is_empty());
2260 assert!(!error_types::STRING_TOO_LONG.is_empty());
2261 assert!(!error_types::STRING_TYPE.is_empty());
2262 assert!(!error_types::INT_TYPE.is_empty());
2263 assert!(!error_types::FLOAT_TYPE.is_empty());
2264 assert!(!error_types::BOOL_TYPE.is_empty());
2265 assert!(!error_types::GREATER_THAN_EQUAL.is_empty());
2266 assert!(!error_types::LESS_THAN_EQUAL.is_empty());
2267 assert!(!error_types::STRING_PATTERN_MISMATCH.is_empty());
2268 assert!(!error_types::VALUE_ERROR.is_empty());
2269 assert!(!error_types::URL_TYPE.is_empty());
2270 assert!(!error_types::UUID_TYPE.is_empty());
2271 assert!(!error_types::JSON_INVALID.is_empty());
2272 assert!(!error_types::JSON_TYPE.is_empty());
2273 assert!(!error_types::TOO_SHORT.is_empty());
2274 assert!(!error_types::TOO_LONG.is_empty());
2275 assert!(!error_types::ENUM.is_empty());
2276 assert!(!error_types::EXTRA_FORBIDDEN.is_empty());
2277 }
2278
2279 #[test]
2280 fn error_types_numeric_range_constants() {
2281 assert_eq!(error_types::GREATER_THAN_EQUAL, "greater_than_equal");
2283 assert_eq!(error_types::LESS_THAN_EQUAL, "less_than_equal");
2284 }
2285
2286 #[test]
2287 fn error_types_collection_constants() {
2288 assert_eq!(error_types::TOO_SHORT, "too_short");
2290 assert_eq!(error_types::TOO_LONG, "too_long");
2291 }
2292
2293 #[test]
2298 fn validation_error_empty_location() {
2299 let error = ValidationError::new(error_types::VALUE_ERROR, vec![]);
2300 assert!(error.loc.is_empty());
2301
2302 let json = serde_json::to_value(&error).unwrap();
2303 assert_eq!(json["loc"], json!([]));
2304 }
2305
2306 #[test]
2307 fn validation_error_deeply_nested_location() {
2308 let error = ValidationError::missing(vec![
2310 LocItem::field("body"),
2311 LocItem::field("data"),
2312 LocItem::field("users"),
2313 LocItem::index(0),
2314 LocItem::field("profile"),
2315 LocItem::field("settings"),
2316 LocItem::index(5),
2317 LocItem::field("value"),
2318 ]);
2319
2320 let json = serde_json::to_value(&error).unwrap();
2321 assert_eq!(
2322 json["loc"],
2323 json!([
2324 "body", "data", "users", 0, "profile", "settings", 5, "value"
2325 ])
2326 );
2327 }
2328
2329 #[test]
2330 fn validation_errors_empty_to_json() {
2331 let errors = ValidationErrors::new();
2332 let json = errors.to_json();
2333 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
2334
2335 assert_eq!(parsed["detail"], json!([]));
2336 }
2337
2338 #[test]
2339 fn validation_errors_many_errors() {
2340 let mut errors = ValidationErrors::new();
2342 for i in 0..100 {
2343 errors.push(ValidationError::missing(loc::query(&format!("param{}", i))));
2344 }
2345
2346 assert_eq!(errors.len(), 100);
2347
2348 let json = errors.to_json();
2349 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
2350 assert_eq!(parsed["detail"].as_array().unwrap().len(), 100);
2351 }
2352
2353 #[test]
2354 fn validation_error_special_characters_in_field_name() {
2355 let error = ValidationError::missing(vec![
2357 LocItem::field("body"),
2358 LocItem::field("user-name"),
2359 LocItem::field("email@domain"),
2360 ]);
2361
2362 let json = serde_json::to_value(&error).unwrap();
2363 assert_eq!(json["loc"], json!(["body", "user-name", "email@domain"]));
2364 }
2365
2366 #[test]
2367 fn validation_error_unicode_in_message() {
2368 let error = ValidationError::new(error_types::VALUE_ERROR, loc::body_field("name"))
2369 .with_msg("名前が無効です");
2370
2371 let json = serde_json::to_value(&error).unwrap();
2372 assert_eq!(json["msg"], "名前が無効です");
2373 }
2374
2375 #[test]
2376 fn validation_error_large_input_value() {
2377 let large_string = "x".repeat(10000);
2379 let error = ValidationError::string_too_long(loc::body_field("bio"), 500)
2380 .with_input(json!(large_string));
2381
2382 let json = serde_json::to_value(&error).unwrap();
2383 assert_eq!(json["input"].as_str().unwrap().len(), 10000);
2384 }
2385
2386 #[test]
2387 fn http_error_empty_detail() {
2388 let error = HttpError::bad_request().with_detail("");
2390 assert_eq!(error.detail, Some(String::new()));
2391
2392 let response = error.into_response();
2393 let body = match response.body_ref() {
2394 ResponseBody::Bytes(b) => b.clone(),
2395 _ => panic!("Expected bytes body"),
2396 };
2397
2398 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2399 assert_eq!(parsed["detail"], "");
2401 }
2402
2403 #[test]
2404 fn http_error_binary_header_value() {
2405 let error = HttpError::bad_request().with_header("X-Binary", vec![0x00, 0xFF, 0x80]);
2407
2408 assert_eq!(error.headers[0].1, vec![0x00, 0xFF, 0x80]);
2409 }
2410
2411 #[test]
2416 #[serial]
2417 fn debug_mode_default_disabled() {
2418 disable_debug_mode();
2420 assert!(!is_debug_mode_enabled());
2421 }
2422
2423 #[test]
2424 #[serial]
2425 fn debug_mode_can_be_enabled_and_disabled() {
2426 disable_debug_mode();
2428 assert!(!is_debug_mode_enabled());
2429
2430 enable_debug_mode();
2432 assert!(is_debug_mode_enabled());
2433
2434 disable_debug_mode();
2436 assert!(!is_debug_mode_enabled());
2437 }
2438
2439 #[test]
2440 fn debug_config_default() {
2441 let config = DebugConfig::default();
2442 assert!(!config.enabled);
2443 assert!(config.debug_header.is_none());
2444 assert!(config.debug_token.is_none());
2445 assert!(!config.allow_unauthenticated);
2446 }
2447
2448 #[test]
2449 fn debug_config_builder() {
2450 let config = DebugConfig::new()
2451 .enable()
2452 .with_debug_header("X-Debug-Token", "secret123");
2453
2454 assert!(config.enabled);
2455 assert_eq!(config.debug_header, Some("X-Debug-Token".to_owned()));
2456 assert_eq!(config.debug_token, Some("secret123".to_owned()));
2457 assert!(!config.allow_unauthenticated);
2458 }
2459
2460 #[test]
2461 fn debug_config_allow_unauthenticated() {
2462 let config = DebugConfig::new().enable().allow_unauthenticated();
2463
2464 assert!(config.enabled);
2465 assert!(config.allow_unauthenticated);
2466 }
2467
2468 #[test]
2469 fn debug_config_is_authorized_when_disabled() {
2470 let config = DebugConfig::new();
2471 let headers: Vec<(String, Vec<u8>)> = vec![];
2472
2473 assert!(!config.is_authorized(&headers));
2475 }
2476
2477 #[test]
2478 fn debug_config_is_authorized_unauthenticated() {
2479 let config = DebugConfig::new().enable().allow_unauthenticated();
2480 let headers: Vec<(String, Vec<u8>)> = vec![];
2481
2482 assert!(config.is_authorized(&headers));
2484 }
2485
2486 #[test]
2487 fn debug_config_is_authorized_with_valid_token() {
2488 let config = DebugConfig::new()
2489 .enable()
2490 .with_debug_header("X-Debug-Token", "my-secret");
2491
2492 let headers = vec![("X-Debug-Token".to_owned(), b"my-secret".to_vec())];
2493
2494 assert!(config.is_authorized(&headers));
2495 }
2496
2497 #[test]
2498 fn debug_config_is_authorized_with_invalid_token() {
2499 let config = DebugConfig::new()
2500 .enable()
2501 .with_debug_header("X-Debug-Token", "my-secret");
2502
2503 let headers = vec![("X-Debug-Token".to_owned(), b"wrong-secret".to_vec())];
2504
2505 assert!(!config.is_authorized(&headers));
2506 }
2507
2508 #[test]
2509 fn debug_config_is_authorized_missing_header() {
2510 let config = DebugConfig::new()
2511 .enable()
2512 .with_debug_header("X-Debug-Token", "my-secret");
2513
2514 let headers: Vec<(String, Vec<u8>)> = vec![];
2515
2516 assert!(!config.is_authorized(&headers));
2517 }
2518
2519 #[test]
2520 fn debug_config_header_case_insensitive() {
2521 let config = DebugConfig::new()
2522 .enable()
2523 .with_debug_header("X-Debug-Token", "my-secret");
2524
2525 let headers = vec![("x-debug-token".to_owned(), b"my-secret".to_vec())];
2526
2527 assert!(config.is_authorized(&headers));
2528 }
2529
2530 #[test]
2531 fn debug_info_new() {
2532 let info = DebugInfo::new();
2533 assert!(info.is_empty());
2534 assert!(info.source_file.is_none());
2535 assert!(info.source_line.is_none());
2536 assert!(info.function_name.is_none());
2537 assert!(info.route_pattern.is_none());
2538 assert!(info.handler_name.is_none());
2539 assert!(info.extra.is_empty());
2540 }
2541
2542 #[test]
2543 fn debug_info_with_source_location() {
2544 let info = DebugInfo::new().with_source_location("src/handlers/user.rs", 42, "get_user");
2545
2546 assert!(!info.is_empty());
2547 assert_eq!(info.source_file, Some("src/handlers/user.rs".to_owned()));
2548 assert_eq!(info.source_line, Some(42));
2549 assert_eq!(info.function_name, Some("get_user".to_owned()));
2550 }
2551
2552 #[test]
2553 fn debug_info_with_route_pattern() {
2554 let info = DebugInfo::new().with_route_pattern("/users/{id}");
2555
2556 assert!(!info.is_empty());
2557 assert_eq!(info.route_pattern, Some("/users/{id}".to_owned()));
2558 }
2559
2560 #[test]
2561 fn debug_info_with_handler_name() {
2562 let info = DebugInfo::new().with_handler_name("UserController::get");
2563
2564 assert!(!info.is_empty());
2565 assert_eq!(info.handler_name, Some("UserController::get".to_owned()));
2566 }
2567
2568 #[test]
2569 fn debug_info_with_extra() {
2570 let info = DebugInfo::new()
2571 .with_extra("user_id", "abc123")
2572 .with_extra("request_id", "req-456");
2573
2574 assert!(!info.is_empty());
2575 assert_eq!(info.extra.get("user_id"), Some(&"abc123".to_owned()));
2576 assert_eq!(info.extra.get("request_id"), Some(&"req-456".to_owned()));
2577 }
2578
2579 #[test]
2580 fn debug_info_full_builder() {
2581 let info = DebugInfo::new()
2582 .with_source_location("src/api/users.rs", 100, "create_user")
2583 .with_route_pattern("/api/users")
2584 .with_handler_name("UsersHandler::create")
2585 .with_extra("method", "POST");
2586
2587 assert!(!info.is_empty());
2588 assert_eq!(info.source_file, Some("src/api/users.rs".to_owned()));
2589 assert_eq!(info.source_line, Some(100));
2590 assert_eq!(info.function_name, Some("create_user".to_owned()));
2591 assert_eq!(info.route_pattern, Some("/api/users".to_owned()));
2592 assert_eq!(info.handler_name, Some("UsersHandler::create".to_owned()));
2593 assert_eq!(info.extra.get("method"), Some(&"POST".to_owned()));
2594 }
2595
2596 #[test]
2597 fn debug_info_serialization() {
2598 let info = DebugInfo::new()
2599 .with_source_location("src/test.rs", 42, "test_fn")
2600 .with_route_pattern("/test");
2601
2602 let json = serde_json::to_value(&info).unwrap();
2603
2604 assert_eq!(json["source_file"], "src/test.rs");
2605 assert_eq!(json["source_line"], 42);
2606 assert_eq!(json["function_name"], "test_fn");
2607 assert_eq!(json["route_pattern"], "/test");
2608 assert!(json.get("handler_name").is_none());
2610 assert!(json.get("extra").is_none());
2611 }
2612
2613 #[test]
2614 fn debug_info_serialization_skip_none() {
2615 let info = DebugInfo::new().with_route_pattern("/test");
2616
2617 let json = serde_json::to_value(&info).unwrap();
2618
2619 assert_eq!(json["route_pattern"], "/test");
2621 assert!(json.get("source_file").is_none());
2622 assert!(json.get("source_line").is_none());
2623 assert!(json.get("function_name").is_none());
2624 }
2625
2626 #[test]
2627 fn http_error_with_debug_info() {
2628 let debug = DebugInfo::new()
2629 .with_source_location("src/handlers.rs", 50, "handle_request")
2630 .with_route_pattern("/api/test");
2631
2632 let error = HttpError::not_found()
2633 .with_detail("Resource not found")
2634 .with_debug_info(debug);
2635
2636 assert!(error.debug_info.is_some());
2637 let info = error.debug_info.unwrap();
2638 assert_eq!(info.source_file, Some("src/handlers.rs".to_owned()));
2639 assert_eq!(info.source_line, Some(50));
2640 }
2641
2642 #[test]
2643 #[serial]
2644 fn http_error_response_without_debug_mode() {
2645 disable_debug_mode();
2646
2647 let error = HttpError::not_found()
2648 .with_detail("User not found")
2649 .with_debug_info(
2650 DebugInfo::new()
2651 .with_source_location("src/test.rs", 42, "test")
2652 .with_route_pattern("/users/{id}"),
2653 );
2654
2655 let response = error.into_response();
2656 let body = match response.body_ref() {
2657 ResponseBody::Bytes(b) => b.clone(),
2658 _ => panic!("Expected bytes body"),
2659 };
2660
2661 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2662
2663 assert_eq!(parsed["detail"], "User not found");
2665 assert!(parsed.get("debug").is_none());
2666 }
2667
2668 #[test]
2669 #[serial]
2670 fn http_error_response_with_debug_mode() {
2671 enable_debug_mode();
2673
2674 let error = HttpError::not_found()
2675 .with_detail("User not found")
2676 .with_debug_info(
2677 DebugInfo::new()
2678 .with_source_location("src/test.rs", 42, "test")
2679 .with_route_pattern("/users/{id}"),
2680 );
2681
2682 let response = error.into_response();
2683 let body = match response.body_ref() {
2684 ResponseBody::Bytes(b) => b.clone(),
2685 _ => panic!("Expected bytes body"),
2686 };
2687
2688 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2689
2690 assert_eq!(parsed["detail"], "User not found");
2692 assert!(parsed.get("debug").is_some());
2693 assert_eq!(parsed["debug"]["source_file"], "src/test.rs");
2694 assert_eq!(parsed["debug"]["source_line"], 42);
2695 assert_eq!(parsed["debug"]["function_name"], "test");
2696 assert_eq!(parsed["debug"]["route_pattern"], "/users/{id}");
2697
2698 disable_debug_mode();
2700 }
2701
2702 #[test]
2703 #[serial]
2704 fn http_error_response_with_debug_mode_no_debug_info() {
2705 enable_debug_mode();
2707
2708 let error = HttpError::not_found().with_detail("User not found");
2709
2710 let response = error.into_response();
2711 let body = match response.body_ref() {
2712 ResponseBody::Bytes(b) => b.clone(),
2713 _ => panic!("Expected bytes body"),
2714 };
2715
2716 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2717
2718 assert_eq!(parsed["detail"], "User not found");
2720 assert!(parsed.get("debug").is_none());
2721
2722 disable_debug_mode();
2724 }
2725
2726 #[test]
2727 fn validation_errors_with_debug_info() {
2728 let errors = ValidationErrors::single(ValidationError::missing(loc::query("q")))
2729 .with_debug_info(
2730 DebugInfo::new()
2731 .with_source_location("src/extractors.rs", 100, "extract_query")
2732 .with_handler_name("SearchHandler::search"),
2733 );
2734
2735 assert!(errors.debug_info.is_some());
2736 }
2737
2738 #[test]
2739 #[serial]
2740 fn validation_errors_response_without_debug_mode() {
2741 disable_debug_mode();
2742
2743 let errors = ValidationErrors::single(ValidationError::missing(loc::query("q")))
2744 .with_debug_info(DebugInfo::new().with_source_location("src/test.rs", 42, "test"));
2745
2746 let response = errors.into_response();
2747 let body = match response.body_ref() {
2748 ResponseBody::Bytes(b) => b.clone(),
2749 _ => panic!("Expected bytes body"),
2750 };
2751
2752 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2753
2754 assert!(parsed["detail"].is_array());
2756 assert!(parsed.get("debug").is_none());
2757 }
2758
2759 #[test]
2760 #[serial]
2761 fn validation_errors_response_with_debug_mode() {
2762 enable_debug_mode();
2763
2764 let errors = ValidationErrors::single(ValidationError::missing(loc::query("q")))
2765 .with_debug_info(
2766 DebugInfo::new()
2767 .with_source_location("src/test.rs", 42, "test")
2768 .with_route_pattern("/search"),
2769 );
2770
2771 let response = errors.into_response();
2772 let body = match response.body_ref() {
2773 ResponseBody::Bytes(b) => b.clone(),
2774 _ => panic!("Expected bytes body"),
2775 };
2776
2777 let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
2778
2779 assert!(parsed["detail"].is_array());
2781 assert!(parsed.get("debug").is_some());
2782 assert_eq!(parsed["debug"]["source_file"], "src/test.rs");
2783 assert_eq!(parsed["debug"]["route_pattern"], "/search");
2784
2785 disable_debug_mode();
2787 }
2788
2789 #[test]
2790 fn validation_errors_merge_preserves_debug_info() {
2791 let mut errors1 = ValidationErrors::single(ValidationError::missing(loc::query("q")))
2792 .with_debug_info(DebugInfo::new().with_source_location("src/a.rs", 1, "a"));
2793
2794 let errors2 = ValidationErrors::single(ValidationError::missing(loc::query("page")))
2795 .with_debug_info(DebugInfo::new().with_source_location("src/b.rs", 2, "b"));
2796
2797 errors1.merge(errors2);
2798
2799 assert!(errors1.debug_info.is_some());
2801 assert_eq!(
2802 errors1.debug_info.as_ref().unwrap().source_file,
2803 Some("src/a.rs".to_owned())
2804 );
2805 }
2806
2807 #[test]
2808 fn validation_errors_merge_takes_other_debug_info_if_none() {
2809 let mut errors1 = ValidationErrors::single(ValidationError::missing(loc::query("q")));
2810
2811 let errors2 = ValidationErrors::single(ValidationError::missing(loc::query("page")))
2812 .with_debug_info(DebugInfo::new().with_source_location("src/b.rs", 2, "b"));
2813
2814 errors1.merge(errors2);
2815
2816 assert!(errors1.debug_info.is_some());
2818 assert_eq!(
2819 errors1.debug_info.as_ref().unwrap().source_file,
2820 Some("src/b.rs".to_owned())
2821 );
2822 }
2823
2824 #[test]
2829 fn response_validation_error_new_is_empty() {
2830 let error = ResponseValidationError::new();
2831 assert!(error.is_empty());
2832 assert_eq!(error.len(), 0);
2833 assert!(error.response_content.is_none());
2834 assert!(error.summary.is_none());
2835 }
2836
2837 #[test]
2838 fn response_validation_error_serialization_failed() {
2839 let error = ResponseValidationError::serialization_failed("failed to serialize DateTime");
2840 assert_eq!(error.len(), 1);
2841 assert!(error.summary.is_some());
2842 assert_eq!(
2843 error.summary.as_deref(),
2844 Some("failed to serialize DateTime")
2845 );
2846 assert_eq!(error.errors[0].error_type, error_types::SERIALIZATION_ERROR);
2847 }
2848
2849 #[test]
2850 fn response_validation_error_model_validation_failed() {
2851 let error = ResponseValidationError::model_validation_failed("missing required field 'id'");
2852 assert_eq!(error.len(), 1);
2853 assert!(error.summary.is_some());
2854 assert_eq!(
2855 error.errors[0].error_type,
2856 error_types::MODEL_VALIDATION_ERROR
2857 );
2858 }
2859
2860 #[test]
2861 fn response_validation_error_with_error() {
2862 let error = ResponseValidationError::new()
2863 .with_error(ValidationError::missing(loc::response_field("user_id")));
2864 assert_eq!(error.len(), 1);
2865 assert_eq!(error.errors[0].loc.len(), 2);
2866 }
2867
2868 #[test]
2869 fn response_validation_error_with_errors() {
2870 let error = ResponseValidationError::new().with_errors(vec![
2871 ValidationError::missing(loc::response_field("id")),
2872 ValidationError::missing(loc::response_field("name")),
2873 ]);
2874 assert_eq!(error.len(), 2);
2875 }
2876
2877 #[test]
2878 fn response_validation_error_with_response_content() {
2879 let content = json!({"name": "Alice", "age": 30});
2880 let error = ResponseValidationError::serialization_failed("test")
2881 .with_response_content(content.clone());
2882 assert!(error.response_content.is_some());
2883 assert_eq!(error.response_content.as_ref().unwrap()["name"], "Alice");
2884 }
2885
2886 #[test]
2887 fn response_validation_error_with_summary() {
2888 let error = ResponseValidationError::new().with_summary("Custom summary");
2889 assert_eq!(error.summary.as_deref(), Some("Custom summary"));
2890 }
2891
2892 #[test]
2893 fn response_validation_error_with_debug_info() {
2894 let error = ResponseValidationError::serialization_failed("test")
2895 .with_debug_info(DebugInfo::new().with_source_location("handler.rs", 42, "get_user"));
2896 assert!(error.debug_info.is_some());
2897 }
2898
2899 #[test]
2900 fn response_validation_error_display() {
2901 let error = ResponseValidationError::new();
2902 assert_eq!(format!("{}", error), "Response validation failed");
2903
2904 let error = ResponseValidationError::new().with_summary("missing field");
2905 assert_eq!(
2906 format!("{}", error),
2907 "Response validation failed: missing field"
2908 );
2909 }
2910
2911 #[test]
2912 #[serial]
2913 fn response_validation_error_into_response_production_mode() {
2914 disable_debug_mode();
2916
2917 let error = ResponseValidationError::serialization_failed("some internal error")
2918 .with_response_content(json!({"secret": "data"}));
2919
2920 let response = error.into_response();
2921 assert_eq!(response.status().as_u16(), 500);
2922
2923 let content_type = response
2925 .headers()
2926 .iter()
2927 .find(|(name, _)| name == "content-type")
2928 .map(|(_, value)| String::from_utf8_lossy(value).to_string());
2929 assert_eq!(content_type, Some("application/json".to_string()));
2930
2931 if let crate::response::ResponseBody::Bytes(bytes) = response.body_ref() {
2933 let body: serde_json::Value = serde_json::from_slice(bytes).unwrap();
2934 assert_eq!(body["error"], "internal_server_error");
2935 assert_eq!(body["detail"], "Internal Server Error");
2936 assert!(body.get("debug").is_none());
2938 } else {
2939 panic!("Expected Bytes body");
2940 }
2941 }
2942
2943 #[test]
2944 #[serial]
2945 fn response_validation_error_into_response_debug_mode() {
2946 enable_debug_mode();
2948
2949 let error = ResponseValidationError::serialization_failed("DateTime serialize failed")
2950 .with_response_content(json!({"created_at": "invalid-date"}))
2951 .with_debug_info(DebugInfo::new().with_source_location("handler.rs", 100, "get_user"));
2952
2953 let response = error.into_response();
2954 assert_eq!(response.status().as_u16(), 500);
2955
2956 if let crate::response::ResponseBody::Bytes(bytes) = response.body_ref() {
2958 let body: serde_json::Value = serde_json::from_slice(bytes).unwrap();
2959 assert_eq!(body["error"], "internal_server_error");
2960 assert!(body.get("debug").is_some());
2962 let debug = &body["debug"];
2963 assert_eq!(debug["summary"], "DateTime serialize failed");
2964 assert!(debug.get("errors").is_some());
2965 assert!(debug.get("response_content").is_some());
2966 } else {
2967 panic!("Expected Bytes body");
2968 }
2969
2970 disable_debug_mode();
2972 }
2973
2974 #[test]
2975 fn response_validation_error_to_log_string() {
2976 let error = ResponseValidationError::serialization_failed("test error")
2977 .with_error(ValidationError::missing(loc::response_field("id")))
2978 .with_response_content(json!({"name": "Alice"}));
2979
2980 let log = error.to_log_string();
2981 assert!(log.contains("Summary: test error"));
2982 assert!(log.contains("serialization_error"));
2983 assert!(log.contains("Response content:"));
2984 assert!(log.contains("Alice"));
2985 }
2986
2987 #[test]
2988 fn response_validation_error_to_log_string_truncates_large_content() {
2989 let large_string = "x".repeat(1000);
2991 let error = ResponseValidationError::serialization_failed("test")
2992 .with_response_content(json!({"data": large_string}));
2993
2994 let log = error.to_log_string();
2995 assert!(log.contains("(truncated)"));
2996 }
2997
2998 #[test]
2999 fn response_validation_error_iter() {
3000 let error = ResponseValidationError::new()
3001 .with_error(ValidationError::missing(loc::response_field("a")))
3002 .with_error(ValidationError::missing(loc::response_field("b")));
3003
3004 let locs: Vec<_> = error.iter().map(|e| e.loc.clone()).collect();
3005 assert_eq!(locs.len(), 2);
3006 }
3007
3008 #[test]
3009 fn loc_response_helper() {
3010 let loc = loc::response();
3011 assert_eq!(loc.len(), 1);
3012 assert!(matches!(&loc[0], LocItem::Field(s) if s == "response"));
3013 }
3014
3015 #[test]
3016 fn loc_response_field_helper() {
3017 let loc = loc::response_field("user_id");
3018 assert_eq!(loc.len(), 2);
3019 assert!(matches!(&loc[0], LocItem::Field(s) if s == "response"));
3020 assert!(matches!(&loc[1], LocItem::Field(s) if s == "user_id"));
3021 }
3022
3023 #[test]
3024 fn loc_response_path_helper() {
3025 let loc = loc::response_path(&["user", "profile", "name"]);
3026 assert_eq!(loc.len(), 4);
3027 assert!(matches!(&loc[0], LocItem::Field(s) if s == "response"));
3028 assert!(matches!(&loc[1], LocItem::Field(s) if s == "user"));
3029 assert!(matches!(&loc[2], LocItem::Field(s) if s == "profile"));
3030 assert!(matches!(&loc[3], LocItem::Field(s) if s == "name"));
3031 }
3032}