use tonic::metadata::{Ascii, MetadataValue};
use tonic::Request;
pub const NODE_METADATA_KEY: &str = "x-talos-node";
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum NodeTarget {
#[default]
Default,
Single(String),
Multiple(Vec<String>),
}
impl NodeTarget {
#[must_use]
pub fn none() -> Self {
Self::Default
}
#[must_use]
pub fn single(node: impl Into<String>) -> Self {
Self::Single(node.into())
}
#[must_use]
pub fn multiple(nodes: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self::Multiple(nodes.into_iter().map(Into::into).collect())
}
#[must_use]
pub fn from_csv(csv: &str) -> Self {
let nodes: Vec<String> = csv
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
match nodes.len() {
0 => Self::Default,
1 => Self::Single(nodes.into_iter().next().unwrap()),
_ => Self::Multiple(nodes),
}
}
#[must_use]
pub fn is_default(&self) -> bool {
matches!(self, Self::Default)
}
#[must_use]
pub fn is_single(&self) -> bool {
matches!(self, Self::Single(_))
}
#[must_use]
pub fn is_multiple(&self) -> bool {
matches!(self, Self::Multiple(_))
}
#[must_use]
pub fn nodes(&self) -> &[String] {
match self {
Self::Default => &[],
Self::Single(node) => std::slice::from_ref(node),
Self::Multiple(nodes) => nodes,
}
}
#[must_use]
pub fn first(&self) -> Option<&str> {
match self {
Self::Default => None,
Self::Single(node) => Some(node),
Self::Multiple(nodes) => nodes.first().map(String::as_str),
}
}
#[must_use]
pub fn to_csv(&self) -> Option<String> {
match self {
Self::Default => None,
Self::Single(node) => Some(node.clone()),
Self::Multiple(nodes) => Some(nodes.join(",")),
}
}
pub fn apply_to_request<T>(&self, mut request: Request<T>) -> Request<T> {
if let Some(node_value) = self.to_csv() {
if let Ok(metadata_value) = node_value.parse::<MetadataValue<Ascii>>() {
request
.metadata_mut()
.insert(NODE_METADATA_KEY, metadata_value);
}
}
request
}
}
impl From<&str> for NodeTarget {
fn from(s: &str) -> Self {
if s.is_empty() {
Self::Default
} else if s.contains(',') {
Self::from_csv(s)
} else {
Self::Single(s.to_string())
}
}
}
impl From<String> for NodeTarget {
fn from(s: String) -> Self {
Self::from(s.as_str())
}
}
impl From<Vec<String>> for NodeTarget {
fn from(nodes: Vec<String>) -> Self {
match nodes.len() {
0 => Self::Default,
1 => Self::Single(nodes.into_iter().next().unwrap()),
_ => Self::Multiple(nodes),
}
}
}
impl From<Option<String>> for NodeTarget {
fn from(opt: Option<String>) -> Self {
match opt {
Some(s) => Self::from(s),
None => Self::Default,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_node_target_default() {
let target = NodeTarget::none();
assert!(target.is_default());
assert!(!target.is_single());
assert!(!target.is_multiple());
assert_eq!(target.nodes(), &[] as &[String]);
assert_eq!(target.first(), None);
assert_eq!(target.to_csv(), None);
}
#[test]
fn test_node_target_single() {
let target = NodeTarget::single("192.168.1.10");
assert!(!target.is_default());
assert!(target.is_single());
assert!(!target.is_multiple());
assert_eq!(target.nodes(), &["192.168.1.10".to_string()]);
assert_eq!(target.first(), Some("192.168.1.10"));
assert_eq!(target.to_csv(), Some("192.168.1.10".to_string()));
}
#[test]
fn test_node_target_multiple() {
let target = NodeTarget::multiple(vec!["192.168.1.10", "192.168.1.11"]);
assert!(!target.is_default());
assert!(!target.is_single());
assert!(target.is_multiple());
assert_eq!(
target.nodes(),
&["192.168.1.10".to_string(), "192.168.1.11".to_string()]
);
assert_eq!(target.first(), Some("192.168.1.10"));
assert_eq!(
target.to_csv(),
Some("192.168.1.10,192.168.1.11".to_string())
);
}
#[test]
fn test_node_target_from_csv() {
assert!(NodeTarget::from_csv("").is_default());
assert_eq!(
NodeTarget::from_csv("192.168.1.10"),
NodeTarget::Single("192.168.1.10".to_string())
);
assert_eq!(
NodeTarget::from_csv("192.168.1.10, 192.168.1.11"),
NodeTarget::Multiple(vec!["192.168.1.10".to_string(), "192.168.1.11".to_string()])
);
}
#[test]
fn test_node_target_from_str() {
let target: NodeTarget = "192.168.1.10".into();
assert_eq!(target, NodeTarget::Single("192.168.1.10".to_string()));
let target: NodeTarget = "192.168.1.10,192.168.1.11".into();
assert!(target.is_multiple());
}
#[test]
fn test_node_target_from_vec() {
let target: NodeTarget = vec!["192.168.1.10".to_string()].into();
assert!(target.is_single());
let target: NodeTarget =
vec!["192.168.1.10".to_string(), "192.168.1.11".to_string()].into();
assert!(target.is_multiple());
let target: NodeTarget = Vec::<String>::new().into();
assert!(target.is_default());
}
#[test]
fn test_apply_to_request() {
let target = NodeTarget::single("192.168.1.10");
let request = Request::new(());
let request = target.apply_to_request(request);
let metadata = request.metadata().get(NODE_METADATA_KEY);
assert!(metadata.is_some());
assert_eq!(metadata.unwrap().to_str().unwrap(), "192.168.1.10");
}
#[test]
fn test_apply_to_request_multiple() {
let target = NodeTarget::multiple(vec!["10.0.0.1", "10.0.0.2"]);
let request = Request::new(());
let request = target.apply_to_request(request);
let metadata = request.metadata().get(NODE_METADATA_KEY);
assert!(metadata.is_some());
assert_eq!(metadata.unwrap().to_str().unwrap(), "10.0.0.1,10.0.0.2");
}
#[test]
fn test_apply_to_request_default() {
let target = NodeTarget::default();
let request = Request::new(());
let request = target.apply_to_request(request);
let metadata = request.metadata().get(NODE_METADATA_KEY);
assert!(metadata.is_none());
}
}