-
Notifications
You must be signed in to change notification settings - Fork 117
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
tokio-boring: Add additional accessors to HandshakeError
#57
base: master
Are you sure you want to change the base?
Conversation
Currently, the `HandshakeError` type in `tokio-boring` provides a `std::error::Error` implementation and an `as_io_error` method that returns an `Option<&std::io::Error>`, as the only ways to access the potentially underlying error. There are a couple of cases where this is somewhat insufficient: * If a user has an owned `HandshakeError`, and needs an owned `io::Error`, they have to create a *new* one, using the `HandshakeError`'s cause and message, like this: ```rust let err: HandshakeError<_> = // ...; if let Some(io_err) = err.as_io_error() { return Err(io::Error::new(io_err.cause(), io_err.to_string())); } // ... ``` This seems like kind of a shame, since it allocates two additional times (for the `String` and then again for the `io::Error` itself), and deallocates the original `io::Error` at the end of the scope. None of this *should* be necessary, since the `HandshakeError` in this case is owned and the original `io::Error` could be returned. * `HandshakeError` only implements `fmt::Debug` and `fmt::Display` when the wrapped I/O stream it's generic over implements `Debug`. This means that bounding the `S` type with `Debug` is necessary for `HandshakeError` to implement the `Error` trait. In some cases, introducing a `Debug` bound on a generic I/O type is kind of a heavyweight requirement. Therefore, it would be nice to have a way to extract a type that *always* implements `Error` from the `HandshakeError`, in cases where the returned I/O stream is just going to be discarded if the handshake failed. This branch introduces new functions on `HandshakeError`: * `HandshakeError::as_ssl_error`, which is analogous to `HandshakeError::as_io_error` but returning an `Option<&boring::error::ErrorStack>` instead of an I/O error. * `HandshakeError::into_io_error` and `HandshakeError::into_ssl_error`, which consume the error and return an `Option<io::Error>` or an `Option<ErrorStack>`, respectively. I noticed that some of the `into_$TYPE` methods on errors in the `boring` crate return `Result`s rather than `Option`s so that the original error can be reused if it couldn't be converted into the requested type. However, this can't easily be done in `HandshakeError`'s `into_io_error` and `into_ssl_error`, because these methods call `MidHandshakeSslStream::into_error`, and (since `MidHandshakeSslStream` can only be constructed by the `boring` crate, not in `tokio-boring`), we can't ever get the `MidHandshakeSslStream` _back_...so we can't return the original `Self` type in this case. Hopefully this should make it much easier to convert `HandshakeError`s into other error types as required in user code! Signed-off-by: Eliza Weisman <[email protected]>
tokio-boring/src/lib.rs
Outdated
|
||
/// Consumes `self` and returns the inner I/O error by value, if this error | ||
/// was caused by an I/O error. | ||
pub fn into_io_error(self) -> Option<io::Error> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I'm not sure that this is valuable for us... If it's not an Io error we lose the original error?
I guess we'd have to use something like if e.as_io_error().is_some() { e.into_io_error.unwrap() {
? that's pretty cumbersome
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about adding an is_io
/is_ssl
, but I wasn't sure if it was necessary given that it would just be as_io_error().is_some()
. I could add that as well, though. IMO it's still less cumbersome than the current code we have, which calls as_io_error
and creates a new io::Error
using values from the one that's passed by reference.
Unfortunately, we can't return Result
s rather than Option
s so that the original error can be reused if it couldn't be converted into an io::Error
, because we have to call MidHandshakeSslStream::into_error
, and (since MidHandshakeSslStream
can only be constructed by the boring
crate, not in tokio-boring
), we can't ever get the MidHandshakeSslStream
back...so we can't return the original Self
type in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@olix0r okay, I've modified these to return Result<io::Error, Self>
and Result<ErrorStack, Self>
so you can now write code like
error
.into_ssl_error()
.map(Into::into)
.or_else(|error| error.into_io_error().map(Into::into))
or similar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome!
Signed-off-by: Eliza Weisman <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the long delay. It makes sense to me that the existing API is insufficient, and I think the type signatures for the new functions are fine. Do be aware though that we're considering larger changes to the error types soon: #20
ssl::HandshakeError::Failure(s) => s.error().ssl_error(), | ||
ssl::HandshakeError::SetupFailure(ref s) => Some(s), | ||
_ => None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this also handle HandshakeError::WouldBlock
? Or is it impossible somehow for WouldBlock to be an SSL error?
ssl::HandshakeError::Failure(s) => s.error().ssl_error(), | |
ssl::HandshakeError::SetupFailure(ref s) => Some(s), | |
_ => None, | |
ssl::HandshakeError::Failure(s) | ssl::HandshakeError::WouldBlock(s) => s.error().ssl_error(), | |
ssl::HandshakeError::SetupFailure(ref s) => Some(s), |
/// that the error value can be reused. | ||
pub fn into_io_error(self) -> Result<io::Error, Self> { | ||
match self.0 { | ||
ssl::HandshakeError::Failure(s) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ssl::HandshakeError::Failure(s) => { | |
ssl::HandshakeError::Failure(s) | ssl::HandshakeError::WouldBlock(s) => { |
Err(Self(ssl::HandshakeError::Failure(s))) | ||
} | ||
} | ||
_ => Err(self), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_ => Err(self), | |
ssl::HandshakeError::SetupFailure(_) => Err(self), |
// TODO(eliza): it's not great that we have to clone in this case, | ||
// but `boring::ssl::Error` doesn't currently expose an | ||
// `into_ssl_error` the way it does for `io::Error`s. We could add | ||
// that in a separate PR, but adding that here would make | ||
// `tokio-boring` depend on a new release of `boring`... | ||
ssl::HandshakeError::Failure(s) => s | ||
.error() | ||
.ssl_error() | ||
.cloned() | ||
.ok_or_else(|| Self(ssl::HandshakeError::Failure(s))), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather add into_ssl_error
for boring too, if you don't mind - you can change them both in the same PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! I was trying to limit the API additions because I wasn't sure what would be preferred by the maintainers, but I'm happy to change that.
Ok(s.into_error().into_io_error().expect( | ||
"if `s.error().io_error().is_some()`, `into_io_error()` must succeed", | ||
)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could avoid the expect()
here by adding a new MidHandshakeSslStream::from_parts
function and using into_parts()
above.
That would be really helpful! I'd love to hear if that new design fixes your issue. |
Btw #142 completely removes |
Currently, the
HandshakeError
type intokio-boring
provides astd::error::Error
implementation and anas_io_error
method thatreturns an
Option<&std::io::Error>
, as the only ways to access thepotentially underlying error. There are a couple of cases where this is
somewhat insufficient:
If a user has an owned
HandshakeError
, and needs an ownedio::Error
, they have to create a new one, using theHandshakeError
's cause and message, like this:This seems like kind of a shame, since it allocates two additional
times (for the
String
and then again for theio::Error
itself),and deallocates the original
io::Error
at the end of the scope. Noneof this should be necessary, since the
HandshakeError
in this caseis owned and the original
io::Error
could be returned.HandshakeError
only implementsfmt::Debug
andfmt::Display
whenthe wrapped I/O stream it's generic over implements
Debug
. Thismeans that bounding the
S
type withDebug
is necessary forHandshakeError
to implement theError
trait. In some cases,introducing a
Debug
bound on a generic I/O type is kind of aheavyweight requirement. Therefore, it would be nice to have a way to
extract a type that always implements
Error
from theHandshakeError
, in cases where the returned I/O stream is just goingto be discarded if the handshake failed.
This branch introduces new functions on
HandshakeError
:HandshakeError::as_ssl_error
, which is analogous toHandshakeError::as_io_error
but returning anOption<&boring::error::ErrorStack>
instead of an I/O error.HandshakeError::into_io_error
andHandshakeError::into_ssl_error
,which consume the error and return an
Option<io::Error>
or anOption<ErrorStack>
, respectively.I noticed that some of the
into_$TYPE
methods on errors in theboring
crate returnResult
s rather thanOption
s so that theoriginal error can be reused if it couldn't be converted into the
requested type. However, this can't easily be done in
HandshakeError
'sinto_io_error
andinto_ssl_error
, because thesemethods call
MidHandshakeSslStream::into_error
, and (sinceMidHandshakeSslStream
can only be constructed by theboring
crate,not in
tokio-boring
), we can't ever get theMidHandshakeSslStream
back...so we can't return the original
Self
type in this case.Hopefully this should make it much easier to convert
HandshakeError
sinto other error types as required in user code!
Signed-off-by: Eliza Weisman [email protected]