hyper: Error type redesign
The Error type in hyper is currently an enum, wrapping up the various cases of things that could go wrong when parsing incoming HTTP data. Some of the problems with the current design:
- Being an
enummeans that adding variants is a breaking change. There is a__Nonexhaustivevariant to try to warn that exhaustive matches can cause breaking changes, but that it’s better when internals are hidden. - Users sometimes try to create
hyper::Errors, even though they really shouldn’t. It is meant only for saying “this thing went wrong while hyper was parsing HTTP”, and nothing else. Part of this can be fixed also by changing the actual error types used, such as in outgoingStreams.
Instead, I’d like to propose making the Error an opaque struct, with some methods to inspect the type and details of an error, somewhat along the lines of std::io::Error.
(Still thinking through the specific code, will add a design section later).
About this issue
- Original URL
- State: open
- Created 7 years ago
- Comments: 29 (14 by maintainers)
I came here via #1652. I think that methods
is_*on anErrorstruct are very unergonomic and a mistake in general.if/else ifchain.is_*methods when the internal enum is extended, leading to holes in the API.is_*methods convey no information about which classes of errors there are (also see previous point). Theis_*methods might be implemented for a small subset, they might overlap, there is no way to know! Contrast this with enums where you know everything is mutually exclusive, and you know (at least for the current version in use) that the set is exhaustive.is_*methods might be too granular or not granular enough for their use cases.If the number of internal errors is large and there is a concern for frequent breaking changes, I am greatly in favour of a two-stage classification. Use one enum for the general error category, and other enums for more granular, specific errors. At the very least the general category should be made public so an API consumer can match on it.
I’ve recently been having a hard time trying to find a way to get to the actual cause of an error that Hyper gives me. For example, when I receive an error that looks like this:
I would like to be able to, somehow, extract the
io::Errorto match againstio::ErrorKind::ConnectionReset, since that’s a specific case I’d like to deal with. I don’t think this is possible with the currentErrortype, or at least, I haven’t been able to figure out how to do it.I was referring to the possibility of a Clippy lint, or compiler warning in future, which would appear after a minor update in which new variants had been added (see the “Unresolved questions” section).
Unless something’s changed since the RFC,
#[non_exhaustive]generates a hard error, not a warning if you try to exhaustively match against of an enum outside of the crate it’s defined in.Regarding just a naming aspect in this design. Chrono has a similar use of the same names for both a variant and nested sub-type. There at least, it can be challenging to get the
useimports right without naming ambiguity. If there are natural names that avoid same-names, that can be friendlier, e.g:A backtrace is neat (but expensive?). I’m not sure I’d want to expose any
enumat all with regards to theErrororErrorKind, because of the inability to grow it. I’d reconsider when and if Rust ever grows some form of private variants (or however is used to prevent exhaustive matches).It’s worth thinking about the things some would do with an
Errorfrom hyper.fmt::Debugandfmt::Display.Responsedepending on the error that occurred? Like, return431when there was aError::TooManyHeaders, perhaps a414if there wereError::TooLarge, and a400with other parse errors.io::Errors, to allow an application to adapt, such as if the error involves too many file descriptors open, or something.Anything else?
I’m thinking of having methods on the type to allow inspecting the type of error it was, since it’s always possible to add more methods. Like,
error.is_uri_too_long()or whatever.