Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.3.x #611

Draft
wants to merge 38 commits into
base: main
Choose a base branch
from
Draft

0.3.x #611

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b6bc038
feat(http): add StateSet::merge
jbr Jan 2, 2024
7cae46a
feat(http): add Conn::shared_state
jbr Jan 2, 2024
125f54c
feat(trillium): add Conn::shared_state
jbr Jan 2, 2024
ab43390
feat(trillium): add a StateSet to Info
jbr Oct 19, 2023
c6cf5dc
feat(trillium)!: remove trillium::init and trillium::Init
jbr Apr 4, 2024
a213f4f
feat(trillium)!: remove Clone for Info
jbr Apr 5, 2024
3d0d942
feat(server-common)!: put Info in an Arc for now
jbr Apr 5, 2024
37ce1bf
feat!: make all conn header apis specify request or response
jbr Apr 5, 2024
dc53f6e
feat(http)!: synthetic len returns usize
jbr Apr 5, 2024
1272746
feat!: remove `impl Handler for Arc<impl Handler>`
jbr Apr 5, 2024
f6530da
feat(http)!: remove reexported httparse error type
jbr Apr 5, 2024
1d2cc32
feat(http)!: make Upgrade #[non_exhaustive], use Buffer, and add peer_ip
jbr Apr 5, 2024
ed31ebd
feat(http)!: remove Stream for ReceivedBody
jbr Apr 5, 2024
4207c4d
feat!: eliminate async_trait
jbr Apr 6, 2024
40a6df9
feat(http)!: support !Send handler functions
jbr Apr 6, 2024
2017bab
feat(client)!: add support for client timeouts
jbr Feb 6, 2024
6b945e5
chore(actions): don't run tarpaulin in verbose mode
jbr Feb 9, 2024
599f019
feat!: use swansong instead of stopper + clone counter
jbr Apr 7, 2024
da9c96c
fix: http trillium-macros dep version
jbr Apr 17, 2024
da57f3d
feat(logger)!: remove deprecated formatters::header
jbr Apr 17, 2024
814fbcf
feat(client)!: remove deprecated 0.2.x placeholders
jbr Apr 17, 2024
5b48de3
feat: parse directly into trillium::Headers
jbr Feb 8, 2024
55bec0a
chore(actions): run parse tests and improve coverage config
jbr Apr 16, 2024
0552651
fix: remove unused imports
jbr Apr 16, 2024
f07e0e2
chore(actions): make tarpaulin run a bit less exhaustive
jbr Apr 16, 2024
a03f9c0
feat!: remove deprecated set_state functions
jbr Apr 17, 2024
aa2998a
feat(server-common)!: remove Clone for Config
jbr Apr 17, 2024
7d82142
feat!: introduce Runtime
jbr Apr 18, 2024
7369b65
fix: fastrand seed change
jbr May 20, 2024
efd9cf3
feat!: use the extracted `type-set` crate instead of trillium_http::S…
jbr May 20, 2024
ac4ef28
test: fix disconnect-body test
jbr May 31, 2024
535b9de
feat: add Headers::entry interface
jbr May 30, 2024
7002c48
feat(http)!: remove deprecated Headers::contains_ignore_ascii_case
jbr May 31, 2024
a325ba5
test: add httparse tests and make errors identical
jbr May 31, 2024
f2365b6
test: add two more corpus tests
jbr May 30, 2024
84e6ee4
chore: add a rustfmt.toml and reformat
jbr Jun 14, 2024
e92b43a
chore: further improvements to format settings
jbr Jun 21, 2024
81ad587
chore: switch over to `///` from `/** */` comments
jbr Jun 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
12 changes: 9 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,25 @@ jobs:
command: test
args: --workspace

- name: Run tests
- name: Run tests (parse)
uses: actions-rs/cargo@v1
with:
command: test
args: -p trillium-http -p trillium-client --features parse

- name: Run tests (smol)
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features trillium-static/smol,trillium-testing/smol

- name: Run tests
- name: Run tests (tokio)
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features trillium-static/tokio,trillium-testing/tokio

- name: Run tests
- name: Run tests (async-std)
uses: actions-rs/cargo@v1
with:
command: test
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Rust Cache
uses: Swatinem/[email protected]
- name: Generate code coverage
run: cargo +nightly tarpaulin --verbose --features smol,trillium-testing/smol,trillium-http/serde --workspace --timeout 120 --out xml
run: cargo +nightly tarpaulin
- name: Upload to codecov.io
uses: codecov/codecov-action@v4
with:
Expand Down
276 changes: 133 additions & 143 deletions api/src/api_conn_ext.rs
Original file line number Diff line number Diff line change
@@ -1,161 +1,154 @@
use crate::{Error, Result};
use mime::Mime;
use serde::{de::DeserializeOwned, Serialize};
use std::future::Future;
use trillium::{
Conn,
KnownHeaderName::{Accept, ContentType},
Status,
};

use crate::{Error, Result};

/// Extension trait that adds api methods to [`trillium::Conn`]
#[trillium::async_trait]
pub trait ApiConnExt {
/**
Sends a json response body. This sets a status code of 200,
serializes the body with serde_json, sets the content-type to
application/json, and [halts](trillium::Conn::halt) the
conn. If serialization fails, a 500 status code is sent as per
[`trillium::conn_try`]


## Examples

```
use trillium_api::{json, ApiConnExt};
async fn handler(conn: trillium::Conn) -> trillium::Conn {
conn.with_json(&json!({ "json macro": "is reexported" }))
}

# use trillium_testing::prelude::*;
assert_ok!(
get("/").on(&handler),
r#"{"json macro":"is reexported"}"#,
"content-type" => "application/json"
);
```

### overriding status code
```
use trillium_api::ApiConnExt;
use serde::Serialize;

#[derive(Serialize)]
struct ApiResponse {
string: &'static str,
number: usize
}

async fn handler(conn: trillium::Conn) -> trillium::Conn {
conn.with_json(&ApiResponse { string: "not the most creative example", number: 100 })
.with_status(201)
}

# use trillium_testing::prelude::*;
assert_response!(
get("/").on(&handler),
Status::Created,
r#"{"string":"not the most creative example","number":100}"#,
"content-type" => "application/json"
);
```
*/
/// Sends a json response body. This sets a status code of 200,
/// serializes the body with serde_json, sets the content-type to
/// application/json, and [halts](trillium::Conn::halt) the
/// conn. If serialization fails, a 500 status code is sent as per
/// [`trillium::conn_try`]
///
///
/// ## Examples
///
/// ```
/// use trillium_api::{json, ApiConnExt};
/// async fn handler(conn: trillium::Conn) -> trillium::Conn {
/// conn.with_json(&json!({ "json macro": "is reexported" }))
/// }
///
/// # use trillium_testing::prelude::*;
/// assert_ok!(
/// get("/").on(&handler),
/// r#"{"json macro":"is reexported"}"#,
/// "content-type" => "application/json"
/// );
/// ```
///
/// ### overriding status code
/// ```
/// use trillium_api::ApiConnExt;
/// use serde::Serialize;
///
/// #[derive(Serialize)]
/// struct ApiResponse {
/// string: &'static str,
/// number: usize
/// }
///
/// async fn handler(conn: trillium::Conn) -> trillium::Conn {
/// conn.with_json(&ApiResponse { string: "not the most creative example", number: 100 })
/// .with_status(201)
/// }
///
/// # use trillium_testing::prelude::*;
/// assert_response!(
/// get("/").on(&handler),
/// Status::Created,
/// r#"{"string":"not the most creative example","number":100}"#,
/// "content-type" => "application/json"
/// );
/// ```
fn with_json(self, response: &impl Serialize) -> Self;

/**
Attempts to deserialize a type from the request body, based on the
request content type.

By default, both application/json and
application/x-www-form-urlencoded are supported, and future
versions may add accepted request content types. Please open an
issue if you need to accept another content type.


To exclusively accept application/json, disable default features
on this crate.

This sets a status code of Status::Ok if and only if no status
code has been explicitly set.

## Examples

### Deserializing to [`Value`]

```
use trillium_api::{ApiConnExt, Value};

async fn handler(mut conn: trillium::Conn) -> trillium::Conn {
let value: Value = trillium::conn_try!(conn.deserialize().await, conn);
conn.with_json(&value)
}

# use trillium_testing::prelude::*;
assert_ok!(
post("/")
.with_request_body(r#"key=value"#)
.with_request_header("content-type", "application/x-www-form-urlencoded")
.on(&handler),
r#"{"key":"value"}"#,
"content-type" => "application/json"
);

```

### Deserializing a concrete type

```
use trillium_api::ApiConnExt;

#[derive(serde::Deserialize)]
struct KvPair { key: String, value: String }

async fn handler(mut conn: trillium::Conn) -> trillium::Conn {
match conn.deserialize().await {
Ok(KvPair { key, value }) => {
conn.with_status(201)
.with_body(format!("{} is {}", key, value))
.halt()
}

Err(_) => conn.with_status(422).with_body("nope").halt()
}
}

# use trillium_testing::prelude::*;
assert_response!(
post("/")
.with_request_body(r#"key=name&value=trillium"#)
.with_request_header("content-type", "application/x-www-form-urlencoded")
.on(&handler),
Status::Created,
r#"name is trillium"#,
);

assert_response!(
post("/")
.with_request_body(r#"name=trillium"#)
.with_request_header("content-type", "application/x-www-form-urlencoded")
.on(&handler),
Status::UnprocessableEntity,
r#"nope"#,
);


```

*/
async fn deserialize<T>(&mut self) -> Result<T>
/// Attempts to deserialize a type from the request body, based on the
/// request content type.
///
/// By default, both application/json and
/// application/x-www-form-urlencoded are supported, and future
/// versions may add accepted request content types. Please open an
/// issue if you need to accept another content type.
///
///
/// To exclusively accept application/json, disable default features
/// on this crate.
///
/// This sets a status code of Status::Ok if and only if no status
/// code has been explicitly set.
///
/// ## Examples
///
/// ### Deserializing to [`Value`]
///
/// ```
/// use trillium_api::{ApiConnExt, Value};
///
/// async fn handler(mut conn: trillium::Conn) -> trillium::Conn {
/// let value: Value = trillium::conn_try!(conn.deserialize().await, conn);
/// conn.with_json(&value)
/// }
///
/// # use trillium_testing::prelude::*;
/// assert_ok!(
/// post("/")
/// .with_request_body(r#"key=value"#)
/// .with_request_header("content-type", "application/x-www-form-urlencoded")
/// .on(&handler),
/// r#"{"key":"value"}"#,
/// "content-type" => "application/json"
/// );
/// ```
///
/// ### Deserializing a concrete type
///
/// ```
/// use trillium_api::ApiConnExt;
///
/// #[derive(serde::Deserialize)]
/// struct KvPair {
/// key: String,
/// value: String,
/// }
///
/// async fn handler(mut conn: trillium::Conn) -> trillium::Conn {
/// match conn.deserialize().await {
/// Ok(KvPair { key, value }) => conn
/// .with_status(201)
/// .with_body(format!("{} is {}", key, value))
/// .halt(),
///
/// Err(_) => conn.with_status(422).with_body("nope").halt(),
/// }
/// }
///
/// # use trillium_testing::prelude::*;
/// assert_response!(
/// post("/")
/// .with_request_body(r#"key=name&value=trillium"#)
/// .with_request_header("content-type", "application/x-www-form-urlencoded")
/// .on(&handler),
/// Status::Created,
/// r#"name is trillium"#,
/// );
///
/// assert_response!(
/// post("/")
/// .with_request_body(r#"name=trillium"#)
/// .with_request_header("content-type", "application/x-www-form-urlencoded")
/// .on(&handler),
/// Status::UnprocessableEntity,
/// r#"nope"#,
/// );
/// ```
fn deserialize<T>(&mut self) -> impl Future<Output = Result<T>> + Send
where
T: DeserializeOwned;

/// Deserializes json without any Accepts header content negotiation
async fn deserialize_json<T>(&mut self) -> Result<T>
fn deserialize_json<T>(&mut self) -> impl Future<Output = Result<T>> + Send
where
T: DeserializeOwned;

/// Serializes the provided body using Accepts header content negotiation
async fn serialize<T>(&mut self, body: &T) -> Result<()>
fn serialize<T>(&mut self, body: &T) -> impl Future<Output = Result<()>> + Send
where
T: Serialize + Sync;

Expand All @@ -166,7 +159,6 @@ pub trait ApiConnExt {
fn content_type(&self) -> Result<Mime>;
}

#[trillium::async_trait]
impl ApiConnExt for Conn {
fn with_json(mut self, response: &impl Serialize) -> Self {
match serde_json::to_string(&response) {
Expand Down Expand Up @@ -261,16 +253,14 @@ impl ApiConnExt for Conn {
match accept {
Some(AcceptableMime::Json) => {
self.set_body(serde_json::to_string(body)?);
self.response_headers_mut()
.insert(ContentType, "application/json");
self.insert_response_header(ContentType, "application/json");
Ok(())
}

#[cfg(feature = "forms")]
Some(AcceptableMime::Form) => {
self.set_body(serde_urlencoded::to_string(body)?);
self.response_headers_mut()
.insert(ContentType, "application/x-www-form-urlencoded");
self.insert_response_header(ContentType, "application/x-www-form-urlencoded");
Ok(())
}

Expand Down
4 changes: 2 additions & 2 deletions api/src/api_handler.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::TryFromConn;
use std::{future::Future, marker::PhantomData, sync::Arc};
use trillium::{async_trait, Conn, Handler, Info, Status, Upgrade};
use trillium::{Conn, Handler, Info, Status, Upgrade};

// A trait for `async fn(conn: &mut Conn, additional: Additional) -> ReturnType`
pub trait MutBorrowConn<'conn, ReturnType, Additional>: Send + Sync + 'conn {
Expand All @@ -16,6 +16,7 @@ where
Fut: Future<Output = ReturnType> + Send + 'conn,
{
type Fut = Fut;

fn call(&self, conn: &'conn mut Conn, additional: Additional) -> Fut {
self(conn, additional)
}
Expand Down Expand Up @@ -78,7 +79,6 @@ where
ApiHandler::from(api_handler)
}

#[async_trait]
impl<TryFromConnHandler, OutputHandler, Extracted> Handler
for ApiHandler<TryFromConnHandler, OutputHandler, Extracted>
where
Expand Down
Loading
Loading