archetect_core/config/
answers.rs

1use std::fs;
2use std::path::PathBuf;
3
4use linked_hash_map::LinkedHashMap;
5use log::debug;
6use pest::error::Error as PestError;
7use pest::iterators::Pair;
8use pest::Parser;
9
10use crate::config::VariableInfo;
11
12pub type AnswerInfo = VariableInfo;
13
14#[derive(Debug, Deserialize, Serialize)]
15pub struct AnswerConfig {
16    #[serde(skip_serializing_if = "LinkedHashMap::is_empty")]
17    answers: LinkedHashMap<String, AnswerInfo>,
18}
19
20#[derive(Debug, PartialEq, thiserror::Error)]
21pub enum AnswerConfigError {
22    #[error("Error parsing answer config: {0}")]
23    ParseError(String),
24    #[error("Missing answer config")]
25    MissingError,
26}
27
28impl From<serde_yaml::Error> for AnswerConfigError {
29    fn from(error: serde_yaml::Error) -> Self {
30        AnswerConfigError::ParseError(error.to_string())
31    }
32}
33
34impl From<std::io::Error> for AnswerConfigError {
35    fn from(_: std::io::Error) -> Self {
36        // TODO: Distinguish between missing and other errors
37        AnswerConfigError::MissingError
38    }
39}
40
41impl AnswerConfig {
42    pub fn load<P: Into<PathBuf>>(path: P) -> Result<AnswerConfig, AnswerConfigError> {
43        let path = path.into();
44        if path.is_dir() {
45            let answer_file_names = vec![
46                "archetect.yml",
47                ".archetect.yml",
48                "archetect.yaml",
49                ".archetect.yaml",
50                ".answers.yaml",
51                "answers.yaml",
52            ];
53            for answer_file_name in answer_file_names {
54                let answers = path.join(answer_file_name);
55                if answers.exists() {
56                    debug!("Reading Archetect config from '{}'", &answers.display());
57                    let config = fs::read_to_string(answers)?;
58                    let config = serde_yaml::from_str::<AnswerConfig>(&config)?;
59                    return Ok(config);
60                }
61            }
62        } else {
63            let config = fs::read_to_string(path)?;
64            let config = serde_yaml::from_str::<AnswerConfig>(&config)?;
65            return Ok(config);
66        }
67
68        // TODO: Return Ok(None) instead of error
69        Err(AnswerConfigError::MissingError)
70    }
71
72    pub fn add_answer(&mut self, identifier: &str, value: &str) {
73        self.answers
74            .insert(identifier.to_owned(), AnswerInfo::with_value(value).build());
75    }
76
77    pub fn with_answer(mut self, identifier: &str, value: &str) -> AnswerConfig {
78        self.add_answer(identifier, value);
79        self
80    }
81
82    pub fn answers(&self) -> &LinkedHashMap<String, AnswerInfo> {
83        &self.answers
84    }
85}
86
87impl Default for AnswerConfig {
88    fn default() -> Self {
89        AnswerConfig {
90            answers: LinkedHashMap::new(),
91        }
92    }
93}
94
95#[derive(Parser)]
96#[grammar = "config/answer_grammar.pest"]
97struct AnswerParser;
98
99#[derive(Debug, PartialEq)]
100pub enum AnswerParseError {
101    PestError(PestError<Rule>),
102}
103
104impl From<PestError<Rule>> for AnswerParseError {
105    fn from(error: PestError<Rule>) -> Self {
106        AnswerParseError::PestError(error)
107    }
108}
109
110fn parse(source: &str) -> Result<(String, AnswerInfo), AnswerParseError> {
111    let mut pairs = AnswerParser::parse(Rule::answer, source)?;
112    Ok(parse_answer(pairs.next().unwrap()))
113}
114
115fn parse_answer(pair: Pair<Rule>) -> (String, AnswerInfo) {
116    assert_eq!(pair.as_rule(), Rule::answer);
117    let mut iter = pair.into_inner();
118    let identifier_pair = iter.next().unwrap();
119    let value_pair = iter.next().unwrap();
120    (
121        parse_identifier(identifier_pair),
122        AnswerInfo::with_value(parse_value(value_pair)).build(),
123    )
124}
125
126fn parse_identifier(pair: Pair<Rule>) -> String {
127    assert_eq!(pair.as_rule(), Rule::identifier);
128    pair.as_str().to_owned()
129}
130
131fn parse_value(pair: Pair<Rule>) -> String {
132    assert_eq!(pair.as_rule(), Rule::string);
133    pair.into_inner().next().unwrap().as_str().to_owned()
134}
135
136impl AnswerInfo {
137    pub fn parse(input: &str) -> Result<(String, AnswerInfo), AnswerParseError> {
138        parse(input)
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_parse_success() {
148        assert_eq!(
149            parse("key=value"),
150            Ok(("key".to_owned(), AnswerInfo::with_value("value").build()))
151        );
152
153        assert_eq!(
154            parse("key = value"),
155            Ok(("key".to_owned(), AnswerInfo::with_value("value").build()))
156        );
157
158        assert_eq!(
159            parse("key = value set"),
160            Ok(("key".to_owned(), AnswerInfo::with_value("value set").build()))
161        );
162
163        assert_eq!(
164            parse("key='value'"),
165            Ok(("key".to_owned(), AnswerInfo::with_value("value").build()))
166        );
167
168        assert_eq!(
169            parse("key='value set'"),
170            Ok(("key".to_owned(), AnswerInfo::with_value("value set").build()))
171        );
172
173        assert_eq!(
174            parse("key = 'value'"),
175            Ok(("key".to_owned(), AnswerInfo::with_value("value").build()))
176        );
177
178        assert_eq!(
179            parse("key=\"value\""),
180            Ok(("key".to_owned(), AnswerInfo::with_value("value").build()))
181        );
182
183        assert_eq!(
184            parse("key=\"value set\""),
185            Ok(("key".to_owned(), AnswerInfo::with_value("value set").build()))
186        );
187
188        assert_eq!(
189            parse("key = \"value\""),
190            Ok(("key".to_owned(), AnswerInfo::with_value("value").build()))
191        );
192
193        assert_eq!(
194            parse("key ="),
195            Ok(("key".to_owned(), AnswerInfo::with_value("").build()))
196        );
197
198        assert_eq!(
199            parse("key =''"),
200            Ok(("key".to_owned(), AnswerInfo::with_value("").build()))
201        );
202
203        assert_eq!(
204            parse(" key =\"\""),
205            Ok(("key".to_owned(), AnswerInfo::with_value("").build()))
206        );
207    }
208
209    #[test]
210    fn test_parse_fail() {
211        match parse("key") {
212            Err(AnswerParseError::PestError(_)) => (),
213            _ => panic!("Error expected"),
214        }
215    }
216
217    #[test]
218    fn test_parse_answer() {
219        assert_eq!(
220            parse_answer(AnswerParser::parse(Rule::answer, "key=value").unwrap().next().unwrap()),
221            ("key".to_owned(), AnswerInfo::with_value("value").build())
222        );
223
224        assert_eq!(
225            parse_answer(
226                AnswerParser::parse(Rule::answer, "key='value'")
227                    .unwrap()
228                    .next()
229                    .unwrap()
230            ),
231            ("key".to_owned(), AnswerInfo::with_value("value").build())
232        );
233
234        assert_eq!(
235            parse_answer(
236                AnswerParser::parse(Rule::answer, "key=\"value\"")
237                    .unwrap()
238                    .next()
239                    .unwrap()
240            ),
241            ("key".to_owned(), AnswerInfo::with_value("value").build())
242        );
243    }
244
245    #[test]
246    fn test_parse_identifier() {
247        assert_eq!(
248            parse_identifier(AnswerParser::parse(Rule::identifier, "key").unwrap().next().unwrap()),
249            "key"
250        );
251    }
252
253    #[test]
254    fn test_parse_value() {
255        assert_eq!(
256            parse_value(AnswerParser::parse(Rule::string, "value").unwrap().next().unwrap()),
257            "value"
258        );
259
260        assert_eq!(
261            parse_value(AnswerParser::parse(Rule::string, "\"value\"").unwrap().next().unwrap()),
262            "value"
263        );
264
265        assert_eq!(
266            parse_value(AnswerParser::parse(Rule::string, "'value'").unwrap().next().unwrap()),
267            "value"
268        );
269    }
270
271    #[test]
272    fn test_serialize_answer_config() {
273        let config = AnswerConfig::default()
274            .with_answer("name", "Order Service")
275            .with_answer("author", "Jane Doe");
276
277        println!("{}", serde_yaml::to_string(&config).unwrap());
278    }
279}