use super::FilterFunction;
use crate::functions::metadata::{ArgumentMetadata, FunctionMetadata, SyntaxVariants};
use minijinja::value::Kwargs;
use minijinja::{Error, ErrorKind, Value};
const VALUE_ARG: ArgumentMetadata = ArgumentMetadata {
name: "value",
arg_type: "string",
required: true,
default: None,
description: "The string value to sanitize",
};
fn extract_string(value: &Value, fn_name: &str) -> Result<String, Error> {
value.as_str().map(|s| s.to_string()).ok_or_else(|| {
Error::new(
ErrorKind::InvalidOperation,
format!("{} requires a string, found: {}", fn_name, value),
)
})
}
pub struct K8sLabelSafe;
impl K8sLabelSafe {
fn compute(value: &str) -> String {
let mut result = value.to_lowercase();
result = result
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' {
c
} else {
'-'
}
})
.collect();
while result.contains("--") {
result = result.replace("--", "-");
}
result = result
.trim_matches(|c: char| !c.is_ascii_alphanumeric())
.to_string();
if result.len() > 63 {
result.truncate(63);
result = result
.trim_end_matches(|c: char| !c.is_ascii_alphanumeric())
.to_string();
}
if result.is_empty() {
result = "default".to_string();
}
result
}
}
impl FilterFunction for K8sLabelSafe {
const NAME: &'static str = "k8s_label_safe";
const METADATA: FunctionMetadata = FunctionMetadata {
name: "k8s_label_safe",
category: "kubernetes",
description: "Sanitize string to be Kubernetes label-safe (max 63 chars, alphanumeric/dashes/underscores/dots)",
arguments: &[VALUE_ARG],
return_type: "string",
examples: &[
"{{ k8s_label_safe(value=\"My App (v2.0)\") }}",
"{{ app_name | k8s_label_safe }}",
],
syntax: SyntaxVariants::FUNCTION_AND_FILTER,
};
fn call_as_function(kwargs: Kwargs) -> Result<Value, Error> {
let value: String = kwargs.get("value")?;
Ok(Value::from(Self::compute(&value)))
}
fn call_as_filter(value: &Value, _kwargs: Kwargs) -> Result<Value, Error> {
let input = extract_string(value, "k8s_label_safe")?;
Ok(Value::from(Self::compute(&input)))
}
}
pub struct K8sDnsLabelSafe;
impl K8sDnsLabelSafe {
fn compute(value: &str) -> String {
let mut result = value.to_lowercase();
result = result
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '-' {
c
} else {
'-'
}
})
.collect();
result = result.trim_matches('-').to_string();
while result.contains("--") {
result = result.replace("--", "-");
}
if result.len() > 63 {
result.truncate(63);
result = result.trim_end_matches('-').to_string();
}
if result.is_empty() {
result = "default".to_string();
}
result
}
}
impl FilterFunction for K8sDnsLabelSafe {
const NAME: &'static str = "k8s_dns_label_safe";
const METADATA: FunctionMetadata = FunctionMetadata {
name: "k8s_dns_label_safe",
category: "kubernetes",
description: "Format DNS-safe label (lowercase, alphanumeric and dashes only, max 63 chars)",
arguments: &[VALUE_ARG],
return_type: "string",
examples: &[
"{{ k8s_dns_label_safe(value=\"My Service Name\") }}",
"{{ service_name | k8s_dns_label_safe }}",
],
syntax: SyntaxVariants::FUNCTION_AND_FILTER,
};
fn call_as_function(kwargs: Kwargs) -> Result<Value, Error> {
let value: String = kwargs.get("value")?;
Ok(Value::from(Self::compute(&value)))
}
fn call_as_filter(value: &Value, _kwargs: Kwargs) -> Result<Value, Error> {
let input = extract_string(value, "k8s_dns_label_safe")?;
Ok(Value::from(Self::compute(&input)))
}
}
pub struct K8sAnnotationSafe;
impl K8sAnnotationSafe {
fn compute(value: &str) -> String {
let result: String = value
.chars()
.map(|c| {
if c == '\n' || c == '\t' || c == '\r' || c.is_control() {
' '
} else {
c
}
})
.collect();
if result.len() > 65536 {
result[..65536].to_string()
} else {
result
}
}
}
impl FilterFunction for K8sAnnotationSafe {
const NAME: &'static str = "k8s_annotation_safe";
const METADATA: FunctionMetadata = FunctionMetadata {
name: "k8s_annotation_safe",
category: "kubernetes",
description: "Sanitize string to be Kubernetes annotation-safe (replaces newlines/control chars)",
arguments: &[VALUE_ARG],
return_type: "string",
examples: &[
"{{ k8s_annotation_safe(value=\"Description with\\nnewlines\") }}",
"{{ description | k8s_annotation_safe }}",
],
syntax: SyntaxVariants::FUNCTION_AND_FILTER,
};
fn call_as_function(kwargs: Kwargs) -> Result<Value, Error> {
let value: String = kwargs.get("value")?;
Ok(Value::from(Self::compute(&value)))
}
fn call_as_filter(value: &Value, _kwargs: Kwargs) -> Result<Value, Error> {
let input = extract_string(value, "k8s_annotation_safe")?;
Ok(Value::from(Self::compute(&input)))
}
}