Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add an anyhow-like Result type for easier error handling #1228

Merged
merged 8 commits into from
Jun 25, 2023
Merged

Conversation

gbj
Copy link
Collaborator

@gbj gbj commented Jun 23, 2023

The Result<T> type provided by a library like anyhow is extremely useful for converting errors of different types into a single type using the ? operator:

async fn fetch_cats(count: CatCount) -> Result<Vec<String>> {
    if count > 0 {
        // make the request
        let res = reqwasm::http::Request::get(&format!(
            "https://api.thecatapi.com/v1/images/search?limit={count}",
        ))
        .send()
        .await?
        // convert it to JSON
        .json::<Vec<Cat>>()
        .await?
        // extract the URL field for each cat
        .into_iter()
        .take(count)
        .map(|cat| cat.url)
        .collect::<Vec<_>>();
        Ok(res)
    } else {
        Err(CatError::NonZeroCats.into())
    }
}

However, anyhow::Result can't be directly rendered into the view of a Leptos component, because anyhow::Error doesn't implement std::error::Error. (In fact, it can't, because it impl<T> From<T> for anyhow::Error where T: std::errror::Error // etc. would mean that an Error implementation for Error conflicts with the default From<T> for T...)

I initially tried using anyhow::Result internally, but this caused issues because anyhow::Error is not Clone, causing lifetime troubles where trying to simply render something like a Resource<(), Result<T>> into the DOM. (Because this forces Resource::with rather than Resource::read, which means a reference, which means a lifetime that's not 'static...)

However, we can get around this by doing something very similar to what anyhow does: providing a leptos::error::Error type that simply wraps an Arc<dyn Error + Send + Sync>. Now, leptos_dom can render anything that it could render before (any Result<T, E> where E: Error + Send + Sync), because those are all Into<Error>), but it can also do the "convert any kind of error into a single type" trick which makes anyhow::Result so convenient.

@gbj
Copy link
Collaborator Author

gbj commented Jun 24, 2023

This can be extended with some additional implications for server function error handling.

Before PR

  • Rendering Result<T, E> requires E: Error
  • Therefore ServerFnError must implement Error
  • Therefore can't implement From<E: Error> for ServerFnError

After PR

  • Render Result<T, E> requires E: Into<leptos::Error>
  • Therefore ServerFnError doesn't need to implement Error, only Into<leptos::Error>
  • Therefore we can implement From<E: Error> for ServerFnError if we remove the Error implementation from ServerFnError itself
  • We can then implement Into<leptos::Error> for ServerFnError with a newtype that actually implements Error
  • This will allow use of ? inside server functions, while also allowing server function results to be rendered directly in the view

@gbj
Copy link
Collaborator Author

gbj commented Jun 24, 2023

To do: adapt additional server function examples to use the try operator.

@M1cha
Copy link
Contributor

M1cha commented Jun 25, 2023

This looks really nice and will simplify user code a lot 👍
Another feature that would improve things would be a custom serialization/deserialization implementation for the Error that's returned from server functions. If you iterate over the causees and collect the descriptions, then you could actually reproduce the whole error stack on the client side.

Of course, that doesn't give all the full original Display an Debug behavior, but at least you could see the full error stack on the client side.

@gbj gbj mentioned this pull request Jun 25, 2023
14 tasks
@gbj
Copy link
Collaborator Author

gbj commented Jun 25, 2023

Another feature that would improve things would be a custom serialization/deserialization implementation for the Error that's returned from server functions. If you iterate over the causees and collect the descriptions, then you could actually reproduce the whole error stack on the client side.

Of course, that doesn't give all the full original Display an Debug behavior, but at least you could see the full error stack on the client side.

So, this is an interesting idea and would certainly be good for developer experience when debugging, but I worry that it might raise some significant security concerns: remember that all of this information would be serialized to JSON and sent from the server to the browser, meaning a malicious actor could have access to a significant amount of information about how your app is structured.

@gbj gbj merged commit 966100c into main Jun 25, 2023
maccesch pushed a commit to maccesch/leptos that referenced this pull request Jun 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants