Skip to content

Result handling #73

@lucasavila00

Description

@lucasavila00

I would like if qubit handled Results for me.

I'm using a database for the queries and every operation returns a Result.

I would like if qubit:

  • Did not send the Err contents to the client-side
  • Had a compile time setting that forced every handler to return a QubitResult
  • Had an option on the client to throw on Errs, generates types for the successful Ok only

For now I'm using a custom result type:

use std::error::Error;
use std::sync::Arc;

use serde::Serialize;
use ts_rs::TS;
#[derive(Debug, Clone)]
pub enum RpcErrorMessage {
    Anyhow(Arc<anyhow::Error>),
    Public(String),
}

impl Serialize for RpcErrorMessage {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let msg = match self {
            RpcErrorMessage::Anyhow(e) => {
                dbg!(e);
                "Internal Server Error"
            }
            RpcErrorMessage::Public(s) => s,
        };
        serializer.serialize_str(msg)
    }
}

#[derive(Debug, Clone, Serialize, TS)]
pub struct RpcError {
    #[ts(as = "String")]
    message: RpcErrorMessage,
}

fn app_anyhow<T: Error + Send + Sync + 'static>(it: T) -> RpcError {
    RpcError {
        message: RpcErrorMessage::Anyhow(Arc::new(anyhow::Error::new(it))),
    }
}

impl<T: Error + Send + Sync + 'static> From<T> for RpcError {
    fn from(value: T) -> Self {
        app_anyhow(value)
    }
}
pub fn public_err<T>(msg: &str) -> Result<T, RpcError> {
    Err(RpcError {
        message: RpcErrorMessage::Public(msg.to_string()),
    })
}

pub type RpcResult<T> = Result<T, RpcError>;

Usage:

#[handler(query)]
async fn read_posts(ctx: AuthCtx) -> RpcResult<Vec<Post>> {
    use crate::schema::posts::dsl::*;
    let results = posts
        .limit(5)
        .select(Post::as_select())
        .load(&mut ctx.pg_pool.get()?)?;

    Ok(results)
}

And an unwrap function in TS

type AnyResult = { Ok: any } | { Err: { message: string } };

type ExtractOk<T extends AnyResult> = T extends { Ok: infer U } ? U : never;

export const Unwrap = <T extends AnyResult>(x: T): ExtractOk<T> => {
    if ("Ok" in x) {
        return x.Ok;
    }
    if ("Err" in x) {
        if ("message" in x.Err) {
            throw new Error(x.Err.message);
        }
    }
    console.log(x);
    throw new Error("Unwrap failed: " + String(x));
};

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions