team04_server/config/
mod.rs1use std::collections::BTreeMap;
4use std::fs::File;
5use std::io::BufReader;
6use std::path::PathBuf;
7use std::sync::Arc;
8
9pub mod board;
11pub mod game;
13#[cfg(test)]
14mod test;
15pub mod unit;
17
18pub use board::BoardConfig;
19pub use game::GameConfig;
20pub use unit::UnitConfig;
21
22pub const CONFIG_VAR: &str = "CONFIG";
25
26#[cfg(not(test))]
28pub const CONFIG_DEFAULT: &str = "/config";
29#[cfg(test)]
30pub const CONFIG_DEFAULT: &str = "integration_test_configs";
31
32pub const CONFIG_UNIT: &str = "unit.json";
34
35pub const CONFIG_GAME: &str = "game.json";
37
38pub const CONFIG_BOARD: &str = "board.json";
40
41pub const CONFIG_PREFIX: &str = "lobby";
43
44#[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
52pub 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 {}