Skip to content

Commit

Permalink
feat: support anyhow::Error in state generation functions (#264)
Browse files Browse the repository at this point in the history
* Support `BlamedError<anyhow::Error>`

`anyhow::Error` cannot implement `std::error::Error` (see
dtolnay/anyhow#136), which Perseus requires
for errors returned from state generation functions. But Perseus
immediately boxes the error, and `anyhow::Error` does support conversion
into `Box<dyn std::error::Error>`. So if we make the conversion from
`BlamedError` to `GenericBlamedError` more general,
`BlamedError<anyhow::Error>` works.

This change should be backwards-compatible because Rust has an impl
`From<E> for Box<dyn Error + Send + Sync + 'a>`. That impl should match
all error types previously accepted by Perseus, and is sufficient to
still accept them after this change.

* Demonstrate `anyhow` support in an example

* Support automatic conversion from `anyhow::Error`

This makes `?` on an `anyhow::Error` work in a function returning
`Result<S, BlamedError<anyhow::Error>>`.
  • Loading branch information
marienz authored Mar 9, 2023
1 parent d7ab697 commit 53ad2ad
Show file tree
Hide file tree
Showing 4 changed files with 15 additions and 11 deletions.
1 change: 1 addition & 0 deletions examples/core/state_generation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ perseus = { path = "../../../packages/perseus", features = [ "hydrate" ] }
sycamore = "^0.8.1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
anyhow = "1"

[dev-dependencies]
fantoccini = "0.17"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,21 @@ pub fn get_template<G: Html>() -> Template<G> {
#[engine_only_fn]
async fn get_build_state(
StateGeneratorInfo { path, .. }: StateGeneratorInfo<()>,
) -> Result<PageState, BlamedError<std::io::Error>> {
) -> Result<PageState, BlamedError<anyhow::Error>> {
// This path is illegal, and can't be rendered
// Because we're using incremental generation, we could get literally anything
// as the `path`
if path == "tests" {
// This tells Perseus to return an error that's the client's fault, with the
// HTTP status code 404 (not found) and the message 'illegal page'. Note that
// this is a `BlamedError<std::io::Error>`, but we could use any error type that
// implements `std::error::Error` (note that this does make `anyhow` a
// bit tricky, if you use it).
// implements `std::error::Error` or can be converted into a boxed `std::error::Error`.
return Err(BlamedError {
// If we used `None` instead, it would default to 400 for the client and 500 for the
// server
blame: ErrorBlame::Client(Some(404)),
// This is just an example, and you could put any error type here, usually your own
error: std::io::Error::new(std::io::ErrorKind::NotFound, "illegal page"),
error: anyhow::anyhow!("illegal page"),
});
}
let title = path.clone();
Expand Down
8 changes: 5 additions & 3 deletions packages/perseus/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,20 +491,22 @@ pub struct BlamedError<E: Send + Sync> {
pub blame: ErrorBlame,
}
#[cfg(engine)]
impl<E: std::error::Error + Send + Sync + 'static> BlamedError<E> {
impl<E: Into<Box<dyn std::error::Error + Send + Sync + 'static>> + Send + Sync> BlamedError<E> {
/// Converts this blamed error into an internal boxed version that is
/// generic over the error type.
pub(crate) fn into_boxed(self) -> GenericBlamedError {
BlamedError {
error: Box::new(self.error),
error: self.error.into(),
blame: self.blame,
}
}
}
// We should be able to convert any error into this easily (e.g. with `?`) with
// the default being to blame the server
#[cfg(engine)]
impl<E: std::error::Error + Send + Sync + 'static> From<E> for BlamedError<E> {
impl<E: Into<Box<dyn std::error::Error + Send + Sync + 'static>> + Send + Sync> From<E>
for BlamedError<E>
{
fn from(error: E) -> Self {
Self {
error,
Expand Down
10 changes: 6 additions & 4 deletions packages/perseus/src/template/fn_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,10 @@ impl<S: Serialize + DeserializeOwned + MakeRx> From<S> for BlamedGeneratorResult
Self::Ok(val)
}
}
impl<S: Serialize + DeserializeOwned + MakeRx, E: std::error::Error + Send + Sync + 'static>
From<Result<S, BlamedError<E>>> for BlamedGeneratorResult<S>
impl<
S: Serialize + DeserializeOwned + MakeRx,
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>> + Send + Sync,
> From<Result<S, BlamedError<E>>> for BlamedGeneratorResult<S>
{
fn from(val: Result<S, BlamedError<E>>) -> Self {
match val {
Expand All @@ -188,8 +190,8 @@ impl From<bool> for BlamedGeneratorResult<bool> {
Self::Ok(val)
}
}
impl<E: std::error::Error + Send + Sync + 'static> From<Result<bool, BlamedError<E>>>
for BlamedGeneratorResult<bool>
impl<E: Into<Box<dyn std::error::Error + Send + Sync + 'static>> + Send + Sync>
From<Result<bool, BlamedError<E>>> for BlamedGeneratorResult<bool>
{
fn from(val: Result<bool, BlamedError<E>>) -> Self {
match val {
Expand Down

0 comments on commit 53ad2ad

Please sign in to comment.