Skip to content

Commit

Permalink
feat: add an anyhow-like Result type for easier error handling (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
gbj committed Jun 30, 2023
1 parent 38e656d commit 0cb921c
Show file tree
Hide file tree
Showing 13 changed files with 238 additions and 122 deletions.
18 changes: 6 additions & 12 deletions examples/fetch/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use leptos::*;
use leptos::{error::Result, *};
use serde::{Deserialize, Serialize};
use thiserror::Error;

Expand All @@ -8,38 +8,32 @@ pub struct Cat {
}

#[derive(Error, Clone, Debug)]
pub enum FetchError {
pub enum CatError {
#[error("Please request more than zero cats.")]
NonZeroCats,
#[error("Error loading data from serving.")]
Request,
#[error("Error deserializaing cat data from request.")]
Json,
}

type CatCount = usize;

async fn fetch_cats(count: CatCount) -> Result<Vec<String>, FetchError> {
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
.map_err(|_| FetchError::Request)?
.await?
// convert it to JSON
.json::<Vec<Cat>>()
.await
.map_err(|_| FetchError::Json)?
.await?
// extract the URL field for each cat
.into_iter()
.take(count)
.map(|cat| cat.url)
.collect::<Vec<_>>();
Ok(res)
} else {
Err(FetchError::NonZeroCats)
Err(CatError::NonZeroCats.into())
}
}

Expand Down
24 changes: 13 additions & 11 deletions examples/session_auth_axum/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,11 @@ pub async fn login(

let user: User = User::get_from_username(username, &pool)
.await
.ok_or("User does not exist.")
.map_err(|e| ServerFnError::ServerError(e.to_string()))?;
.ok_or_else(|| {
ServerFnError::ServerError("User does not exist.".into())
})?;

match verify(password, &user.password)
.map_err(|e| ServerFnError::ServerError(e.to_string()))?
{
match verify(password, &user.password)? {
true => {
auth.login_user(user.id);
auth.remember_user(remember.is_some());
Expand Down Expand Up @@ -204,13 +203,16 @@ pub async fn signup(
.bind(username.clone())
.bind(password_hashed)
.execute(&pool)
.await
.map_err(|e| ServerFnError::ServerError(e.to_string()))?;
.await?;

let user = User::get_from_username(username, &pool)
.await
.ok_or("Signup failed: User does not exist.")
.map_err(|e| ServerFnError::ServerError(e.to_string()))?;
let user =
User::get_from_username(username, &pool)
.await
.ok_or_else(|| {
ServerFnError::ServerError(
"Signup failed: User does not exist.".into(),
)
})?;

auth.login_user(user.id);
auth.remember_user(remember.is_some());
Expand Down
17 changes: 5 additions & 12 deletions examples/session_auth_axum/src/todo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@ if #[cfg(feature = "ssr")] {

pub fn pool(cx: Scope) -> Result<SqlitePool, ServerFnError> {
use_context::<SqlitePool>(cx)
.ok_or("Pool missing.")
.map_err(|e| ServerFnError::ServerError(e.to_string()))
.ok_or_else(|| ServerFnError::ServerError("Pool missing.".into()))
}

pub fn auth(cx: Scope) -> Result<AuthSession, ServerFnError> {
use_context::<AuthSession>(cx)
.ok_or("Auth session missing.")
.map_err(|e| ServerFnError::ServerError(e.to_string()))
.ok_or_else(|| ServerFnError::ServerError("Auth session missing.".into()))
}

#[derive(sqlx::FromRow, Clone)]
Expand Down Expand Up @@ -64,11 +62,7 @@ pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
let mut rows =
sqlx::query_as::<_, SqlTodo>("SELECT * FROM todos").fetch(&pool);

while let Some(row) = rows
.try_next()
.await
.map_err(|e| ServerFnError::ServerError(e.to_string()))?
{
while let Some(row) = rows.try_next().await? {
todos.push(row);
}

Expand Down Expand Up @@ -117,12 +111,11 @@ pub async fn add_todo(cx: Scope, title: String) -> Result<(), ServerFnError> {
pub async fn delete_todo(cx: Scope, id: u16) -> Result<(), ServerFnError> {
let pool = pool(cx)?;

sqlx::query("DELETE FROM todos WHERE id = $1")
Ok(sqlx::query("DELETE FROM todos WHERE id = $1")
.bind(id)
.execute(&pool)
.await
.map(|_| ())
.map_err(|e| ServerFnError::ServerError(e.to_string()))
.map(|_| ())?)
}

#[component]
Expand Down
13 changes: 4 additions & 9 deletions examples/todo_app_sqlite/src/todo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ cfg_if! {
use sqlx::{Connection, SqliteConnection};

pub async fn db() -> Result<SqliteConnection, ServerFnError> {
SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string()))
Ok(SqliteConnection::connect("sqlite:Todos.db").await?)
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
Expand Down Expand Up @@ -43,11 +43,7 @@ pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
let mut todos = Vec::new();
let mut rows =
sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn);
while let Some(row) = rows
.try_next()
.await
.map_err(|e| ServerFnError::ServerError(e.to_string()))?
{
while let Some(row) = rows.try_next().await? {
todos.push(row);
}

Expand Down Expand Up @@ -76,12 +72,11 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
let mut conn = db().await?;

sqlx::query("DELETE FROM todos WHERE id = $1")
Ok(sqlx::query("DELETE FROM todos WHERE id = $1")
.bind(id)
.execute(&mut conn)
.await
.map(|_| ())
.map_err(|e| ServerFnError::ServerError(e.to_string()))
.map(|_| ())?)
}

#[component]
Expand Down
13 changes: 4 additions & 9 deletions examples/todo_app_sqlite_axum/src/todo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ cfg_if! {
// use http::{header::SET_COOKIE, HeaderMap, HeaderValue, StatusCode};

pub async fn db() -> Result<SqliteConnection, ServerFnError> {
SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string()))
Ok(SqliteConnection::connect("sqlite:Todos.db").await?)
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
Expand Down Expand Up @@ -47,11 +47,7 @@ pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
let mut todos = Vec::new();
let mut rows =
sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn);
while let Some(row) = rows
.try_next()
.await
.map_err(|e| ServerFnError::ServerError(e.to_string()))?
{
while let Some(row) = rows.try_next().await? {
todos.push(row);
}

Expand Down Expand Up @@ -93,12 +89,11 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
let mut conn = db().await?;

sqlx::query("DELETE FROM todos WHERE id = $1")
Ok(sqlx::query("DELETE FROM todos WHERE id = $1")
.bind(id)
.execute(&mut conn)
.await
.map(|_| ())
.map_err(|e| ServerFnError::ServerError(e.to_string()))
.map(|_| ())?)
}

#[component]
Expand Down
13 changes: 4 additions & 9 deletions examples/todo_app_sqlite_viz/src/todo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ cfg_if! {
// use http::{header::SET_COOKIE, HeaderMap, HeaderValue, StatusCode};

pub async fn db() -> Result<SqliteConnection, ServerFnError> {
SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string()))
Ok(SqliteConnection::connect("sqlite:Todos.db").await?)
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
Expand Down Expand Up @@ -47,11 +47,7 @@ pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
let mut todos = Vec::new();
let mut rows =
sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn);
while let Some(row) = rows
.try_next()
.await
.map_err(|e| ServerFnError::ServerError(e.to_string()))?
{
while let Some(row) = rows.try_next().await? {
todos.push(row);
}

Expand Down Expand Up @@ -93,12 +89,11 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
let mut conn = db().await?;

sqlx::query("DELETE FROM todos WHERE id = $1")
Ok(sqlx::query("DELETE FROM todos WHERE id = $1")
.bind(id)
.execute(&mut conn)
.await
.map(|_| ())
.map_err(|e| ServerFnError::ServerError(e.to_string()))
.map(|_| ())?)
}

#[component]
Expand Down
6 changes: 6 additions & 0 deletions leptos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,19 @@ pub use leptos_dom::{
Class, CollectView, Errors, Fragment, HtmlElement, IntoAttribute,
IntoClass, IntoProperty, IntoStyle, IntoView, NodeRef, Property, View,
};

/// Types to make it easier to handle errors in your application.
pub mod error {
pub use server_fn::error::{Error, Result};
}
#[cfg(not(any(target_arch = "wasm32", feature = "template_macro")))]
pub use leptos_macro::view as template;
pub use leptos_macro::{component, server, slot, view, Params};
pub use leptos_reactive::*;
pub use leptos_server::{
self, create_action, create_multi_action, create_server_action,
create_server_multi_action, Action, MultiAction, ServerFn, ServerFnError,
ServerFnErrorErr,
};
pub use server_fn::{self, ServerFn as _};
pub use typed_builder;
Expand Down
1 change: 1 addition & 0 deletions leptos_dom/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ indexmap = "1.9"
itertools = "0.10"
js-sys = "0.3"
leptos_reactive = { workspace = true }
server_fn = { workspace = true }
once_cell = "1"
pad-adapter = "0.1"
paste = "1"
Expand Down
43 changes: 16 additions & 27 deletions leptos_dom/src/components/errors.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::{HydrationCtx, IntoView};
use cfg_if::cfg_if;
use leptos_reactive::{signal_prelude::*, use_context, RwSignal};
use std::{borrow::Cow, collections::HashMap, error::Error, sync::Arc};
use server_fn::error::Error;
use std::{borrow::Cow, collections::HashMap};

/// A struct to hold all the possible errors that could be provided by child Views
#[derive(Debug, Clone, Default)]
#[repr(transparent)]
pub struct Errors(HashMap<ErrorKey, Arc<dyn Error + Send + Sync>>);
pub struct Errors(HashMap<ErrorKey, Error>);

/// A unique key for an error that occurs at a particular location in the user interface.
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
Expand All @@ -24,7 +25,7 @@ where
}

impl IntoIterator for Errors {
type Item = (ErrorKey, Arc<dyn Error + Send + Sync>);
type Item = (ErrorKey, Error);
type IntoIter = IntoIter;

#[inline(always)]
Expand All @@ -35,15 +36,10 @@ impl IntoIterator for Errors {

/// An owning iterator over all the errors contained in the [Errors] struct.
#[repr(transparent)]
pub struct IntoIter(
std::collections::hash_map::IntoIter<
ErrorKey,
Arc<dyn Error + Send + Sync>,
>,
);
pub struct IntoIter(std::collections::hash_map::IntoIter<ErrorKey, Error>);

impl Iterator for IntoIter {
type Item = (ErrorKey, Arc<dyn Error + Send + Sync>);
type Item = (ErrorKey, Error);

#[inline(always)]
fn next(
Expand All @@ -55,16 +51,10 @@ impl Iterator for IntoIter {

/// An iterator over all the errors contained in the [Errors] struct.
#[repr(transparent)]
pub struct Iter<'a>(
std::collections::hash_map::Iter<
'a,
ErrorKey,
Arc<dyn Error + Send + Sync>,
>,
);
pub struct Iter<'a>(std::collections::hash_map::Iter<'a, ErrorKey, Error>);

impl<'a> Iterator for Iter<'a> {
type Item = (&'a ErrorKey, &'a Arc<dyn Error + Send + Sync>);
type Item = (&'a ErrorKey, &'a Error);

#[inline(always)]
fn next(
Expand All @@ -77,7 +67,7 @@ impl<'a> Iterator for Iter<'a> {
impl<T, E> IntoView for Result<T, E>
where
T: IntoView + 'static,
E: Error + Send + Sync + 'static,
E: Into<Error>,
{
fn into_view(self, cx: leptos_reactive::Scope) -> crate::View {
let id = ErrorKey(HydrationCtx::peek().fragment.to_string().into());
Expand All @@ -92,6 +82,7 @@ where
stuff.into_view(cx)
}
Err(error) => {
let error = error.into();
match errors {
Some(errors) => {
errors.update({
Expand Down Expand Up @@ -133,6 +124,7 @@ where
}
}
}

impl Errors {
/// Returns `true` if there are no errors.
#[inline(always)]
Expand All @@ -143,24 +135,21 @@ impl Errors {
/// Add an error to Errors that will be processed by `<ErrorBoundary/>`
pub fn insert<E>(&mut self, key: ErrorKey, error: E)
where
E: Error + Send + Sync + 'static,
E: Into<Error>,
{
self.0.insert(key, Arc::new(error));
self.0.insert(key, error.into());
}

/// Add an error with the default key for errors outside the reactive system
pub fn insert_with_default_key<E>(&mut self, error: E)
where
E: Error + Send + Sync + 'static,
E: Into<Error>,
{
self.0.insert(Default::default(), Arc::new(error));
self.0.insert(Default::default(), error.into());
}

/// Remove an error to Errors that will be processed by `<ErrorBoundary/>`
pub fn remove(
&mut self,
key: &ErrorKey,
) -> Option<Arc<dyn Error + Send + Sync>> {
pub fn remove(&mut self, key: &ErrorKey) -> Option<Error> {
self.0.remove(key)
}

Expand Down
Loading

0 comments on commit 0cb921c

Please sign in to comment.