use crate::OverlayFile;
use crate::{OverlayDef, parse_from_string};
use log::debug;
use std::{collections::HashMap, fs, path::Path};
pub trait OverlayRegistry {
fn get_by_filename(&self, name: &str) -> Option<&OverlayFile>;
fn get_by_name(&self, name: &str) -> Result<Option<&OverlayDef>, &'static str>;
fn get_by_fqn(&self, name: &str) -> Result<&OverlayDef, &'static str>;
fn list_by_namespace(&self, namespace: &str) -> Vec<&OverlayFile>;
fn list_all(&self) -> Vec<String>;
}
#[derive(Debug, Clone)]
pub struct OverlayLocalRegistry {
overlays: HashMap<String, OverlayFile>,
}
impl OverlayLocalRegistry {
pub fn from_dir<P: AsRef<Path>>(dir: P) -> Result<Self, std::io::Error> {
let mut overlays = HashMap::new();
for entry in fs::read_dir(dir)? {
let path = entry?.path();
if path.extension().and_then(|s| s.to_str()) == Some("overlayfile")
&& let Some(name) = Self::overlay_name_from_path(&path)
{
let content = fs::read_to_string(&path)?;
debug!("Parsing overlay file: {}", path.display());
let schema = parse_from_string(content);
overlays.insert(name, schema.unwrap());
}
}
Ok(OverlayLocalRegistry { overlays })
}
pub fn from_file<P: AsRef<Path>>(file: P) -> Result<Self, std::io::Error> {
let path = file.as_ref();
if path.extension().and_then(|s| s.to_str()) != Some("overlayfile") {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"File does not have .overlayfile extension",
));
}
let mut overlays = HashMap::new();
if let Some(name) = Self::overlay_name_from_path(path) {
debug!("Parsing overlay file: {}", path.display());
let content = fs::read_to_string(path)?;
let schema = parse_from_string(content);
overlays.insert(name, schema.unwrap());
}
Ok(OverlayLocalRegistry { overlays })
}
fn overlay_name_from_path(path: &Path) -> Option<String> {
path.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string())
}
pub fn new() -> Self {
OverlayLocalRegistry {
overlays: HashMap::new(),
}
}
}
impl Default for OverlayLocalRegistry {
fn default() -> Self {
Self::new()
}
}
impl OverlayRegistry for OverlayLocalRegistry {
fn get_by_filename(&self, name: &str) -> Option<&OverlayFile> {
self.overlays.get(name)
}
fn get_by_fqn(&self, overlay_name: &str) -> Result<&OverlayDef, &'static str> {
debug!("Getting overlay by fq name: {}", overlay_name);
let (namespace, name) = overlay_name
.split_once(':')
.map(|(ns, n)| (Some(ns), n))
.unwrap_or((None, overlay_name));
let (name, version) = name
.split_once("/")
.ok_or("Invalid overlay name format: version not found or in wrong format")?;
let name = name.to_ascii_lowercase();
let namespace = namespace.map(|ns| ns.to_ascii_lowercase());
self.overlays
.values()
.flat_map(|overlay_file| &overlay_file.overlays_def)
.find(|o| {
let o_ns = o.namespace.as_ref().map(|s| s.to_ascii_lowercase());
o_ns == namespace
&& o.name.eq_ignore_ascii_case(&name)
&& o.version.eq_ignore_ascii_case(version)
})
.ok_or("Overlay definition not found in registry")
}
fn get_by_name(&self, name: &str) -> Result<Option<&OverlayDef>, &'static str> {
debug!("Getting overlay by name: {}", name);
let overlay_def = self.overlays.values().find_map(|overlay_file| {
overlay_file
.overlays_def
.iter()
.find(|o| o.name.eq_ignore_ascii_case(name))
});
Ok(overlay_def)
}
fn list_all(&self) -> Vec<String> {
self.overlays
.iter()
.flat_map(|(_, overlay_file)| {
overlay_file.overlays_def.iter().map(|o| {
let namespace = o
.namespace
.as_ref()
.map_or(String::new(), |ns| format!("{:?}:", ns));
format!("{}{}/{}", namespace, o.name, o.version)
})
})
.collect::<Vec<String>>()
}
fn list_by_namespace(&self, _namespace: &str) -> Vec<&OverlayFile> {
todo!()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_overlay_registry() {
let _ = env_logger::builder().is_test(true).try_init();
let registry = OverlayLocalRegistry::from_dir("core_overlays").unwrap();
assert_eq!(registry.list_all().len(), 13);
assert!(registry.get_by_filename("semantic").is_some());
assert_eq!(registry.get_by_fqn("label/2.0.0").unwrap().name, "label");
let semantic_overlay_file = registry.get_by_filename("semantic").unwrap();
assert_eq!(semantic_overlay_file.overlays_def.len(), 13);
let label_overlay = semantic_overlay_file.overlays_def.first().unwrap();
assert_eq!(label_overlay.name, "label");
}
}