use crate::error::{Error, Result};
use crate::response::Response;
use crate::state::StateMap;
use bytes::Bytes;
use http::{HeaderMap, Method, StatusCode, Uri};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug)]
pub struct Call {
method: Method,
uri: Uri,
headers: HeaderMap,
params: HashMap<String, String>,
body: Bytes,
state: Arc<StateMap>,
extensions: http::Extensions,
}
impl Call {
pub fn new(method: Method, uri: Uri, headers: HeaderMap, body: Bytes) -> Self {
Self {
method,
uri,
headers,
params: HashMap::new(),
body,
state: Arc::new(StateMap::default()),
extensions: http::Extensions::new(),
}
}
pub fn method(&self) -> &Method {
&self.method
}
pub fn uri(&self) -> &Uri {
&self.uri
}
pub fn path(&self) -> &str {
self.uri.path()
}
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
pub fn header(&self, name: &str) -> Option<&str> {
self.headers.get(name).and_then(|v| v.to_str().ok())
}
pub fn query_string(&self) -> &str {
self.uri.query().unwrap_or("")
}
pub fn query(&self, key: &str) -> Option<String> {
form_urlencoded_first(self.query_string(), key)
}
pub(crate) fn set_params(&mut self, params: HashMap<String, String>) {
self.params = params;
}
pub(crate) fn set_state(&mut self, state: Arc<StateMap>) {
self.state = state;
}
pub(crate) fn seed_extensions(&mut self, ext: http::Extensions) {
self.extensions.extend(ext);
}
pub fn state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
self.state.get::<T>()
}
pub fn params_iter(&self) -> impl Iterator<Item = (&str, &str)> {
self.params.iter().map(|(k, v)| (k.as_str(), v.as_str()))
}
pub fn param_raw(&self, name: &str) -> Option<&str> {
self.params.get(name).map(|s| s.as_str())
}
pub fn param<T>(&self, name: &str) -> Result<T>
where
T: std::str::FromStr,
T::Err: std::fmt::Display,
{
let raw = self
.param_raw(name)
.ok_or_else(|| Error::bad_request(format!("missing path param `{name}`")))?;
raw.parse::<T>()
.map_err(|e| Error::bad_request(format!("bad path param `{name}`: {e}")))
}
pub async fn receive_bytes(&mut self) -> Bytes {
std::mem::take(&mut self.body)
}
pub async fn receive_text(&mut self) -> Result<String> {
let bytes = self.receive_bytes().await;
String::from_utf8(bytes.to_vec())
.map_err(|e| Error::bad_request(format!("body is not valid UTF-8: {e}")))
}
pub fn insert<T: Clone + Send + Sync + 'static>(&mut self, value: T) {
self.extensions.insert(value);
}
pub fn get<T: Clone + Send + Sync + 'static>(&self) -> Option<T> {
self.extensions.get::<T>().cloned()
}
pub fn respond_text(&self, body: impl Into<String>) -> Response {
Response::text(body)
}
pub fn respond_status(&self, status: StatusCode) -> Response {
Response::new(status)
}
}
fn form_urlencoded_first(query: &str, key: &str) -> Option<String> {
for pair in query.split('&') {
let mut it = pair.splitn(2, '=');
let k = it.next().unwrap_or("");
if k == key {
let v = it.next().unwrap_or("");
return Some(percent_decode(v));
}
}
None
}
fn percent_decode(s: &str) -> String {
let bytes = s.as_bytes();
let mut out = Vec::with_capacity(bytes.len());
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'+' => {
out.push(b' ');
i += 1;
}
b'%' if i + 2 < bytes.len() => {
let hi = (bytes[i + 1] as char).to_digit(16);
let lo = (bytes[i + 2] as char).to_digit(16);
match (hi, lo) {
(Some(h), Some(l)) => {
out.push((h * 16 + l) as u8);
i += 3;
}
_ => {
out.push(bytes[i]);
i += 1;
}
}
}
b => {
out.push(b);
i += 1;
}
}
}
String::from_utf8_lossy(&out).into_owned()
}
#[cfg(test)]
mod tests {
use super::*;
fn call(path: &str, body: &str) -> Call {
Call::new(
Method::GET,
path.parse::<Uri>().unwrap(),
HeaderMap::new(),
Bytes::from(body.to_string()),
)
}
#[test]
fn reads_method_and_path() {
let c = call("/a/b?x=1", "");
assert_eq!(c.method(), &Method::GET);
assert_eq!(c.path(), "/a/b");
}
#[test]
fn parses_query() {
let c = call("/s?q=hello+world&n=5", "");
assert_eq!(c.query("q").as_deref(), Some("hello world"));
assert_eq!(c.query("n").as_deref(), Some("5"));
assert_eq!(c.query("missing"), None);
}
#[test]
fn parses_query_percent_encoded() {
let c = call("/s?q=hello%20world", "");
assert_eq!(c.query("q").as_deref(), Some("hello world"));
}
#[test]
fn parses_path_param() {
let mut c = call("/users/42", "");
let mut p = HashMap::new();
p.insert("id".to_string(), "42".to_string());
c.set_params(p);
let id: u64 = c.param("id").unwrap();
assert_eq!(id, 42);
assert!(c.param::<u64>("missing").is_err());
}
#[tokio::test]
async fn receives_text_body() {
let mut c = call("/", "payload");
assert_eq!(c.receive_text().await.unwrap(), "payload");
}
#[test]
fn state_round_trips() {
use crate::state::StateMap;
let mut c = call("/", "");
let mut sm = StateMap::default();
sm.insert(99u32);
c.set_state(std::sync::Arc::new(sm));
assert_eq!(*c.state::<u32>().unwrap(), 99);
}
#[test]
fn extensions_round_trip() {
#[derive(Clone, PartialEq, Debug)]
struct User(u32);
let mut c = call("/", "");
assert!(c.get::<User>().is_none());
c.insert(User(7));
assert_eq!(c.get::<User>(), Some(User(7)));
}
}