Skip to content

Commit

Permalink
Add axum extras integration (#213)
Browse files Browse the repository at this point in the history
Add axum integration for `IntoParams` of `Path` and `Query` parameters.
Refactor extension argument resolving logic and refactor actix-web and rocket
extension logic. Also fix extension related tests and docs.

Allow comparing types by their `path` for `IntoParams` types. Previously
comparison was done based on `Ident` causing name clashes in case there
would be `IntoParams` type with same name but different module.

Remove some unnecessary clones and address clippy lint rules. Fix axum
and actix-web extensions.
  • Loading branch information
juhaku authored Jul 13, 2022
1 parent a2b319f commit 6aeac50
Show file tree
Hide file tree
Showing 18 changed files with 700 additions and 329 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ jobs:
cargo test --test component_derive_test --features chrono,decimal,uuid
cargo test --test component_derive_test --features chrono_with_format
cargo test --test path_derive_rocket --features rocket_extras,json
cargo test --test path_derive_axum_test --features axum_extras,json
elif [[ "${{ matrix.testset }}" == "utoipa-gen" ]] && [[ ${{ steps.changes.outputs.gen_changed }} == true ]]; then
cargo test -p utoipa-gen --features utoipa/actix_extras
elif [[ "${{ matrix.testset }}" == "utoipa-swagger-ui" ]] && [[ ${{ steps.changes.outputs.swagger_changed }} == true ]]; then
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ default = ["json"]
debug = ["utoipa-gen/debug"]
actix_extras = ["utoipa-gen/actix_extras"]
rocket_extras = ["utoipa-gen/rocket_extras"]
axum_extras = ["utoipa-gen/axum_extras"]
json = ["serde_json", "utoipa-gen/json"]
chrono = ["utoipa-gen/chrono"]
chrono_with_format = ["utoipa-gen/chrono_with_format"]
Expand All @@ -39,6 +40,7 @@ utoipa-gen = { version = "1.1.0", path = "./utoipa-gen" }

[dev-dependencies]
actix-web = { version = "4", features = [ "macros" ], default-features = false }
axum = "0.5"
assert-json-diff = "2"
paste = "1"
chrono = { version = "0.4", features = ["serde"] }
Expand Down
1 change: 1 addition & 0 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ if [[ "$crate" == "utoipa" ]]; then
cargo test --test component_derive_test --features chrono,decimal,uuid
cargo test --test component_derive_test --features chrono_with_format
cargo test --test path_derive_rocket --features rocket_extras,json
cargo test --test path_derive_axum_test --features axum_extras,json
elif [[ "$crate" == "utoipa-gen" ]]; then
cargo test -p utoipa-gen --features utoipa/actix_extras
elif [[ "$crate" == "utoipa-swagger-ui" ]]; then
Expand Down
2 changes: 1 addition & 1 deletion tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ macro_rules! assert_value {
($value:expr=> $( $path:literal = $expected:literal, $error:literal)* ) => {{
$(
let p = &*format!("/{}", $path.replace(".", "/").replace("[", "").replace("]", ""));
let actual = crate::common::value_as_string(Some($value.pointer(p).unwrap_or(&serde_json::Value::Null)));
let actual = $crate::common::value_as_string(Some($value.pointer(p).unwrap_or(&serde_json::Value::Null)));
assert_eq!(actual, $expected, "{}: {} expected to be: {} but was: {}", $error, $path, $expected, actual);
)*
}};
Expand Down
22 changes: 22 additions & 0 deletions tests/path_derive_actix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ fn path_with_struct_variables_with_into_params() {
}

#[utoipa::path(
params(
Person,
Filter
),
responses(
(status = 200, description = "success response")
)
Expand Down Expand Up @@ -432,6 +436,10 @@ fn derive_path_with_struct_variables_with_into_params() {
}

#[utoipa::path(
params(
Person,
Filter
),
responses(
(status = 200, description = "success response")
)
Expand Down Expand Up @@ -489,6 +497,9 @@ fn derive_path_with_multiple_instances_same_path_params() {
struct Id(u64);

#[utoipa::path(
params(
Id
),
responses(
(status = 200, description = "success response")
)
Expand All @@ -500,6 +511,9 @@ fn derive_path_with_multiple_instances_same_path_params() {
}

#[utoipa::path(
params(
Id
),
responses(
(status = 200, description = "success response")
)
Expand Down Expand Up @@ -542,6 +556,7 @@ fn derive_path_with_multiple_into_params_names() {
struct IdAndName(u64, String);

#[utoipa::path(
params(IdAndName),
responses(
(status = 200, description = "success response")
)
Expand Down Expand Up @@ -611,6 +626,10 @@ fn derive_into_params_with_custom_attributes() {
}

#[utoipa::path(
params(
Person,
Filter
),
responses(
(status = 200, description = "success response")
)
Expand Down Expand Up @@ -693,6 +712,9 @@ fn derive_into_params_in_another_module() {

/// Foo test
#[utoipa::path(
params(
params::FooParams,
),
responses(
(status = 200, description = "Todo foo operation success"),
)
Expand Down
140 changes: 140 additions & 0 deletions tests/path_derive_axum_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#![cfg(feature = "axum_extras")]
#![cfg(feature = "serde_json")]

use assert_json_diff::assert_json_eq;
use axum::extract::{Path, Query};
use serde::Deserialize;
use serde_json::json;
use utoipa::{IntoParams, OpenApi};

#[test]
fn derive_path_params_into_params_axum() {
#[derive(Deserialize, IntoParams)]
#[allow(unused)]
struct Person {
/// Id of person
id: i64,
/// Name of person
name: String,
}

pub mod custom {
use serde::Deserialize;
use utoipa::IntoParams;
#[derive(Deserialize, IntoParams)]
#[allow(unused)]
pub(super) struct Filter {
/// Age filter for user
#[deprecated]
age: Option<Vec<String>>,
}
}

#[utoipa::path(
get,
path = "/person/{id}/{name}",
params(Person, custom::Filter),
responses(
(status = 200, description = "success response")
)
)]
#[allow(unused)]
async fn get_person(person: Path<Person>, query: Query<custom::Filter>) {}

#[derive(OpenApi)]
#[openapi(handlers(get_person))]
struct ApiDoc;

let doc = serde_json::to_value(ApiDoc::openapi()).unwrap();
let parameters = doc
.pointer("/paths/~1person~1{id}~1{name}/get/parameters")
.unwrap();

assert_json_eq!(
parameters,
&json!([
{
"description": "Id of person",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "int64",
"type": "integer",
},
},
{
"description": "Name of person",
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string",
},
},
{
"deprecated": true,
"description": "Age filter for user",
"in": "query",
"name": "age",
"required": false,
"schema": {
"items": {
"type": "string",
},
"type": "array",
}
},
])
)
}

#[test]
fn derive_path_params_into_params_unnamed() {
#[derive(Deserialize, IntoParams)]
#[into_params(names("id", "name"))]
struct IdAndName(u64, String);

#[utoipa::path(
get,
path = "/person/{id}/{name}",
params(IdAndName),
responses(
(status = 200, description = "success response")
)
)]
#[allow(unused)]
async fn get_person(person: Path<IdAndName>) {}

#[derive(OpenApi)]
#[openapi(handlers(get_person))]
struct ApiDoc;

let doc = serde_json::to_value(ApiDoc::openapi()).unwrap();
let parameters = doc
.pointer("/paths/~1person~1{id}~1{name}/get/parameters")
.unwrap();

assert_json_eq!(
parameters,
&json!([
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "int64",
"type": "integer",
},
},
{
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string",
},
},
])
)
}
2 changes: 2 additions & 0 deletions utoipa-gen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ utoipa = { path = ".." }
serde_json = "1"
serde = "1"
actix-web = { version = "4", features = [ "macros" ], default-features = false }
axum = "0.5"

[features]
debug = ["syn/extra-traits"]
Expand All @@ -38,3 +39,4 @@ json = []
decimal = []
rocket_extras = ["regex", "lazy_static"]
uuid = ["dep:uuid"]
axum_extras = []
Loading

0 comments on commit 6aeac50

Please sign in to comment.