From d61867a27cc092ea6379ed01075c469e6a2d0d03 Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Wed, 1 Apr 2026 10:02:49 +0200 Subject: [PATCH 1/3] Remove deprecated DELETE /invocations/{invocation_id} from Schema OpenAPI --- crates/admin/src/rest_api/invocations.rs | 24 ++---------------------- crates/admin/src/rest_api/mod.rs | 7 ++++++- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/crates/admin/src/rest_api/invocations.rs b/crates/admin/src/rest_api/invocations.rs index af9e924a44..4ce3a587a9 100644 --- a/crates/admin/src/rest_api/invocations.rs +++ b/crates/admin/src/rest_api/invocations.rs @@ -36,7 +36,7 @@ use crate::generate_meta_api_error; use crate::rest_api::create_envelope_header; use crate::state::AdminServiceState; -#[derive(Debug, Default, Deserialize, utoipa::ToSchema)] +#[derive(Debug, Default, Deserialize)] pub enum DeletionMode { #[default] #[serde(alias = "cancel")] @@ -46,7 +46,7 @@ pub enum DeletionMode { #[serde(alias = "purge")] Purge, } -#[derive(Debug, Default, Deserialize, utoipa::ToSchema)] +#[derive(Debug, Default, Deserialize)] pub struct DeleteInvocationParams { pub mode: Option, } @@ -54,26 +54,6 @@ pub struct DeleteInvocationParams { /// Delete an invocation /// /// Use kill_invocation/cancel_invocation/purge_invocation instead. -#[utoipa::path( - delete, - path = "/invocations/{invocation_id}", - operation_id = "delete_invocation", - tag = "invocation", - params( - ("invocation_id" = String, Path, description = "Invocation identifier."), - ( - "mode" = Option, Query, - description = "If cancel, it will gracefully terminate the invocation. \ - If kill, it will terminate the invocation with a hard stop. \ - If purge, it will only cleanup the response for completed invocations, - and leave unaffected an in-flight invocation." - ), - ), - responses( - (status = 202, description = "Accepted"), - MetaApiError - ) -)] #[deprecated] pub async fn delete_invocation( State(mut state): State< diff --git a/crates/admin/src/rest_api/mod.rs b/crates/admin/src/rest_api/mod.rs index f8c815930d..a267764d25 100644 --- a/crates/admin/src/rest_api/mod.rs +++ b/crates/admin/src/rest_api/mod.rs @@ -102,7 +102,6 @@ where .routes(routes!(handlers::list_service_handlers)) .routes(routes!(handlers::get_service_handler)) // Invocation endpoints - .routes(routes!(invocations::delete_invocation)) .routes(routes!(invocations::kill_invocation)) .routes(routes!(invocations::cancel_invocation)) .routes(routes!(invocations::purge_invocation)) @@ -135,6 +134,12 @@ where "/deployments/{deployment}", axum::routing::put(deployments::update_deployment), ) + // Deprecated DELETE invocation + .route( + "/invocations/{invocation_id}", + #[allow(deprecated)] + axum::routing::delete(invocations::delete_invocation), + ) // Internal batch operation routes (for UI only, not documented in OpenAPI) .route( "/internal/invocations_batch_operations/kill", From 4113565efad33af0434441427776e5eda7ee3cdf Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Wed, 1 Apr 2026 17:24:20 +0200 Subject: [PATCH 2/3] Few changes to deployment response models/schemas, to make it working with openapi-generator. This makes no difference for serde, just changes the generated schema. --- cli/src/clients/admin_interface.rs | 16 +- crates/admin-rest-model/src/deployments.rs | 567 ++++++++++++--------- crates/admin-rest-model/src/invocations.rs | 30 +- crates/admin/src/rest_api/deployments.rs | 16 +- 4 files changed, 376 insertions(+), 253 deletions(-) diff --git a/cli/src/clients/admin_interface.rs b/cli/src/clients/admin_interface.rs index b22bb2ad88..3d25136d29 100644 --- a/cli/src/clients/admin_interface.rs +++ b/cli/src/clients/admin_interface.rs @@ -325,7 +325,7 @@ impl Deployment { deployment_response: DeploymentResponse, ) -> (DeploymentId, Self, Vec) { match deployment_response { - DeploymentResponse::Http { + DeploymentResponse::Http(HttpDeploymentResponse { id, uri, protocol_type, @@ -338,7 +338,7 @@ impl Deployment { metadata, sdk_version, .. - } => ( + }) => ( id, Deployment::Http { uri, @@ -353,7 +353,7 @@ impl Deployment { }, services, ), - DeploymentResponse::Lambda { + DeploymentResponse::Lambda(LambdaDeploymentResponse { id, arn, assume_role_arn, @@ -365,7 +365,7 @@ impl Deployment { metadata, sdk_version, .. - } => ( + }) => ( id, Deployment::Lambda { arn, @@ -386,7 +386,7 @@ impl Deployment { detailed_deployment_response: DetailedDeploymentResponse, ) -> (DeploymentId, Self, Vec) { match detailed_deployment_response { - DetailedDeploymentResponse::Http { + DetailedDeploymentResponse::Http(HttpDetailedDeploymentResponse { id, uri, protocol_type, @@ -399,7 +399,7 @@ impl Deployment { metadata, sdk_version, .. - } => ( + }) => ( id, Deployment::Http { uri, @@ -414,7 +414,7 @@ impl Deployment { }, services, ), - DetailedDeploymentResponse::Lambda { + DetailedDeploymentResponse::Lambda(LambdaDetailedDeploymentResponse { id, arn, assume_role_arn, @@ -426,7 +426,7 @@ impl Deployment { metadata, sdk_version, .. - } => ( + }) => ( id, Deployment::Lambda { arn, diff --git a/crates/admin-rest-model/src/deployments.rs b/crates/admin-rest-model/src/deployments.rs index 99bc2827d5..c765eea78f 100644 --- a/crates/admin-rest-model/src/deployments.rs +++ b/crates/admin-rest-model/src/deployments.rs @@ -186,304 +186,399 @@ pub struct ListDeploymentsResponse { pub deployments: Vec, } +/// Deployment response for HTTP deployments +#[serde_as] #[cfg_attr(feature = "schema", derive(utoipa::ToSchema))] #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(untagged)] -pub enum DeploymentResponse { - /// Deployment response for HTTP deployments - #[cfg_attr(feature = "schema", schema(title = "HttpDeploymentResponse"))] - Http { - /// # Deployment ID - id: DeploymentId, +pub struct HttpDeploymentResponse { + /// # Deployment ID + pub id: DeploymentId, - /// # Deployment URI - /// - /// URI used to invoke this service deployment. - #[serde(with = "serde_with::As::")] - #[cfg_attr(feature = "schema", schema(value_type = String, format = "uri"))] - uri: Uri, + /// # Deployment URI + /// + /// URI used to invoke this service deployment. + #[serde_as(as = "serde_with::DisplayFromStr")] + #[cfg_attr(feature = "schema", schema(value_type = String, format = "uri"))] + pub uri: Uri, - /// # Protocol Type - /// - /// Protocol type used to invoke this service deployment. - protocol_type: ProtocolType, + /// # Protocol Type + /// + /// Protocol type used to invoke this service deployment. + pub protocol_type: ProtocolType, - /// # HTTP Version - /// - /// HTTP Version used to invoke this service deployment. - #[serde(with = "http_serde::version")] - #[cfg_attr(feature = "schema", schema(value_type = String))] - http_version: Version, + /// # HTTP Version + /// + /// HTTP Version used to invoke this service deployment. + #[serde(with = "http_serde::version")] + #[cfg_attr(feature = "schema", schema(value_type = String))] + pub http_version: Version, - /// # Additional headers - /// - /// Additional headers used to invoke this service deployment. - #[serde(skip_serializing_if = "SerdeableHeaderHashMap::is_empty")] - #[serde(default)] - additional_headers: SerdeableHeaderHashMap, + /// # Additional headers + /// + /// Additional headers used to invoke this service deployment. + #[serde(skip_serializing_if = "SerdeableHeaderHashMap::is_empty")] + #[serde(default)] + pub additional_headers: SerdeableHeaderHashMap, - /// # Metadata - /// - /// Deployment metadata. - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - metadata: HashMap, + /// # Metadata + /// + /// Deployment metadata. + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub metadata: HashMap, - #[serde(with = "serde_with::As::")] - #[cfg_attr(feature = "schema", schema(value_type = String))] - created_at: humantime::Timestamp, + #[serde_as(as = "serde_with::DisplayFromStr")] + #[cfg_attr(feature = "schema", schema(value_type = String))] + pub created_at: humantime::Timestamp, - /// # Minimum Service Protocol version - /// - /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. - min_protocol_version: i32, + /// # Minimum Service Protocol version + /// + /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. + pub min_protocol_version: i32, - /// # Maximum Service Protocol version - /// - /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. - max_protocol_version: i32, + /// # Maximum Service Protocol version + /// + /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. + pub max_protocol_version: i32, - /// # SDK version - /// - /// SDK library and version declared during registration. - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - sdk_version: Option, + /// # SDK version + /// + /// SDK library and version declared during registration. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub sdk_version: Option, - /// # Services - /// - /// List of services exposed by this deployment. - services: Vec, + /// # Services + /// + /// List of services exposed by this deployment. + pub services: Vec, - /// # Info - /// - /// List of configuration/deprecation information related to this deployment. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - info: Vec, - }, - /// Deployment response for Lambda deployments - #[cfg_attr(feature = "schema", schema(title = "LambdaDeploymentResponse"))] - Lambda { - /// # Deployment ID - id: DeploymentId, + /// # Info + /// + /// List of configuration/deprecation information related to this deployment. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub info: Vec, +} - /// # Lambda ARN - /// - /// Lambda ARN used to invoke this service deployment. - arn: LambdaARN, +/// Deployment response for Lambda deployments +#[serde_as] +#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LambdaDeploymentResponse { + /// # Deployment ID + pub id: DeploymentId, - /// # Assume role ARN - /// - /// Assume role ARN used to invoke this deployment. Check https://docs.restate.dev/category/aws-lambda for more details. - #[serde(default, skip_serializing_if = "Option::is_none")] - assume_role_arn: Option, + /// # Lambda ARN + /// + /// Lambda ARN used to invoke this service deployment. + pub arn: LambdaARN, - /// # Compression - /// - /// Compression algorithm used for invoking Lambda. - #[serde(default, skip_serializing_if = "Option::is_none")] - compression: Option, + /// # Assume role ARN + /// + /// Assume role ARN used to invoke this deployment. Check https://docs.restate.dev/category/aws-lambda for more details. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub assume_role_arn: Option, - /// # Additional headers - /// - /// Additional headers used to invoke this service deployment. - #[serde(default, skip_serializing_if = "SerdeableHeaderHashMap::is_empty")] - additional_headers: SerdeableHeaderHashMap, + /// # Compression + /// + /// Compression algorithm used for invoking Lambda. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub compression: Option, - /// # Metadata - /// - /// Deployment metadata. - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - metadata: HashMap, + /// # Additional headers + /// + /// Additional headers used to invoke this service deployment. + #[serde(default, skip_serializing_if = "SerdeableHeaderHashMap::is_empty")] + pub additional_headers: SerdeableHeaderHashMap, - #[serde(with = "serde_with::As::")] - #[cfg_attr(feature = "schema", schema(value_type = String))] - created_at: humantime::Timestamp, + /// # Metadata + /// + /// Deployment metadata. + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub metadata: HashMap, - /// # Minimum Service Protocol version - /// - /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. - min_protocol_version: i32, + #[serde_as(as = "serde_with::DisplayFromStr")] + #[cfg_attr(feature = "schema", schema(value_type = String))] + pub created_at: humantime::Timestamp, - /// # Maximum Service Protocol version - /// - /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. - max_protocol_version: i32, + /// # Minimum Service Protocol version + /// + /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. + pub min_protocol_version: i32, - /// # SDK version - /// - /// SDK library and version declared during registration. - #[serde(default, skip_serializing_if = "Option::is_none")] - sdk_version: Option, + /// # Maximum Service Protocol version + /// + /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. + pub max_protocol_version: i32, - /// # Services - /// - /// List of services exposed by this deployment. - services: Vec, + /// # SDK version + /// + /// SDK library and version declared during registration. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sdk_version: Option, - /// # Info - /// - /// List of configuration/deprecation information related to this deployment. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - info: Vec, - }, + /// # Services + /// + /// List of services exposed by this deployment. + pub services: Vec, + + /// # Info + /// + /// List of configuration/deprecation information related to this deployment. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub info: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum DeploymentResponse { + Http(HttpDeploymentResponse), + Lambda(LambdaDeploymentResponse), +} + +#[cfg(feature = "schema")] +impl utoipa::__dev::ComposeSchema for DeploymentResponse { + fn compose( + _: Vec>, + ) -> utoipa::openapi::RefOr { + utoipa::openapi::schema::AnyOfBuilder::new() + .item(utoipa::openapi::Ref::from_schema_name( + ::name().to_string(), + )) + .item(utoipa::openapi::Ref::from_schema_name( + ::name().to_string(), + )) + .into() + } +} + +#[cfg(feature = "schema")] +impl utoipa::ToSchema for DeploymentResponse { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("DeploymentResponse") + } + + fn schemas( + schemas: &mut Vec<( + String, + utoipa::openapi::RefOr, + )>, + ) { + schemas.push(( + ::name().to_string(), + ::schema(), + )); + ::schemas(schemas); + schemas.push(( + ::name().to_string(), + ::schema(), + )); + ::schemas(schemas); + } } impl DeploymentResponse { pub fn id(&self) -> DeploymentId { match self { - Self::Http { id, .. } => *id, - Self::Lambda { id, .. } => *id, + Self::Http(h) => h.id, + Self::Lambda(l) => l.id, } } } -/// Detailed information about Restate deployments +/// Detailed deployment response for HTTP deployments +#[serde_as] #[cfg_attr(feature = "schema", derive(utoipa::ToSchema))] #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(untagged)] -pub enum DetailedDeploymentResponse { - /// Detailed deployment response for HTTP deployments - #[cfg_attr(feature = "schema", schema(title = "HttpDetailedDeploymentResponse"))] - Http { - /// # Deployment ID - id: DeploymentId, +pub struct HttpDetailedDeploymentResponse { + /// # Deployment ID + pub id: DeploymentId, - /// # Deployment URI - /// - /// URI used to invoke this service deployment. - #[serde(with = "serde_with::As::")] - #[cfg_attr(feature = "schema", schema(value_type = String, format = "uri"))] - uri: Uri, + /// # Deployment URI + /// + /// URI used to invoke this service deployment. + #[serde_as(as = "serde_with::DisplayFromStr")] + #[cfg_attr(feature = "schema", schema(value_type = String, format = "uri"))] + pub uri: Uri, - /// # Protocol Type - /// - /// Protocol type used to invoke this service deployment. - protocol_type: ProtocolType, + /// # Protocol Type + /// + /// Protocol type used to invoke this service deployment. + pub protocol_type: ProtocolType, - /// # HTTP Version - /// - /// HTTP Version used to invoke this service deployment. - #[serde(with = "http_serde::version")] - #[cfg_attr(feature = "schema", schema(value_type = String))] - http_version: Version, + /// # HTTP Version + /// + /// HTTP Version used to invoke this service deployment. + #[serde(with = "http_serde::version")] + #[cfg_attr(feature = "schema", schema(value_type = String))] + pub http_version: Version, - /// # Additional headers - /// - /// Additional headers used to invoke this service deployment. - #[serde(default, skip_serializing_if = "SerdeableHeaderHashMap::is_empty")] - additional_headers: SerdeableHeaderHashMap, + /// # Additional headers + /// + /// Additional headers used to invoke this service deployment. + #[serde(default, skip_serializing_if = "SerdeableHeaderHashMap::is_empty")] + pub additional_headers: SerdeableHeaderHashMap, - /// # Metadata - /// - /// Deployment metadata. - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - metadata: HashMap, + /// # Metadata + /// + /// Deployment metadata. + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub metadata: HashMap, - #[serde(with = "serde_with::As::")] - #[cfg_attr(feature = "schema", schema(value_type = String))] - created_at: humantime::Timestamp, + #[serde_as(as = "serde_with::DisplayFromStr")] + #[cfg_attr(feature = "schema", schema(value_type = String))] + pub created_at: humantime::Timestamp, - /// # Minimum Service Protocol version - /// - /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. - min_protocol_version: i32, + /// # Minimum Service Protocol version + /// + /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. + pub min_protocol_version: i32, - /// # Maximum Service Protocol version - /// - /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. - max_protocol_version: i32, + /// # Maximum Service Protocol version + /// + /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. + pub max_protocol_version: i32, - /// # SDK version - /// - /// SDK library and version declared during registration. - #[serde(default, skip_serializing_if = "Option::is_none")] - sdk_version: Option, + /// # SDK version + /// + /// SDK library and version declared during registration. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sdk_version: Option, - /// # Services - /// - /// List of services exposed by this deployment. - services: Vec, + /// # Services + /// + /// List of services exposed by this deployment. + pub services: Vec, - /// # Info - /// - /// List of configuration/deprecation information related to this deployment. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - info: Vec, - }, - /// Detailed deployment response for Lambda deployments - #[cfg_attr(feature = "schema", schema(title = "LambdaDetailedDeploymentResponse"))] - Lambda { - /// # Deployment ID - id: DeploymentId, + /// # Info + /// + /// List of configuration/deprecation information related to this deployment. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub info: Vec, +} - /// # Lambda ARN - /// - /// Lambda ARN used to invoke this service deployment. - arn: LambdaARN, +/// Detailed deployment response for Lambda deployments +#[serde_as] +#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LambdaDetailedDeploymentResponse { + /// # Deployment ID + pub id: DeploymentId, - /// # Assume role ARN - /// - /// Assume role ARN used to invoke this deployment. Check https://docs.restate.dev/category/aws-lambda for more details. - #[serde(default, skip_serializing_if = "Option::is_none")] - assume_role_arn: Option, + /// # Lambda ARN + /// + /// Lambda ARN used to invoke this service deployment. + pub arn: LambdaARN, - /// # Compression - /// - /// Compression algorithm used for invoking Lambda. - #[serde(default, skip_serializing_if = "Option::is_none")] - compression: Option, + /// # Assume role ARN + /// + /// Assume role ARN used to invoke this deployment. Check https://docs.restate.dev/category/aws-lambda for more details. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub assume_role_arn: Option, - /// # Additional headers - /// - /// Additional headers used to invoke this service deployment. - #[serde(default, skip_serializing_if = "SerdeableHeaderHashMap::is_empty")] - additional_headers: SerdeableHeaderHashMap, + /// # Compression + /// + /// Compression algorithm used for invoking Lambda. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub compression: Option, - /// # Metadata - /// - /// Deployment metadata. - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - metadata: HashMap, + /// # Additional headers + /// + /// Additional headers used to invoke this service deployment. + #[serde(default, skip_serializing_if = "SerdeableHeaderHashMap::is_empty")] + pub additional_headers: SerdeableHeaderHashMap, - #[serde(with = "serde_with::As::")] - #[cfg_attr(feature = "schema", schema(value_type = String))] - created_at: humantime::Timestamp, + /// # Metadata + /// + /// Deployment metadata. + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub metadata: HashMap, - /// # Minimum Service Protocol version - /// - /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. - min_protocol_version: i32, + #[serde_as(as = "serde_with::DisplayFromStr")] + #[cfg_attr(feature = "schema", schema(value_type = String))] + pub created_at: humantime::Timestamp, - /// # Maximum Service Protocol version - /// - /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. - max_protocol_version: i32, + /// # Minimum Service Protocol version + /// + /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. + pub min_protocol_version: i32, - /// # SDK version - /// - /// SDK library and version declared during registration. - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(default)] - sdk_version: Option, + /// # Maximum Service Protocol version + /// + /// During registration, the SDKs declare a range from minimum (included) to maximum (included) Service Protocol supported version. + pub max_protocol_version: i32, - /// # Services - /// - /// List of services exposed by this deployment. - services: Vec, + /// # SDK version + /// + /// SDK library and version declared during registration. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sdk_version: Option, - /// # Info - /// - /// List of configuration/deprecation information related to this deployment. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - info: Vec, - }, + /// # Services + /// + /// List of services exposed by this deployment. + pub services: Vec, + + /// # Info + /// + /// List of configuration/deprecation information related to this deployment. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub info: Vec, +} + +/// Detailed information about Restate deployments +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum DetailedDeploymentResponse { + Http(HttpDetailedDeploymentResponse), + Lambda(LambdaDetailedDeploymentResponse), +} + +#[cfg(feature = "schema")] +impl utoipa::__dev::ComposeSchema for DetailedDeploymentResponse { + fn compose( + _: Vec>, + ) -> utoipa::openapi::RefOr { + utoipa::openapi::schema::AnyOfBuilder::new() + .item(utoipa::openapi::Ref::from_schema_name( + ::name().to_string(), + )) + .item(utoipa::openapi::Ref::from_schema_name( + ::name().to_string(), + )) + .into() + } +} + +#[cfg(feature = "schema")] +impl utoipa::ToSchema for DetailedDeploymentResponse { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("DetailedDeploymentResponse") + } + + fn schemas( + schemas: &mut Vec<( + String, + utoipa::openapi::RefOr, + )>, + ) { + schemas.push(( + ::name().to_string(), + ::schema(), + )); + ::schemas(schemas); + schemas.push(( + ::name().to_string(), + ::schema(), + )); + ::schemas(schemas); + } } impl DetailedDeploymentResponse { pub fn id(&self) -> DeploymentId { match self { - Self::Http { id, .. } => *id, - Self::Lambda { id, .. } => *id, + Self::Http(h) => h.id, + Self::Lambda(l) => l.id, } } } diff --git a/crates/admin-rest-model/src/invocations.rs b/crates/admin-rest-model/src/invocations.rs index d93a42e280..d7c3e3bcbc 100644 --- a/crates/admin-rest-model/src/invocations.rs +++ b/crates/admin-rest-model/src/invocations.rs @@ -13,8 +13,12 @@ use restate_types::invocation::client as invocation_client; use serde::{Deserialize, Serialize}; /// Specifies which deployment to use when resuming or restarting an invocation. +/// +/// Can be one of: +/// - `"Keep"` or `"keep"`: Keep the currently pinned deployment +/// - `"Latest"` or `"latest"`: Use the latest deployment +/// - Any other string: Use the specified deployment ID #[derive(Debug, Default, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))] pub enum PatchDeploymentId { /// Keep the currently pinned deployment #[default] @@ -28,6 +32,30 @@ pub enum PatchDeploymentId { Id(String), } +// We just generate a simple string type here, +// because utoipa won't be able to generate a good schema by itself for this query param +#[cfg(feature = "schema")] +impl utoipa::PartialSchema for PatchDeploymentId { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::ObjectBuilder::new() + .schema_type(utoipa::openapi::schema::Type::String) + .description(Some( + "Specifies which deployment to use. \ + Use 'keep' to keep the currently pinned deployment, \ + 'latest' to use the latest deployment, \ + or provide a specific deployment ID.", + )) + .into() + } +} + +#[cfg(feature = "schema")] +impl utoipa::ToSchema for PatchDeploymentId { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("PatchDeploymentId") + } +} + impl PatchDeploymentId { /// Convert to the internal client representation. /// Returns an error string if the deployment ID cannot be parsed. diff --git a/crates/admin/src/rest_api/deployments.rs b/crates/admin/src/rest_api/deployments.rs index a3ea192b2c..d8a699f56a 100644 --- a/crates/admin/src/rest_api/deployments.rs +++ b/crates/admin/src/rest_api/deployments.rs @@ -448,7 +448,7 @@ fn to_deployment_response( http_version, protocol_type, address, - } => DeploymentResponse::Http { + } => DeploymentResponse::Http(HttpDeploymentResponse { id, uri: address, protocol_type, @@ -464,12 +464,12 @@ fn to_deployment_response( .map(|(name, revision)| ServiceNameRevPair { name, revision }) .collect(), info, - }, + }), DeploymentType::Lambda { arn, assume_role_arn, compression, - } => DeploymentResponse::Lambda { + } => DeploymentResponse::Lambda(LambdaDeploymentResponse { id, arn, assume_role_arn: assume_role_arn.map(Into::into), @@ -485,7 +485,7 @@ fn to_deployment_response( .map(|(name, revision)| ServiceNameRevPair { name, revision }) .collect(), info, - }, + }), } } @@ -508,7 +508,7 @@ fn to_detailed_deployment_response( http_version, protocol_type, address, - } => DetailedDeploymentResponse::Http { + } => DetailedDeploymentResponse::Http(HttpDetailedDeploymentResponse { id, uri: address, protocol_type, @@ -521,12 +521,12 @@ fn to_detailed_deployment_response( sdk_version, services, info, - }, + }), DeploymentType::Lambda { arn, assume_role_arn, compression, - } => DetailedDeploymentResponse::Lambda { + } => DetailedDeploymentResponse::Lambda(LambdaDetailedDeploymentResponse { id, arn, assume_role_arn: assume_role_arn.map(Into::into), @@ -539,7 +539,7 @@ fn to_detailed_deployment_response( sdk_version, services, info, - }, + }), } } From eb4a7cb2d91ae6911c5a85569750a884cbc841b0 Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Thu, 2 Apr 2026 10:54:48 +0200 Subject: [PATCH 3/3] Add type discriminator to DeploymentResponse and DetailedDeploymentResponse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch DeploymentResponse and DetailedDeploymentResponse from #[serde(untagged)] to #[serde(tag = "type", rename_all = "lowercase")], and replace the manual ComposeSchema/ToSchema impls with derive. The untagged representation was problematic for OpenAPI code generators: utoipa emits oneOf for untagged enums, and the Java openapi-generator deserializer for oneOf tries ALL variants and requires exactly 1 match. With a lenient ObjectMapper (FAIL_ON_UNKNOWN_PROPERTIES=false), both Http and Lambda variants would match, causing deserialization failures. The previous workaround used manual anyOf schema impls, but that only fixes the "both match" problem — with anyOf first-match semantics, Lambda responses would still incorrectly deserialize as Http (the unknown Lambda-specific fields get silently dropped). Using an internally tagged enum with a "type" discriminator solves both issues: code generators use the discriminator value for unambiguous variant selection, and the schema is generated correctly by utoipa's derive without manual impls. The CLI defines its own local untagged versions of these enums for backward-compatible deserialization — it reuses the same inner structs (HttpDeploymentResponse, etc.) but wraps them in #[serde(untagged)] enums so the CLI can still talk to older servers that don't emit the "type" field. Co-Authored-By: Claude Opus 4.6 (1M context) --- cli/src/clients/admin_interface.rs | 48 +++++++++++- cli/src/clients/mod.rs | 2 + cli/src/commands/deployments/register.rs | 6 +- cli/src/commands/services/list.rs | 2 +- crates/admin-rest-model/src/deployments.rs | 88 +--------------------- 5 files changed, 57 insertions(+), 89 deletions(-) diff --git a/cli/src/clients/admin_interface.rs b/cli/src/clients/admin_interface.rs index 3d25136d29..a30684f9a3 100644 --- a/cli/src/clients/admin_interface.rs +++ b/cli/src/clients/admin_interface.rs @@ -14,7 +14,11 @@ use futures::StreamExt; use futures::stream; use http::{Uri, Version}; use indicatif::ProgressBar; -use restate_admin_rest_model::deployments::*; +use restate_admin_rest_model::deployments::{ + HttpDeploymentResponse, HttpDetailedDeploymentResponse, LambdaDeploymentResponse, + LambdaDetailedDeploymentResponse, RegisterDeploymentRequest, RegisterDeploymentResponse, + ServiceNameRevPair, +}; use restate_admin_rest_model::invocations::RestartAsNewInvocationResponse; use restate_admin_rest_model::services::*; use restate_admin_rest_model::version::VersionInformation; @@ -23,8 +27,50 @@ use restate_serde_util::SerdeableHeaderHashMap; use restate_types::identifiers::{DeploymentId, LambdaARN}; use restate_types::schema::deployment::ProtocolType; use restate_types::schema::service::ServiceMetadata; +use serde::Deserialize; use std::collections::HashMap; +// Local untagged versions of deployment response enums for backward-compatible deserialization. +// The server model uses #[serde(tag = "type")] for OpenAPI discriminator support, but the CLI +// needs #[serde(untagged)] to also work with older servers that don't emit the "type" field. + +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +pub enum DeploymentResponse { + Http(HttpDeploymentResponse), + Lambda(LambdaDeploymentResponse), +} + +impl DeploymentResponse { + pub fn id(&self) -> DeploymentId { + match self { + Self::Http(h) => h.id, + Self::Lambda(l) => l.id, + } + } +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +pub enum DetailedDeploymentResponse { + Http(HttpDetailedDeploymentResponse), + Lambda(LambdaDetailedDeploymentResponse), +} + +impl DetailedDeploymentResponse { + pub fn id(&self) -> DeploymentId { + match self { + Self::Http(h) => h.id, + Self::Lambda(l) => l.id, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct ListDeploymentsResponse { + pub deployments: Vec, +} + const MAX_PARALLEL_REQUESTS: usize = 500; pub trait AdminClientInterface { diff --git a/cli/src/clients/mod.rs b/cli/src/clients/mod.rs index 52880db6ac..7d0a747a65 100644 --- a/cli/src/clients/mod.rs +++ b/cli/src/clients/mod.rs @@ -20,5 +20,7 @@ pub use self::admin_client::AdminClient; pub use self::admin_client::Error as MetasClientError; pub use self::admin_client::{MAX_ADMIN_API_VERSION, MIN_ADMIN_API_VERSION}; pub use self::admin_interface::Deployment; +pub use self::admin_interface::DeploymentResponse; +pub use self::admin_interface::DetailedDeploymentResponse; pub use self::admin_interface::{AdminClientInterface, batch_execute}; pub use self::datafusion_http_client::DataFusionHttpClient; diff --git a/cli/src/commands/deployments/register.rs b/cli/src/commands/deployments/register.rs index 0830d8c63c..8756c5e05d 100644 --- a/cli/src/commands/deployments/register.rs +++ b/cli/src/commands/deployments/register.rs @@ -19,9 +19,9 @@ use http::{HeaderName, HeaderValue, StatusCode, Uri}; use indicatif::ProgressBar; use indoc::indoc; -use restate_admin_rest_model::deployments::{ - DetailedDeploymentResponse, RegisterDeploymentRequest, RegisterDeploymentResponse, -}; +use restate_admin_rest_model::deployments::{RegisterDeploymentRequest, RegisterDeploymentResponse}; + +use crate::clients::DetailedDeploymentResponse; use restate_admin_rest_model::version::AdminApiVersion; use restate_cli_util::ui::console::{Styled, StyledTable, confirm_or_exit}; use restate_cli_util::ui::stylesheet::Style; diff --git a/cli/src/commands/services/list.rs b/cli/src/commands/services/list.rs index 067fb2a2fb..24f69148d5 100644 --- a/cli/src/commands/services/list.rs +++ b/cli/src/commands/services/list.rs @@ -14,7 +14,7 @@ use anyhow::{Context, Result}; use cling::prelude::*; use comfy_table::Table; -use restate_admin_rest_model::deployments::DeploymentResponse; +use crate::clients::DeploymentResponse; use restate_cli_util::ui::console::StyledTable; use restate_cli_util::ui::watcher::Watch; use restate_cli_util::{c_error, c_println}; diff --git a/crates/admin-rest-model/src/deployments.rs b/crates/admin-rest-model/src/deployments.rs index c765eea78f..09d09d5285 100644 --- a/crates/admin-rest-model/src/deployments.rs +++ b/crates/admin-rest-model/src/deployments.rs @@ -328,54 +328,14 @@ pub struct LambdaDeploymentResponse { pub info: Vec, } +#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))] #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(untagged)] +#[serde(tag = "type", rename_all = "lowercase")] pub enum DeploymentResponse { Http(HttpDeploymentResponse), Lambda(LambdaDeploymentResponse), } -#[cfg(feature = "schema")] -impl utoipa::__dev::ComposeSchema for DeploymentResponse { - fn compose( - _: Vec>, - ) -> utoipa::openapi::RefOr { - utoipa::openapi::schema::AnyOfBuilder::new() - .item(utoipa::openapi::Ref::from_schema_name( - ::name().to_string(), - )) - .item(utoipa::openapi::Ref::from_schema_name( - ::name().to_string(), - )) - .into() - } -} - -#[cfg(feature = "schema")] -impl utoipa::ToSchema for DeploymentResponse { - fn name() -> std::borrow::Cow<'static, str> { - std::borrow::Cow::Borrowed("DeploymentResponse") - } - - fn schemas( - schemas: &mut Vec<( - String, - utoipa::openapi::RefOr, - )>, - ) { - schemas.push(( - ::name().to_string(), - ::schema(), - )); - ::schemas(schemas); - schemas.push(( - ::name().to_string(), - ::schema(), - )); - ::schemas(schemas); - } -} - impl DeploymentResponse { pub fn id(&self) -> DeploymentId { match self { @@ -526,54 +486,14 @@ pub struct LambdaDetailedDeploymentResponse { } /// Detailed information about Restate deployments +#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))] #[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(untagged)] +#[serde(tag = "type", rename_all = "lowercase")] pub enum DetailedDeploymentResponse { Http(HttpDetailedDeploymentResponse), Lambda(LambdaDetailedDeploymentResponse), } -#[cfg(feature = "schema")] -impl utoipa::__dev::ComposeSchema for DetailedDeploymentResponse { - fn compose( - _: Vec>, - ) -> utoipa::openapi::RefOr { - utoipa::openapi::schema::AnyOfBuilder::new() - .item(utoipa::openapi::Ref::from_schema_name( - ::name().to_string(), - )) - .item(utoipa::openapi::Ref::from_schema_name( - ::name().to_string(), - )) - .into() - } -} - -#[cfg(feature = "schema")] -impl utoipa::ToSchema for DetailedDeploymentResponse { - fn name() -> std::borrow::Cow<'static, str> { - std::borrow::Cow::Borrowed("DetailedDeploymentResponse") - } - - fn schemas( - schemas: &mut Vec<( - String, - utoipa::openapi::RefOr, - )>, - ) { - schemas.push(( - ::name().to_string(), - ::schema(), - )); - ::schemas(schemas); - schemas.push(( - ::name().to_string(), - ::schema(), - )); - ::schemas(schemas); - } -} - impl DetailedDeploymentResponse { pub fn id(&self) -> DeploymentId { match self {