masterror/response/
details.rs1#[cfg(not(feature = "serde_json"))]
6use alloc::string::String;
7
8#[cfg(feature = "serde_json")]
9use serde::Serialize;
10#[cfg(feature = "serde_json")]
11use serde_json::{Value as JsonValue, to_value};
12
13use super::core::ErrorResponse;
14#[cfg(feature = "serde_json")]
15use crate::{AppError, AppResult};
16
17#[cfg(not(feature = "serde_json"))]
18impl ErrorResponse {
19 #[must_use]
21 pub fn with_details_text(mut self, details: impl Into<String>) -> Self {
22 self.details = Some(details.into());
23 self
24 }
25}
26
27#[cfg(feature = "serde_json")]
28impl ErrorResponse {
29 #[must_use]
31 pub fn with_details_json(mut self, details: JsonValue) -> Self {
32 self.details = Some(details);
33 self
34 }
35
36 #[allow(clippy::result_large_err)]
65 pub fn with_details<T>(self, payload: T) -> AppResult<Self>
66 where
67 T: Serialize
68 {
69 let details = to_value(payload).map_err(|e| AppError::bad_request(e.to_string()))?;
70 Ok(self.with_details_json(details))
71 }
72}
73#[cfg(feature = "serde_json")]
74use alloc::string::ToString;
75
76#[cfg(test)]
77mod tests {
78 use crate::{AppCode, ErrorResponse};
79
80 #[cfg(not(feature = "serde_json"))]
81 #[test]
82 fn with_details_text_attaches_string() {
83 let resp = ErrorResponse::new(400, AppCode::BadRequest, "error")
84 .unwrap()
85 .with_details_text("detailed explanation");
86 assert_eq!(resp.details.as_deref(), Some("detailed explanation"));
87 }
88
89 #[cfg(not(feature = "serde_json"))]
90 #[test]
91 fn with_details_text_accepts_owned_string() {
92 let resp = ErrorResponse::new(422, AppCode::Validation, "invalid")
93 .unwrap()
94 .with_details_text(String::from("field error"));
95 assert_eq!(resp.details.as_deref(), Some("field error"));
96 }
97
98 #[cfg(feature = "serde_json")]
99 #[test]
100 fn with_details_json_attaches_value() {
101 use serde_json::json;
102 let details = json!({"field": "email", "error": "invalid"});
103 let resp = ErrorResponse::new(422, AppCode::Validation, "bad input")
104 .unwrap()
105 .with_details_json(details.clone());
106 assert_eq!(resp.details, Some(details));
107 }
108
109 #[cfg(feature = "serde_json")]
110 #[test]
111 fn with_details_serializes_struct() {
112 use serde::Serialize;
113 #[derive(Serialize)]
114 struct ErrorInfo {
115 field: String,
116 code: u32
117 }
118 let info = ErrorInfo {
119 field: "username".to_owned(),
120 code: 1001
121 };
122 let resp = ErrorResponse::new(400, AppCode::BadRequest, "validation failed")
123 .unwrap()
124 .with_details(info)
125 .unwrap();
126 assert!(resp.details.is_some());
127 let details = resp.details.unwrap();
128 assert_eq!(details["field"], "username");
129 assert_eq!(details["code"], 1001);
130 }
131
132 #[cfg(feature = "serde_json")]
133 #[test]
134 fn with_details_serializes_nan_as_null() {
135 use serde::Serialize;
136 #[derive(Serialize)]
137 struct DataWithNaN {
138 value: f64
139 }
140 let data = DataWithNaN {
141 value: f64::NAN
142 };
143 let resp = ErrorResponse::new(500, AppCode::Internal, "error")
144 .unwrap()
145 .with_details(data)
146 .unwrap();
147 assert!(resp.details.is_some());
148 let details = resp.details.unwrap();
149 assert!(details["value"].is_null());
150 }
151
152 #[cfg(feature = "serde_json")]
153 #[test]
154 fn with_details_preserves_other_fields() {
155 use serde::Serialize;
156 #[derive(Serialize)]
157 struct Extra {
158 info: String
159 }
160 let mut resp = ErrorResponse::new(429, AppCode::RateLimited, "too many").unwrap();
161 resp.retry = Some(crate::response::core::RetryAdvice {
162 after_seconds: 60
163 });
164 let resp = resp
165 .with_details(Extra {
166 info: "try later".to_owned()
167 })
168 .unwrap();
169 assert!(resp.details.is_some());
170 assert!(resp.retry.is_some());
171 assert_eq!(resp.status, 429);
172 assert_eq!(resp.code, AppCode::RateLimited);
173 }
174
175 #[cfg(not(feature = "serde_json"))]
176 #[test]
177 fn with_details_text_builder_pattern() {
178 let resp = ErrorResponse::new(404, AppCode::NotFound, "missing")
179 .unwrap()
180 .with_details_text("resource not found in database");
181 assert_eq!(resp.status, 404);
182 assert_eq!(resp.message, "missing");
183 assert!(resp.details.is_some());
184 }
185}