@@ -16,6 +16,9 @@ use http::StatusCode;
1616#[ derive( Debug , Display ) ]
1717pub enum TrustedServerError {
1818 /// Client-side input/validation error resulting in a 400 Bad Request.
19+ ///
20+ /// **Note:** `message` is returned to clients in the HTTP response body.
21+ /// Keep it free of internal implementation details.
1922 #[ display( "Bad request: {message}" ) ]
2023 BadRequest { message : String } ,
2124 /// Configuration errors that prevent the server from starting.
@@ -87,7 +90,10 @@ pub trait IntoHttpResponse {
8790 /// Convert the error into an HTTP status code.
8891 fn status_code ( & self ) -> StatusCode ;
8992
90- /// Get the error message to show to users (uses the Display implementation).
93+ /// Get a safe, user-facing error message.
94+ ///
95+ /// Client errors (4xx) return a brief description; server/integration errors
96+ /// return a generic message. Full error details are preserved in server logs.
9197 fn user_message ( & self ) -> String ;
9298}
9399
@@ -112,7 +118,92 @@ impl IntoHttpResponse for TrustedServerError {
112118 }
113119
114120 fn user_message ( & self ) -> String {
115- // Use the Display implementation which already has the specific error message
116- self . to_string ( )
121+ match self {
122+ // Client errors (4xx) — safe to surface a brief description
123+ Self :: BadRequest { message } => format ! ( "Bad request: {message}" ) ,
124+ // Consent strings may contain user data; return category only.
125+ Self :: GdprConsent { .. } => "GDPR consent error" . to_string ( ) ,
126+ Self :: InvalidHeaderValue { .. } => "Invalid header value" . to_string ( ) ,
127+ Self :: InvalidUtf8 { .. } => "Invalid request data" . to_string ( ) ,
128+ // Server/integration errors (5xx/502/503) — generic message only.
129+ // Full details are already logged via log::error! in to_error_response.
130+ _ => "An internal error occurred" . to_string ( ) ,
131+ }
132+ }
133+ }
134+
135+ #[ cfg( test) ]
136+ mod tests {
137+ use super :: * ;
138+
139+ #[ test]
140+ fn server_errors_return_generic_message ( ) {
141+ let cases = [
142+ TrustedServerError :: Configuration {
143+ message : "secret db path" . into ( ) ,
144+ } ,
145+ TrustedServerError :: KvStore {
146+ store_name : "users" . into ( ) ,
147+ message : "timeout" . into ( ) ,
148+ } ,
149+ TrustedServerError :: Proxy {
150+ message : "upstream 10.0.0.1 refused" . into ( ) ,
151+ } ,
152+ TrustedServerError :: SyntheticId {
153+ message : "seed file missing" . into ( ) ,
154+ } ,
155+ TrustedServerError :: Template {
156+ message : "render failed" . into ( ) ,
157+ } ,
158+ TrustedServerError :: Auction {
159+ message : "bid timeout" . into ( ) ,
160+ } ,
161+ TrustedServerError :: Gam {
162+ message : "api key invalid" . into ( ) ,
163+ } ,
164+ TrustedServerError :: Prebid {
165+ message : "adapter error" . into ( ) ,
166+ } ,
167+ TrustedServerError :: Integration {
168+ integration : "foo" . into ( ) ,
169+ message : "connection refused" . into ( ) ,
170+ } ,
171+ TrustedServerError :: Settings {
172+ message : "parse failed" . into ( ) ,
173+ } ,
174+ TrustedServerError :: InsecureDefault {
175+ field : "api_key" . into ( ) ,
176+ } ,
177+ ] ;
178+ for error in & cases {
179+ assert_eq ! (
180+ error. user_message( ) ,
181+ "An internal error occurred" ,
182+ "should not leak details for {error:?}" ,
183+ ) ;
184+ }
185+ }
186+
187+ #[ test]
188+ fn client_errors_return_safe_descriptions ( ) {
189+ let error = TrustedServerError :: BadRequest {
190+ message : "missing field" . into ( ) ,
191+ } ;
192+ assert_eq ! ( error. user_message( ) , "Bad request: missing field" ) ;
193+
194+ let error = TrustedServerError :: GdprConsent {
195+ message : "no consent string" . into ( ) ,
196+ } ;
197+ assert_eq ! ( error. user_message( ) , "GDPR consent error" ) ;
198+
199+ let error = TrustedServerError :: InvalidHeaderValue {
200+ message : "non-ascii" . into ( ) ,
201+ } ;
202+ assert_eq ! ( error. user_message( ) , "Invalid header value" ) ;
203+
204+ let error = TrustedServerError :: InvalidUtf8 {
205+ message : "byte 0xff" . into ( ) ,
206+ } ;
207+ assert_eq ! ( error. user_message( ) , "Invalid request data" ) ;
117208 }
118209}
0 commit comments