Cactus/config/
read_properties.rs

1use std::collections::HashMap;
2use std::error::Error;
3use std::io::BufRead;
4use std::{fmt, io};
5
6#[derive(Debug)]
7pub struct PropertyNotFoundError<'a>(&'a str);
8
9impl<'a> Error for PropertyNotFoundError<'a> {}
10
11impl<'a> fmt::Display for PropertyNotFoundError<'a> {
12    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13        writeln!(f, "Property {:?} not found", self.0)
14    }
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Default)]
18pub struct Properties(HashMap<String, String>);
19
20impl From<HashMap<String, String>> for Properties {
21    fn from(value: HashMap<String, String>) -> Self {
22        Self(value)
23    }
24}
25
26impl Properties {
27    /// Gets the corresponding value for a property key.
28    pub fn get_property<'a>(&self, key: &'a str) -> Result<&'_ str, PropertyNotFoundError<'a>> {
29        self.0
30            .get(key)
31            .map(String::as_ref)
32            .ok_or(PropertyNotFoundError(key))
33    }
34}
35
36#[derive(Debug)]
37pub struct PropertiesParseError {
38    pub line_number: usize,
39    pub kind: PropertiesParseErrorKind,
40}
41
42impl PropertiesParseError {
43    fn new_io(line_number: usize, error: io::Error) -> Self {
44        Self {
45            line_number,
46            kind: PropertiesParseErrorKind::Io(error),
47        }
48    }
49
50    fn new_invalid_kvp(line_number: usize, line: &str) -> Self {
51        Self {
52            line_number,
53            kind: PropertiesParseErrorKind::InvalidKeyValuePair(InvalidKeyValuePairError(
54                line.to_string(),
55            )),
56        }
57    }
58}
59
60#[derive(Debug)]
61pub struct InvalidKeyValuePairError(String);
62
63impl Error for InvalidKeyValuePairError {}
64
65impl fmt::Display for InvalidKeyValuePairError {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        write!(f, "cannot parse a key-value pair from {:?}", self.0)
68    }
69}
70
71#[derive(Debug)]
72pub enum PropertiesParseErrorKind {
73    Io(io::Error),
74    InvalidKeyValuePair(InvalidKeyValuePairError),
75}
76
77impl Error for PropertiesParseError {
78    fn source(&self) -> Option<&(dyn Error + 'static)> {
79        match &self.kind {
80            PropertiesParseErrorKind::Io(e) => Some(e),
81            PropertiesParseErrorKind::InvalidKeyValuePair(e) => Some(e),
82        }
83    }
84}
85
86impl fmt::Display for PropertiesParseError {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        write!(
89            f,
90            "Error while parsing properties file (line {})",
91            self.line_number
92        )
93    }
94}
95
96/// Parses a configuration file
97///
98/// # Arguments
99/// * `reader` - A buffered reader that provides the input data.
100///
101/// # Returns
102///
103/// Returns a Result of `Properties`
104pub fn read_properties<R: BufRead>(reader: &mut R) -> Result<Properties, PropertiesParseError> {
105    let mut properties = Properties::default();
106
107    for (i, line) in reader.lines().enumerate() {
108        let line_number = i + 1;
109
110        let line = line.map_err(|e| PropertiesParseError::new_io(line_number, e))?;
111
112        let line = line.trim(); // Remove the spaces or tabulations
113
114        // This check allows commenting with either "#" or "!".
115        if line.is_empty() || line.starts_with("#") || line.starts_with("!") {
116            continue;
117        }
118
119        let (field, value) = line
120            .split_once('=')
121            .ok_or_else(|| PropertiesParseError::new_invalid_kvp(line_number, line))?;
122
123        let key = field.trim().to_string();
124        let value = value.trim().to_string();
125        properties.0.insert(key, value);
126    }
127
128    Ok(properties)
129}