Skip to content

Conversation

carllerche
Copy link
Collaborator

This PR adds a dependency on the bytes crate, but does not expose it at all in the public API. It also adds a str::Str type that indicates that the inner Bytes is UTF-8 encoded, but the intension is to not expose this type at all. It will be reused as part of other types.

Continuation of #3 & #5

src/method.rs Outdated
/// let method = Method::extension("FOO");
/// assert_eq!(method, "FOO");
/// ```
pub fn extension(s: &str) -> Method {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, something I've noticed a while ago in hyper is that the Method::Extension(String) constructor allows constructing an invalid method. It's bothered me a little, and maybe this is the time to fix it.

Perhaps instead of easily constructing with any string at all, we'd need to at least check that the string only contains characters in the legal range. According to RFC7230, here's the ABNF:

method = token
token = 1*tchar
tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
    "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, maybe we have two options to get an extension:

  1. A FromStr implementation
  2. Method::from_str_unchecked(s: &str) -> Method

The first option would parse, and if it is a standard method, it would set it to the right enum variant.

The second option will always set it to the extension, and it is up to the caller to ensure that the given string is a valid method and not actually one of the existing standard methods.

@carllerche
Copy link
Collaborator Author

We may decide not to use bytes in this case because the Bytes struct is fairly large. In this case, it may be better to use:

enum Extension {
    Inline { data: [u8; 7], len: u8 },
    Allocated(String),
}

Given this list of methods: https://www.iana.org/assignments/http-methods/http-methods.xhtml, most methods are under 7 chars.

However, this is an internal detail, so I'm not super worried right now.

@carllerche
Copy link
Collaborator Author

Ok, I removed bytes for this case. HTTP methods are going to be usually pretty small, so the zero-copy functionality of bytes is over kill. By not using it, we save some space! woohoo.

Copy link
Contributor

@alexcrichton alexcrichton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might as a .as_str() method as well (shorthand for the .as_ref() impl) to just explicitly have a method for converting to a string.

src/method.rs Outdated
pub struct Method(Inner);

#[derive(Debug)]
pub struct FromBytesError;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make this an opaque struct (with zero size) so we can extend it in the future if need be. Something like:

pub struct FromBytesError { _priv: () }

src/method.rs Outdated
/// method, i.e. one that is defined as a constant in this module.
///
/// The function is unsafe as the input is not checked as valid UTF-8.
pub unsafe fn from_bytes_unchecked(src: &[u8]) -> Method {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naively I'd expect us to not have this and instead of From<&str> for Method and let users do their own str::from_utf8_unchecked perhaps?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly, but see above too. I would rather not have to always do string comparison. That would defeat the point of having an enum at all.

pub fn is_safe(&self) -> bool {
match self.0 {
Get | Head | Options | Trace => true,
_ => false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this may be incorrect in the face of from_bytes_unchecked because we could hav ExtensionInline(b"GET"), right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

src/method.rs Outdated
}

fn cause(&self) -> Option<&Error> {
None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a default method so I think it's safe to omit

/// Currently includes 8 variants representing the 8 methods defined in
/// [RFC 7230](https://tools.ietf.org/html/rfc7231#section-4.1), plus PATCH,
/// and an Extension variant for all extensions.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the derived PartailEq and Hash may also be incorrect in the presence of from_bytes_unchecked (similar to below) as it's possible to have Inner::Get and Inner::ExtensionInline(b"GET")

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that is the point! We want to avoid string comparisons if we don't have to, and from_bytes_unchecked is unsafe. So, if you do it wrong, that is on you :)

@carllerche
Copy link
Collaborator Author

I'm going to remove from_bytes_unchecked for now in favor of debating it later.

@alexcrichton
Copy link
Contributor

I guess as a naive user I would expect:

assert_eq!(Method::from("GET"), GET);

for all variants of Method::from that we have. I understand the performance implications, but it's just way too surprising for this to be a core library and to have such an assertion fail.

@carllerche
Copy link
Collaborator Author

@alexcrichton well, that's why From shouldn't be used for it :)

Anyway, I got rid of the fn, so we can debate it later.

Copy link
Member

@seanmonstar seanmonstar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good to me.


#[derive(Debug)]
pub struct FromBytesError {
_priv: (),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I again wonder if InvalidMethodError is more descriptive..

@carllerche carllerche merged commit edeaf0b into master Mar 18, 2017
@carllerche carllerche deleted the method branch March 18, 2017 19:34
@carllerche carllerche mentioned this pull request Mar 18, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants