leptos-rs / leptos

Build fast web applications with Rust.
https://leptos.dev
MIT License
16.17k stars 636 forks source link

`NoCustomError` should be replaced by `Infallible` (or another empty `enum`) #3154

Open nicolas-guichard opened 4 hours ago

nicolas-guichard commented 4 hours ago

When matching over a ServerFnError, we always have to cover the WrappedServerError case, even though it should not be used when CustErr = NoCustomError.

This happens because NoCustomError is an empty struct, which can be constructed.

Instead I think we should use an empty enum such as std::convert::Infallible, which would:

This would however be a breaking change.

nicolas-guichard commented 4 hours ago

If/when this is implemented, we could provide an infallible conversion from ServerFnError<NoCustomError> to ServerFnError<CustErr>:

impl<CustErr> From<ServerFnError<NoCustomError>> for ServerFnError<CustErr> {
    fn from(value: ServerFnError<NoCustomError>) -> Self {
        match self {
            // not needed since Rust 1.82 if NoCustomError becomes uninhabited:
            // ServerFnError::<NoCustomError>::WrappedServerError(_) => unreachable!(),
            ServerFnError::<NoCustomError>::Registration(s) => ServerFnError::<CustErr>::Registration(s),
            ServerFnError::<NoCustomError>::Request(s) => ServerFnError::<CustErr>::Request(s),
            ServerFnError::<NoCustomError>::Response(s) => ServerFnError::<CustErr>::Response(s),
            ServerFnError::<NoCustomError>::ServerError(s) => ServerFnError::<CustErr>::ServerError(s),
            ServerFnError::<NoCustomError>::Deserialization(s) => ServerFnError::<CustErr>::Deserialization(s),
            ServerFnError::<NoCustomError>::Serialization(s) => ServerFnError::<CustErr>::Serialization(s),
            ServerFnError::<NoCustomError>::Args(s) => ServerFnError::<CustErr>::Args(s),
            ServerFnError::<NoCustomError>::MissingArg(s) => ServerFnError::<CustErr>::MissingArg(s),
        }
    }
}

Currently to enable the ? operator, I parameterize the functions that return ServerFnError to take the CustError from their caller.

gbj commented 2 hours ago

Could you provide an example of the problem you're trying to solve?

nicolas-guichard commented 2 hours ago

For instance let's say I have a helper function like this:

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

I want to write this:

enum GetUsernameError {
    NotLoggedIn,
}

#[server]
pub async fn get_username() -> Result<Option<User>, ServerFnError<GetUsernameError>> {
    use crate::todo::ssr::auth;

    let auth = auth()?; // ← this currently fails, adding the From impl above solves that, and making NoCustomError uninhabited makes it infallible

    match auth.current_user {
        None => Err(GetUsernameError::NotLoggedIn.into()),
        Some(user) => Ok(user.name),
    }
}

If you don't have anything against those additions, I'll send PRs your way.

gbj commented 2 hours ago

Sure. Can't say I understand this one, but I have never used the custom errors so I am sure I will once I see a PR!