specta-rs / tauri-specta

Completely typesafe Tauri commands
MIT License
368 stars 40 forks source link

Confusion about return types using tauri specta! #90

Closed dan-myles closed 5 months ago

dan-myles commented 5 months ago

Hello!

Just filing an issue because I'm a bit confused about the return types of some tauri commands.. When returning a Result from rust (using anyhow) it gives me this typescript type

image (Server is some type that im using on my frontend, and its specifics don't matter too much)

Why the void? Should it not just return the "Ok" type and throw if it its "Err"?

I understand its something to do with catching the error here... but shouldn't catching it then give me the Ok end?

Brendonovich commented 5 months ago

A few things here:

Should it not just return the "Ok" type and throw if it its "Err"?

Throwing known errors would lose typesafety, hence why we use the result object

dan-myles commented 5 months ago

Okay, that makes a lot more sense. How would one go about implementing specta::Type for a custom error enum...

I'm trying to use specta w/ thiserror but specta does not like it :/

heres my sample error

/// Custom Result that curries errors, and implements Serialize for Tauri
pub(crate) type Result<T> = std::result::Result<T, Error>;

/// Error Type that represents all possible errors that can occur in the application
#[derive(Debug, thiserror::Error, specta::Type)]
pub(crate) enum Error {
    // IO Error
    #[error(transparent)]
    Io(#[from] std::io::Error),

    // HTTP Error
    #[error(transparent)]
    Reqwest(#[from] reqwest::Error),

    // Valve A2S Error
    #[error(transparent)]
    A2S(#[from] a2s::errors::Error),

    // Serde JSON Error
    #[error(transparent)]
    SerdeJson(#[from] serde_json::Error),

    // System Time Error
    #[error(transparent)]
    SystemTime(#[from] std::time::SystemTimeError),
}

impl serde::Serialize for Error {
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: serde::ser::Serializer,
    {
        serializer.serialize_str(self.to_string().as_ref())
    }
}

I guess im just looking for a little guidance on how to use specta w/ returning errors

oscartbeaumont commented 5 months ago

Our error handling is still a bit new but the way it works right now is nothing different to the handling of the ok type. It is not aware of thiserror or any other error handling crate, the type just must be Serialize + Type as Brendan noted.

So enum variant's variant identifiers and enums variants bodies are the only thing that is exported as you've seen.

#[derive(Error, Debug, Serialize, Type)]
#[serde(tag = "type", content = "data")] // You don't need this but I prefer it
pub enum MyError {
    #[error("io error: {0}")]
    IoError(
        #[serde(skip)] // io::Error is not `Serialize` or `Type` so we wanna skip it
        #[from]
        std::io::Error,
    ),
    #[error("some other error: {0}")]
    AnotherError(String),
}
export type MyError = { type: "IoError" } | { type: "AnotherError"; data: string };

What I would probably recommend is:

#[derive(Error, Debug, Serialize, Type)]
#[serde(tag = "type", content = "data")]
pub enum MyError2 {
    #[error("io error: {0}")]
    IoError(String),
}

impl From<std::io::Error> for MyError2 {
    fn from(error: std::io::Error) -> Self {
        Self::IoError(error.to_string())
    }
}

#[tauri::command]
#[specta::specta]
fn my_command() -> Result<(), MyError2> {
    // some_method()?; // This will work because `?` does `From` conversion.

    Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!").into()) // We use `into` here to do the `From` conversion.
}

The downside of this approach is it's a bit less useful if your matching on errors from Rust but you could also have a "frontend" and a "backend" error enum and convert between them using From .

Cross posting from Discord just so I can easily search for it later.

dan-myles commented 5 months ago

https://discord.com/channels/616186924390023171/1237861770106896499 Dropping this link to the channel in case anyone runs into this error and wants to read more conversation about it. If you're not in the Tauri discord click here is an invite!