1use std::{collections::HashMap, fmt::Display, path::Path, str::FromStr};
2
3use figment::{
4 providers::{Env, Format, Toml},
5 Figment,
6};
7use serde::Deserialize;
8use serde_inline_default::serde_inline_default;
9
10use crate::OptionalJson;
11
12fn generate_hash() -> String {
13 use rand::Rng;
14 let mut rng = rand::thread_rng();
15 let mut hash = String::new();
16 for _ in 0..32 {
17 hash.push_str(&format!("{:x}", rng.gen_range(0..16)));
18 }
19 hash
20}
21
22#[derive(Deserialize, Default, Debug, Clone)]
24pub struct AppConfig {
25 pub server: Option<ServerConfig>,
27 #[serde(default = "WebwareConfig::default")]
29 pub webware: WebwareConfig,
30 #[serde(default)]
32 pub mock_resources: Vec<MockResource>,
33 #[serde(default)]
35 pub debug: bool,
36}
37
38impl AppConfig {
39 pub fn new() -> Result<Self, figment::Error> {
44 Figment::new()
45 .merge(Toml::file("config.toml"))
46 .merge(Env::prefixed("APP__").split("__"))
47 .extract()
48 }
49
50 pub fn from_file(file: &Path) -> Result<Self, figment::Error> {
55 Figment::new()
56 .merge(Toml::file(file))
57 .merge(Env::prefixed("APP__").split("__"))
58 .extract()
59 }
60
61 pub fn with_mock_resource(mut self, resource: MockResource) -> Self {
63 self.mock_resources.push(resource);
64 self
65 }
66}
67
68#[derive(Deserialize, Debug, Clone)]
70pub struct ServerConfig {
71 pub bind_address: String,
73}
74
75#[derive(Deserialize, Default, Debug, Clone)]
77pub struct WebwareConfig {
78 #[serde(default)]
80 pub webservices: WebservicesConfig,
81 #[serde(default)]
83 pub credentials: CredentialsConfig,
84}
85
86#[derive(Deserialize, Debug, Clone)]
88pub struct CredentialsConfig {
89 #[serde(default = "generate_hash")]
93 pub service_pass: String,
94 #[serde(default = "generate_hash")]
98 pub application_id: String,
99}
100
101impl Default for CredentialsConfig {
102 fn default() -> Self {
103 CredentialsConfig {
104 service_pass: generate_hash(),
105 application_id: generate_hash(),
106 }
107 }
108}
109
110#[serde_inline_default]
112#[derive(Deserialize, Debug, Clone)]
113pub struct WebservicesConfig {
114 #[serde(default = "generate_hash")]
118 pub vendor_hash: String,
119 #[serde(default = "generate_hash")]
123 pub application_hash: String,
124 #[serde_inline_default(1)]
128 pub version: u32,
129 #[serde_inline_default("1".to_string())]
133 pub application_secret: String,
134}
135
136impl Default for WebservicesConfig {
137 fn default() -> Self {
138 WebservicesConfig {
139 vendor_hash: generate_hash(),
140 application_hash: generate_hash(),
141 version: 1,
142 application_secret: "1".to_string(),
143 }
144 }
145}
146
147#[derive(Deserialize, Debug, Clone)]
149#[serde(tag = "type")]
150pub enum FileOrString {
151 File {
153 file: String
155 },
156 String {
158 value: String
160 },
161 Empty,
163}
164
165impl FileOrString {
166 pub fn as_string(&self) -> String {
172 match self {
173 FileOrString::File { file } => std::fs::read_to_string(file).unwrap(),
174 FileOrString::String { value } => value.clone(),
175 FileOrString::Empty => "".to_string(),
176 }
177 }
178
179 pub fn as_json_value(&self) -> OptionalJson {
185 match self {
186 FileOrString::File { file: _ } => OptionalJson(Some(serde_json::from_str(&self.as_string()).unwrap())),
187 FileOrString::String { value: _ } => OptionalJson(Some(serde_json::from_str(&self.as_string()).unwrap())),
188 FileOrString::Empty => OptionalJson(None),
189 }
190 }
191}
192
193#[derive(Deserialize, Debug, Clone, PartialEq)]
197pub enum MockResourceMethod {
198 #[serde(rename = "GET")]
202 Get,
203 #[serde(rename = "INSERT")]
207 Insert,
208 #[serde(rename = "PUT")]
212 Put,
213 #[serde(rename = "DELETE")]
217 Delete,
218 #[serde(rename = "EXEC")]
222 Exec,
223}
224
225impl FromStr for MockResourceMethod {
226 type Err = String;
227
228 fn from_str(s: &str) -> Result<Self, Self::Err> {
229 match s {
230 "GET" => Ok(MockResourceMethod::Get),
231 "INSERT" => Ok(MockResourceMethod::Insert),
232 "PUT" => Ok(MockResourceMethod::Put),
233 "DELETE" => Ok(MockResourceMethod::Delete),
234 "EXEC" => Ok(MockResourceMethod::Exec),
235 _ => Err(format!("Unknown method: {}", s)),
236 }
237 }
238}
239
240impl Display for MockResourceMethod {
241 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242 match self {
243 MockResourceMethod::Get => write!(f, "GET"),
244 MockResourceMethod::Insert => write!(f, "INSERT"),
245 MockResourceMethod::Put => write!(f, "PUT"),
246 MockResourceMethod::Delete => write!(f, "DELETE"),
247 MockResourceMethod::Exec => write!(f, "EXEC"),
248 }
249 }
250}
251
252#[derive(Deserialize, Debug, Clone)]
257pub struct MockResource {
258 pub data_source: FileOrString,
260 pub function: String,
264 pub method: MockResourceMethod,
266 pub revision: u32,
268
269 pub parameters: Option<HashMap<String, String>>,
271}
272
273impl Display for MockResource {
274 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275 write!(
276 f,
277 "MockResource {{ function: {}, method: {}, revision: {}, parameters: {} }}",
278 self.function, self.method, self.revision, match self.parameters {
279 Some(ref parameters) => serde_json::to_string(parameters).unwrap(),
280 None => "None".to_string(),
281 }
282 )
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use std::str::FromStr;
289
290 use pretty_assertions::assert_eq;
291
292 macro_rules! one_line_assert_eq {
293 ($fn:ident, $left:expr, $right:expr) => {
294 #[test]
295 fn $fn() {
296 assert_eq!($left, $right);
297 }
298 };
299 }
300
301 #[test]
302 fn default_config() {
303 let config = super::AppConfig::new().unwrap();
304 assert_eq!(config.mock_resources.is_empty(), true);
305 assert_eq!(config.debug, false);
306 assert_eq!(config.webware.credentials.service_pass.len(), 32);
307 assert_eq!(config.webware.credentials.application_id.len(), 32);
308 assert_eq!(config.webware.webservices.vendor_hash.len(), 32);
309 assert_eq!(config.webware.webservices.application_hash.len(), 32);
310 assert_eq!(config.webware.webservices.version, 1);
311 assert_eq!(config.webware.webservices.application_secret, "1".to_string());
312 }
313
314 #[test]
315 fn config_from_file() {
316 figment::Jail::expect_with(|jail| {
317 jail.create_file("test-config.toml", r#"[server]
318 bind_address = "0.0.0.0:3000"
319
320 [[mock_resources]]
321 data_source.type = "Empty"
322 function = "ARTIKEL"
323 method = "INSERT"
324 revision = 1
325 parameters.ARTNR = "MeinArtikel""#)?;
326
327 let config = super::AppConfig::from_file(std::path::Path::new("test-config.toml")).unwrap();
328 assert_eq!(config.server.unwrap().bind_address, "0.0.0.0:3000");
329 assert_eq!(config.mock_resources.len(), 1);
330 assert_eq!(config.mock_resources[0].function, "ARTIKEL");
331 assert_eq!(config.mock_resources[0].method, super::MockResourceMethod::Insert);
332 assert_eq!(config.mock_resources[0].revision, 1);
333 assert_eq!(config.mock_resources[0].parameters.as_ref().unwrap().get("ARTNR").unwrap(), "MeinArtikel");
334
335 Ok(())
336 });
337 }
338
339 one_line_assert_eq!(method_get_to_string, super::MockResourceMethod::Get.to_string(), "GET");
340 one_line_assert_eq!(method_insert_to_string, super::MockResourceMethod::Insert.to_string(), "INSERT");
341 one_line_assert_eq!(method_put_to_string, super::MockResourceMethod::Put.to_string(), "PUT");
342 one_line_assert_eq!(method_delete_to_string, super::MockResourceMethod::Delete.to_string(), "DELETE");
343 one_line_assert_eq!(method_exec_to_string, super::MockResourceMethod::Exec.to_string(), "EXEC");
344 one_line_assert_eq!(mock_resource_without_params_to_string, super::MockResource {
345 data_source: super::FileOrString::File {
346 file: "data/artikel_clean.json".to_string(),
347 },
348 function: "ARTIKEL".to_string(),
349 method: super::MockResourceMethod::Get,
350 revision: 3,
351 parameters: None,
352 }.to_string(), "MockResource { function: ARTIKEL, method: GET, revision: 3, parameters: None }");
353 one_line_assert_eq!(mock_resource_with_params_to_string, super::MockResource {
354 data_source: super::FileOrString::File {
355 file: "data/artikel_art_nr_clean.json".to_string(),
356 },
357 function: "ARTIKEL".to_string(),
358 method: super::MockResourceMethod::Get,
359 revision: 3,
360 parameters: Some(wwsvc_rs::collection! {
361 "FELDER".to_string() => "ART_1_25".to_string(),
362 })
363 }.to_string(), "MockResource { function: ARTIKEL, method: GET, revision: 3, parameters: {\"FELDER\":\"ART_1_25\"} }");
364 one_line_assert_eq!(unknown_method_from_str, super::MockResourceMethod::from_str("UNKNOWN").unwrap_err(), "Unknown method: UNKNOWN");
365 one_line_assert_eq!(empty_as_str, super::FileOrString::Empty.as_string(), "");
366}