talos_api_rs/client/
node_target.rs1use tonic::metadata::{Ascii, MetadataValue};
30use tonic::Request;
31
32pub const NODE_METADATA_KEY: &str = "x-talos-node";
34
35#[derive(Debug, Clone, PartialEq, Eq, Default)]
37pub enum NodeTarget {
38 #[default]
40 Default,
41 Single(String),
43 Multiple(Vec<String>),
45}
46
47impl NodeTarget {
48 #[must_use]
50 pub fn none() -> Self {
51 Self::Default
52 }
53
54 #[must_use]
56 pub fn single(node: impl Into<String>) -> Self {
57 Self::Single(node.into())
58 }
59
60 #[must_use]
62 pub fn multiple(nodes: impl IntoIterator<Item = impl Into<String>>) -> Self {
63 Self::Multiple(nodes.into_iter().map(Into::into).collect())
64 }
65
66 #[must_use]
68 pub fn from_csv(csv: &str) -> Self {
69 let nodes: Vec<String> = csv
70 .split(',')
71 .map(|s| s.trim().to_string())
72 .filter(|s| !s.is_empty())
73 .collect();
74
75 match nodes.len() {
76 0 => Self::Default,
77 1 => Self::Single(nodes.into_iter().next().unwrap()),
78 _ => Self::Multiple(nodes),
79 }
80 }
81
82 #[must_use]
84 pub fn is_default(&self) -> bool {
85 matches!(self, Self::Default)
86 }
87
88 #[must_use]
90 pub fn is_single(&self) -> bool {
91 matches!(self, Self::Single(_))
92 }
93
94 #[must_use]
96 pub fn is_multiple(&self) -> bool {
97 matches!(self, Self::Multiple(_))
98 }
99
100 #[must_use]
102 pub fn nodes(&self) -> &[String] {
103 match self {
104 Self::Default => &[],
105 Self::Single(node) => std::slice::from_ref(node),
106 Self::Multiple(nodes) => nodes,
107 }
108 }
109
110 #[must_use]
112 pub fn first(&self) -> Option<&str> {
113 match self {
114 Self::Default => None,
115 Self::Single(node) => Some(node),
116 Self::Multiple(nodes) => nodes.first().map(String::as_str),
117 }
118 }
119
120 #[must_use]
122 pub fn to_csv(&self) -> Option<String> {
123 match self {
124 Self::Default => None,
125 Self::Single(node) => Some(node.clone()),
126 Self::Multiple(nodes) => Some(nodes.join(",")),
127 }
128 }
129
130 pub fn apply_to_request<T>(&self, mut request: Request<T>) -> Request<T> {
132 if let Some(node_value) = self.to_csv() {
133 if let Ok(metadata_value) = node_value.parse::<MetadataValue<Ascii>>() {
134 request
135 .metadata_mut()
136 .insert(NODE_METADATA_KEY, metadata_value);
137 }
138 }
139 request
140 }
141}
142
143impl From<&str> for NodeTarget {
144 fn from(s: &str) -> Self {
145 if s.is_empty() {
146 Self::Default
147 } else if s.contains(',') {
148 Self::from_csv(s)
149 } else {
150 Self::Single(s.to_string())
151 }
152 }
153}
154
155impl From<String> for NodeTarget {
156 fn from(s: String) -> Self {
157 Self::from(s.as_str())
158 }
159}
160
161impl From<Vec<String>> for NodeTarget {
162 fn from(nodes: Vec<String>) -> Self {
163 match nodes.len() {
164 0 => Self::Default,
165 1 => Self::Single(nodes.into_iter().next().unwrap()),
166 _ => Self::Multiple(nodes),
167 }
168 }
169}
170
171impl From<Option<String>> for NodeTarget {
172 fn from(opt: Option<String>) -> Self {
173 match opt {
174 Some(s) => Self::from(s),
175 None => Self::Default,
176 }
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn test_node_target_default() {
186 let target = NodeTarget::none();
187 assert!(target.is_default());
188 assert!(!target.is_single());
189 assert!(!target.is_multiple());
190 assert_eq!(target.nodes(), &[] as &[String]);
191 assert_eq!(target.first(), None);
192 assert_eq!(target.to_csv(), None);
193 }
194
195 #[test]
196 fn test_node_target_single() {
197 let target = NodeTarget::single("192.168.1.10");
198 assert!(!target.is_default());
199 assert!(target.is_single());
200 assert!(!target.is_multiple());
201 assert_eq!(target.nodes(), &["192.168.1.10".to_string()]);
202 assert_eq!(target.first(), Some("192.168.1.10"));
203 assert_eq!(target.to_csv(), Some("192.168.1.10".to_string()));
204 }
205
206 #[test]
207 fn test_node_target_multiple() {
208 let target = NodeTarget::multiple(vec!["192.168.1.10", "192.168.1.11"]);
209 assert!(!target.is_default());
210 assert!(!target.is_single());
211 assert!(target.is_multiple());
212 assert_eq!(
213 target.nodes(),
214 &["192.168.1.10".to_string(), "192.168.1.11".to_string()]
215 );
216 assert_eq!(target.first(), Some("192.168.1.10"));
217 assert_eq!(
218 target.to_csv(),
219 Some("192.168.1.10,192.168.1.11".to_string())
220 );
221 }
222
223 #[test]
224 fn test_node_target_from_csv() {
225 assert!(NodeTarget::from_csv("").is_default());
226 assert_eq!(
227 NodeTarget::from_csv("192.168.1.10"),
228 NodeTarget::Single("192.168.1.10".to_string())
229 );
230 assert_eq!(
231 NodeTarget::from_csv("192.168.1.10, 192.168.1.11"),
232 NodeTarget::Multiple(vec!["192.168.1.10".to_string(), "192.168.1.11".to_string()])
233 );
234 }
235
236 #[test]
237 fn test_node_target_from_str() {
238 let target: NodeTarget = "192.168.1.10".into();
239 assert_eq!(target, NodeTarget::Single("192.168.1.10".to_string()));
240
241 let target: NodeTarget = "192.168.1.10,192.168.1.11".into();
242 assert!(target.is_multiple());
243 }
244
245 #[test]
246 fn test_node_target_from_vec() {
247 let target: NodeTarget = vec!["192.168.1.10".to_string()].into();
248 assert!(target.is_single());
249
250 let target: NodeTarget =
251 vec!["192.168.1.10".to_string(), "192.168.1.11".to_string()].into();
252 assert!(target.is_multiple());
253
254 let target: NodeTarget = Vec::<String>::new().into();
255 assert!(target.is_default());
256 }
257
258 #[test]
259 fn test_apply_to_request() {
260 let target = NodeTarget::single("192.168.1.10");
261 let request = Request::new(());
262 let request = target.apply_to_request(request);
263
264 let metadata = request.metadata().get(NODE_METADATA_KEY);
265 assert!(metadata.is_some());
266 assert_eq!(metadata.unwrap().to_str().unwrap(), "192.168.1.10");
267 }
268
269 #[test]
270 fn test_apply_to_request_multiple() {
271 let target = NodeTarget::multiple(vec!["10.0.0.1", "10.0.0.2"]);
272 let request = Request::new(());
273 let request = target.apply_to_request(request);
274
275 let metadata = request.metadata().get(NODE_METADATA_KEY);
276 assert!(metadata.is_some());
277 assert_eq!(metadata.unwrap().to_str().unwrap(), "10.0.0.1,10.0.0.2");
278 }
279
280 #[test]
281 fn test_apply_to_request_default() {
282 let target = NodeTarget::default();
283 let request = Request::new(());
284 let request = target.apply_to_request(request);
285
286 let metadata = request.metadata().get(NODE_METADATA_KEY);
287 assert!(metadata.is_none());
288 }
289}