From 1a11a5ccb1c6d303e60fe4c2d4af98585710be8f Mon Sep 17 00:00:00 2001 From: JuliDi <20155974+JuliDi@users.noreply.github.com> Date: Thu, 17 Jul 2025 18:27:08 +0200 Subject: [PATCH 1/4] wip: change impl macros to allow one more extractor --- dropshot/src/extractor/common.rs | 1 + dropshot/src/handler.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/dropshot/src/extractor/common.rs b/dropshot/src/extractor/common.rs index f6635fe3d..14be68395 100644 --- a/dropshot/src/extractor/common.rs +++ b/dropshot/src/extractor/common.rs @@ -207,3 +207,4 @@ macro_rules! impl_rqextractor_for_tuple { // extractors and exactly one exclusive extractor. impl_rqextractor_for_tuple!(S1); impl_rqextractor_for_tuple!(S1, S2); +impl_rqextractor_for_tuple!(S1, S2, S3); diff --git a/dropshot/src/handler.rs b/dropshot/src/handler.rs index de589a4ee..9625e4319 100644 --- a/dropshot/src/handler.rs +++ b/dropshot/src/handler.rs @@ -680,6 +680,7 @@ impl_HttpHandlerFunc_for_func_with_params!(); impl_HttpHandlerFunc_for_func_with_params!((0, T0)); impl_HttpHandlerFunc_for_func_with_params!((0, T1), (1, T2)); impl_HttpHandlerFunc_for_func_with_params!((0, T1), (1, T2), (2, T3)); +impl_HttpHandlerFunc_for_func_with_params!((0, T1), (1, T2), (2, T3), (3, T4)); /// `RouteHandler` abstracts an `HttpHandlerFunc` in a /// way that allows callers to invoke the handler without knowing the handler's From 347eb561177186f68178ef5b94c8dd8a496b80e8 Mon Sep 17 00:00:00 2001 From: JuliDi <20155974+JuliDi@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:14:22 +0200 Subject: [PATCH 2/4] allow up to 5 extractors, add tests for 4 --- dropshot/src/extractor/common.rs | 3 +- dropshot/src/handler.rs | 7 +++ dropshot/src/lib.rs | 2 +- dropshot/tests/integration-tests/openapi.rs | 34 +++++++++++ dropshot/tests/test_openapi.json | 63 +++++++++++++++++++++ dropshot/tests/test_openapi_fuller.json | 63 +++++++++++++++++++++ 6 files changed, 170 insertions(+), 2 deletions(-) diff --git a/dropshot/src/extractor/common.rs b/dropshot/src/extractor/common.rs index 14be68395..4fe10f11b 100644 --- a/dropshot/src/extractor/common.rs +++ b/dropshot/src/extractor/common.rs @@ -203,8 +203,9 @@ macro_rules! impl_rqextractor_for_tuple { } }} -// Implement `RequestExtractor` for any tuple consisting of 0-2 shared +// Implement `RequestExtractor` for any tuple consisting of 0-4 shared // extractors and exactly one exclusive extractor. impl_rqextractor_for_tuple!(S1); impl_rqextractor_for_tuple!(S1, S2); impl_rqextractor_for_tuple!(S1, S2, S3); +impl_rqextractor_for_tuple!(S1, S2, S3, S4); diff --git a/dropshot/src/handler.rs b/dropshot/src/handler.rs index 9625e4319..c978a4fdb 100644 --- a/dropshot/src/handler.rs +++ b/dropshot/src/handler.rs @@ -681,6 +681,13 @@ impl_HttpHandlerFunc_for_func_with_params!((0, T0)); impl_HttpHandlerFunc_for_func_with_params!((0, T1), (1, T2)); impl_HttpHandlerFunc_for_func_with_params!((0, T1), (1, T2), (2, T3)); impl_HttpHandlerFunc_for_func_with_params!((0, T1), (1, T2), (2, T3), (3, T4)); +impl_HttpHandlerFunc_for_func_with_params!( + (0, T1), + (1, T2), + (2, T3), + (3, T4), + (4, T5) +); /// `RouteHandler` abstracts an `HttpHandlerFunc` in a /// way that allows callers to invoke the handler without knowing the handler's diff --git a/dropshot/src/lib.rs b/dropshot/src/lib.rs index c0f18a766..c8999c687 100644 --- a/dropshot/src/lib.rs +++ b/dropshot/src/lib.rs @@ -342,7 +342,7 @@ //! //! `Query` and `Path` impl `SharedExtractor`. `TypedBody`, `UntypedBody`, //! `StreamingBody`, and `RawRequest` impl `ExclusiveExtractor`. Your function -//! may accept 0-3 extractors, but only one can be `ExclusiveExtractor`, and it +//! may accept 0-5 extractors, but only one can be `ExclusiveExtractor`, and it //! must be the last one. Otherwise, the order of extractor arguments does not //! matter. //! diff --git a/dropshot/tests/integration-tests/openapi.rs b/dropshot/tests/integration-tests/openapi.rs index 401b019c6..dabf86c73 100644 --- a/dropshot/tests/integration-tests/openapi.rs +++ b/dropshot/tests/integration-tests/openapi.rs @@ -616,6 +616,39 @@ async fn handler30( todo!(); } +#[derive(Deserialize, JsonSchema)] +struct PathArgs31 { + #[expect(unused)] + aa: String, +} + +#[derive(Deserialize, JsonSchema)] +struct Headers31 { + #[expect(unused)] + header_a: String, +} + +#[derive(Deserialize, JsonSchema)] +struct Query31 { + #[expect(unused)] + query_a: String, +} + +#[endpoint { + method = GET, + path = "/testing/{aa}", + tags = ["it"] +}] +async fn handler31( + _: RequestContext<()>, + _: Path, + _: Header, + _: Query, + _: UntypedBody, +) -> Result, HttpError> { + todo!(); +} + fn make_api( maybe_tag_config: Option, ) -> Result, ApiDescriptionRegisterError> { @@ -655,6 +688,7 @@ fn make_api( api.register(handler28)?; api.register(handler29)?; api.register(handler30)?; + api.register(handler31)?; Ok(api) } diff --git a/dropshot/tests/test_openapi.json b/dropshot/tests/test_openapi.json index 8f063da22..ddd1765e4 100644 --- a/dropshot/tests/test_openapi.json +++ b/dropshot/tests/test_openapi.json @@ -764,6 +764,69 @@ } } }, + "/testing/{aa}": { + "get": { + "tags": [ + "it" + ], + "operationId": "handler31", + "parameters": [ + { + "in": "path", + "name": "aa", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "header_a", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "query_a", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CoolStruct" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/testing/{aa}/{bb}": { "put": { "tags": [ diff --git a/dropshot/tests/test_openapi_fuller.json b/dropshot/tests/test_openapi_fuller.json index caa2d7eb6..1ab6b3da1 100644 --- a/dropshot/tests/test_openapi_fuller.json +++ b/dropshot/tests/test_openapi_fuller.json @@ -772,6 +772,69 @@ } } }, + "/testing/{aa}": { + "get": { + "tags": [ + "it" + ], + "operationId": "handler31", + "parameters": [ + { + "in": "path", + "name": "aa", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "header_a", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "query_a", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CoolStruct" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/testing/{aa}/{bb}": { "put": { "tags": [ From c0e4706bfa61a8fb0285ad28a4188212774baa0f Mon Sep 17 00:00:00 2001 From: JuliDi <20155974+JuliDi@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:21:03 +0200 Subject: [PATCH 3/4] add test for 5 extractors with custom sharedextractor impl --- dropshot/tests/integration-tests/openapi.rs | 40 +++++++++++++ dropshot/tests/test_openapi.json | 63 +++++++++++++++++++++ dropshot/tests/test_openapi_fuller.json | 63 +++++++++++++++++++++ 3 files changed, 166 insertions(+) diff --git a/dropshot/tests/integration-tests/openapi.rs b/dropshot/tests/integration-tests/openapi.rs index dabf86c73..399b2cb1c 100644 --- a/dropshot/tests/integration-tests/openapi.rs +++ b/dropshot/tests/integration-tests/openapi.rs @@ -649,6 +649,45 @@ async fn handler31( todo!(); } +#[derive(Debug, Clone, Serialize, JsonSchema)] +pub struct CustomShared32 { + pub a: String, +} + +#[async_trait::async_trait] +impl dropshot::SharedExtractor for CustomShared32 { + async fn from_request( + _rqctx: &RequestContext, + ) -> Result { + Ok(Self { a: "test".to_string() }) + } + + fn metadata( + _body_content_type: dropshot::ApiEndpointBodyContentType, + ) -> dropshot::ExtractorMetadata { + dropshot::ExtractorMetadata { + extension_mode: dropshot::ExtensionMode::None, + parameters: vec![], + } + } +} + +#[endpoint { + method = GET, + path = "/testing32/{aa}", + tags = ["it"] +}] +async fn handler32( + _: RequestContext<()>, + _: Path, + _: Header, + _: Query, + _: CustomShared32, + _: UntypedBody, +) -> Result, HttpError> { + todo!(); +} + fn make_api( maybe_tag_config: Option, ) -> Result, ApiDescriptionRegisterError> { @@ -689,6 +728,7 @@ fn make_api( api.register(handler29)?; api.register(handler30)?; api.register(handler31)?; + api.register(handler32)?; Ok(api) } diff --git a/dropshot/tests/test_openapi.json b/dropshot/tests/test_openapi.json index ddd1765e4..9dfaeb021 100644 --- a/dropshot/tests/test_openapi.json +++ b/dropshot/tests/test_openapi.json @@ -871,6 +871,69 @@ } } }, + "/testing32/{aa}": { + "get": { + "tags": [ + "it" + ], + "operationId": "handler32", + "parameters": [ + { + "in": "path", + "name": "aa", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "header_a", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "query_a", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CoolStruct" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/thing_with_headers": { "get": { "tags": [ diff --git a/dropshot/tests/test_openapi_fuller.json b/dropshot/tests/test_openapi_fuller.json index 1ab6b3da1..bfc94458b 100644 --- a/dropshot/tests/test_openapi_fuller.json +++ b/dropshot/tests/test_openapi_fuller.json @@ -879,6 +879,69 @@ } } }, + "/testing32/{aa}": { + "get": { + "tags": [ + "it" + ], + "operationId": "handler32", + "parameters": [ + { + "in": "path", + "name": "aa", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "header_a", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "query_a", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CoolStruct" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/thing_with_headers": { "get": { "tags": [ From c4aa97a56432a6bd76da7b1d797c20d4d102598f Mon Sep 17 00:00:00 2001 From: JuliDi <20155974+JuliDi@users.noreply.github.com> Date: Thu, 31 Jul 2025 11:47:12 +0200 Subject: [PATCH 4/4] update changelog for pr 1382 --- CHANGELOG.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index a95a4279c..316348960 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -15,6 +15,8 @@ https://github.com/oxidecomputer/dropshot/compare/v0.16.2\...HEAD[Full list of commits] +* https://github.com/oxidecomputer/dropshot/pull/1382[#1382] Allow 2 more `SharedExtractor`s to be used with the `endpoint` macro + == 0.16.2 (released 2025-05-21) https://github.com/oxidecomputer/dropshot/compare/v0.16.1\...v0.16.2[Full list of commits]