use std::{
convert::TryInto,
fmt::{self, Debug, Formatter},
net::IpAddr,
};
use trillium_http::{
transport::{BoxedTransport, Transport},
Body, HeaderName, HeaderValues, Headers, Method, ReceivedBody, StateSet, Status,
};
/**
# A Trillium HTTP connection.
A Conn represents both the request and response of a http connection,
as well as any application state that is associated with that
connection.
## `with_{attribute}` naming convention
A convention that is used throughout trillium is that any interface
that is named `with_{attribute}` will take ownership of the conn, set
the attribute and return the conn, enabling chained calls like:
```
struct MyState(&'static str);
async fn handler(mut conn: trillium::Conn) -> trillium::Conn {
conn.with_header("content-type", "text/plain")
.with_state(MyState("hello"))
.with_body("hey there")
.with_status(418)
}
use trillium_testing::prelude::*;
assert_response!(
get("/").on(&handler),
Status::ImATeapot,
"hey there",
"content-type" => "text/plain"
);
```
If you need to set a property on the conn without moving it,
`set_{attribute}` associated functions will be your huckleberry, as is
conventional in other rust projects.
## State
Every trillium Conn contains a state type which is a set that contains
at most one element for each type. State is the primary way that
handlers attach data to a conn as it passes through a tuple
handler. State access should generally be implemented by libraries
using a private type and exposed with a `ConnExt` trait. See [library
patterns](https://trillium.rs/library_patterns.html#state) for more
elaboration and examples.
## In relation to [`trillium_http::Conn`]
`trillium::Conn` is currently implemented as an abstraction on top of a
[`trillium_http::Conn`]. In particular, `trillium::Conn` boxes the
transport using a [`BoxedTransport`](trillium_http::transport::BoxedTransport)
so that application code can be written without transport
generics. See [`Transport`](trillium_http::transport::Transport) for further
reading on this.
*/
pub struct Conn {
inner: trillium_http::Conn<BoxedTransport>,
halted: bool,
path: Vec<String>,
}
impl Debug for Conn {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Conn")
.field("inner", &self.inner)
.field("halted", &self.halted)
.field("path", &self.path)
.finish()
}
}
impl<T: Transport + 'static> From<trillium_http::Conn<T>> for Conn {
fn from(inner: trillium_http::Conn<T>) -> Self {
Self {
inner: inner.map_transport(BoxedTransport::new),
halted: false,
path: vec![],
}
}
}
impl Conn {
/**
`Conn::ok` is a convenience function for the common pattern of
setting a body and a 200 status in one call. It is exactly
identical to `conn.with_status(200).with_body(body).halt()`
```
use trillium::Conn;
use trillium_testing::prelude::*;
let mut conn = get("/").on(&|conn: Conn| async move { conn.ok("hello") });
assert_body!(&mut conn, "hello");
assert_status!(&conn, 200);
assert!(conn.is_halted());
```
*/
#[must_use]
pub fn ok(self, body: impl Into<Body>) -> Self {
self.with_status(200).with_body(body).halt()
}
/**
returns the response status for this `Conn`, if it has been set.
```
use trillium_testing::prelude::*;
let mut conn = get("/").on(&());
assert!(conn.status().is_none());
conn.set_status(200);
assert_eq!(conn.status().unwrap(), Status::Ok);
```
*/
pub fn status(&self) -> Option<Status> {
self.inner.status()
}
/// assigns a status to this response. see [`Conn::status`] for example usage
pub fn set_status(&mut self, status: impl TryInto<Status>) {
self.inner.set_status(status);
}
/**
sets the response status for this `Conn` and returns it. note that
this does not set the halted status.
```
use trillium_testing::prelude::*;
let conn = get("/").on(&|conn: Conn| async move {
conn.with_status(418)
});
let status = conn.status().unwrap();
assert_eq!(status, Status::ImATeapot);
assert_eq!(status, 418);
assert!(!conn.is_halted());
```
*/
#[must_use]
pub fn with_status(mut self, status: impl TryInto<Status>) -> Self {
self.set_status(status);
self
}
/**
Sets the response body from any `impl Into<Body>` and returns the
`Conn` for fluent chaining. Note that this does not set the response
status or halted. See [`Conn::ok`] for a function that does both
of those.
```
use trillium_testing::prelude::*;
let conn = get("/").on(&|conn: Conn| async move {
conn.with_body("hello")
});
assert_eq!(conn.response_len(), Some(5));
```
*/
#[must_use]
pub fn with_body(mut self, body: impl Into<Body>) -> Self {
self.set_body(body);
self
}
/**
Sets the response body from any `impl Into<Body>`. Note that this does not set the response
status or halted.
```
use trillium_testing::prelude::*;
let mut conn = get("/").on(&());
conn.set_body("hello");
assert_eq!(conn.response_len(), Some(5));
```
*/
pub fn set_body(&mut self, body: impl Into<Body>) {
self.inner.set_response_body(body);
}
/**
Removes the response body from the `Conn`
```
use trillium_testing::prelude::*;
let mut conn = get("/").on(&());
conn.set_body("hello");
let mut body = conn.take_response_body().unwrap();
assert_eq!(body.len(), Some(5));
assert_eq!(conn.response_len(), None);
```
*/
pub fn take_response_body(&mut self) -> Option<Body> {
self.inner.take_response_body()
}
/**
Attempts to retrieve a &T from the state set
```
use trillium_testing::prelude::*;
struct Hello;
let mut conn = get("/").on(&());
assert!(conn.state::<Hello>().is_none());
conn.set_state(Hello);
assert!(conn.state::<Hello>().is_some());
```
*/
pub fn state<T: 'static>(&self) -> Option<&T> {
self.inner.state().get()
}
/// Attempts to retrieve a &mut T from the state set
pub fn state_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.inner.state_mut().get_mut()
}
/// Puts a new type into the state set. see [`Conn::state`]
/// for an example. returns the previous instance of this type, if
/// any
pub fn set_state<T: Send + Sync + 'static>(&mut self, val: T) -> Option<T> {
self.inner.state_mut().insert(val)
}
/// Puts a new type into the state set and returns the
/// `Conn`. this is useful for fluent chaining
#[must_use]
pub fn with_state<T: Send + Sync + 'static>(mut self, val: T) -> Self {
self.set_state(val);
self
}
/// Removes a type from the state set and returns it, if present
pub fn take_state<T: Send + Sync + 'static>(&mut self) -> Option<T> {
self.inner.state_mut().take()
}
/**
Either returns the current &mut T from the state set, or
inserts a new one with the provided default function and
returns a mutable reference to it
*/
pub fn mut_state_or_insert_with<T, F>(&mut self, default: F) -> &mut T
where
T: Send + Sync + 'static,
F: FnOnce() -> T,
{
self.inner.state_mut().get_or_insert_with(default)
}
/**
Returns a [ReceivedBody] that references this `Conn`. The `Conn`
retains all data and holds the singular transport, but the
[`ReceivedBody`] provides an interface to read body content.
See also: [`Conn::request_body_string`] for a convenience function
when the content is expected to be utf8.
# Examples
```
use trillium_testing::prelude::*;
let mut conn = get("/").with_request_body("request body").on(&());
# trillium_testing::block_on(async {
let request_body = conn.request_body().await;
assert_eq!(request_body.content_length(), Some(12));
assert_eq!(request_body.read_string().await.unwrap(), "request body");
# });
```
*/
pub async fn request_body(&mut self) -> ReceivedBody<'_, BoxedTransport> {
self.inner.request_body().await
}
/**
Convenience function to read the content of a request body as a `String`.
# Errors
This will return an error variant if either there is an IO failure
on the underlying transport or if the body content is not a utf8
string.
# Examples
```
use trillium_testing::prelude::*;
let mut conn = get("/").with_request_body("request body").on(&());
# trillium_testing::block_on(async {
assert_eq!(conn.request_body_string().await.unwrap(), "request body");
# });
```
*/
#[allow(clippy::missing_errors_doc)] // this is a false positive
pub async fn request_body_string(&mut self) -> trillium_http::Result<String> {
self.request_body().await.read_string().await
}
/**
if there is a response body for this conn and it has a known
fixed length, it is returned from this function
```
use trillium_testing::prelude::*;
let mut conn = get("/").on(&|conn: trillium::Conn| async move {
conn.with_body("hello")
});
assert_eq!(conn.response_len(), Some(5));
```
*/
pub fn response_len(&self) -> Option<u64> {
self.inner.response_body().and_then(Body::len)
}
/**
returns the request method for this conn.
```
use trillium_testing::prelude::*;
let mut conn = get("/").on(&());
assert_eq!(conn.method(), Method::Get);
```
*/
pub fn method(&self) -> Method {
self.inner.method()
}
/// returns the request headers
///
/// stability note: this may become `request_headers` at some point
pub fn headers(&self) -> &Headers {
self.inner.request_headers()
}
/// returns the mutable response headers
///
/// stability note: this may become `response_headers` at some point
pub fn headers_mut(&mut self) -> &mut Headers {
self.inner.response_headers_mut()
}
/**
insert a header name and value/values into the response headers
and return the conn. for a slight performance improvement, use a
[`KnownHeaderName`](crate::KnownHeaderName) as the first argument instead of a
str.
```
use trillium_testing::prelude::*;
let mut conn = get("/").on(&|conn: trillium::Conn| async move {
conn.with_header("content-type", "application/html")
});
```
*/
#[must_use]
pub fn with_header(
mut self,
header_name: impl Into<HeaderName<'static>>,
header_value: impl Into<HeaderValues>,
) -> Self {
self.headers_mut().insert(header_name, header_value);
self
}
/**
returns the path for this request. note that this may not
represent the entire http request path if running nested
routers.
*/
pub fn path(&self) -> &str {
self.path.last().map_or_else(|| self.inner.path(), |p| &**p)
}
/**
returns query part of the request path
```
use trillium_testing::prelude::*;
let conn = get("/a/b?c&d=e").on(&());
assert_eq!(conn.querystring(), "c&d=e");
let conn = get("/a/b").on(&());
assert_eq!(conn.querystring(), "");
```
*/
pub fn querystring(&self) -> &str {
self.inner.querystring()
}
/**
sets the `halted` attribute of this conn, preventing later
processing in a given tuple handler. returns
the conn for fluent chaining
```
use trillium_testing::prelude::*;
let mut conn = get("/").on(&|conn: trillium::Conn| async move {
conn.halt()
});
assert!(conn.is_halted());
```
*/
#[must_use]
pub fn halt(mut self) -> Self {
self.set_halted(true);
self
}
/**
sets the `halted` attribute of this conn. see [`Conn::halt`].
```
use trillium_testing::prelude::*;
let mut conn = get("/").on(&());
assert!(!conn.is_halted());
conn.set_halted(true);
assert!(conn.is_halted());
```
*/
pub fn set_halted(&mut self, halted: bool) {
self.halted = halted;
}
/// retrieves the halted state of this conn. see [`Conn::halt`].
pub const fn is_halted(&self) -> bool {
self.halted
}
/// predicate function to indicate whether the connection is
/// secure. note that this does not necessarily indicate that the
/// transport itself is secure, as it may indicate that
/// `trillium_http` is behind a trusted reverse proxy that has
/// terminated tls and provided appropriate headers to indicate
/// this.
pub fn is_secure(&self) -> bool {
self.inner.is_secure()
}
/// returns an immutable reference to the inner
/// [`trillium_http::Conn`]. please open an issue if you need to do
/// this in application code.
///
/// stability note: hopefully this can go away at some point, but
/// for now is an escape hatch in case `trillium_http::Conn`
/// presents interfaces that cannot be reached otherwise.
pub const fn inner(&self) -> &trillium_http::Conn<BoxedTransport> {
&self.inner
}
/// returns a mutable reference to the inner
/// [`trillium_http::Conn`]. please open an issue if you need to
/// do this in application code.
///
/// stability note: hopefully this can go away at some point, but
/// for now is an escape hatch in case `trillium_http::Conn`
/// presents interfaces that cannot be reached otherwise.
pub fn inner_mut(&mut self) -> &mut trillium_http::Conn<BoxedTransport> {
&mut self.inner
}
/// transforms this `trillium::Conn` into a `trillium_http::Conn`
/// with the specified transport type. Please note that this will
/// panic if you attempt to downcast from trillium's boxed
/// transport into the wrong transport type. Also note that this
/// is a lossy conversion, dropping the halted state and any
/// nested router path data.
pub fn into_inner<T: Transport>(self) -> trillium_http::Conn<T> {
self.inner.map_transport(|t| {
*t.downcast()
.expect("attempted to downcast to the wrong transport type")
})
}
/// retrieves the remote ip address for this conn, if available.
pub fn peer_ip(&self) -> Option<IpAddr> {
self.inner().peer_ip()
}
/// sets the remote ip address for this conn.
pub fn set_peer_ip(&mut self, peer_ip: Option<IpAddr>) {
self.inner_mut().set_peer_ip(peer_ip);
}
/// for router implementations. pushes a route segment onto the path
pub fn push_path(&mut self, path: String) {
self.path.push(path);
}
/// for router implementations. removes a route segment onto the path
pub fn pop_path(&mut self) {
self.path.pop();
}
}
impl AsMut<StateSet> for Conn {
fn as_mut(&mut self) -> &mut StateSet {
self.inner.state_mut()
}
}
impl AsRef<StateSet> for Conn {
fn as_ref(&self) -> &StateSet {
self.inner.state()
}
}