Skip to content
73 changes: 73 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ async-lock = { default-features = false, version = "3.4.0" }
base16 = { default-features = false, version = "0.2.1" }
digest = { default-features = false, version = "0.10.7" }
sha2 = { default-features = false, version = "0.10.8" }
bon = { default-features = false, version = "3.0.0" }

[profile.release]
codegen-units = 1
Expand Down
1 change: 1 addition & 0 deletions server_fn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ send_wrapper = { features = [
"futures",
], optional = true, workspace = true, default-features = true }
thiserror = { workspace = true, default-features = true }
bon = { workspace = true }

# registration system
inventory = { optional = true, workspace = true, default-features = true }
Expand Down
6 changes: 4 additions & 2 deletions server_fn/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ pub mod browser {
Err(OutputStreamError::from_server_fn_error(
ServerFnErrorErr::Request(err.to_string()),
)
.ser())
.ser()
.body)
}
});
let stream = SendWrapper::new(stream);
Expand Down Expand Up @@ -281,7 +282,8 @@ pub mod reqwest {
Err(e) => Err(OutputStreamError::from_server_fn_error(
ServerFnErrorErr::Request(e.to_string()),
)
.ser()),
.ser()
.body),
}),
write.with(|msg: Bytes| async move {
Ok::<
Expand Down
4 changes: 2 additions & 2 deletions server_fn/src/codec/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ where
async fn into_res(self) -> Result<Response, E> {
Response::try_from_stream(
Streaming::CONTENT_TYPE,
self.into_inner().map_err(|e| e.ser()),
self.into_inner().map_err(|e| e.ser().body),
)
}
}
Expand Down Expand Up @@ -255,7 +255,7 @@ where
Response::try_from_stream(
Streaming::CONTENT_TYPE,
self.into_inner()
.map(|stream| stream.map(Into::into).map_err(|e| e.ser())),
.map(|stream| stream.map(Into::into).map_err(|e| e.ser().body)),
)
}
}
Expand Down
27 changes: 21 additions & 6 deletions server_fn/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ impl<E: FromServerFnError> ServerFnUrlError<E> {
let mut url = Url::parse(base)?;
url.query_pairs_mut()
.append_pair("__path", &self.path)
.append_pair("__err", &URL_SAFE.encode(self.error.ser()));
.append_pair("__err", &URL_SAFE.encode(self.error.ser().body));
Ok(url)
}

Expand Down Expand Up @@ -536,7 +536,7 @@ impl<E: FromServerFnError> Display for ServerFnErrorWrapper<E> {
write!(
f,
"{}",
<E::Encoder as FormatType>::into_encoded_string(self.0.ser())
<E::Encoder as FormatType>::into_encoded_string(self.0.ser().body)
)
}
}
Expand All @@ -560,6 +560,17 @@ impl<E: FromServerFnError> FromStr for ServerFnErrorWrapper<E> {
}
}

/// Response parts returned by [`FromServerFnError::ser`] to be returned to the client.
#[derive(bon::Builder)]
#[non_exhaustive]
pub struct ServerFnErrorResponseParts {
/// The raw [`Bytes`] of the serialized error.
pub body: Bytes,
/// The value of the `CONTENT_TYPE` associated constant for the `FromServerFnError`
/// implementation. Used to set the `content-type` header in http responses.
pub content_type: &'static str,
}

/// A trait for types that can be returned from a server function.
pub trait FromServerFnError: std::fmt::Debug + Sized + 'static {
/// The encoding strategy used to serialize and deserialize this error type. Must implement the [`Encodes`](server_fn::Encodes) trait for references to the error type.
Expand All @@ -568,17 +579,21 @@ 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`].
fn ser(&self) -> Bytes {
Self::Encoder::encode(self).unwrap_or_else(|e| {
/// Converts the custom error type to [`ServerFnErrorResponseParts`].
fn ser(&self) -> ServerFnErrorResponseParts {
let body = Self::Encoder::encode(self).unwrap_or_else(|e| {
Self::Encoder::encode(&Self::from_server_fn_error(
ServerFnErrorErr::Serialization(e.to_string()),
))
.expect(
"error serializing should success at least with the \
Serialization error",
)
})
});
ServerFnErrorResponseParts::builder()
.body(body)
.content_type(Self::Encoder::CONTENT_TYPE)
.build()
}

/// Deserializes the custom error type from a [`&str`].
Expand Down
16 changes: 10 additions & 6 deletions server_fn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -667,8 +667,9 @@ where
ServerFnErrorErr::Serialization(e.to_string()),
)
.ser()
.body
}),
Err(err) => Err(err.ser()),
Err(err) => Err(err.ser().body),
};
serialize_result(result)
});
Expand Down Expand Up @@ -711,9 +712,10 @@ where
),
)
.ser()
.body
})
}
Err(err) => Err(err.ser()),
Err(err) => Err(err.ser().body),
};
let result = serialize_result(result);
if sink.send(result).await.is_err() {
Expand Down Expand Up @@ -781,7 +783,8 @@ fn deserialize_result<E: FromServerFnError>(
return Err(E::from_server_fn_error(
ServerFnErrorErr::Deserialization("Data is empty".into()),
)
.ser());
.ser()
.body);
}

let tag = bytes[0];
Expand All @@ -793,7 +796,8 @@ fn deserialize_result<E: FromServerFnError>(
_ => Err(E::from_server_fn_error(ServerFnErrorErr::Deserialization(
"Invalid data tag".into(),
))
.ser()), // Invalid tag
.ser()
.body), // Invalid tag
}
}

Expand Down Expand Up @@ -883,7 +887,7 @@ pub struct ServerFnTraitObj<Req, Res> {
method: Method,
handler: fn(Req) -> Pin<Box<dyn Future<Output = Res> + Send>>,
middleware: fn() -> MiddlewareSet<Req, Res>,
ser: fn(ServerFnErrorErr) -> Bytes,
ser: middleware::ServerFnErrorSerializer,
}

impl<Req, Res> ServerFnTraitObj<Req, Res> {
Expand Down Expand Up @@ -959,7 +963,7 @@ where
fn run(
&mut self,
req: Req,
_ser: fn(ServerFnErrorErr) -> Bytes,
_err_ser: middleware::ServerFnErrorSerializer,
) -> Pin<Box<dyn Future<Output = Res> + Send>> {
let handler = self.handler;
Box::pin(async move { handler(req).await })
Expand Down
Loading
Loading