http-types icon indicating copy to clipboard operation
http-types copied to clipboard

Consider more rigorous handling of http::StatusCode conversion to StatusCode

Open khodzha opened this issue 4 years ago • 12 comments

Right now conversion from http::StatusCode (which is just an u16 in 100..=600 range) to StatusCode just expect()s the result for example here: https://github.com/http-rs/http-types/blob/539638273de768a24354b65999ad9317b6659204/src/response.rs#L101-L103

this could cause a crash in response conversion for example here: https://github.com/http-rs/http-types/blob/539638273de768a24354b65999ad9317b6659204/src/hyperium_http.rs#L123

if some server responds erroneously with some status that belongs to 100..=600 range but is not implemented in StatusCode enum (for example 229), the conversion From<http::Response<Body>> for Response will panic despite it being From and not TryFrom

khodzha avatar Jun 23 '20 13:06 khodzha

also this is just plain unwrap() which can fail in the same way https://github.com/http-rs/http-types/blob/539638273de768a24354b65999ad9317b6659204/src/hyperium_http.rs#L21-L23

khodzha avatar Jun 23 '20 13:06 khodzha

Related: Request does the same thing but for Url.

https://github.com/http-rs/http-types/blob/539638273de768a24354b65999ad9317b6659204/src/request.rs#L47-L54

I'm not sure what the correct answer is here - do we make ::new() return a Result? Do this in a way that swallows errors?

Fishrock123 avatar Jun 25 '20 19:06 Fishrock123

Related: making Request::new() or Response::new() return a Result makes the bounds complain that the errors are not Send/Sync (I forget which).

Fishrock123 avatar Jun 25 '20 19:06 Fishrock123

and current Error already must include valid StatusCode https://github.com/http-rs/http-types/blob/dae51d7cf462ada802bd21cf4bc8c97dbacd7b97/src/error.rs#L15-L18

khodzha avatar Jun 25 '20 20:06 khodzha

and current Error already must include valid StatusCode

That would just be 500 in the case of a Response.

Fishrock123 avatar Jun 25 '20 21:06 Fishrock123

This is kind of a blocker for anything that tries to download arbitrary resource. Would it make sense to fallback to X00 code in case of error as described in the RFC or perhaps set a fallback code Invalid = 0 value:

  For example, if an unrecognized status code of 471 is received by a
   client, the client can assume that there was something wrong with its
   request and treat the response as if it had received a 400 (Bad
   Request) status code.  

isra17 avatar Jul 16 '20 02:07 isra17

This would look like https://github.com/http-rs/http-types/commit/ae64fd0b84f0ac27d71c37e7bdae2972b8571635 , what do you think?

isra17 avatar Jul 16 '20 03:07 isra17

Hmm, I'm not sure if that is the correct way to go. However, something occurred to me while reading that: we can take better advantage of Rust enums' ability to store different things.

That is we could have something like:

pub enum StatusCode {
    Unknown(u16),
    // ...
}

and in the match:

code => StatusCode::Unknown(code),

I think that would be better than coercing to 400. It would handle any arbitrary value the server could realistically send, let us match on that in specific, and yet still let us inspect the value for logging/debugging.

Fishrock123 avatar Jul 16 '20 16:07 Fishrock123

You cannot mix custom discriminant values (X = 1) with tuple or struct variants (Y(u16)).

isra17 avatar Jul 16 '20 16:07 isra17

Hmm, That's unfortunate. Perhaps StatusCode should look like:

pub enum StatusCode {
    Unknown(u16),
    Continue,
    BadRequest,
    // ... etc
}

With custom overloads for as, etc to make it act like a discriminant enum? It doesn't need to be C-compatible so that seems like an ok thing to do, even if it's a bit more code?

Fishrock123 avatar Jul 16 '20 17:07 Fishrock123

Or to keep it sized as u16, perhaps:

pub struct StatusCode(u16);
#[allow(non_upper_case_globals)]
impl StatusCode {
  pub const Continue: Self = StatusCode(100);
  pub const Ok: Self = StatusCode(200);
  // ...
}

Actually this might be the most efficient way to not lose information and not alias any values?

isra17 avatar Jul 17 '20 12:07 isra17

Has there been any more thought into how this should be solved? Had an issue with this when integrating with an external API, so would love to help solve this bug.

I've done a first pass on implementation of both of the solutions suggested above to get an idea of how they might look in practice:

  • Enum solution: https://github.com/http-rs/http-types/compare/master...mipli:unknown-statuscode-as-enum
  • Struct solution: https://github.com/http-rs/http-types/compare/master...mipli:unkown-statuscode-as-struct

With custom overloads for as, etc to make it act like a discriminant enum?

@Fishrock123 Is it possible to do a custom overload for as? As far as I could find that was not possible, which means that this change would require new major version release, since StatusCode::Ok as u16 will no longer work.

If either of these solutions is something you would like as a solution for this, I can make a proper PR and we can finalize the solution there. If not, please let me know how what I can do to help find a solution for this problem.

mipli avatar Aug 06 '20 16:08 mipli