Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relationship between Diagnostic and StdError #164

Closed
TheNeikos opened this issue Apr 27, 2022 · 7 comments
Closed

Relationship between Diagnostic and StdError #164

TheNeikos opened this issue Apr 27, 2022 · 7 comments

Comments

@TheNeikos
Copy link
Contributor

I would like to understand where miette currently tries to position itself in the 'Rust error story'. To do this, I'm opening this issue to see what I might have missed and/or not understood correctly.


Miette exposes a Diagnostic trait that extends on top of the Error trait with additional information so as to report better looking and more informative errors.

To do so, it currently imposes the following relationship Diagnostic: StdError (StdError is a supertrait of Diagnostic). This means that any Diagnostic is also an StdError.
StdError has multiple advantages that speak for it:

  • It requires a 'Display' implementation, forcing developers to give a textual representation of the error that is fit for 'end-user consumption'.
  • It has a .source/cause method that gives access to the error that 'cause'd it

Diagnostic builds on top of this with the ability to specify spans with labels, severity, etc... .
This gives it several nice things that seperate it from StdError:

  • Users can be more precisely informed with visual language on where errors occured
  • Colors (through severity) give them a color indicator on how 'bad' or useful the information is
  • Through the .source method of StdError on can get access to the underlying errors and print those too, potentially going as far as to the root error that caused it.

This means that for a given Diagnostic and finally Report one can have the following relationship:


             ┌────────┐
     ┌───────┤ Report ├────┐
     │       └────────┘    │
     │                     │
     │                     │
┌────▼─────┐          ┌────▼─────┐
│Diagnostic│          │Diagnostic│
└────┬─────┘          └─────┬────┘
     │                      │
     │                      │
     │                      │
 ┌───▼────┐            ┌────▼───┐
 │StdError│            │StdError│
 └────────┘            └────┬───┘
                            │
                            │
                            │
                       ┌────▼───┐
                       │StdError│
                       └────────┘

Important to note is that while the Top-level Diagnostic/Report has multiple 'Diagnostics' below it, they are currently only 'related'. (For example multiple logically distinct errors in a configuration file).

It is also not possible to 'chain' Diagnostics, as there is no .source equivalent for Diagnostic. This means that either a single diagnostic is created at the root (potentially wrapping a StdError) and then transported all the way to the base or that extra information is added later on. (Through for example the with_source_code transforms). An error, once made into a Diagnostic cannot be re-integrated into the chain of errors except as a logically distinct 'related' issue.


To me, this disconnect feels weird (and is mostly due to Rust constraints from its current state). I was expecting that I could effectively represent a 'tree' of Diagnostics with the following relationship:

                         ┌────────┐
                 ┌───────┤ Report ├────┐
                 │       └────────┘    │
                 │                     │
                 │                     │
            ┌────▼─────┐          ┌────▼─────┐
            │Diagnostic│          │Diagnostic├────────┐
            └────┬─────┘          └────┬─────┘        │
                 │                     │              │
                 │                     │              │
                 │                     │              │
┌────────┐  ┌────▼─────┐          ┌────▼─────┐   ┌────▼─────┐   ┌────────┐
│StdError├──►Diagnostic│          │Diagnostic│   │Diagnostic◄───┤StdError│
└────────┘  └──────────┘          └────┬─────┘   └──────────┘   └────────┘
                                       │
                                       │
                                       │
                                  ┌────▼─────┐
                                  │Diagnostic│
                                  └──────────┘

Where any StdError could be converted to a Diagnostic to be used as part of the tree of errors an application may expose but that Diagnostic does not require the supertrait Error anymore.

This brings its own problems with it though, as now libraries that expose a Diagnostic has no direct compatibility to std::error::Error.

From what I understand, this is currently heavily geared towards maximum compatibility with the StdError ecosystem but at the same time Report does not implement StdError anyway.

This leaves me wondering, why does Diagnostic require Error as a supertrait? If one wants to keep compatibility with StdError then one can just implement it (with thiserror or manually). If Diagnostic simply has a Display bound, then they are even compatible together (since thiserror implements Display for you too).


NB: English is not my first language, and I hope I didn't miss my tone here: I am trying to understand how Miette came to be and where it would like to be in the future?


This is maybe related to #139?
I am using StdError to disambiguate on "Error"

@zkat
Copy link
Owner

zkat commented Apr 27, 2022

tl;dr: miette, as it is, is mostly a set of reporters for errors + a macro to make it easy to define extra things. In the long run, most of what you're talking about will be made moot, and there will be no Diagnostic trait. Instead, the Diagnostic macro will use the Provider API to enhance regular std::error::Errors with the extra stuff, and miette::Report will also go away entirely, in favor of color-eyre and such.

@TheNeikos
Copy link
Contributor Author

Ah great! So you'd expose some miette specific 'extension points' where users then store the data in their type that implement std::error::Error and if it exists, it prints it, if not then it skips it?

@zkat
Copy link
Owner

zkat commented Apr 27, 2022

yep.

But that's quite a ways off. So miette::Diagnostic is the best compromise I could come up with for the time being.

@TheNeikos
Copy link
Contributor Author

Since this is nothing that can be solved now, I'll close the issue.

@Porges
Copy link
Contributor

Porges commented Aug 9, 2024

@zkat tl;dr: miette, as it is, is mostly a set of reporters for errors + a macro to make it easy to define extra things. In the long run, most of what you're talking about will be made moot, and there will be no Diagnostic trait. Instead, the Diagnostic macro will use the Provider API to enhance regular std::error::Errors with the extra stuff, and miette::Report will also go away entirely, in favor of color-eyre and such.

Do you know if the current state of the Provider API (I think error-stack uses it now?) sufficient to achieve this now, or are there more capabilities needed?

@zkat
Copy link
Owner

zkat commented Aug 9, 2024

I believe the Provider API was basically abandoned, unfortunately

@Porges
Copy link
Contributor

Porges commented Aug 9, 2024

I believe the Provider API was basically abandoned, unfortunately

Ah, I see that when error-stack say they "use the Provider API" they mean they have their own Report type which implements it, not std::error::Error.

std::error::Error did get the provide/request parts but they're nightly-only.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants