use serde::{Deserialize, Serialize};
use super::SelectorOperator;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SelectorRequirement {
pub key: String,
pub operator: SelectorOperator,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub values: Vec<String>,
}
impl SelectorRequirement {
pub fn validate(&self) -> crate::error::ModelResult<()> {
use std::borrow::Cow;
if self.key.is_empty() {
return Err(crate::ModelError::Invalid(Cow::Borrowed(
"selector requirement key must not be empty",
)));
}
match self.operator {
SelectorOperator::In | SelectorOperator::NotIn => {
if self.values.is_empty() {
return Err(crate::ModelError::Invalid(Cow::Owned(format!(
"selector requirement '{}' with operator {} must have non-empty values",
self.key, self.operator,
))));
}
}
SelectorOperator::Exists | SelectorOperator::DoesNotExist => {
if !self.values.is_empty() {
return Err(crate::ModelError::Invalid(Cow::Owned(format!(
"selector requirement '{}' with operator {} must have empty values",
self.key, self.operator,
))));
}
}
}
Ok(())
}
#[inline]
pub fn r#in(key: impl Into<String>, values: Vec<String>) -> Self {
Self {
key: key.into(),
operator: SelectorOperator::In,
values,
}
}
#[inline]
pub fn not_in(key: impl Into<String>, values: Vec<String>) -> Self {
Self {
key: key.into(),
operator: SelectorOperator::NotIn,
values,
}
}
#[inline]
pub fn exists(key: impl Into<String>) -> Self {
Self {
key: key.into(),
operator: SelectorOperator::Exists,
values: vec![],
}
}
#[inline]
pub fn does_not_exist(key: impl Into<String>) -> Self {
Self {
key: key.into(),
operator: SelectorOperator::DoesNotExist,
values: vec![],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn in_constructor() {
let req = SelectorRequirement::r#in("gpu", vec!["a100".into(), "h100".into()]);
assert_eq!(req.key, "gpu");
assert_eq!(req.operator, SelectorOperator::In);
assert_eq!(req.values, vec!["a100", "h100"]);
}
#[test]
fn not_in_constructor() {
let req = SelectorRequirement::not_in("zone", vec!["us-west".into()]);
assert_eq!(req.operator, SelectorOperator::NotIn);
}
#[test]
fn exists_constructor() {
let req = SelectorRequirement::exists("gpu");
assert_eq!(req.operator, SelectorOperator::Exists);
assert!(req.values.is_empty());
}
#[test]
fn does_not_exist_constructor() {
let req = SelectorRequirement::does_not_exist("tainted");
assert_eq!(req.operator, SelectorOperator::DoesNotExist);
assert!(req.values.is_empty());
}
#[test]
fn serde_roundtrip() {
let req = SelectorRequirement::r#in("tier", vec!["prod".into(), "staging".into()]);
let json = serde_json::to_string(&req).unwrap();
let back: SelectorRequirement = serde_json::from_str(&json).unwrap();
assert_eq!(back, req);
}
#[test]
fn serde_skips_empty_values() {
let req = SelectorRequirement::exists("gpu");
let json = serde_json::to_string(&req).unwrap();
assert!(
!json.contains("values"),
"empty values should be skipped: {json}"
);
}
}