archetect_core/config/
catalog.rs

1use crate::source::{Source, SourceError};
2use std::fs;
3use std::path::{Path, PathBuf};
4
5pub const CATALOG_FILE_NAME: &str = "catalog.yml";
6
7#[derive(Debug, Serialize, Deserialize, Clone)]
8pub struct Catalog {
9    entries: Vec<CatalogEntry>,
10}
11
12impl Catalog {
13    pub fn new() -> Catalog {
14        Catalog { entries: vec![] }
15    }
16
17    pub fn load(source: Source) -> Result<Catalog, CatalogError> {
18        // TODO: Support both yml and yaml extensions
19        let catalog_path = match source {
20            Source::LocalFile { path } => path,
21            Source::RemoteHttp { url: _, path } => path,
22            Source::RemoteGit { url: _, path, gitref: _ } => path.join(CATALOG_FILE_NAME),
23            Source::LocalDirectory { path } => path.join(CATALOG_FILE_NAME),
24        };
25
26        if !catalog_path.exists() {
27            return Err(CatalogError::NotFound(catalog_path));
28        }
29
30        let catalog = fs::read_to_string(&catalog_path)?;
31        let catalog = serde_yaml::from_str(&catalog)?;
32        Ok(catalog)
33    }
34
35    pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), CatalogError> {
36        let yaml = serde_yaml::to_string(&self)?;
37        fs::write(path, &yaml)?;
38        Ok(())
39    }
40
41    pub fn entries(&self) -> &[CatalogEntry] {
42        self.entries.as_slice()
43    }
44
45    pub fn entries_owned(self) -> Vec<CatalogEntry> {
46        self.entries
47    }
48}
49
50#[derive(Debug, Serialize, Deserialize, Clone)]
51pub enum CatalogEntry {
52    #[serde(rename = "group")]
53    Group {
54        description: String,
55        entries: Vec<CatalogEntry>,
56    },
57    #[serde(rename = "catalog")]
58    Catalog { description: String, source: String },
59    #[serde(rename = "archetype")]
60    Archetype { description: String, source: String },
61}
62
63impl CatalogEntry {
64    pub fn description(&self) -> &str {
65        match self {
66            CatalogEntry::Group {
67                description,
68                entries: _,
69            } => description.as_str(),
70            CatalogEntry::Catalog { description, source: _ } => description.as_str(),
71            CatalogEntry::Archetype { description, source: _ } => description.as_str(),
72        }
73    }
74}
75
76#[derive(Debug, thiserror::Error)]
77pub enum CatalogError {
78    #[error("Catalog File is Empty")]
79    EmptyCatalog,
80    #[error("Selected Catalog Group is Empty")]
81    EmptyGroup,
82    #[error("Invalid Catalog Source: {0}")]
83    SourceError(SourceError),
84    #[error("Catalog not found: {0}")]
85    NotFound(PathBuf),
86    #[error("Catalog IO Error: {0}")]
87    IOError(std::io::Error),
88    #[error("Catalog Format Error: {0}")]
89    YamlError(serde_yaml::Error),
90}
91
92impl From<std::io::Error> for CatalogError {
93    fn from(e: std::io::Error) -> Self {
94        CatalogError::IOError(e)
95    }
96}
97
98impl From<serde_yaml::Error> for CatalogError {
99    fn from(e: serde_yaml::Error) -> Self {
100        CatalogError::YamlError(e)
101    }
102}
103
104impl From<SourceError> for CatalogError {
105    fn from(cause: SourceError) -> Self {
106        CatalogError::SourceError(cause)
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn test() {
116        let catalog = prototype_catalog();
117        let yaml = serde_yaml::to_string(&catalog).unwrap();
118        println!("{}", yaml);
119    }
120
121    #[test]
122    fn test_catalog_group() {
123        let group = lang_group();
124
125        let yaml = serde_yaml::to_string(&group).unwrap();
126        println!("{}", yaml);
127    }
128
129    fn prototype_catalog() -> Catalog {
130        Catalog {
131            entries: vec![
132                lang_group(),
133                CatalogEntry::Catalog {
134                    description: "Java".to_owned(),
135                    source: "~/projects/catalogs/java.yml".to_owned(),
136                },
137            ],
138        }
139    }
140
141    fn lang_group() -> CatalogEntry {
142        CatalogEntry::Group {
143            description: "Languages".to_owned(),
144            entries: vec![rust_group(), python_group()],
145        }
146    }
147
148    fn rust_group() -> CatalogEntry {
149        CatalogEntry::Group {
150            description: "Rust".to_owned(),
151            entries: vec![rust_cli_archetype(), rust_cli_workspace_archetype()],
152        }
153    }
154
155    fn rust_cli_archetype() -> CatalogEntry {
156        CatalogEntry::Archetype {
157            description: "Rust CLI".to_owned(),
158            source: "~/projects/test_archetypes/rust-cie".to_owned(),
159        }
160    }
161
162    fn rust_cli_workspace_archetype() -> CatalogEntry {
163        CatalogEntry::Archetype {
164            description: "Rust CLI Workspace".to_owned(),
165            source: "~/projects/test_archetypes/rust-cie".to_owned(),
166        }
167    }
168
169    fn python_group() -> CatalogEntry {
170        CatalogEntry::Group {
171            description: "Python".to_owned(),
172            entries: vec![CatalogEntry::Archetype {
173                description: "Python Service".to_owned(),
174                source: "~/projects/python/python-service".to_owned(),
175            }],
176        }
177    }
178}