From 1ea6242100280763a84d1d2aab8a4ff3a1fc9f7c Mon Sep 17 00:00:00 2001 From: Kurt Wolf Date: Fri, 27 Sep 2024 07:51:34 -0700 Subject: [PATCH] fix #24 tuples in Path cause panic --- Justfile | 5 ++- core/src/schema.rs | 70 ++++++++++++++++++++--------- core/src/schema/axum.rs | 6 ++- core/src/schema/tuple.rs | 60 ------------------------- oasgen/Justfile | 5 ++- oasgen/tests/test-axum.rs | 2 +- oasgen/tests/test-axum/03-path.rs | 6 +++ oasgen/tests/test-axum/03-path.yaml | 15 +++++++ 8 files changed, 83 insertions(+), 86 deletions(-) delete mode 100644 core/src/schema/tuple.rs diff --git a/Justfile b/Justfile index 4925b53..2c7df7b 100644 --- a/Justfile +++ b/Justfile @@ -1,5 +1,6 @@ set dotenv-load := true set positional-arguments +set export help: @just --list --unsorted @@ -8,8 +9,8 @@ build: cargo build alias b := build -test: - @just oasgen/test +test *ARGS: + @just oasgen/test "$ARGS" check: cargo check diff --git a/core/src/schema.rs b/core/src/schema.rs index 90a928c..cffa049 100644 --- a/core/src/schema.rs +++ b/core/src/schema.rs @@ -25,7 +25,6 @@ mod bigdecimal; mod http; #[cfg(feature = "sid")] mod sid; -mod tuple; pub trait OaSchema { fn schema() -> Schema; @@ -79,6 +78,37 @@ macro_rules! impl_oa_schema_passthrough { }; } +// We have to define this macro instead of defining OaSchema for tuples because +// the Path types have to implement parameters(). parameters calls out to T::schema_ref() +// because we need to implement something like Path : OaSchema, +// but tuples don't work the same way, because schema doesn't return multiple schemas. + +// The alternative is a second trait interface like OaSchemaTuple, and we'd impl +// for axum::extract::Path and friends +#[macro_export] +macro_rules! impl_parameters { + // Pattern for generic axum types with tuple generics (A1, A2, etc.) + ($($path:ident)::+, $($A:ident),+) => { + impl<$($A: OaSchema),+> OaSchema for $($path)::+<($($A,)+)> { + fn schema() -> Schema { + panic!("Call parameters() for this type, not schema()."); + } + + fn parameters() -> Vec> { + vec![ + $( + ReferenceOr::Item(oa::Parameter::path(stringify!($A), $A::schema_ref())), + )+ + ] + } + + fn body_schema() -> Option> { + None + } + } + }; +} + impl OaSchema for () { fn schema() -> Schema { panic!("Call body_schema() for (), not schema().") @@ -115,21 +145,27 @@ impl OaSchema for Vec where T: OaSchema, { - fn schema_ref() -> ReferenceOr { - let inner = T::schema_ref(); - ReferenceOr::Item(Schema::new_array(inner)) - } - fn schema() -> Schema { let inner = T::schema(); Schema::new_array(inner) } + + fn schema_ref() -> ReferenceOr { + let inner = T::schema_ref(); + ReferenceOr::Item(Schema::new_array(inner)) + } } impl OaSchema for Option where T: OaSchema, { + fn schema() -> Schema { + let mut schema = T::schema(); + schema.nullable = true; + schema + } + fn schema_ref() -> ReferenceOr { let mut schema = T::schema_ref(); let Some(s) = schema.as_mut() else { @@ -138,26 +174,20 @@ where s.nullable = true; schema } - - fn schema() -> Schema { - let mut schema = T::schema(); - schema.nullable = true; - schema - } } impl OaSchema for Result where T: OaSchema, { - fn schema_ref() -> ReferenceOr { - T::schema_ref() - } - fn schema() -> Schema { T::schema() } + fn schema_ref() -> ReferenceOr { + T::schema_ref() + } + fn body_schema() -> Option> { T::body_schema() } @@ -167,13 +197,13 @@ impl OaSchema for HashMap where V: OaSchema, { - fn schema_ref() -> ReferenceOr { - ReferenceOr::Item(Schema::new_map(V::schema_ref())) - } - fn schema() -> Schema { Schema::new_map(V::schema()) } + + fn schema_ref() -> ReferenceOr { + ReferenceOr::Item(Schema::new_map(V::schema_ref())) + } } #[cfg(feature = "uuid")] diff --git a/core/src/schema/axum.rs b/core/src/schema/axum.rs index 215331a..89c6af6 100644 --- a/core/src/schema/axum.rs +++ b/core/src/schema/axum.rs @@ -1,7 +1,7 @@ use openapiv3::{ReferenceOr, Schema}; use openapiv3 as oa; -use crate::OaSchema; +use crate::{impl_parameters, OaSchema}; impl OaSchema for axum::extract::Json { fn schema() -> Schema { @@ -104,3 +104,7 @@ impl OaSchema for serde_qs::axum::QsQuery { } fn body_schema() -> Option> { None } } + +impl_parameters!(axum::extract::Path, A1); +impl_parameters!(axum::extract::Path, A1, A2); +impl_parameters!(axum::extract::Path, A1, A2, A3); \ No newline at end of file diff --git a/core/src/schema/tuple.rs b/core/src/schema/tuple.rs deleted file mode 100644 index 101b19e..0000000 --- a/core/src/schema/tuple.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::OaSchema; -use openapiv3::{RefOr, Schema, Parameter}; - -impl OaSchema for (A1,) where A1: OaSchema { - fn schema() -> Schema { - panic!("Call parameters() or body_schema() for tuples, not schema()") - } - - fn parameters() -> Vec> { - vec![Parameter::path("param1", A1::schema_ref()).into()] - } - - fn body_schema() -> Option> { - A1::body_schema() - } -} - -impl OaSchema for (A1, A2) - where - A1: OaSchema, - A2: OaSchema, -{ - fn schema() -> Schema { - panic!("Call parameters() or body_schema() for tuples, not schema()") - } - - fn parameters() -> Vec> { - vec![ - Parameter::path("param1", A1::schema_ref()).into(), - Parameter::path("param2", A2::schema_ref()).into(), - ] - } - - fn body_schema() -> Option> { - A2::body_schema() - } -} - -impl OaSchema for (A1, A2, A3) - where - A1: OaSchema, - A2: OaSchema, - A3: OaSchema, -{ - fn schema() -> Schema { - panic!("Call parameters() or body_schema() for tuples, not schema()") - } - - fn parameters() -> Vec> { - vec![ - Parameter::path("param1", A1::schema_ref()).into(), - Parameter::path("param2", A2::schema_ref()).into(), - Parameter::path("param3", A3::schema_ref()).into(), - ] - } - - fn body_schema() -> Option> { - A3::body_schema() - } -} \ No newline at end of file diff --git a/oasgen/Justfile b/oasgen/Justfile index e50f5e6..00cfd63 100644 --- a/oasgen/Justfile +++ b/oasgen/Justfile @@ -1,5 +1,6 @@ set dotenv-load := true set positional-arguments +set export help: @just --list --unsorted @@ -18,8 +19,8 @@ fix: run *ARGS: cargo run --example actix --features actix -test: - cargo test --all-features "$@" +test *ARGS: + cargo test --all-features "$ARGS" alias r := run diff --git a/oasgen/tests/test-axum.rs b/oasgen/tests/test-axum.rs index d687967..b3b6a0e 100644 --- a/oasgen/tests/test-axum.rs +++ b/oasgen/tests/test-axum.rs @@ -1,5 +1,5 @@ #[test] -fn run_tests() { +fn test_axum() { let t = trybuild::TestCases::new(); t.pass("tests/test-axum/01-hello.rs"); t.pass("tests/test-axum/02-query.rs"); diff --git a/oasgen/tests/test-axum/03-path.rs b/oasgen/tests/test-axum/03-path.rs index 2411862..57ac38f 100644 --- a/oasgen/tests/test-axum/03-path.rs +++ b/oasgen/tests/test-axum/03-path.rs @@ -14,10 +14,16 @@ async fn get_task(Path(_id): Path) -> Json<()> { Json(()) } +#[oasgen] +async fn get_stuff(Path((_id, _tu)): Path<(u64, u64)>) -> Json<()> { + Json(()) +} + fn main() { use pretty_assertions::assert_eq; let server = Server::axum() .get("/tasks/:id/", get_task) + .get("/tasks/:id/:tu", get_stuff) ; let spec = serde_yaml::to_string(&server.openapi).unwrap(); diff --git a/oasgen/tests/test-axum/03-path.yaml b/oasgen/tests/test-axum/03-path.yaml index 1001aed..8a65bec 100644 --- a/oasgen/tests/test-axum/03-path.yaml +++ b/oasgen/tests/test-axum/03-path.yaml @@ -13,6 +13,21 @@ paths: in: path style: simple responses: {} + /tasks/{id}/{tu}: + get: + operationId: get_stuff + parameters: + - name: id + schema: + type: integer + in: path + style: simple + - name: tu + schema: + type: integer + in: path + style: simple + responses: {} components: schemas: TaskFilter: