use kube::CustomResource; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; pub fn sanitize_bouncer_name(input: &str) -> String { input .to_lowercase() .chars() .map(|c| if c.is_alphanumeric() || c == '-' { c } else { '-' }) .collect::() .trim_matches('-') .to_string() } // Minimal SojuBouncer spec -- just enough for the portal to create CRs. // The full CRD is defined in the soju-operator crate. #[derive(CustomResource, Debug, Clone, Serialize, Deserialize, JsonSchema)] #[kube( group = "irc.now", version = "v1alpha1", kind = "SojuBouncer", namespaced, status = "SojuBouncerStatus" )] pub struct SojuBouncerSpec { pub hostname: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub owner: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub hostname_cloak: Option, } #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct SojuBouncerStatus { #[serde(default)] pub conditions: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct SojuBouncerCondition { #[serde(rename = "type")] pub type_: String, pub status: String, #[serde(default)] pub reason: Option, #[serde(default)] pub message: Option, } #[cfg(test)] mod tests { use super::*; #[test] fn bouncer_name_sanitizes_input() { assert_eq!(sanitize_bouncer_name("My Bouncer!"), "my-bouncer"); assert_eq!(sanitize_bouncer_name("test_123"), "test-123"); } #[test] fn bouncer_name_trims_dashes() { assert_eq!(sanitize_bouncer_name("--hello--"), "hello"); } #[test] fn bouncer_name_handles_empty() { assert_eq!(sanitize_bouncer_name(""), ""); } }