use std::collections::HashMap;
use crate::rest_types::LabTopology;
use crate::{CmlResult, CmlUser, rt};
use log::debug;
use regex::Regex;
use thiserror::Error;
#[derive(Debug, Clone, Copy, Error)]
pub enum NodeSearchError {
#[error("No lab matching the desired lab descriptor")]
NoMatchingLab,
#[error("No node matching the desired node descriptor within the specified lab")]
NoMatchingNode,
#[error("Multiple labs matched by lab title. Try renaming the labs, or using a lab ID")]
MultipleMatchingLabs,
#[error("Multiple nodes matched by node title within the lab. Try renaming the nodes, or using a node ID")]
MultipleMatchingNodes,
}
#[derive(Debug, Clone)]
pub struct NodeCtx {
host: String,
user: String,
lab: (String, String),
node: (String, String),
meta: rt::NodeDescription,
}
impl NodeCtx {
pub fn host(&self) -> &str { &self.host }
pub fn user(&self) -> &str { &self.user }
pub fn lab(&self) -> (&str, &str) { (&self.lab.0, &self.lab.1) }
pub fn node(&self) -> (&str, &str) { (&self.node.0, &self.node.1) }
pub fn meta(&self) -> &rt::NodeDescription { &self.meta }
pub async fn search(client: &CmlUser, lab: &str, device: &str) -> CmlResult<Result<NodeCtx, NodeSearchError>> {
let labs = client.labs(true).await?;
let topos = client.lab_topologies(&labs, false).await?
.into_iter()
.map(|(s, t)| (s.to_string(), t.expect("Lab removed during searching")))
.collect::<HashMap<_, _>>();
let (lab_id, lab_topo) = match <(String, LabTopology)>::find_single(topos.into_iter(), lab) {
Err(se) => return Ok(Err(se)),
Ok(s) => s
};
let (node_id, node_data) = match <rt::labeled::Data<rt::NodeDescription>>::find_single(lab_topo.nodes, device) {
Err(se) => return Ok(Err(se)),
Ok(node) => (node.id, node.data)
};
Ok(Ok(NodeCtx {
host: client.host().to_owned(),
user: client.username().to_owned(),
lab: (lab_id, lab_topo.title),
node: (node_id, node_data.label.clone()),
meta: node_data,
}))
}
pub async fn search_pat(client: &CmlUser, lab: &Regex, node: &Regex) -> CmlResult<HashMap<String, (LabTopology, Vec<(String, ())>)>> {
let labs = client.labs(true).await?;
let mut matched_lab_id = false;
let mut topos: Vec<(String, LabTopology)> = client.lab_topologies(&labs, false).await?
.into_iter()
.map(|(s, t)| {
(s.to_string(), t.expect("Lab removed during searching"))
})
.filter(|(lab_id, topo)| {
let idmatches = lab.is_match(&lab_id);
if idmatches { matched_lab_id = true; }
idmatches || lab.is_match(&topo.title)
})
.collect();
let labregstr = lab.as_str();
if matched_lab_id && labregstr.starts_with('^') && labregstr.ends_with('$') {
topos.retain(|(lab_id, _)| lab.is_match(&lab_id));
}
todo!()
}
}
trait NamedMatcher {
fn matcher_kind() -> &'static str;
fn none_found() -> NodeSearchError;
fn multiple_found() -> NodeSearchError;
fn id(&self) -> &str;
fn label(&self) -> &str;
fn state(&self) -> rt::State;
fn matches(&self, descriptor: &str) -> bool {
self.id() == descriptor || self.label() == descriptor
}
fn find_single<'a, N: NamedMatcher + Sized, I: IntoIterator<Item = N>>(s: I, desc: &'_ str) -> Result<N, NodeSearchError> {
let mut matching: Vec<N> = s.into_iter()
.filter(|lab_data| lab_data.matches(desc))
.inspect(|n| debug!("found {} matching the provided description: (id, name, state) = {:?}", N::matcher_kind(), (n.id(), n.label(), n.state())))
.collect();
if matching.len() == 0 {
Err(Self::none_found())
} else if matching.len() == 1 {
Ok(matching.remove(0))
} else {
let by_id = matching.iter()
.position(|n| n.id() == desc);
if let Some(res_i) = by_id {
debug!("Found multiple {}s for {} description {:?} - interpreting it as an ID", N::matcher_kind(), N::matcher_kind(), desc);
Ok(matching.remove(res_i))
} else {
Err(Self::multiple_found())
}
}
}
}
impl NamedMatcher for (String, LabTopology) {
fn matcher_kind() -> &'static str { "lab" }
fn none_found() -> NodeSearchError { NodeSearchError::NoMatchingLab }
fn multiple_found() -> NodeSearchError { NodeSearchError::MultipleMatchingLabs }
fn id(&self) -> &str { &self.0 }
fn label(&self) -> &str { &self.1.title }
fn state(&self) -> rt::State { self.1.state }
}
impl NamedMatcher for rt::labeled::Data<rt::NodeDescription> {
fn matcher_kind() -> &'static str { "node" }
fn none_found() -> NodeSearchError { NodeSearchError::NoMatchingNode }
fn multiple_found() -> NodeSearchError { NodeSearchError::MultipleMatchingNodes }
fn id(&self) -> &str { &self.id }
fn label(&self) -> &str { &self.data.label }
fn state(&self) -> rt::State { self.data.state }
}