use std::{collections::HashMap, sync::Arc};
use http::Method;
use crate::LayerId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct RouteIndex(usize);
impl RouteIndex {
const NONE: Self = Self(usize::MAX);
const fn new(index: usize) -> Self {
debug_assert!(index != usize::MAX, "route index cannot be usize::MAX");
Self(index)
}
const fn get(self) -> Option<usize> {
match self.0 {
usize::MAX => None,
index => Some(index),
}
}
const fn is_none(self) -> bool {
self.0 == usize::MAX
}
}
impl Default for RouteIndex {
fn default() -> Self {
Self::NONE
}
}
const STANDARD_METHODS: [Method; 9] = [
Method::GET,
Method::POST,
Method::PUT,
Method::DELETE,
Method::PATCH,
Method::HEAD,
Method::OPTIONS,
Method::TRACE,
Method::CONNECT,
];
const GET: usize = 0;
const HEAD: usize = 5;
fn standard_slot(method: &Method) -> Option<usize> {
match method.as_str() {
"GET" => Some(GET),
"POST" => Some(1),
"PUT" => Some(2),
"DELETE" => Some(3),
"PATCH" => Some(4),
"HEAD" => Some(HEAD),
"OPTIONS" => Some(6),
"TRACE" => Some(7),
"CONNECT" => Some(8),
_ => None,
}
}
#[derive(Debug, Default)]
pub(crate) struct Endpoint {
standard: [RouteIndex; STANDARD_METHODS.len()],
other: HashMap<Method, usize>,
path_params: Box<[Arc<str>]>,
layers: Box<[LayerId]>,
}
impl Endpoint {
pub(crate) fn new(path_params: Box<[Arc<str>]>, layers: Box<[LayerId]>) -> Self {
Self {
standard: Default::default(),
other: HashMap::new(),
path_params,
layers,
}
}
pub(crate) fn get(&self, method: &Method) -> Option<usize> {
match standard_slot(method) {
Some(slot) => self.standard[slot].get(),
None => self.other.get(method).copied(),
}
}
pub(crate) fn insert(&mut self, method: Method, index: usize) {
match standard_slot(&method) {
Some(slot) => self.standard[slot] = RouteIndex::new(index),
None => {
self.other.insert(method, index);
}
}
}
pub(crate) fn alias_head_to_get(&mut self) {
if self.standard[HEAD].is_none() {
self.standard[HEAD] = self.standard[GET];
}
}
pub(crate) fn methods(&self) -> impl Iterator<Item = &Method> {
STANDARD_METHODS
.iter()
.enumerate()
.filter(|(slot, _)| !self.standard[*slot].is_none())
.map(|(_, method)| method)
.chain(self.other.keys())
}
pub(crate) fn path_params(&self) -> &[Arc<str>] {
&self.path_params
}
pub(crate) fn layers(&self) -> &[LayerId] {
&self.layers
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn route_index_wraps_and_unwraps() {
let index = RouteIndex::new(7);
assert_eq!(index.get(), Some(7));
assert!(!index.is_none());
}
#[test]
fn route_index_zero_is_a_real_index() {
let index = RouteIndex::new(0);
assert_eq!(index.get(), Some(0));
assert!(!index.is_none());
}
#[test]
fn route_index_none_is_absent() {
assert_eq!(RouteIndex::NONE.get(), None);
assert!(RouteIndex::NONE.is_none());
assert_eq!(RouteIndex::default(), RouteIndex::NONE);
}
#[test]
fn empty_endpoint_has_no_routes() {
let endpoint = Endpoint::default();
assert_eq!(endpoint.get(&Method::GET), None);
assert_eq!(endpoint.get(&Method::POST), None);
assert_eq!(endpoint.methods().count(), 0);
}
#[test]
fn inserts_and_reads_back_standard_methods() {
let mut endpoint = Endpoint::default();
endpoint.insert(Method::GET, 0);
endpoint.insert(Method::POST, 1);
endpoint.insert(Method::DELETE, 2);
assert_eq!(endpoint.get(&Method::GET), Some(0));
assert_eq!(endpoint.get(&Method::POST), Some(1));
assert_eq!(endpoint.get(&Method::DELETE), Some(2));
assert_eq!(endpoint.get(&Method::PUT), None);
}
#[test]
fn insert_overwrites_the_same_method() {
let mut endpoint = Endpoint::default();
endpoint.insert(Method::GET, 0);
endpoint.insert(Method::GET, 5);
assert_eq!(endpoint.get(&Method::GET), Some(5));
}
#[test]
fn inserts_and_reads_back_extension_methods() {
let purge = Method::from_bytes(b"PURGE").unwrap();
let mut endpoint = Endpoint::default();
endpoint.insert(purge.clone(), 3);
assert_eq!(endpoint.get(&purge), Some(3));
assert_eq!(endpoint.get(&Method::GET), None);
}
#[test]
fn alias_points_head_at_get() {
let mut endpoint = Endpoint::default();
endpoint.insert(Method::GET, 4);
endpoint.alias_head_to_get();
assert_eq!(endpoint.get(&Method::HEAD), Some(4));
}
#[test]
fn alias_does_not_override_explicit_head() {
let mut endpoint = Endpoint::default();
endpoint.insert(Method::GET, 4);
endpoint.insert(Method::HEAD, 9);
endpoint.alias_head_to_get();
assert_eq!(endpoint.get(&Method::HEAD), Some(9));
}
#[test]
fn alias_without_get_leaves_head_absent() {
let mut endpoint = Endpoint::default();
endpoint.alias_head_to_get();
assert_eq!(endpoint.get(&Method::HEAD), None);
}
#[test]
fn methods_lists_standard_then_extension() {
let purge = Method::from_bytes(b"PURGE").unwrap();
let mut endpoint = Endpoint::default();
endpoint.insert(Method::POST, 1);
endpoint.insert(Method::GET, 0);
endpoint.insert(purge.clone(), 2);
let methods: Vec<&Method> = endpoint.methods().collect();
assert_eq!(methods, vec![&Method::GET, &Method::POST, &purge]);
}
}