swarm-secret-client: derive Kind's segment strings via strum instead of a hand-written match

This commit is contained in:
damocles 2026-09-11 20:41:52 +02:00
commit 0d6c003fa8
4 changed files with 41 additions and 9 deletions

22
Cargo.lock generated
View file

@ -4662,6 +4662,27 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.6.1" version = "2.6.1"
@ -4775,6 +4796,7 @@ dependencies = [
"rustify_derive", "rustify_derive",
"serde", "serde",
"serde_json", "serde_json",
"strum",
"thiserror 2.0.18", "thiserror 2.0.18",
"vaultrs", "vaultrs",
] ]

View file

@ -113,6 +113,10 @@ schemars = "1.0"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
similar = "2" similar = "2"
# `derive`-only: `Kind`'s segment strings via `#[strum(serialize = "...")]`
# instead of a hand-written match, so the derive macro is the single source
# of truth (see `swarm-secret-client::path::Kind`).
strum = { version = "0.28.0", features = ["derive"] }
tokio = { version = "1", features = [ tokio = { version = "1", features = [
"fs", "fs",
"io-util", "io-util",

View file

@ -13,6 +13,7 @@ reqwest.workspace = true
rustify.workspace = true rustify.workspace = true
rustify_derive.workspace = true rustify_derive.workspace = true
serde.workspace = true serde.workspace = true
strum.workspace = true
thiserror.workspace = true thiserror.workspace = true
vaultrs.workspace = true vaultrs.workspace = true

View file

@ -25,19 +25,28 @@ pub const ROOT: &str = "swarm";
/// misspelled kind is a 403 at provision time rather than anything a compiler /// misspelled kind is a 403 at provision time rather than anything a compiler
/// sees. [`Kind::ALL`] exists so a test can enumerate the set instead of /// sees. [`Kind::ALL`] exists so a test can enumerate the set instead of
/// restating it. /// restating it.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] ///
/// The segment string lives once, on the variant itself
/// (`#[strum(serialize = "...")]`), rather than a second time in a
/// hand-written `as_str()` match — [`strum::IntoStaticStr`] derives that
/// conversion, so the attribute is the only place a segment is spelled.
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::IntoStaticStr)]
pub enum Kind { pub enum Kind {
/// One agent container's own secrets. /// One agent container's own secrets.
#[strum(serialize = "agents")]
Agent, Agent,
/// One hive's secrets, held on behalf of whatever runs there. An identity /// One hive's secrets, held on behalf of whatever runs there. An identity
/// minted per hive rather than per agent lands here even though an agent /// minted per hive rather than per agent lands here even though an agent
/// is what uses it. /// is what uses it.
#[strum(serialize = "hives")]
Hive, Hive,
/// One swarm service — the things a swarm runs beside the controller. /// One swarm service — the things a swarm runs beside the controller.
#[strum(serialize = "services")]
Service, Service,
/// The controller itself. There is one per swarm, so the name segment /// The controller itself. There is one per swarm, so the name segment
/// below it does not vary; the shape stays uniform anyway, because one /// below it does not vary; the shape stays uniform anyway, because one
/// grant pattern over `<kind>/<name>` is cheaper than a special case. /// grant pattern over `<kind>/<name>` is cheaper than a special case.
#[strum(serialize = "controller")]
Controller, Controller,
} }
@ -47,15 +56,11 @@ impl Kind {
pub const ALL: [Kind; 4] = [Kind::Agent, Kind::Hive, Kind::Service, Kind::Controller]; pub const ALL: [Kind; 4] = [Kind::Agent, Kind::Hive, Kind::Service, Kind::Controller];
/// The path segment, which is also what the store's grant is written /// The path segment, which is also what the store's grant is written
/// against. /// against. Thin wrapper over the derived `Into<&'static str>` so call
/// sites keep the same method-call shape as before.
#[must_use] #[must_use]
pub const fn as_str(self) -> &'static str { pub fn as_str(self) -> &'static str {
match self { self.into()
Kind::Agent => "agents",
Kind::Hive => "hives",
Kind::Service => "services",
Kind::Controller => "controller",
}
} }
/// What to call the name in an error — singular, because the message reads /// What to call the name in an error — singular, because the message reads