Skip to content

error-stack support for multiple related errors #727

Closed

Description

@indietyp

I have been checking out error-stack lately, and it seems very promising, great work! For my specific usecase I would want report multiple errors at once, while not natively supported by the std::error::Error trait, crates like miette have the concept of related errors.

From my investigation into the library error-stack doesn't currently support this feature, is this correct? Are there any plans for supporting such a thing in the future? or would you be open for a PR implementing something like this? I think something like this would be a great addition to the library!

What I'd love to have is a DAG like the diagram below:

flowchart BT
  err1{{std::io::Error}}
  err2{{std::io::Error}}
  err3{{crate1::Error}}

  ctx1[crate2::Context]
  ctx2[crate3::Context]
  ctx3[crate4::Context]
  ctx4[crate2::Context2]
  ctx5[crate3::Context2]
 
  err1 --> ctx1
  ctx1 --> ctx2
  err2 --> ctx2
  ctx2 --> ctx3
  err3 --> ctx4
  ctx4 --> ctx5
  ctx5 --> ctx3
Loading

Activity

  1. indietyp commented on Jul 1, 2022

    @indietyp
    MemberAuthor

    For the "source" of the error (with compatibility to std::err::Error) we could either only take the left hand side, or linearize the resulting graph through topological sorting.

  2. self-assigned this
    on Jul 1, 2022
  3. indietyp commented on Jul 1, 2022

    @indietyp
    MemberAuthor

    Just as a reference, the topological sorting using DFS of the graph shown above would result in the following:

    flowchart BT;
       a{{std::io::Error}} --> b{{crate1::Error}}
       b --> c[crate2::Context2]
       c --> d[crate3::Context2]
       d --> e{{std::io::Error}}
       e --> f[crate2::Context]
       f --> g[crate3::Context]
       g --> h[crate4::Context]
    
    Loading
  4. TimDiekmann commented on Jul 1, 2022

    @TimDiekmann
    Member

    Hi @indietyp and thanks for filing this!

    Generally, Pull Request are highly appreciated!

    This is a pretty good scenario and we haven't thought about this yet. However, it's possible to attach any data to a Report, even other reports like:

    let report_a = Report::new(...);
    let report_b = Report::new(...);
    report_a.attach(report_b);

    Later it's then possible to call report_a.request_ref() or even for each frame in report_a.frames() to call frame.request_ref() (Note that requesting currently requires a nightly compiler). I think this is almost the behavior you are looking for?

    However, there is one issue: Report has a generic for the current Context, so you would need to specify which kind of Report you want to get back.
    Do you have a specific solution in mind? Maybe it makes sense to talk about a potential idea before opening a PR.

  5. indietyp commented on Jul 1, 2022

    @indietyp
    MemberAuthor

    My suggestion would be to have a new trait: ChangeContext (or named differently), which is implemented for Report and all tuple sizes, where each element must be a Report, but their context doesn't need to be the same.

    The thing that would need to be changed is that Frame would have multiple sources.

    Traversing the "graph" via .frames() would be more challenging, and I don't have a good way of doing that just yet.
    Displaying the error would also probably involve further indentation on a "cross-road," e.g., where two reports/frames are joined.

    ChangeContext::change_context<T: Context>(self, context: T) -> Report<T>

    if a: Report<C1>, b: Report<C2>, c: C3 then: (a, b).change_context(c) -> Report<C3>

  6. indietyp commented on Jul 1, 2022

    @indietyp
    MemberAuthor

    but to your earlier point, the provider API would kind of make sense, but I would need to know the context type in advance, then traverse through, which is doable, but a lot of pain

  7. indietyp commented on Jul 1, 2022

    @indietyp
    MemberAuthor

    I had a thought when traversing frames, we could, in theory, use BFS or DFS (or the topological sorting), but this would mean that we wouldn't be able to guarantee that every frame before a parent of the previous one.

  8. TimDiekmann commented on Jul 3, 2022

    @TimDiekmann
    Member

    I thought about this for a long time and I think I came up with a possible solution.
    First of all, the ReportImpl abstraction should be removed, so Report refers to Frame directly. This can be done by not storing the backtrace/span trace on Report but as an attachment, if they were captured. I think this moves both to a less "special" spot and make the behavior for the user more intuitive. I think this should be done regardless of related errors.

    Now, a Report is only a Frame anymore. We now have two options (method naming TBD):

    • Add a Report::attach_report method (which can be merged with attach once specialization is a thing):
      Stores the Frames of the attached Report inline of self
    • Add a Report::combine method:
      Stores the Frames of the attached Report next to the current frame, e.g. by making Frame::source a Vec<Frame>

    I'd prefer the second option, especially as it's not special casing Report when attaching but "simply" add a feature. For printing, I have a few thoughts. The structure is now a tree (but we keep pushing onto the root of it), Backtrace and SpanTrace as attachments instead could print the number of frames: Printing could happen like this:

    Current context
    | attachment
    | attachment
    |\
    | Another context
    | | attachment
    | |
    | Other context
    | | attachment
    | | backtrace with 15 frames
    |
    Other context
    | attachment
    | backtrace with 12 frames
    | span trace with 3 frames
    

    What do you think about that?

  9. locked and limited conversation to collaborators on Jul 3, 2022
  10. converted this issue into a discussion #737 on Jul 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions