There are many changes involved with this, but let's just talk about
user-facing changes.
- Creating a `Client` and `Server` now needs a Tokio `Core` event loop
to attach to.
- `Request` and `Response` both no longer implement the
`std::io::{Read,Write}` traits, but instead represent their bodies as a
`futures::Stream` of items, where each item is a `Chunk`.
- The `Client.request` method now takes a `Request`, instead of being
used as a builder, and returns a `Future` that resolves to `Response`.
- The `Handler` trait for servers is no more, and instead the Tokio
`Service` trait is used. This allows interoperability with generic
middleware.
BREAKING CHANGE: A big sweeping set of breaking changes.
190 lines
4.7 KiB
Rust
190 lines
4.7 KiB
Rust
//! The HTTP request method
|
|
use std::fmt;
|
|
use std::str::FromStr;
|
|
use std::convert::AsRef;
|
|
|
|
use error::Error;
|
|
use self::Method::{Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch,
|
|
Extension};
|
|
|
|
|
|
/// The Request Method (VERB)
|
|
///
|
|
/// 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.
|
|
///
|
|
/// It may make sense to grow this to include all variants currently
|
|
/// registered with IANA, if they are at all common to use.
|
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
|
pub enum Method {
|
|
/// OPTIONS
|
|
Options,
|
|
/// GET
|
|
Get,
|
|
/// POST
|
|
Post,
|
|
/// PUT
|
|
Put,
|
|
/// DELETE
|
|
Delete,
|
|
/// HEAD
|
|
Head,
|
|
/// TRACE
|
|
Trace,
|
|
/// CONNECT
|
|
Connect,
|
|
/// PATCH
|
|
Patch,
|
|
/// Method extensions. An example would be `let m = Extension("FOO".to_string())`.
|
|
Extension(String)
|
|
}
|
|
|
|
impl AsRef<str> for Method {
|
|
fn as_ref(&self) -> &str {
|
|
match *self {
|
|
Options => "OPTIONS",
|
|
Get => "GET",
|
|
Post => "POST",
|
|
Put => "PUT",
|
|
Delete => "DELETE",
|
|
Head => "HEAD",
|
|
Trace => "TRACE",
|
|
Connect => "CONNECT",
|
|
Patch => "PATCH",
|
|
Extension(ref s) => s.as_ref()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Method {
|
|
/// Whether a method is considered "safe", meaning the request is
|
|
/// essentially read-only.
|
|
///
|
|
/// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.1)
|
|
/// for more words.
|
|
pub fn safe(&self) -> bool {
|
|
match *self {
|
|
Get | Head | Options | Trace => true,
|
|
_ => false
|
|
}
|
|
}
|
|
|
|
/// Whether a method is considered "idempotent", meaning the request has
|
|
/// the same result if executed multiple times.
|
|
///
|
|
/// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.2) for
|
|
/// more words.
|
|
pub fn idempotent(&self) -> bool {
|
|
if self.safe() {
|
|
true
|
|
} else {
|
|
match *self {
|
|
Put | Delete => true,
|
|
_ => false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for Method {
|
|
type Err = Error;
|
|
fn from_str(s: &str) -> Result<Method, Error> {
|
|
if s == "" {
|
|
Err(Error::Method)
|
|
} else {
|
|
Ok(match s {
|
|
"OPTIONS" => Options,
|
|
"GET" => Get,
|
|
"POST" => Post,
|
|
"PUT" => Put,
|
|
"DELETE" => Delete,
|
|
"HEAD" => Head,
|
|
"TRACE" => Trace,
|
|
"CONNECT" => Connect,
|
|
"PATCH" => Patch,
|
|
_ => Extension(s.to_owned())
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Method {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
fmt.write_str(match *self {
|
|
Options => "OPTIONS",
|
|
Get => "GET",
|
|
Post => "POST",
|
|
Put => "PUT",
|
|
Delete => "DELETE",
|
|
Head => "HEAD",
|
|
Trace => "TRACE",
|
|
Connect => "CONNECT",
|
|
Patch => "PATCH",
|
|
Extension(ref s) => s.as_ref()
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Default for Method {
|
|
fn default() -> Method {
|
|
Method::Get
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::collections::HashMap;
|
|
use std::str::FromStr;
|
|
use error::Error;
|
|
use super::Method;
|
|
use super::Method::{Get, Post, Put, Extension};
|
|
|
|
#[test]
|
|
fn test_safe() {
|
|
assert_eq!(true, Get.safe());
|
|
assert_eq!(false, Post.safe());
|
|
}
|
|
|
|
#[test]
|
|
fn test_idempotent() {
|
|
assert_eq!(true, Get.idempotent());
|
|
assert_eq!(true, Put.idempotent());
|
|
assert_eq!(false, Post.idempotent());
|
|
}
|
|
|
|
#[test]
|
|
fn test_from_str() {
|
|
assert_eq!(Get, FromStr::from_str("GET").unwrap());
|
|
assert_eq!(Extension("MOVE".to_owned()),
|
|
FromStr::from_str("MOVE").unwrap());
|
|
let x: Result<Method, _> = FromStr::from_str("");
|
|
if let Err(Error::Method) = x {
|
|
} else {
|
|
panic!("An empty method is invalid!")
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_fmt() {
|
|
assert_eq!("GET".to_owned(), format!("{}", Get));
|
|
assert_eq!("MOVE".to_owned(),
|
|
format!("{}", Extension("MOVE".to_owned())));
|
|
}
|
|
|
|
#[test]
|
|
fn test_hashable() {
|
|
let mut counter: HashMap<Method,usize> = HashMap::new();
|
|
counter.insert(Get, 1);
|
|
assert_eq!(Some(&1), counter.get(&Get));
|
|
}
|
|
|
|
#[test]
|
|
fn test_as_str() {
|
|
assert_eq!(Get.as_ref(), "GET");
|
|
assert_eq!(Post.as_ref(), "POST");
|
|
assert_eq!(Put.as_ref(), "PUT");
|
|
assert_eq!(Extension("MOVE".to_owned()).as_ref(), "MOVE");
|
|
}
|
|
}
|