zkat / miette

Fancy extension for std::error::Error with pretty, detailed diagnostic printing.
https://docs.rs/miette
Apache License 2.0
1.98k stars 113 forks source link

guidance on best practice #207

Open mimoo opened 2 years ago

mimoo commented 2 years ago

Hello!

I've been using miette successfully in different projects and it really is an amazing library! So thank-you so much for working on that.

I have a question as to what is the best practice when you have different kind of errors that you want to propagate as Diagnostic.

For example in my current situation I have one single error type:

#[derive(Diagnostic, Debug, Error)]
#[error("Looks like something went wrong...")]
pub struct Error {
    #[help]
    pub kind: ErrorKind,

    #[label("here")]
    pub span: Span,
}

with a single span (btw, I'm thinking of using spans: Vec<Span> instead, would that work as is?)

and errors is an enum that directs towards different error types:

#[derive(Error, Diagnostic, Debug, Clone)]
pub enum ErrorKind {
    #[error(transparent)]
    ParserError(ParserError),
    #[error(transparent)]
    LinkerError(LinkerError),
    // ...
}

where I want each error to be displayed differently:

#[derive(Error, Diagnostic, Debug, Clone)]
#[diagnostic(code(parser))]
pub enum ParserError {
    #[error("the method called is not a static method")]
    Thing,
}

I'm using the diagnostic(code(...)) macro for that, but it seems to only work at the top level (when applied on Error itself). Am I doing something wrong? Is my setup weird and not expected?

zkat commented 2 years ago

https://github.com/kdl-org/kdl-rs/blob/main/src/error.rs this is basically what I'm doing.

As far as why the code doesn't work: I'm pretty sure it's because you need to put the code on the variant, for enums. it's a bit late for me to double-check what the macro is doing, though.

mimoo commented 2 years ago

I tried all sorts of combinations (including the variant too) but couldn't make it work :o

thanks for the pointer I'll look at it!

databrecht commented 1 year ago

@mimoo, perhaps we made the same assumption. I assumed that if I would nest enums the 'code' that would be shown would always be the most granular code (at least the most granular one that has diagnostics. However, it seems that this was not the intention since unless I'm doing something wrong, only the top-level error is transformed (if I understood correctly) into diagnostics which means that the code is only available there. Even with a custom error handler, if I'm not mistaken, you can't access the code of errors further in the chain (but I'm far from a Rust expert so I might not understand the code perfectly).

I worked around it by writing a nested match prior to transforming it into a report. That matcher is tedious to write but in the end, I don't think most people will have that many sub types, many variants but not that many types so for my case it was fine.

pub fn gen_report(error: ServiceRuntimeError) -> ControllerOrMiddlewareError {
        let match_err = error.clone();
        match match_err {
            ServiceRuntimeError::Internal(sub_error) => {
                let status_code = StatusCode::INTERNAL_SERVER_ERROR;
                let res = match sub_error {
                    InternalError::Security(e) => {
                        ControllerOrMiddlewareError::gen(e.into(), error.into(), status_code)
                    }
                    ...

I then have two choices,

Combining that with the JSON handler I can now get:

{
  "code": "error::oauth_state_parameter_incorrect",
  "message": "internal error",
  "trace": {
    "message": "internal error",
    "severity": "error",
    "causes": [
      "security error",
      "oauth state parameter was incorrect"
    ],
    "labels": [

    ],
    "related": [

    ]
  }
}

zkat, thanks for providing this library, I misunderstood a few things but it's definitely a gem that brings us closer to beautiful error reporting.

mimoo commented 1 year ago

btw I couldn't make it work myself so I ended up doing this:

#[derive(Diagnostic, Debug, Error)]
#[error("Looks like something went wrong in {label}")]
pub struct Error {
    /// A hint as to where the error happened (e.g. type-checker, parser, lexer, etc.)
    pub label: &'static str,

where the label is used in the error string