openapi: work around issue for flattenned untagged enum (fix #1249)

This commit is contained in:
Alex Auvolat 2026-01-06 09:55:42 +01:00
parent 02677af546
commit cf2f058f60
2 changed files with 113 additions and 43 deletions

View File

@ -103,7 +103,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AddBucketAliasRequest"
"$ref": "#/components/schemas/BucketAliasEnum"
}
}
},
@ -1409,7 +1409,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RemoveBucketAliasRequest"
"$ref": "#/components/schemas/BucketAliasEnum"
}
}
},
@ -1722,24 +1722,6 @@
},
"components": {
"schemas": {
"AddBucketAliasRequest": {
"allOf": [
{
"$ref": "#/components/schemas/BucketAliasEnum"
},
{
"type": "object",
"required": [
"bucketId"
],
"properties": {
"bucketId": {
"type": "string"
}
}
}
]
},
"AddBucketAliasResponse": {
"$ref": "#/components/schemas/GetBucketInfoResponse"
},
@ -1957,9 +1939,13 @@
{
"type": "object",
"required": [
"bucketId",
"globalAlias"
],
"properties": {
"bucketId": {
"type": "string"
},
"globalAlias": {
"type": "string"
}
@ -1968,6 +1954,7 @@
{
"type": "object",
"required": [
"bucketId",
"localAlias",
"accessKeyId"
],
@ -1975,6 +1962,9 @@
"accessKeyId": {
"type": "string"
},
"bucketId": {
"type": "string"
},
"localAlias": {
"type": "string"
}
@ -3912,6 +3902,46 @@
}
]
},
"NodeRoleChangeRequest": {
"oneOf": [
{
"type": "object",
"required": [
"id",
"remove"
],
"properties": {
"id": {
"type": "string",
"description": "ID of the node for which this change applies"
},
"remove": {
"type": "boolean",
"description": "Set `remove` to `true` to remove the node from the layout"
}
}
},
{
"allOf": [
{
"$ref": "#/components/schemas/NodeAssignedRole"
},
{
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"description": "ID of the node for which this change applies"
}
}
}
]
}
]
},
"NodeUpdateTrackers": {
"type": "object",
"required": [
@ -3973,24 +4003,6 @@
}
]
},
"RemoveBucketAliasRequest": {
"allOf": [
{
"$ref": "#/components/schemas/BucketAliasEnum"
},
{
"type": "object",
"required": [
"bucketId"
],
"properties": {
"bucketId": {
"type": "string"
}
}
}
]
},
"RemoveBucketAliasResponse": {
"$ref": "#/components/schemas/GetBucketInfoResponse"
},
@ -4180,7 +4192,7 @@
"roles": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NodeRoleChange"
"$ref": "#/components/schemas/NodeRoleChangeRequest"
},
"description": "New node roles to assign or remove in the cluster layout"
}

View File

@ -1,7 +1,8 @@
#![allow(dead_code)]
#![allow(non_snake_case)]
use utoipa::{Modify, OpenApi};
use serde::{Deserialize, Serialize};
use utoipa::{Modify, OpenApi, ToSchema};
use crate::api::*;
@ -246,7 +247,7 @@ For example to declare 100GB, you must set `capacity: 100000000000`.
Garage uses internally the International System of Units (SI), it assumes that 1kB = 1000 bytes, and displays storage as kB, MB, GB (and not KiB, MiB, GiB that assume 1KiB = 1024 bytes).
",
request_body(
content=UpdateClusterLayoutRequest,
content=UpdateClusterLayoutRequestOpenapi,
description="
To add a new node to the layout or to change the configuration of an existing node, simply set the values you want (`zone`, `capacity`, and `tags`).
To remove a node, simply pass the `remove: true` field.
@ -262,6 +263,45 @@ Contrary to the CLI that may update only a subset of the fields capacity, zone a
)]
fn UpdateClusterLayout() -> () {}
// Hack: we cannot use the UpdateClusterLayoutRequest from api.rs,
// as it contains (via NodeRoleChange) an untagged enum flattenned into
// a struct, which breaks the openapi generator.
// See issue #1249.
// Instead, we use a rewritten version of the NodeRoleChange struct where
// the struct fields are distributed into the enum variants (this is an equivalent
// representation, but this way we avoid having to rewrite all uses of the original
// struct in the Garage codebase).
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[schema(as = UpdateClusterLayoutRequest)]
pub struct UpdateClusterLayoutRequestOpenapi {
/// New node roles to assign or remove in the cluster layout
#[serde(default)]
pub roles: Vec<NodeRoleChangeOpenapi>,
/// New layout computation parameters to use
#[serde(default)]
pub parameters: Option<LayoutParameters>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[schema(as = NodeRoleChangeRequest)]
#[serde(untagged)]
pub enum NodeRoleChangeOpenapi {
#[serde(rename_all = "camelCase")]
Remove {
/// ID of the node for which this change applies
id: String,
/// Set `remove` to `true` to remove the node from the layout
remove: bool,
},
#[serde(rename_all = "camelCase")]
Update {
/// ID of the node for which this change applies
id: String,
#[serde(flatten)]
role: NodeAssignedRole,
},
}
#[utoipa::path(post,
path = "/v2/PreviewClusterLayoutChanges",
tag = "Cluster layout",
@ -586,7 +626,7 @@ fn DenyBucketKey() -> () {}
path = "/v2/AddBucketAlias",
tag = "Bucket alias",
description = "Add an alias for the target bucket. This can be either a global or a local alias, depending on which fields are specified.",
request_body = AddBucketAliasRequest,
request_body = BucketAliasEnumOpenapi,
responses(
(status = 200, description = "Returns exhaustive information about the bucket", body = AddBucketAliasResponse),
(status = 500, description = "Internal server error")
@ -598,7 +638,7 @@ fn AddBucketAlias() -> () {}
path = "/v2/RemoveBucketAlias",
tag = "Bucket alias",
description = "Remove an alias for the target bucket. This can be either a global or a local alias, depending on which fields are specified.",
request_body = RemoveBucketAliasRequest,
request_body = BucketAliasEnumOpenapi,
responses(
(status = 200, description = "Returns exhaustive information about the bucket", body = RemoveBucketAliasResponse),
(status = 500, description = "Internal server error")
@ -606,6 +646,24 @@ fn AddBucketAlias() -> () {}
)]
fn RemoveBucketAlias() -> () {}
// Hack for issue #1249 (see UpdateClusterLayout)
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(untagged)]
#[schema(as = BucketAliasEnum)]
pub enum BucketAliasEnumOpenapi {
#[serde(rename_all = "camelCase")]
Global {
bucket_id: String,
global_alias: String,
},
#[serde(rename_all = "camelCase")]
Local {
bucket_id: String,
local_alias: String,
access_key_id: String,
},
}
// **********************************************
// Node operations
// **********************************************