Skip to main content

fastapi_core/
error.rs

1//! Error types.
2//!
3//! This module provides error types for HTTP responses with support for
4//! debug mode that can include additional diagnostic information.
5//!
6//! # Debug Mode
7//!
8//! Debug mode can be enabled to include additional diagnostic information
9//! in error responses:
10//!
11//! - Source location (file, line, function)
12//! - Full validation error context
13//! - Handler name and route pattern
14//!
15//! Debug mode is controlled per-error via the `with_debug_info` method,
16//! and should only be enabled when the application is in debug mode AND
17//! the request includes a valid debug token (if configured).
18//!
19//! # Example
20//!
21//! ```
22//! use fastapi_core::error::{HttpError, DebugInfo};
23//!
24//! // Production mode - no debug info
25//! let error = HttpError::not_found().with_detail("User not found");
26//!
27//! // Debug mode - with source location
28//! let error = HttpError::not_found()
29//!     .with_detail("User not found")
30//!     .with_debug_info(DebugInfo::new()
31//!         .with_source_location(file!(), line!(), "get_user")
32//!         .with_route_pattern("/users/{id}"));
33//! ```
34
35use crate::response::{IntoResponse, Response, ResponseBody, StatusCode};
36use serde::{Serialize, Serializer};
37use std::collections::HashMap;
38use std::sync::atomic::{AtomicBool, Ordering};
39
40// ============================================================================
41// Debug Mode Configuration
42// ============================================================================
43
44/// Global flag for thread-local debug mode state.
45/// This is used when debug info needs to be propagated through error creation.
46static DEBUG_MODE_ENABLED: AtomicBool = AtomicBool::new(false);
47
48/// Enable debug mode globally.
49///
50/// When debug mode is enabled, errors can include additional diagnostic
51/// information in their responses. This should be controlled by the
52/// application configuration and request-level debug token validation.
53pub fn enable_debug_mode() {
54    DEBUG_MODE_ENABLED.store(true, Ordering::SeqCst);
55}
56
57/// Disable debug mode globally.
58pub fn disable_debug_mode() {
59    DEBUG_MODE_ENABLED.store(false, Ordering::SeqCst);
60}
61
62/// Check if debug mode is enabled globally.
63#[must_use]
64pub fn is_debug_mode_enabled() -> bool {
65    DEBUG_MODE_ENABLED.load(Ordering::SeqCst)
66}
67
68/// Debug configuration for secure debug mode access.
69///
70/// This allows configuring a debug header that must be present with a
71/// secret token for debug information to be included in responses.
72///
73/// # Example
74///
75/// ```
76/// use fastapi_core::error::DebugConfig;
77///
78/// // Require X-Debug-Token header with a secret
79/// let config = DebugConfig::new()
80///     .with_debug_header("X-Debug-Token", "my-secret-token");
81///
82/// // Or allow debug mode without authentication (dangerous!)
83/// let config = DebugConfig::new().allow_unauthenticated();
84/// ```
85#[derive(Debug, Clone)]
86pub struct DebugConfig {
87    /// Enable debug mode for the application.
88    pub enabled: bool,
89    /// Header name for debug token authentication.
90    pub debug_header: Option<String>,
91    /// Expected token value for debug access.
92    pub debug_token: Option<String>,
93    /// Allow debug mode without authentication (dangerous in production).
94    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    /// Create a new debug configuration with debug mode disabled.
110    #[must_use]
111    pub fn new() -> Self {
112        Self::default()
113    }
114
115    /// Enable debug mode.
116    #[must_use]
117    pub fn enable(mut self) -> Self {
118        self.enabled = true;
119        self
120    }
121
122    /// Configure the debug header and token for authenticated debug access.
123    ///
124    /// When configured, debug information will only be included in responses
125    /// if the request includes this header with the expected token value.
126    #[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    /// Allow debug mode without authentication.
138    ///
139    /// # Warning
140    ///
141    /// This is dangerous in production as it exposes internal details
142    /// to anyone. Only use for development/testing.
143    #[must_use]
144    pub fn allow_unauthenticated(mut self) -> Self {
145        self.allow_unauthenticated = true;
146        self
147    }
148
149    /// Check if a request is authorized for debug mode.
150    ///
151    /// Returns true if:
152    /// - Debug mode is disabled (debug info won't be shown anyway)
153    /// - `allow_unauthenticated` is true
154    /// - The request includes the correct debug header/token
155    ///
156    /// # Security
157    ///
158    /// Token comparison uses constant-time comparison to prevent timing attacks.
159    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        // Check for debug header
169        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                        // Use constant-time comparison to prevent timing attacks
174                        return constant_time_str_eq(token, expected_token);
175                    }
176                }
177            }
178        }
179
180        false
181    }
182}
183
184/// Constant-time string comparison to prevent timing attacks.
185///
186/// This function compares two strings in constant time (for same-length inputs)
187/// to prevent timing-based side-channel attacks.
188fn 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// ============================================================================
205// Debug Information
206// ============================================================================
207
208/// Debug information to include in error responses when debug mode is enabled.
209///
210/// This struct holds diagnostic information that helps developers understand
211/// where and why an error occurred. This information should NEVER be included
212/// in production responses as it can leak sensitive implementation details.
213///
214/// # Fields
215///
216/// - `source_file`: The file where the error originated
217/// - `source_line`: The line number in the source file
218/// - `function_name`: The function or handler that generated the error
219/// - `route_pattern`: The matched route pattern (e.g., "/users/{id}")
220/// - `handler_name`: The name of the handler function
221/// - `extra`: Additional key-value debug information
222///
223/// # Example
224///
225/// ```
226/// use fastapi_core::error::DebugInfo;
227///
228/// let debug = DebugInfo::new()
229///     .with_source_location("src/handlers/user.rs", 42, "get_user")
230///     .with_route_pattern("/users/{id}")
231///     .with_extra("user_id_received", "abc123");
232/// ```
233#[derive(Debug, Clone, Default, Serialize)]
234pub struct DebugInfo {
235    /// Source file path.
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub source_file: Option<String>,
238    /// Source line number.
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub source_line: Option<u32>,
241    /// Function or handler name.
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub function_name: Option<String>,
244    /// Matched route pattern.
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub route_pattern: Option<String>,
247    /// Handler name (may differ from function_name for wrapped handlers).
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub handler_name: Option<String>,
250    /// Additional debug context.
251    #[serde(skip_serializing_if = "HashMap::is_empty")]
252    pub extra: HashMap<String, String>,
253}
254
255impl DebugInfo {
256    /// Create empty debug info.
257    #[must_use]
258    pub fn new() -> Self {
259        Self::default()
260    }
261
262    /// Set the source location.
263    #[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    /// Set the route pattern.
277    #[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    /// Set the handler name.
284    #[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    /// Add extra debug information.
291    #[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    /// Check if this debug info is empty (has no data).
298    #[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 to capture the current source location for debug info.
310///
311/// # Example
312///
313/// ```
314/// use fastapi_core::{debug_location, error::DebugInfo};
315///
316/// fn my_handler() -> DebugInfo {
317///     debug_location!()
318/// }
319/// ```
320#[macro_export]
321macro_rules! debug_location {
322    () => {
323        $crate::error::DebugInfo::new().with_source_location(
324            file!(),
325            line!(),
326            // Get a reasonable function name approximation
327            module_path!(),
328        )
329    };
330    ($func_name:expr) => {
331        $crate::error::DebugInfo::new().with_source_location(file!(), line!(), $func_name)
332    };
333}
334
335// ============================================================================
336// Location Types for Validation Errors (FastAPI-compatible)
337// ============================================================================
338
339/// Location item for validation error paths.
340///
341/// FastAPI uses tuples where items can be either strings (field names)
342/// or integers (array indices). This enum models that structure.
343///
344/// # Examples
345///
346/// ```
347/// use fastapi_core::error::LocItem;
348///
349/// // Field name
350/// let field = LocItem::field("email");
351/// assert_eq!(field.as_str(), Some("email"));
352///
353/// // Array index
354/// let idx = LocItem::index(0);
355/// assert_eq!(idx.as_index(), Some(0));
356/// ```
357#[derive(Debug, Clone, PartialEq, Eq)]
358pub enum LocItem {
359    /// Field name (string).
360    Field(String),
361    /// Array index (integer).
362    Index(usize),
363}
364
365impl LocItem {
366    /// Create a field location item.
367    #[must_use]
368    pub fn field(name: impl Into<String>) -> Self {
369        Self::Field(name.into())
370    }
371
372    /// Create an index location item.
373    #[must_use]
374    pub fn index(idx: usize) -> Self {
375        Self::Index(idx)
376    }
377
378    /// Get the field name if this is a Field variant.
379    #[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    /// Get the index if this is an Index variant.
388    #[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
427// ============================================================================
428// Location Prefixes (FastAPI conventions)
429// ============================================================================
430
431/// Location prefixes for different extraction sources.
432pub mod loc {
433    use super::LocItem;
434
435    /// Path parameter location: `["path", "param_name"]`
436    #[must_use]
437    pub fn path(param: &str) -> Vec<LocItem> {
438        vec![LocItem::field("path"), LocItem::field(param)]
439    }
440
441    /// Query parameter location: `["query", "param_name"]`
442    #[must_use]
443    pub fn query(param: &str) -> Vec<LocItem> {
444        vec![LocItem::field("query"), LocItem::field(param)]
445    }
446
447    /// Header location: `["header", "header_name"]`
448    #[must_use]
449    pub fn header(name: &str) -> Vec<LocItem> {
450        vec![LocItem::field("header"), LocItem::field(name)]
451    }
452
453    /// Cookie location: `["cookie", "cookie_name"]`
454    #[must_use]
455    pub fn cookie(name: &str) -> Vec<LocItem> {
456        vec![LocItem::field("cookie"), LocItem::field(name)]
457    }
458
459    /// Body root location: `["body"]`
460    #[must_use]
461    pub fn body() -> Vec<LocItem> {
462        vec![LocItem::field("body")]
463    }
464
465    /// Body field location: `["body", "field"]`
466    #[must_use]
467    pub fn body_field(field: &str) -> Vec<LocItem> {
468        vec![LocItem::field("body"), LocItem::field(field)]
469    }
470
471    /// Body nested path: `["body", "field", "nested", ...]`
472    #[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    /// Body with array index: `["body", "items", 0, "name"]`
482    #[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    /// Response root location: `["response"]`
492    #[must_use]
493    pub fn response() -> Vec<LocItem> {
494        vec![LocItem::field("response")]
495    }
496
497    /// Response field location: `["response", "field"]`
498    #[must_use]
499    pub fn response_field(field: &str) -> Vec<LocItem> {
500        vec![LocItem::field("response"), LocItem::field(field)]
501    }
502
503    /// Response nested path: `["response", "field", "nested", ...]`
504    #[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
514// ============================================================================
515// Common Validation Error Types (FastAPI/Pydantic conventions)
516// ============================================================================
517
518/// Common validation error type strings (matching Pydantic v2).
519pub mod error_types {
520    /// Required field is missing.
521    pub const MISSING: &str = "missing";
522    /// String is too short.
523    pub const STRING_TOO_SHORT: &str = "string_too_short";
524    /// String is too long.
525    pub const STRING_TOO_LONG: &str = "string_too_long";
526    /// Value is not a valid string.
527    pub const STRING_TYPE: &str = "string_type";
528    /// Value is not a valid integer.
529    pub const INT_TYPE: &str = "int_type";
530    /// Value is not a valid float.
531    pub const FLOAT_TYPE: &str = "float_type";
532    /// Value is not a valid boolean.
533    pub const BOOL_TYPE: &str = "bool_type";
534    /// Value is less than minimum.
535    pub const GREATER_THAN_EQUAL: &str = "greater_than_equal";
536    /// Value is greater than maximum.
537    pub const LESS_THAN_EQUAL: &str = "less_than_equal";
538    /// Value does not match pattern.
539    pub const STRING_PATTERN_MISMATCH: &str = "string_pattern_mismatch";
540    /// Value is not a valid email.
541    pub const VALUE_ERROR: &str = "value_error";
542    /// Value is not a valid URL.
543    pub const URL_TYPE: &str = "url_type";
544    /// Value is not a valid UUID.
545    pub const UUID_TYPE: &str = "uuid_type";
546    /// JSON parsing failed.
547    pub const JSON_INVALID: &str = "json_invalid";
548    /// JSON type mismatch.
549    pub const JSON_TYPE: &str = "json_type";
550    /// Array has too few items.
551    pub const TOO_SHORT: &str = "too_short";
552    /// Array has too many items.
553    pub const TOO_LONG: &str = "too_long";
554    /// Value is not in enum.
555    pub const ENUM: &str = "enum";
556    /// Extra field not allowed.
557    pub const EXTRA_FORBIDDEN: &str = "extra_forbidden";
558
559    // Response validation error types
560    /// Response failed to serialize (e.g., JSON serialization error).
561    pub const SERIALIZATION_ERROR: &str = "serialization_error";
562    /// Response doesn't match the declared response model.
563    pub const MODEL_VALIDATION_ERROR: &str = "model_validation_error";
564}
565
566// ============================================================================
567// HTTP Error
568// ============================================================================
569
570/// HTTP error that produces a response.
571///
572/// When debug mode is enabled, errors can include additional diagnostic
573/// information via the `debug_info` field. This information is conditionally
574/// serialized into the response when `is_debug_mode_enabled()` returns true.
575///
576/// # Production Mode (default)
577///
578/// ```json
579/// {"detail": "Not Found"}
580/// ```
581///
582/// # Debug Mode (when enabled)
583///
584/// ```json
585/// {
586///   "detail": "Not Found",
587///   "debug": {
588///     "source_file": "src/handlers/user.rs",
589///     "source_line": 42,
590///     "function_name": "get_user",
591///     "route_pattern": "/users/{id}"
592///   }
593/// }
594/// ```
595#[derive(Debug)]
596pub struct HttpError {
597    /// Status code.
598    pub status: StatusCode,
599    /// Error detail message.
600    pub detail: Option<String>,
601    /// Additional headers.
602    pub headers: Vec<(String, Vec<u8>)>,
603    /// Debug information (only included in response when debug mode is enabled).
604    pub debug_info: Option<DebugInfo>,
605}
606
607impl HttpError {
608    /// Create a new HTTP error.
609    #[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    /// Add a detail message.
620    #[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    /// Add a header.
627    #[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    /// Add debug information.
634    ///
635    /// This information will only be included in the response when debug mode
636    /// is enabled (via `enable_debug_mode()`).
637    ///
638    /// # Example
639    ///
640    /// ```
641    /// use fastapi_core::error::{HttpError, DebugInfo};
642    ///
643    /// let error = HttpError::not_found()
644    ///     .with_detail("User not found")
645    ///     .with_debug_info(DebugInfo::new()
646    ///         .with_source_location("src/handlers/user.rs", 42, "get_user")
647    ///         .with_route_pattern("/users/{id}"));
648    /// ```
649    #[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    /// Add debug information at the current source location.
656    ///
657    /// This is a convenience method that captures the current file and line.
658    /// Note: This captures the location where this method is called, not
659    /// where the error originated. For accurate source tracking, use
660    /// `with_debug_info` with a `DebugInfo` created via the `debug_location!` macro.
661    #[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, // Line unknown when called this way
666            function_name,
667        ))
668    }
669
670    /// Create a 400 Bad Request error.
671    #[must_use]
672    pub fn bad_request() -> Self {
673        Self::new(StatusCode::BAD_REQUEST)
674    }
675
676    /// Create a 401 Unauthorized error.
677    #[must_use]
678    pub fn unauthorized() -> Self {
679        Self::new(StatusCode::UNAUTHORIZED)
680    }
681
682    /// Create a 403 Forbidden error.
683    #[must_use]
684    pub fn forbidden() -> Self {
685        Self::new(StatusCode::FORBIDDEN)
686    }
687
688    /// Create a 404 Not Found error.
689    #[must_use]
690    pub fn not_found() -> Self {
691        Self::new(StatusCode::NOT_FOUND)
692    }
693
694    /// Create a 500 Internal Server Error.
695    #[must_use]
696    pub fn internal() -> Self {
697        Self::new(StatusCode::INTERNAL_SERVER_ERROR)
698    }
699
700    /// Create a 413 Payload Too Large error.
701    #[must_use]
702    pub fn payload_too_large() -> Self {
703        Self::new(StatusCode::PAYLOAD_TOO_LARGE)
704    }
705
706    /// Create a 415 Unsupported Media Type error.
707    #[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        // Conditionally include debug info based on global debug mode flag
721        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// ============================================================================
773// Validation Error (FastAPI-compatible)
774// ============================================================================
775
776/// A single validation error item.
777///
778/// This structure matches FastAPI/Pydantic v2's validation error format exactly,
779/// allowing for seamless compatibility with clients expecting FastAPI responses.
780///
781/// # Examples
782///
783/// ```
784/// use fastapi_core::error::{ValidationError, loc, error_types};
785/// use serde_json::json;
786///
787/// // Missing required field
788/// let error = ValidationError::missing(loc::query("q"));
789/// assert_eq!(error.error_type, "missing");
790///
791/// // String too short with context
792/// let error = ValidationError::new(error_types::STRING_TOO_SHORT, loc::body_field("name"))
793///     .with_msg("String should have at least 3 characters")
794///     .with_input(json!("ab"))
795///     .with_ctx_value("min_length", json!(3));
796/// ```
797#[derive(Debug, Clone, Serialize)]
798pub struct ValidationError {
799    /// Error type identifier (e.g., "missing", "string_too_short").
800    #[serde(rename = "type")]
801    pub error_type: &'static str,
802
803    /// Location path as a tuple of strings and integers.
804    ///
805    /// Examples:
806    /// - `["query", "q"]` for query parameter
807    /// - `["body", "user", "email"]` for nested body field
808    /// - `["body", "items", 0, "name"]` for array item
809    pub loc: Vec<LocItem>,
810
811    /// Human-readable error message.
812    pub msg: String,
813
814    /// The input value that failed validation.
815    ///
816    /// This is the actual value the user provided that caused the error.
817    #[serde(skip_serializing_if = "Option::is_none")]
818    pub input: Option<serde_json::Value>,
819
820    /// Additional context about the validation constraint.
821    ///
822    /// Examples:
823    /// - `{"min_length": 3}` for string_too_short
824    /// - `{"ge": 0}` for greater_than_equal
825    /// - `{"pattern": "^\\d+$"}` for pattern mismatch
826    #[serde(skip_serializing_if = "Option::is_none")]
827    pub ctx: Option<HashMap<String, serde_json::Value>>,
828}
829
830impl ValidationError {
831    /// Create a new validation error.
832    #[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    /// Create a "missing" error for a required field.
844    #[must_use]
845    pub fn missing(loc: Vec<LocItem>) -> Self {
846        Self::new(error_types::MISSING, loc)
847    }
848
849    /// Create a "string_too_short" error.
850    #[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    /// Create a "string_too_long" error.
861    #[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    /// Create a type error (e.g., expected int, got string).
872    #[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    /// Create a JSON parsing error.
885    #[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    /// Set the human-readable message.
891    #[must_use]
892    pub fn with_msg(mut self, msg: impl Into<String>) -> Self {
893        self.msg = msg.into();
894        self
895    }
896
897    /// Set the input value.
898    #[must_use]
899    pub fn with_input(mut self, input: serde_json::Value) -> Self {
900        self.input = Some(input);
901        self
902    }
903
904    /// Add a context key-value pair.
905    #[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    /// Set the full context map.
914    #[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    /// Add location items to the path.
921    #[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    /// Append a location item to the path.
930    #[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    /// Get the default message for an error type.
937    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// ============================================================================
954// Validation Errors Collection
955// ============================================================================
956
957/// Collection of validation errors (422 Unprocessable Entity response).
958///
959/// This collects multiple validation errors from extractors and validators,
960/// producing a FastAPI-compatible error response format.
961///
962/// # Examples
963///
964/// ```
965/// use fastapi_core::error::{ValidationErrors, ValidationError, loc, error_types};
966/// use serde_json::json;
967///
968/// let mut errors = ValidationErrors::new();
969///
970/// // Add multiple errors
971/// errors.push(ValidationError::missing(loc::query("q")));
972/// errors.push(ValidationError::string_too_short(loc::body_field("name"), 3)
973///     .with_input(json!("ab")));
974///
975/// // Check and convert
976/// if !errors.is_empty() {
977///     let json = errors.to_json();
978///     assert!(json.contains("missing"));
979/// }
980/// ```
981#[derive(Debug, Clone, Default)]
982pub struct ValidationErrors {
983    /// The collected errors.
984    pub errors: Vec<ValidationError>,
985    /// The original input body (if available).
986    pub body: Option<serde_json::Value>,
987    /// Debug information (only included in response when debug mode is enabled).
988    pub debug_info: Option<DebugInfo>,
989}
990
991impl ValidationErrors {
992    /// Create empty validation errors.
993    #[must_use]
994    pub fn new() -> Self {
995        Self {
996            errors: Vec::new(),
997            body: None,
998            debug_info: None,
999        }
1000    }
1001
1002    /// Create from a single error.
1003    #[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    /// Create from multiple errors.
1013    #[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    /// Add an error.
1023    pub fn push(&mut self, error: ValidationError) {
1024        self.errors.push(error);
1025    }
1026
1027    /// Add multiple errors.
1028    pub fn extend(&mut self, errors: impl IntoIterator<Item = ValidationError>) {
1029        self.errors.extend(errors);
1030    }
1031
1032    /// Set the original body that failed validation.
1033    #[must_use]
1034    pub fn with_body(mut self, body: serde_json::Value) -> Self {
1035        self.body = Some(body);
1036        self
1037    }
1038
1039    /// Add debug information.
1040    ///
1041    /// This information will only be included in the response when debug mode
1042    /// is enabled (via `enable_debug_mode()`).
1043    #[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    /// Check if there are any errors.
1050    #[must_use]
1051    pub fn is_empty(&self) -> bool {
1052        self.errors.is_empty()
1053    }
1054
1055    /// Get the number of errors.
1056    #[must_use]
1057    pub fn len(&self) -> usize {
1058        self.errors.len()
1059    }
1060
1061    /// Get an iterator over the errors.
1062    pub fn iter(&self) -> impl Iterator<Item = &ValidationError> {
1063        self.errors.iter()
1064    }
1065
1066    /// Convert to FastAPI-compatible JSON string.
1067    #[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    /// Convert to JSON bytes.
1081    #[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    /// Merge another ValidationErrors into this one.
1095    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    /// Add a prefix to all error locations.
1106    #[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        // Conditionally include debug info based on global debug mode flag
1162        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// ============================================================================
1188// Response Validation Error (500 Internal Server Error)
1189// ============================================================================
1190
1191/// Response validation error for internal failures (500 Internal Server Error).
1192///
1193/// This error type is used when a response fails to serialize or validate
1194/// against the expected response model. Unlike [`ValidationErrors`] which
1195/// indicates client errors (422), this represents a bug in the server code
1196/// and returns a 500 status.
1197///
1198/// # When This Is Used
1199///
1200/// - Response fails to serialize to JSON
1201/// - Response doesn't match the declared response_model
1202/// - Internal data transformation fails
1203///
1204/// # Security Note
1205///
1206/// Error details and the original response content are logged server-side but
1207/// are NOT exposed to clients (unless debug mode is explicitly enabled).
1208/// This prevents leaking internal implementation details.
1209///
1210/// # Examples
1211///
1212/// ```
1213/// use fastapi_core::error::{ResponseValidationError, ValidationError, loc};
1214///
1215/// // Serialization failure
1216/// let error = ResponseValidationError::serialization_failed(
1217///     "failed to serialize field 'created_at': invalid date format"
1218/// );
1219///
1220/// // Response model validation failure
1221/// let error = ResponseValidationError::new()
1222///     .with_error(ValidationError::missing(loc::response_field("user_id")))
1223///     .with_response_content(serde_json::json!({"name": "Alice"}));
1224/// ```
1225#[derive(Debug, Clone, Default)]
1226pub struct ResponseValidationError {
1227    /// The validation errors that occurred.
1228    pub errors: Vec<ValidationError>,
1229    /// The response content that failed validation (for logging only).
1230    /// This is NOT included in the response to clients.
1231    pub response_content: Option<serde_json::Value>,
1232    /// A summary message for logging.
1233    pub summary: Option<String>,
1234    /// Debug information (only included in response when debug mode is enabled).
1235    pub debug_info: Option<DebugInfo>,
1236}
1237
1238impl ResponseValidationError {
1239    /// Create a new empty response validation error.
1240    #[must_use]
1241    pub fn new() -> Self {
1242        Self::default()
1243    }
1244
1245    /// Create a serialization failure error.
1246    ///
1247    /// Use this when response serialization to JSON fails.
1248    #[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    /// Create a response model validation failure error.
1266    ///
1267    /// Use this when the response doesn't match the declared response_model.
1268    #[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    /// Add a validation error.
1286    #[must_use]
1287    pub fn with_error(mut self, error: ValidationError) -> Self {
1288        self.errors.push(error);
1289        self
1290    }
1291
1292    /// Add multiple validation errors.
1293    #[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    /// Set the response content that failed validation.
1300    ///
1301    /// This is logged server-side but NOT exposed to clients.
1302    #[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    /// Set a summary message for logging.
1309    #[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    /// Add debug information.
1316    ///
1317    /// This information will only be included in the response when debug mode
1318    /// is enabled (via `enable_debug_mode()`).
1319    #[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    /// Check if there are no errors.
1326    #[must_use]
1327    pub fn is_empty(&self) -> bool {
1328        self.errors.is_empty()
1329    }
1330
1331    /// Get the number of errors.
1332    #[must_use]
1333    pub fn len(&self) -> usize {
1334        self.errors.len()
1335    }
1336
1337    /// Get an iterator over the errors.
1338    pub fn iter(&self) -> impl Iterator<Item = &ValidationError> {
1339        self.errors.iter()
1340    }
1341
1342    /// Log the error details (for server-side logging).
1343    ///
1344    /// This returns a formatted string suitable for logging that includes
1345    /// the error details and response content (if any).
1346    #[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            // Truncate large content for logging (UTF-8 safe)
1375            let content_str = serde_json::to_string(content).unwrap_or_default();
1376            let truncated = if content_str.len() > 500 {
1377                // Find a safe UTF-8 boundary near 500 chars
1378                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        // Always log the full error details server-side
1408        // In a real application, this would use the logging system
1409        // For now, we include it in debug_info if debug mode is enabled
1410
1411        // Build the response body
1412        let body = if is_debug_mode_enabled() {
1413            // In debug mode, include error details
1414            #[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            // In production, return a generic error message
1451            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// ============================================================================
1461// Tests
1462// ============================================================================
1463
1464#[cfg(test)]
1465mod tests {
1466    use super::*;
1467    use serde_json::json;
1468    use serial_test::serial;
1469
1470    // ========================================================================
1471    // LocItem Tests
1472    // ========================================================================
1473
1474    #[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    // ========================================================================
1521    // Location Helper Tests
1522    // ========================================================================
1523
1524    #[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    // ========================================================================
1591    // ValidationError Tests
1592    // ========================================================================
1593
1594    #[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    // ========================================================================
1703    // ValidationError Serialization Tests
1704    // ========================================================================
1705
1706    #[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()); // skip_serializing_if = None
1715        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    // ========================================================================
1742    // ValidationErrors Collection Tests
1743    // ========================================================================
1744
1745    #[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    // ========================================================================
1856    // ValidationErrors JSON Output Tests
1857    // ========================================================================
1858
1859    #[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        // This tests the exact format FastAPI/Pydantic v2 produces
1884        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        // First error: missing query param
1893        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        // Second error: string too short
1898        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        // Test the complex nested case: body > items[0] > name
1907        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    // ========================================================================
1924    // IntoResponse Tests
1925    // ========================================================================
1926
1927    #[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        // Check content-type header
1935        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    // ========================================================================
1944    // Error Types Constants Tests
1945    // ========================================================================
1946
1947    #[test]
1948    fn error_types_match_pydantic() {
1949        // Verify our constants match Pydantic v2 error types
1950        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    // ========================================================================
1962    // HttpError Tests
1963    // ========================================================================
1964
1965    #[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        // Just verify it compiles and we can use it as a trait object
2082        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        // Extract body
2111        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        // When no detail is provided, should use canonical reason
2123        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        // Check custom header is present
2144        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        // Should have content-type plus our two custom headers
2163        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        // Test all status code variants produce valid JSON
2181        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            // Should parse as valid JSON
2200            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            // Should have detail field
2209            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        // Verify our error format matches FastAPI's HTTPException
2221        // FastAPI returns: {"detail": "message"}
2222        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        // Only "detail" key, no extra fields
2233        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        // Verify builder pattern works correctly with method chaining
2241        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    // ========================================================================
2252    // Additional Error Types Constants Tests
2253    // ========================================================================
2254
2255    #[test]
2256    fn error_types_all_constants_defined() {
2257        // Verify all error type constants are non-empty
2258        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        // Verify numeric range error types
2282        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        // Verify collection-related error types
2289        assert_eq!(error_types::TOO_SHORT, "too_short");
2290        assert_eq!(error_types::TOO_LONG, "too_long");
2291    }
2292
2293    // ========================================================================
2294    // Edge Cases and Error Conditions
2295    // ========================================================================
2296
2297    #[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        // Test very deeply nested location path
2309        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        // Test with many errors to ensure performance
2341        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        // Test field names with special characters
2356        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        // Test with large input value
2378        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        // Explicitly setting empty string as detail
2389        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        // Empty detail should be used as-is
2400        assert_eq!(parsed["detail"], "");
2401    }
2402
2403    #[test]
2404    fn http_error_binary_header_value() {
2405        // Test header with non-UTF8 bytes
2406        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    // ========================================================================
2412    // Debug Mode Tests
2413    // ========================================================================
2414
2415    #[test]
2416    #[serial]
2417    fn debug_mode_default_disabled() {
2418        // Ensure debug mode is disabled by default
2419        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        // Start disabled
2427        disable_debug_mode();
2428        assert!(!is_debug_mode_enabled());
2429
2430        // Enable
2431        enable_debug_mode();
2432        assert!(is_debug_mode_enabled());
2433
2434        // Disable again
2435        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        // Not authorized when debug mode is disabled
2474        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        // Authorized when allow_unauthenticated is true
2483        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        // handler_name and extra should be omitted when empty/None
2609        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        // Only route_pattern should be present
2620        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        // Should only have detail, no debug info
2664        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 for this test
2672        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        // Should have both detail and debug info
2691        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        // Clean up
2699        disable_debug_mode();
2700    }
2701
2702    #[test]
2703    #[serial]
2704    fn http_error_response_with_debug_mode_no_debug_info() {
2705        // Enable debug mode but don't add debug info
2706        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        // Should only have detail, no debug field
2719        assert_eq!(parsed["detail"], "User not found");
2720        assert!(parsed.get("debug").is_none());
2721
2722        // Clean up
2723        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        // Should only have detail array, no debug info
2755        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        // Should have both detail and debug info
2780        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        // Clean up
2786        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        // Should keep the first debug_info
2800        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        // Should take debug_info from errors2
2817        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    // ========================================================================
2825    // ResponseValidationError Tests
2826    // ========================================================================
2827
2828    #[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        // Ensure debug mode is off
2915        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        // Check content-type header
2924        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        // Check body - should NOT include internal details
2932        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            // Should NOT include debug info or response content
2937            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
2947        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        // Check body - should include debug info
2957        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            // Should include debug info
2961            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        // Restore default state
2971        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        // Create a large response content
2990        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}