team04_server/config/
mod.rs

1//! Loading of the config files, including serialization and deserialization.
2
3use std::collections::BTreeMap;
4use std::fs::File;
5use std::io::BufReader;
6use std::path::PathBuf;
7use std::sync::Arc;
8
9/// The **board config**
10pub mod board;
11/// The **game config**
12pub mod game;
13#[cfg(test)]
14mod test;
15/// The **unit config**
16pub mod unit;
17
18pub use board::BoardConfig;
19pub use game::GameConfig;
20pub use unit::UnitConfig;
21
22/// The name of the environment variable which defines the location of the
23/// config files.
24pub const CONFIG_VAR: &str = "CONFIG";
25
26/// The default directory for searching for config files.
27#[cfg(not(test))]
28pub const CONFIG_DEFAULT: &str = "/config";
29#[cfg(test)]
30pub const CONFIG_DEFAULT: &str = "integration_test_configs";
31
32/// The filename of the unit config file
33pub const CONFIG_UNIT: &str = "unit.json";
34
35/// The filename of the game config file
36pub const CONFIG_GAME: &str = "game.json";
37
38/// The filename of the board config file
39pub const CONFIG_BOARD: &str = "board.json";
40
41/// The prefix every config directory needs to have
42pub const CONFIG_PREFIX: &str = "lobby";
43
44/// A struct encapsulating a set of configs needed to run a lobby.
45#[derive(Clone, Debug)]
46pub struct ConfigSet {
47    pub game_config: Arc<GameConfig>,
48    pub unit_config: Arc<UnitConfig>,
49    pub board_config: Arc<BoardConfig>,
50}
51
52/// Loads all configurations from the path specified in the environment.
53pub fn load_configs() -> Result<BTreeMap<u64, ConfigSet>, ConfigError> {
54    let config_path = match std::env::var(CONFIG_VAR) {
55        Ok(s) => s,
56        Err(std::env::VarError::NotPresent) => CONFIG_DEFAULT.to_string(),
57        Err(e) => {
58            return Err(ConfigError::from(e));
59        }
60    };
61
62    let mut configs = BTreeMap::new();
63    for entry in std::fs::read_dir(&config_path)
64        .map_err(|e| ConfigError::IoError(config_path.as_str().into(), e))?
65    {
66        let entry = entry.map_err(|e| ConfigError::IoError(config_path.as_str().into(), e))?;
67        let fname = entry.file_name();
68        let fname = match fname.to_str() {
69            None => continue,
70            Some(f) => f,
71        };
72
73        if !entry
74            .path()
75            .metadata()
76            .map_err(|e| ConfigError::IoError(entry.path(), e))?
77            .is_dir()
78            || !fname.starts_with(CONFIG_PREFIX)
79        {
80            continue;
81        }
82        let idx = match fname.split_at(CONFIG_PREFIX.len()).1.parse::<u64>() {
83            Ok(i) => i,
84            Err(_) => continue,
85        };
86
87        configs.insert(idx, load_from(entry.path())?);
88    }
89    if configs.is_empty() {
90        return Err(ConfigError::NoConfigs);
91    }
92    Ok(configs)
93}
94
95fn load_from(path: PathBuf) -> Result<ConfigSet, ConfigError> {
96    Ok(ConfigSet {
97        game_config: Arc::new(
98            serde_json::from_reader(BufReader::new(
99                File::open(path.join(CONFIG_GAME))
100                    .map_err(|e| ConfigError::IoError(path.join(CONFIG_GAME), e))?,
101            ))
102            .map_err(|e| (CONFIG_GAME, e))?,
103        ),
104        unit_config: Arc::new(
105            serde_json::from_reader(BufReader::new(
106                File::open(path.join(CONFIG_UNIT))
107                    .map_err(|e| ConfigError::IoError(path.join(CONFIG_UNIT), e))?,
108            ))
109            .map_err(|e| (CONFIG_UNIT, e))?,
110        ),
111        board_config: {
112            let board_config = Arc::<BoardConfig>::new(
113                serde_json::from_reader(BufReader::new(
114                    File::open(path.join(CONFIG_BOARD))
115                        .map_err(|e| ConfigError::IoError(path.join(CONFIG_BOARD), e))?,
116                ))
117                .map_err(|e| (CONFIG_BOARD, e))?,
118            );
119            crate::board::routing::calculate_routing(board_config.clone());
120            board_config
121        },
122    })
123}
124
125#[derive(Debug)]
126pub enum ConfigError {
127    VarError(std::env::VarError),
128    IoError(PathBuf, std::io::Error),
129    SerdeError(&'static str, serde_json::Error),
130    NoConfigs,
131}
132
133impl std::fmt::Display for ConfigError {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
135        match self {
136            Self::VarError(e) => write!(f, "'{}' environment variable error: {}", CONFIG_VAR, e),
137            Self::IoError(p, e) => write!(f, "IO Error for {}: {}", p.display(), e),
138            Self::SerdeError(file, e) => write!(f, "Parsing error in {}: {}", file, e),
139            Self::NoConfigs => write!(f, "No config files found"),
140        }
141    }
142}
143
144impl From<std::env::VarError> for ConfigError {
145    fn from(e: std::env::VarError) -> Self {
146        Self::VarError(e)
147    }
148}
149
150impl From<(&'static str, serde_json::Error)> for ConfigError {
151    fn from(e: (&'static str, serde_json::Error)) -> Self {
152        Self::SerdeError(e.0, e.1)
153    }
154}
155
156impl std::error::Error for ConfigError {}