Comment

Comment by bccdee

I'm not sure I like how they're trying to dynamically cast to an error type.

  Err(report) => {
      // For machines: find and handle the structured error
      if let Some(err) = find_error::<StorageError>(&report) {
          if err.status == ErrorStatus::Temporary {
               return queue_for_retry(report);
          }
          return Err(map_to_http_status(err.kind));
      }
They get it right elsewhere when they describe errors for machines as being "flat and actionable." `StorageError` is that, but the outer `Err(report)` is not. You shouldn't be guessing which types of error you might run into; you should be exhaustively enumerating them.

I'd rather have something like this:

  struct Exn<T> {
      trace: Trace,
      err: T,
  }
  
  impl<T> Exn<T> {
      #[track_caller]
      fn wrap<U: From<T>>(self, msg: String) -> Exn<U> {
          Exn {
              trace: self.trace.add_context(Location::caller(), msg),
              err: self.err.into(),
          }
      }
  }
That way your `err` field is always a structured error, but you still get a context trace. With a bit more tweaking, you can make the trace tree-shaped rather than linear, too, if you want.

I think actionable error types need to be exhaustively matchable, at least for any Rust error that you expect a machine to be handling. Details a human is interested in can be preserved at each layer by the trace, while details the machine cares about will be pruned and reinterpreted at every layer, so the machine-readable info is kept flat, relevant, and matchable.

parent bccdee 10 months ago

Discussion

Live from the official Hacker News API

A modern reader for Hacker News · SvelteKit + Skeleton