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] diff --git a/dropshot/src/extractor/common.rs b/dropshot/src/extractor/common.rs index f6635fe3d..4fe10f11b 100644 --- a/dropshot/src/extractor/common.rs +++ b/dropshot/src/extractor/common.rs @@ -203,7 +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 de589a4ee..c978a4fdb 100644 --- a/dropshot/src/handler.rs +++ b/dropshot/src/handler.rs @@ -680,6 +680,14 @@ 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)); +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..399b2cb1c 100644 --- a/dropshot/tests/integration-tests/openapi.rs +++ b/dropshot/tests/integration-tests/openapi.rs @@ -616,6 +616,78 @@ 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!(); +} + +#[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> { @@ -655,6 +727,8 @@ fn make_api( api.register(handler28)?; 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 8f063da22..9dfaeb021 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": [ @@ -808,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 caa2d7eb6..bfc94458b 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": [ @@ -816,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": [