Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9ac211f
fix: Set content type header for server function errors
spencewenski Aug 6, 2025
ed942bf
Use CONTENT_TYPE header from `actix` re-export
spencewenski Aug 6, 2025
bd317c8
Add `-` in doc comment
spencewenski Aug 6, 2025
a507872
Per PR suggestion, fix with an API-breaking change
spencewenski Aug 7, 2025
6b6cafe
Rename `BoxedService#ser` field and use bon-3.0.0
spencewenski Aug 8, 2025
43e76de
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 8, 2025
9949f79
Fix actix stream/websocket responses
spencewenski Aug 8, 2025
fe08880
Fix stream/websocket responses in a couple other places
spencewenski Aug 8, 2025
058a4fe
Replace `todo` doc comment with actual comment
spencewenski Aug 8, 2025
dda564b
Update doc comments
spencewenski Aug 19, 2025
198824d
Revert "Update doc comments"
spencewenski Aug 25, 2025
8f3e65b
Revert "Replace `todo` doc comment with actual comment"
spencewenski Aug 25, 2025
427258f
Revert "Fix stream/websocket responses in a couple other places"
spencewenski Aug 25, 2025
d446fa2
Revert "Fix actix stream/websocket responses"
spencewenski Aug 25, 2025
c2afd96
Revert "[autofix.ci] apply automated fixes"
spencewenski Aug 25, 2025
8a1f474
Revert "Rename `BoxedService#ser` field and use bon-3.0.0"
spencewenski Aug 25, 2025
b9c81d1
Revert "Per PR suggestion, fix with an API-breaking change"
spencewenski Aug 25, 2025
3758db9
Merge branch 'main' into server-fn-err-content-type-fix
spencewenski Aug 25, 2025
02a9810
Revert commits to go back to the original semver-compatible approach
spencewenski Aug 25, 2025
b03565e
Merge branch 'main' into server-fn-err-content-type-fix
spencewenski Aug 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions server_fn/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ pub trait FromServerFnError: std::fmt::Debug + Sized + 'static {
/// Converts a [`ServerFnErrorErr`] into the application-specific custom error type.
fn from_server_fn_error(value: ServerFnErrorErr) -> Self;

/// Converts the custom error type to a [`String`].
/// Serializes the custom error type to bytes, according to the encoding given by `Self::Encoding`.
fn ser(&self) -> Bytes {
Self::Encoder::encode(self).unwrap_or_else(|e| {
Self::Encoder::encode(&Self::from_server_fn_error(
Expand All @@ -581,7 +581,7 @@ pub trait FromServerFnError: std::fmt::Debug + Sized + 'static {
})
}

/// Deserializes the custom error type from a [`&str`].
/// Deserializes the custom error type, according to the encoding given by `Self::Encoding`.
fn de(data: Bytes) -> Self {
Self::Encoder::decode(data).unwrap_or_else(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
Expand Down
10 changes: 6 additions & 4 deletions server_fn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,16 +307,18 @@ pub trait ServerFn: Send + Sized {
.await
.map(|res| (res, None))
.unwrap_or_else(|e| {
(
let mut response =
<<Self as ServerFn>::Server as crate::Server<
Self::Error,
Self::InputStreamError,
Self::OutputStreamError,
>>::Response::error_response(
Self::PATH, e.ser()
),
Some(e),
)
);
let content_type =
<Self::Error as FromServerFnError>::Encoder::CONTENT_TYPE;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This formatting is kind of weird, but it's what cargo fmt wants.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd prefer to modify error_response() to take and set the content type from the codec, then we wouldn't need to define a content_type function on Response

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also prefer that, but I was trying to avoid introducing a breaking change. Would you prefer to modify error_response even though it would be a breaking change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it'll be better to have the better api and do the breaking change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, sounds good. I’ll update when I get a chance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the PR, please take a look when you get a chance. I made a few additional breaking changes that I thought made sense and also resolve the middleware issue I originally tried fixing in #4216.

response.content_type(content_type);
(response, Some(e))
});

// if it accepts HTML, we'll redirect to the Referer
Expand Down
8 changes: 8 additions & 0 deletions server_fn/src/middleware/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ mod axum {
let inner = self.call(req);
Box::pin(async move {
inner.await.unwrap_or_else(|e| {
// TODO: This does not set the Content-Type on the response. Doing so will
// require a breaking change in order to get the correct encoding from the
// error's `FromServerFnError::Encoder::CONTENT_TYPE` impl.
// Note: This only applies to middleware errors.
let err =
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
Response::<Body>::error_response(&path, err)
Expand Down Expand Up @@ -149,6 +153,10 @@ mod actix {
let inner = self.call(req);
Box::pin(async move {
inner.await.unwrap_or_else(|e| {
// TODO: This does not set the Content-Type on the response. Doing so will
// require a breaking change in order to get the correct encoding from the
// error's `FromServerFnError::Encoder::CONTENT_TYPE` impl.
// Note: This only applies to middleware errors.
let err =
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
ActixResponse::error_response(&path, err).take()
Expand Down
8 changes: 7 additions & 1 deletion server_fn/src/response/actix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::error::{
use actix_web::{
http::{
header,
header::{HeaderValue, LOCATION},
header::{HeaderValue, CONTENT_TYPE, LOCATION},
StatusCode,
},
HttpResponse,
Expand Down Expand Up @@ -80,6 +80,12 @@ impl Res for ActixResponse {
))
}

fn content_type(&mut self, content_type: &str) {
if let Ok(content_type) = HeaderValue::from_str(content_type) {
self.0.headers_mut().insert(CONTENT_TYPE, content_type);
}
}

fn redirect(&mut self, path: &str) {
if let Ok(path) = HeaderValue::from_str(path) {
*self.0.status_mut() = StatusCode::FOUND;
Expand Down
7 changes: 7 additions & 0 deletions server_fn/src/response/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ impl Res for Response<Body> {
.unwrap()
}

fn content_type(&mut self, content_type: &str) {
if let Ok(content_type) = HeaderValue::from_str(content_type) {
self.headers_mut()
.insert(header::CONTENT_TYPE, content_type);
}
}

fn redirect(&mut self, path: &str) {
if let Ok(path) = HeaderValue::from_str(path) {
self.headers_mut().insert(header::LOCATION, path);
Expand Down
7 changes: 7 additions & 0 deletions server_fn/src/response/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ impl Res for Response<Body> {
.unwrap()
}

fn content_type(&mut self, content_type: &str) {
if let Ok(content_type) = HeaderValue::from_str(content_type) {
self.headers_mut()
.insert(header::CONTENT_TYPE, content_type);
}
}

fn redirect(&mut self, path: &str) {
if let Ok(path) = HeaderValue::from_str(path) {
self.headers_mut().insert(header::LOCATION, path);
Expand Down
13 changes: 11 additions & 2 deletions server_fn/src/response/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,14 @@ where

/// Represents the response as created by the server;
pub trait Res {
/// Converts an error into a response, with a `500` status code and the error text as its body.
/// Converts an error into a response, with a `500` status code and the error as its body.
fn error_response(path: &str, err: Bytes) -> Self;

/// Set the `Content-Type` header for the response.
fn content_type(&mut self, #[allow(unused_variables)] content_type: &str) {
// TODO 0.9: remove this method and default implementation. It is only included here
// to allow setting the `Content-Type` header for error responses without requiring a
// semver-incompatible change.
}
/// Redirect the response by setting a 302 code and Location header.
fn redirect(&mut self, path: &str);
}
Expand Down Expand Up @@ -103,6 +108,10 @@ impl Res for BrowserMockRes {
unreachable!()
}

fn content_type(&mut self, _content_type: &str) {
unreachable!()
}

fn redirect(&mut self, _path: &str) {
unreachable!()
}
Expand Down
Loading