11use axum_core:: response:: IntoResponse ;
22use futures_util:: TryStreamExt ;
3+ use http:: header:: LOCATION ;
34use http:: StatusCode ;
45use serde:: { Deserialize , Serialize } ;
5- use std:: fmt:: Debug ;
66
77use crate :: HttpError ;
88
@@ -81,23 +81,19 @@ pub enum ServerFnError {
8181 /// Occurs on the server if there is an error creating an HTTP response.
8282 #[ error( "error creating response {0}" ) ]
8383 Response ( String ) ,
84+ }
8485
85- /// A redirect response returned while running a server function or extractor.
86- ///
87- /// This is treated as control-flow rather than an ordinary error so we can preserve the
88- /// `Location` header (which is otherwise lost when converting axum responses into `ServerFnError`).
89- #[ error( "redirect ({code}) to {location}" ) ]
90- Redirect {
91- /// HTTP status code associated with the redirect (typically 302/303/307/308).
92- code : u16 ,
93- /// The value of the `Location` header.
94- location : String ,
95- /// Marker source to let core treat this as expected control-flow (not an error log).
96- #[ doc( hidden) ]
97- #[ serde( skip, default ) ]
98- #[ source]
99- control_flow : dioxus_core:: RedirectControlFlow ,
100- } ,
86+ // Reserved key used to encode redirects in `ServerFnError::ServerError.details`.
87+ const REDIRECT_DETAILS_KEY : & str = "__dioxus_redirect" ;
88+
89+ fn is_redirection_code ( code : u16 ) -> bool {
90+ ( 300 ..400 ) . contains ( & code)
91+ }
92+
93+ fn redirect_location_from_details ( details : & Option < serde_json:: Value > ) -> Option < & str > {
94+ let details = details. as_ref ( ) ?;
95+ let obj = details. as_object ( ) ?;
96+ obj. get ( REDIRECT_DETAILS_KEY ) ?. as_str ( )
10197}
10298
10399impl ServerFnError {
@@ -112,18 +108,38 @@ impl ServerFnError {
112108
113109 /// Create a redirect error (control-flow) with a status code and `Location`.
114110 pub fn redirect ( code : u16 , location : impl Into < String > ) -> Self {
115- ServerFnError :: Redirect {
111+ let location = location. into ( ) ;
112+ ServerFnError :: ServerError {
113+ message : format ! ( "redirect ({code}) to {location}" ) ,
114+ details : Some ( serde_json:: json!( { REDIRECT_DETAILS_KEY : location } ) ) ,
116115 code,
117- location : location. into ( ) ,
118- control_flow : dioxus_core:: RedirectControlFlow :: default ( ) ,
116+ }
117+ }
118+
119+ /// Returns the redirect location if this error represents a redirect.
120+ pub fn redirect_location ( & self ) -> Option < & str > {
121+ match self {
122+ ServerFnError :: ServerError { code, details, .. } if is_redirection_code ( * code) => {
123+ redirect_location_from_details ( details)
124+ }
125+ _ => None ,
119126 }
120127 }
121128
122129 /// Create a new server error (status code 500) with a message and details.
123130 pub async fn from_axum_response ( resp : axum_core:: response:: Response ) -> Self {
124- let status = resp. status ( ) ;
125- let message = resp
126- . into_body ( )
131+ let ( parts, body) = resp. into_parts ( ) ;
132+ let status = parts. status ;
133+
134+ // If this is a redirect, preserve the location in structured details so other layers
135+ // (like SSR) can turn it back into a redirect response.
136+ if status. is_redirection ( ) {
137+ if let Some ( location) = parts. headers . get ( LOCATION ) . and_then ( |v| v. to_str ( ) . ok ( ) ) {
138+ return ServerFnError :: redirect ( status. as_u16 ( ) , location) ;
139+ }
140+ }
141+
142+ let message = body
127143 . into_data_stream ( )
128144 . try_fold ( Vec :: new ( ) , |mut acc, chunk| async move {
129145 acc. extend_from_slice ( & chunk) ;
@@ -164,9 +180,6 @@ impl From<ServerFnError> for http::StatusCode {
164180 ServerFnError :: ServerError { code, .. } => {
165181 http:: StatusCode :: from_u16 ( code) . unwrap_or ( http:: StatusCode :: INTERNAL_SERVER_ERROR )
166182 }
167- ServerFnError :: Redirect { code, .. } => {
168- http:: StatusCode :: from_u16 ( code) . unwrap_or ( http:: StatusCode :: INTERNAL_SERVER_ERROR )
169- }
170183 ServerFnError :: Request ( err) => match err {
171184 RequestError :: Status ( _, code) => http:: StatusCode :: from_u16 ( code)
172185 . unwrap_or ( http:: StatusCode :: INTERNAL_SERVER_ERROR ) ,
@@ -195,7 +208,6 @@ impl From<ServerFnError> for HttpError {
195208 fn from ( value : ServerFnError ) -> Self {
196209 let status = StatusCode :: from_u16 ( match & value {
197210 ServerFnError :: ServerError { code, .. } => * code,
198- ServerFnError :: Redirect { code, .. } => * code,
199211 _ => 500 ,
200212 } )
201213 . unwrap_or ( StatusCode :: INTERNAL_SERVER_ERROR ) ;
@@ -225,23 +237,6 @@ impl From<HttpError> for ServerFnError {
225237impl IntoResponse for ServerFnError {
226238 fn into_response ( self ) -> axum_core:: response:: Response {
227239 match self {
228- Self :: Redirect { code, location, .. } => {
229- use http:: header:: LOCATION ;
230- let status =
231- StatusCode :: from_u16 ( code) . unwrap_or ( StatusCode :: INTERNAL_SERVER_ERROR ) ;
232- axum_core:: response:: Response :: builder ( )
233- . status ( status)
234- . header ( LOCATION , location)
235- . body ( axum_core:: body:: Body :: empty ( ) )
236- . unwrap_or_else ( |_| {
237- axum_core:: response:: Response :: builder ( )
238- . status ( StatusCode :: INTERNAL_SERVER_ERROR )
239- . body ( axum_core:: body:: Body :: from (
240- "{\" error\" :\" Internal Server Error\" }" ,
241- ) )
242- . unwrap ( )
243- } )
244- }
245240 Self :: ServerError {
246241 message,
247242 code,
0 commit comments