From 20a5e419f998c7da191369683206acd6d1f7227b Mon Sep 17 00:00:00 2001 From: lif <> Date: Wed, 25 Feb 2026 07:05:47 +0000 Subject: [PATCH 1/5] Explicitly include HTTP statuses in OpenAPI document for WebSockets rather than wildcard default, which made generated clients not expect any error responses from the server at all. (#1548) --- dropshot/src/websocket.rs | 45 ++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/dropshot/src/websocket.rs b/dropshot/src/websocket.rs index a2fee96ec..d964ebffd 100644 --- a/dropshot/src/websocket.rs +++ b/dropshot/src/websocket.rs @@ -8,9 +8,10 @@ use crate::api_description::ExtensionMode; use crate::body::Body; +use crate::handler::HttpHandlerResult; use crate::{ - ApiEndpointBodyContentType, ExclusiveExtractor, ExtractorMetadata, - HttpError, RequestContext, ServerContext, + ApiEndpointBodyContentType, ApiEndpointResponse, ExclusiveExtractor, + ExtractorMetadata, HttpError, HttpResponse, RequestContext, ServerContext, }; use async_trait::async_trait; use base64::Engine; @@ -45,7 +46,37 @@ pub type WebsocketChannelResult = /// [WebsocketUpgrade::handle]'s return type. /// The `#[endpoint]` handler must return the value returned by /// [WebsocketUpgrade::handle]. (This is done for you by `#[channel]`.) -pub type WebsocketEndpointResult = Result, HttpError>; +pub type WebsocketEndpointResult = Result; + +pub struct SwitchingToWebsocket { + accept_key: String, +} + +impl HttpResponse for SwitchingToWebsocket { + fn to_result(self) -> HttpHandlerResult { + Response::builder() + .status(StatusCode::SWITCHING_PROTOCOLS) + .header(header::CONNECTION, "Upgrade") + .header(header::UPGRADE, "websocket") + .header(header::SEC_WEBSOCKET_ACCEPT, self.accept_key) + .body(Body::empty()) + .map_err(Into::into) + } + fn response_metadata() -> ApiEndpointResponse { + const UPGRADE_DESCRIPTION: &str = + "Negotiating protocol upgrade from HTTP/1.1 to WebSocket."; + ApiEndpointResponse { + schema: None, + headers: vec![], + success: Some(StatusCode::SWITCHING_PROTOCOLS), + // FIXME(?): this becomes empty-string when schema is None + description: Some(UPGRADE_DESCRIPTION.to_string()), + } + } + fn status_code(&self) -> StatusCode { + StatusCode::SWITCHING_PROTOCOLS + } +} /// The upgraded connection passed as the last argument to the websocket /// handler function. [`WebsocketConnection::into_inner`] can be used to @@ -257,13 +288,7 @@ impl WebsocketUpgrade { } } }); - Response::builder() - .status(StatusCode::SWITCHING_PROTOCOLS) - .header(header::CONNECTION, "Upgrade") - .header(header::UPGRADE, "websocket") - .header(header::SEC_WEBSOCKET_ACCEPT, accept_key) - .body(Body::empty()) - .map_err(Into::into) + Ok(SwitchingToWebsocket { accept_key }) } } } From 7547772b96b803d056dbd7bd1b219cd20b0f69f3 Mon Sep 17 00:00:00 2001 From: lif <> Date: Wed, 25 Feb 2026 07:24:45 +0000 Subject: [PATCH 2/5] Allow description in OpenAPI even in absence of endpoint schema --- dropshot/src/api_description.rs | 14 ++++++++++---- dropshot/src/websocket.rs | 3 +-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/dropshot/src/api_description.rs b/dropshot/src/api_description.rs index 23fd0de17..357d24b60 100644 --- a/dropshot/src/api_description.rs +++ b/dropshot/src/api_description.rs @@ -996,10 +996,16 @@ impl ApiDescription { }, ); openapiv3::Response { - // TODO: perhaps we should require even free-form - // responses to have a description since it's required - // by OpenAPI. - description: "".to_string(), + description: if let Some(description) = + &endpoint.response.description + { + description.clone() + } else { + // TODO: perhaps we should require even free-form + // responses to have a description since it's required + // by OpenAPI. + "".to_string() + }, content, ..Default::default() } diff --git a/dropshot/src/websocket.rs b/dropshot/src/websocket.rs index d964ebffd..c69d7e37b 100644 --- a/dropshot/src/websocket.rs +++ b/dropshot/src/websocket.rs @@ -64,12 +64,11 @@ impl HttpResponse for SwitchingToWebsocket { } fn response_metadata() -> ApiEndpointResponse { const UPGRADE_DESCRIPTION: &str = - "Negotiating protocol upgrade from HTTP/1.1 to WebSocket."; + "Negotiating protocol upgrade from HTTP/1.1 to WebSocket"; ApiEndpointResponse { schema: None, headers: vec![], success: Some(StatusCode::SWITCHING_PROTOCOLS), - // FIXME(?): this becomes empty-string when schema is None description: Some(UPGRADE_DESCRIPTION.to_string()), } } From 6aaa101cddd560f08022496b923b642ea66aa8aa Mon Sep 17 00:00:00 2001 From: lif <> Date: Wed, 25 Feb 2026 07:27:09 +0000 Subject: [PATCH 3/5] Update expected output for changed WebsocketEndpointResult type --- dropshot/tests/fail/bad_channel17.stderr | 4 ++-- dropshot/tests/fail/bad_channel18.stderr | 4 ++-- dropshot/tests/fail/bad_channel19.stderr | 4 ++-- dropshot/tests/fail/bad_channel4.stderr | 4 ++-- dropshot/tests/fail/bad_channel5.stderr | 4 ++-- dropshot/tests/fail/bad_channel9.stderr | 4 ++-- dropshot/tests/fail/bad_trait_channel17.stderr | 4 ++-- dropshot/tests/fail/bad_trait_channel18.stderr | 4 ++-- dropshot/tests/fail/bad_trait_channel19.stderr | 4 ++-- dropshot/tests/fail/bad_trait_channel4.stderr | 4 ++-- dropshot/tests/fail/bad_trait_channel5.stderr | 4 ++-- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/dropshot/tests/fail/bad_channel17.stderr b/dropshot/tests/fail/bad_channel17.stderr index 24b0d8579..d4d10b78b 100644 --- a/dropshot/tests/fail/bad_channel17.stderr +++ b/dropshot/tests/fail/bad_channel17.stderr @@ -38,7 +38,7 @@ note: required by a bound in `need_shared_extractor` | ------------------- required by a bound in this function = note: this error originates in the attribute macro `channel` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `fn(RequestContext<()>, WebsocketConnection, WebsocketUpgrade) -> impl Future, HttpError>> {two_websocket_channels_adapter}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied +error[E0277]: the trait bound `fn(RequestContext<()>, WebsocketConnection, WebsocketUpgrade) -> impl Future> {two_websocket_channels_adapter}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied --> tests/fail/bad_channel17.rs:16:1 | 12 | / #[channel { @@ -49,7 +49,7 @@ error[E0277]: the trait bound `fn(RequestContext<()>, WebsocketConnection, Webso 16 | async fn two_websocket_channels( | ^^^^^ unsatisfied trait bound | - = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(RequestContext<()>, WebsocketConnection, WebsocketUpgrade) -> impl Future, HttpError>> {two_websocket_channels_adapter}` + = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(RequestContext<()>, WebsocketConnection, WebsocketUpgrade) -> impl Future> {two_websocket_channels_adapter}` note: required by a bound in `ApiEndpoint::::new` --> src/api_description.rs | diff --git a/dropshot/tests/fail/bad_channel18.stderr b/dropshot/tests/fail/bad_channel18.stderr index d1287c03e..50d75c793 100644 --- a/dropshot/tests/fail/bad_channel18.stderr +++ b/dropshot/tests/fail/bad_channel18.stderr @@ -77,7 +77,7 @@ note: required by a bound in `validate_websocket_connection_type` 26 | _query: Query, | ^^^^^ required by this bound in `validate_websocket_connection_type` -error[E0277]: the trait bound `fn(RequestContext<()>, WebsocketConnection, WebsocketUpgrade) -> impl Future, HttpError>> {websocket_channel_not_last_adapter}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied +error[E0277]: the trait bound `fn(RequestContext<()>, WebsocketConnection, WebsocketUpgrade) -> impl Future> {websocket_channel_not_last_adapter}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied --> tests/fail/bad_channel18.rs:23:1 | 19 | / #[channel { @@ -88,7 +88,7 @@ error[E0277]: the trait bound `fn(RequestContext<()>, WebsocketConnection, Webso 23 | async fn websocket_channel_not_last( | ^^^^^ unsatisfied trait bound | - = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(RequestContext<()>, WebsocketConnection, WebsocketUpgrade) -> impl Future, HttpError>> {websocket_channel_not_last_adapter}` + = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(RequestContext<()>, WebsocketConnection, WebsocketUpgrade) -> impl Future> {websocket_channel_not_last_adapter}` note: required by a bound in `ApiEndpoint::::new` --> src/api_description.rs | diff --git a/dropshot/tests/fail/bad_channel19.stderr b/dropshot/tests/fail/bad_channel19.stderr index 54e55d3c6..0903f873b 100644 --- a/dropshot/tests/fail/bad_channel19.stderr +++ b/dropshot/tests/fail/bad_channel19.stderr @@ -38,7 +38,7 @@ note: required by a bound in `need_shared_extractor` | ------ required by a bound in this function = note: this error originates in the attribute macro `channel` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `fn(RequestContext<()>, std::string::String, WebsocketUpgrade) -> impl Future, HttpError>> {non_extractor_as_last_argument_adapter}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied +error[E0277]: the trait bound `fn(RequestContext<()>, std::string::String, WebsocketUpgrade) -> impl Future> {non_extractor_as_last_argument_adapter}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied --> tests/fail/bad_channel19.rs:23:1 | 19 | / #[channel { @@ -49,7 +49,7 @@ error[E0277]: the trait bound `fn(RequestContext<()>, std::string::String, Webso 23 | async fn non_extractor_as_last_argument( | ^^^^^ unsatisfied trait bound | - = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(RequestContext<()>, std::string::String, WebsocketUpgrade) -> impl Future, HttpError>> {non_extractor_as_last_argument_adapter}` + = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(RequestContext<()>, std::string::String, WebsocketUpgrade) -> impl Future> {non_extractor_as_last_argument_adapter}` note: required by a bound in `ApiEndpoint::::new` --> src/api_description.rs | diff --git a/dropshot/tests/fail/bad_channel4.stderr b/dropshot/tests/fail/bad_channel4.stderr index 22e0f2faa..d7bb884aa 100644 --- a/dropshot/tests/fail/bad_channel4.stderr +++ b/dropshot/tests/fail/bad_channel4.stderr @@ -185,7 +185,7 @@ note: required by a bound in `dropshot::Query` | ^^^^^^^^^^^^^^^^ required by this bound in `Query` = note: this error originates in the attribute macro `channel` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `fn(RequestContext<()>, dropshot::Query, WebsocketUpgrade) -> impl Future, HttpError>> {bad_channel_adapter}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied +error[E0277]: the trait bound `fn(RequestContext<()>, dropshot::Query, WebsocketUpgrade) -> impl Future> {bad_channel_adapter}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied --> tests/fail/bad_channel4.rs:20:1 | 16 | / #[channel { @@ -196,7 +196,7 @@ error[E0277]: the trait bound `fn(RequestContext<()>, dropshot::Query` is not implemented for fn item `fn(RequestContext<()>, dropshot::Query, WebsocketUpgrade) -> impl Future, HttpError>> {bad_channel_adapter}` + = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(RequestContext<()>, dropshot::Query, WebsocketUpgrade) -> impl Future> {bad_channel_adapter}` note: required by a bound in `ApiEndpoint::::new` --> src/api_description.rs | diff --git a/dropshot/tests/fail/bad_channel5.stderr b/dropshot/tests/fail/bad_channel5.stderr index a5ba4bbb9..3e6d08202 100644 --- a/dropshot/tests/fail/bad_channel5.stderr +++ b/dropshot/tests/fail/bad_channel5.stderr @@ -96,7 +96,7 @@ note: required by a bound in `dropshot::Query` | ^^^^^^^^^^^^^^^^ required by this bound in `Query` = note: this error originates in the attribute macro `channel` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `fn(RequestContext<()>, dropshot::Query, WebsocketUpgrade) -> impl Future, HttpError>> {bad_channel_adapter}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied +error[E0277]: the trait bound `fn(RequestContext<()>, dropshot::Query, WebsocketUpgrade) -> impl Future> {bad_channel_adapter}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied --> tests/fail/bad_channel5.rs:22:1 | 18 | / #[channel { @@ -107,7 +107,7 @@ error[E0277]: the trait bound `fn(RequestContext<()>, dropshot::Query` is not implemented for fn item `fn(RequestContext<()>, dropshot::Query, WebsocketUpgrade) -> impl Future, HttpError>> {bad_channel_adapter}` + = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(RequestContext<()>, dropshot::Query, WebsocketUpgrade) -> impl Future> {bad_channel_adapter}` note: required by a bound in `ApiEndpoint::::new` --> src/api_description.rs | diff --git a/dropshot/tests/fail/bad_channel9.stderr b/dropshot/tests/fail/bad_channel9.stderr index 50b11810f..6450aabf5 100644 --- a/dropshot/tests/fail/bad_channel9.stderr +++ b/dropshot/tests/fail/bad_channel9.stderr @@ -26,7 +26,7 @@ help: the trait `RequestContextArgument` is implemented for `RequestContext` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the attribute macro `channel` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `fn(dropshot::Query, WebsocketUpgrade) -> impl Future, HttpError>> {bad_channel_adapter}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied +error[E0277]: the trait bound `fn(dropshot::Query, WebsocketUpgrade) -> impl Future> {bad_channel_adapter}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied --> tests/fail/bad_channel9.rs:23:1 | 19 | / #[channel { @@ -37,7 +37,7 @@ error[E0277]: the trait bound `fn(dropshot::Query, WebsocketUpgrade 23 | async fn bad_channel( | ^^^^^ unsatisfied trait bound | - = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(dropshot::Query, WebsocketUpgrade) -> impl Future, HttpError>> {bad_channel_adapter}` + = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(dropshot::Query, WebsocketUpgrade) -> impl Future> {bad_channel_adapter}` note: required by a bound in `ApiEndpoint::::new` --> src/api_description.rs | diff --git a/dropshot/tests/fail/bad_trait_channel17.stderr b/dropshot/tests/fail/bad_trait_channel17.stderr index f55434e1f..1f6c056b4 100644 --- a/dropshot/tests/fail/bad_trait_channel17.stderr +++ b/dropshot/tests/fail/bad_trait_channel17.stderr @@ -35,7 +35,7 @@ note: required by a bound in `ApiEndpoint::::new_for_types` | FuncParams: RequestExtractor + 'static, | ^^^^^^^^^^^^^^^^ required by this bound in `ApiEndpoint::::new_for_types` -error[E0277]: the trait bound `fn(dropshot::RequestContext<::Context>, dropshot::WebsocketConnection, WebsocketUpgrade) -> impl Future, HttpError>> {two_websocket_channels_adapter::}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied +error[E0277]: the trait bound `fn(dropshot::RequestContext<::Context>, dropshot::WebsocketConnection, WebsocketUpgrade) -> impl Future> {two_websocket_channels_adapter::}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied --> tests/fail/bad_trait_channel17.rs:15:5 | 10 | #[dropshot::api_description] @@ -48,7 +48,7 @@ error[E0277]: the trait bound `fn(dropshot::RequestContext<` is not implemented for fn item `fn(dropshot::RequestContext<::Context>, dropshot::WebsocketConnection, WebsocketUpgrade) -> impl Future, HttpError>> {two_websocket_channels_adapter::}` + = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(dropshot::RequestContext<::Context>, dropshot::WebsocketConnection, WebsocketUpgrade) -> impl Future> {two_websocket_channels_adapter::}` note: required by a bound in `ApiEndpoint::::new` --> src/api_description.rs | diff --git a/dropshot/tests/fail/bad_trait_channel18.stderr b/dropshot/tests/fail/bad_trait_channel18.stderr index 1f347ffe8..f49a0396c 100644 --- a/dropshot/tests/fail/bad_trait_channel18.stderr +++ b/dropshot/tests/fail/bad_trait_channel18.stderr @@ -74,7 +74,7 @@ note: required by a bound in `ApiEndpoint::::new_for_types` | FuncParams: RequestExtractor + 'static, | ^^^^^^^^^^^^^^^^ required by this bound in `ApiEndpoint::::new_for_types` -error[E0277]: the trait bound `fn(dropshot::RequestContext<::Context>, dropshot::WebsocketConnection, WebsocketUpgrade) -> impl Future, HttpError>> {websocket_channel_not_last_adapter::}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied +error[E0277]: the trait bound `fn(dropshot::RequestContext<::Context>, dropshot::WebsocketConnection, WebsocketUpgrade) -> impl Future> {websocket_channel_not_last_adapter::}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied --> tests/fail/bad_trait_channel18.rs:22:5 | 17 | #[dropshot::api_description] @@ -87,7 +87,7 @@ error[E0277]: the trait bound `fn(dropshot::RequestContext<` is not implemented for fn item `fn(dropshot::RequestContext<::Context>, dropshot::WebsocketConnection, WebsocketUpgrade) -> impl Future, HttpError>> {websocket_channel_not_last_adapter::}` + = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(dropshot::RequestContext<::Context>, dropshot::WebsocketConnection, WebsocketUpgrade) -> impl Future> {websocket_channel_not_last_adapter::}` note: required by a bound in `ApiEndpoint::::new` --> src/api_description.rs | diff --git a/dropshot/tests/fail/bad_trait_channel19.stderr b/dropshot/tests/fail/bad_trait_channel19.stderr index eafb33b5a..ebd41109f 100644 --- a/dropshot/tests/fail/bad_trait_channel19.stderr +++ b/dropshot/tests/fail/bad_trait_channel19.stderr @@ -35,7 +35,7 @@ note: required by a bound in `ApiEndpoint::::new_for_types` | FuncParams: RequestExtractor + 'static, | ^^^^^^^^^^^^^^^^ required by this bound in `ApiEndpoint::::new_for_types` -error[E0277]: the trait bound `fn(dropshot::RequestContext<::Context>, std::string::String, WebsocketUpgrade) -> impl Future, HttpError>> {middle_not_shared_extractor_adapter::}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied +error[E0277]: the trait bound `fn(dropshot::RequestContext<::Context>, std::string::String, WebsocketUpgrade) -> impl Future> {middle_not_shared_extractor_adapter::}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied --> tests/fail/bad_trait_channel19.rs:23:5 | 19 | #[dropshot::api_description] @@ -48,7 +48,7 @@ error[E0277]: the trait bound `fn(dropshot::RequestContext<` is not implemented for fn item `fn(dropshot::RequestContext<::Context>, std::string::String, WebsocketUpgrade) -> impl Future, HttpError>> {middle_not_shared_extractor_adapter::}` + = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(dropshot::RequestContext<::Context>, std::string::String, WebsocketUpgrade) -> impl Future> {middle_not_shared_extractor_adapter::}` note: required by a bound in `ApiEndpoint::::new` --> src/api_description.rs | diff --git a/dropshot/tests/fail/bad_trait_channel4.stderr b/dropshot/tests/fail/bad_trait_channel4.stderr index 5b5039151..80d1b2687 100644 --- a/dropshot/tests/fail/bad_trait_channel4.stderr +++ b/dropshot/tests/fail/bad_trait_channel4.stderr @@ -358,7 +358,7 @@ note: required by a bound in `dropshot::Query` | pub struct Query { | ^^^^^^^^^^^^^^^^ required by this bound in `Query` -error[E0277]: the trait bound `fn(dropshot::RequestContext<::Context>, dropshot::Query, WebsocketUpgrade) -> impl Future, HttpError>> {bad_channel_adapter::}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied +error[E0277]: the trait bound `fn(dropshot::RequestContext<::Context>, dropshot::Query, WebsocketUpgrade) -> impl Future> {bad_channel_adapter::}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied --> tests/fail/bad_trait_channel4.rs:20:5 | 16 | #[dropshot::api_description] @@ -371,7 +371,7 @@ error[E0277]: the trait bound `fn(dropshot::RequestContext<` is not implemented for fn item `fn(dropshot::RequestContext<::Context>, dropshot::Query, WebsocketUpgrade) -> impl Future, HttpError>> {bad_channel_adapter::}` + = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(dropshot::RequestContext<::Context>, dropshot::Query, WebsocketUpgrade) -> impl Future> {bad_channel_adapter::}` note: required by a bound in `ApiEndpoint::::new` --> src/api_description.rs | diff --git a/dropshot/tests/fail/bad_trait_channel5.stderr b/dropshot/tests/fail/bad_trait_channel5.stderr index 2b5d9b47d..1f1080537 100644 --- a/dropshot/tests/fail/bad_trait_channel5.stderr +++ b/dropshot/tests/fail/bad_trait_channel5.stderr @@ -187,7 +187,7 @@ note: required by a bound in `dropshot::Query` | pub struct Query { | ^^^^^^^^^^^^^^^^ required by this bound in `Query` -error[E0277]: the trait bound `fn(dropshot::RequestContext<::Context>, dropshot::Query, WebsocketUpgrade) -> impl Future, HttpError>> {bad_channel_adapter::}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied +error[E0277]: the trait bound `fn(dropshot::RequestContext<::Context>, dropshot::Query, WebsocketUpgrade) -> impl Future> {bad_channel_adapter::}: dropshot::handler::HttpHandlerFunc<_, _, _>` is not satisfied --> tests/fail/bad_trait_channel5.rs:21:5 | 17 | #[dropshot::api_description] @@ -200,7 +200,7 @@ error[E0277]: the trait bound `fn(dropshot::RequestContext<` is not implemented for fn item `fn(dropshot::RequestContext<::Context>, dropshot::Query, WebsocketUpgrade) -> impl Future, HttpError>> {bad_channel_adapter::}` + = help: the trait `dropshot::handler::HttpHandlerFunc<_, _, _>` is not implemented for fn item `fn(dropshot::RequestContext<::Context>, dropshot::Query, WebsocketUpgrade) -> impl Future> {bad_channel_adapter::}` note: required by a bound in `ApiEndpoint::::new` --> src/api_description.rs | From 41d5a632121a76bb5dae85f754f1ed6b12836fdc Mon Sep 17 00:00:00 2001 From: lif <> Date: Wed, 25 Feb 2026 07:29:22 +0000 Subject: [PATCH 4/5] Update expected OpenAPI generator output for WebSocket channels with explicit StatusCodes, and allowing descriptions in freeform responses --- dropshot/tests/test_openapi.json | 12 +++++++++--- dropshot/tests/test_openapi_fuller.json | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/dropshot/tests/test_openapi.json b/dropshot/tests/test_openapi.json index 9dfaeb021..2519f020b 100644 --- a/dropshot/tests/test_openapi.json +++ b/dropshot/tests/test_openapi.json @@ -328,13 +328,19 @@ ], "operationId": "vzerolower", "responses": { - "default": { - "description": "", + "101": { + "description": "Negotiating protocol upgrade from HTTP/1.1 to WebSocket", "content": { "*/*": { "schema": {} } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-websocket": {} @@ -348,7 +354,7 @@ "operationId": "handler18", "responses": { "200": { - "description": "", + "description": "successful operation", "content": { "*/*": { "schema": {} diff --git a/dropshot/tests/test_openapi_fuller.json b/dropshot/tests/test_openapi_fuller.json index bfc94458b..0cfba6ea3 100644 --- a/dropshot/tests/test_openapi_fuller.json +++ b/dropshot/tests/test_openapi_fuller.json @@ -336,13 +336,19 @@ ], "operationId": "vzerolower", "responses": { - "default": { - "description": "", + "101": { + "description": "Negotiating protocol upgrade from HTTP/1.1 to WebSocket", "content": { "*/*": { "schema": {} } } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-websocket": {} @@ -356,7 +362,7 @@ "operationId": "handler18", "responses": { "200": { - "description": "", + "description": "successful operation", "content": { "*/*": { "schema": {} From af3d9dc1aaf768b9baba6cc3eaaf60a73463e8cc Mon Sep 17 00:00:00 2001 From: "Adam H. Leventhal" Date: Thu, 5 Mar 2026 15:43:48 -0800 Subject: [PATCH 5/5] 101 response should have no content --- dropshot/src/api_description.rs | 2 +- dropshot/src/websocket.rs | 10 +++++++--- dropshot/tests/test_openapi.json | 7 +------ dropshot/tests/test_openapi_fuller.json | 7 +------ 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/dropshot/src/api_description.rs b/dropshot/src/api_description.rs index 357d24b60..51380a0d0 100644 --- a/dropshot/src/api_description.rs +++ b/dropshot/src/api_description.rs @@ -1,4 +1,4 @@ -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company //! Describes the endpoints and handler functions in your API diff --git a/dropshot/src/websocket.rs b/dropshot/src/websocket.rs index c69d7e37b..e9d5963a7 100644 --- a/dropshot/src/websocket.rs +++ b/dropshot/src/websocket.rs @@ -1,4 +1,5 @@ -// Copyright 2023 Oxide Computer Company +// Copyright 2026 Oxide Computer Company + //! Implements websocket upgrades as an Extractor for use in API route handler //! parameters to indicate that the given endpoint is meant to be upgraded to //! a websocket. @@ -6,7 +7,7 @@ //! This exposes a raw upgraded HTTP connection to a user-provided async future, //! which will be spawned to handle the incoming connection. -use crate::api_description::ExtensionMode; +use crate::api_description::{ApiSchemaGenerator, ExtensionMode}; use crate::body::Body; use crate::handler::HttpHandlerResult; use crate::{ @@ -66,7 +67,10 @@ impl HttpResponse for SwitchingToWebsocket { const UPGRADE_DESCRIPTION: &str = "Negotiating protocol upgrade from HTTP/1.1 to WebSocket"; ApiEndpointResponse { - schema: None, + schema: Some(ApiSchemaGenerator::Static { + schema: Box::new(schemars::schema::Schema::Bool(false)), + dependencies: Default::default(), + }), headers: vec![], success: Some(StatusCode::SWITCHING_PROTOCOLS), description: Some(UPGRADE_DESCRIPTION.to_string()), diff --git a/dropshot/tests/test_openapi.json b/dropshot/tests/test_openapi.json index 2519f020b..bf49feecf 100644 --- a/dropshot/tests/test_openapi.json +++ b/dropshot/tests/test_openapi.json @@ -329,12 +329,7 @@ "operationId": "vzerolower", "responses": { "101": { - "description": "Negotiating protocol upgrade from HTTP/1.1 to WebSocket", - "content": { - "*/*": { - "schema": {} - } - } + "description": "Negotiating protocol upgrade from HTTP/1.1 to WebSocket" }, "4XX": { "$ref": "#/components/responses/Error" diff --git a/dropshot/tests/test_openapi_fuller.json b/dropshot/tests/test_openapi_fuller.json index 0cfba6ea3..ffc25577c 100644 --- a/dropshot/tests/test_openapi_fuller.json +++ b/dropshot/tests/test_openapi_fuller.json @@ -337,12 +337,7 @@ "operationId": "vzerolower", "responses": { "101": { - "description": "Negotiating protocol upgrade from HTTP/1.1 to WebSocket", - "content": { - "*/*": { - "schema": {} - } - } + "description": "Negotiating protocol upgrade from HTTP/1.1 to WebSocket" }, "4XX": { "$ref": "#/components/responses/Error"