pavex/router/allowed_methods.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
use http::HeaderValue;
use smallvec::SmallVec;
pub mod method_allow_list {
use http::Method;
use super::MethodAllowList;
/// The type returned by [`MethodAllowList::into_iter`].
///
/// It lets you iterate over the allowed methods.
pub struct IntoIter {
methods: smallvec::IntoIter<[Method; 5]>,
}
impl Iterator for IntoIter {
type Item = Method;
fn next(&mut self) -> Option<Self::Item> {
self.methods.next()
}
}
impl IntoIterator for MethodAllowList {
type Item = Method;
type IntoIter = IntoIter;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
methods: self.methods.into_iter(),
}
}
}
}
use crate::http::Method;
/// The set of HTTP methods that are allowed for a given path.
///
/// # Example
///
/// ```rust
/// use pavex::router::AllowedMethods;
/// use pavex::response::Response;
/// use pavex::http::header::{ALLOW, HeaderValue};
/// use itertools::Itertools;
///
/// /// A fallback handler that returns a `404 Not Found` if the request path
/// /// doesn't match any of the registered routes, or a `405 Method Not Allowed`
/// /// if the request path matches a registered route but there is no handler
/// /// for its HTTP method.
/// pub async fn fallback(allowed_methods: AllowedMethods) -> Response {
/// if let Some(header_value) = allowed_methods.allow_header_value() {
/// Response::method_not_allowed()
/// .insert_header(ALLOW, header_value)
/// } else {
/// Response::not_found()
/// }
/// }
/// ```
///
/// # Framework primitive
///
/// `AllowedMethods` is a framework primitive—you don't need to register any constructor
/// with [`Blueprint`] to use it in your application.
///
/// # Use cases
///
/// [`AllowedMethods`] comes into the play when implementing [fallback handlers]: it is necessary
/// to set the `Allow` header to the correct value when returning a `405 Method Not Allowed`
/// response after a routing failure.
///
/// [`Blueprint`]: crate::blueprint::Blueprint
/// [fallback handlers]: crate::blueprint::Blueprint::fallback
#[derive(Debug, Clone)]
pub enum AllowedMethods {
/// Only a finite set of HTTP methods are allowed for a given path.
Some(MethodAllowList),
/// All HTTP methods are allowed for a given path, including custom ones.
All,
}
impl AllowedMethods {
/// The value that should be set for the `Allow` header
/// in a `405 Method Not Allowed` response for this route path.
///
/// It returns `None` if all methods are allowed.
/// It returns the comma-separated list of accepted HTTP methods otherwise.
pub fn allow_header_value(&self) -> Option<HeaderValue> {
match self {
AllowedMethods::Some(m) => m.allow_header_value(),
AllowedMethods::All => None,
}
}
}
#[derive(Debug, Clone)]
/// The variant of [`AllowedMethods`] that only allows a finite set of HTTP methods for a given path.
///
/// Check out [`AllowedMethods`] for more information.
pub struct MethodAllowList {
// We use 5 as our inlining limit because that's going to fit
// all methods in the most common case
// (i.e. `GET`/`POST`/`PUT`/`DELETE`/`PATCH` on a certain route path).
methods: SmallVec<[Method; 5]>,
}
impl FromIterator<Method> for MethodAllowList {
/// Create a new instance of [`MethodAllowList`] from an iterator
/// that yields [`Method`]s.
fn from_iter<I: IntoIterator<Item = Method>>(iter: I) -> Self {
Self {
methods: SmallVec::from_iter(iter),
}
}
}
impl MethodAllowList {
/// Iterate over the allowed methods, returned as a reference.
pub fn iter(&self) -> impl Iterator<Item = &Method> {
self.methods.iter()
}
/// Get the number of allowed methods.
pub fn len(&self) -> usize {
self.methods.len()
}
/// Check if there are no allowed methods.
pub fn is_empty(&self) -> bool {
self.methods.is_empty()
}
/// The value that should be set for the `Allow` header
/// in a `405 Method Not Allowed` response for this route path.
///
/// It returns `None` if there are no allowed methods.
/// It returns the comma-separated list of allowed methods otherwise.
pub fn allow_header_value(&self) -> Option<HeaderValue> {
if self.methods.is_empty() {
None
} else {
let allow_header = join(&mut self.methods.iter().map(|method| method.as_str()), ",");
Some(
HeaderValue::from_str(&allow_header)
.expect("Failed to assemble `Allow` header value"),
)
}
}
}
// Inlined from `itertools to avoid adding a dependency.
fn join<'a, I>(iter: &mut I, separator: &str) -> String
where
I: Iterator<Item = &'a str>,
{
use std::fmt::Write;
match iter.next() {
None => String::new(),
Some(first_elt) => {
let mut result = String::with_capacity(separator.len() * iter.size_hint().0);
write!(&mut result, "{}", first_elt).unwrap();
iter.for_each(|element| {
result.push_str(separator);
write!(&mut result, "{}", element).unwrap();
});
result
}
}
}
impl From<MethodAllowList> for AllowedMethods {
fn from(methods: MethodAllowList) -> Self {
Self::Some(methods)
}
}