rsconfig/lib.rs
1#![warn(missing_docs)]
2
3//! A simple configuration library that allows developers to quickly make configuration for their apps.
4
5/// Contains useful functions for importing from files
6pub mod files;
7
8use serde_json::Value;
9use yaml_rust::Yaml;
10
11use std::io;
12
13/// Represents a configuration struct that can be created from commandline arguments.
14/// ### Example Code
15/// ```rust
16/// use rsconfig::CommandlineConfig;
17///
18/// use std::env;
19///
20/// // our config class that we can expand upon to add different values
21/// // to expand upon it, simply add more fields and update the import function(s)
22/// #[derive(Debug)]
23/// struct TestConfig {
24/// test: bool
25/// }
26///
27/// impl CommandlineConfig for TestConfig {
28/// fn from_env_args(args: Vec<String>) -> Self {
29/// // check if commandline args contains --test
30/// Self { test: args.contains(&"--test".to_string()) }
31/// }
32/// }
33///
34///
35/// fn main() {
36/// // fetch commandline args
37/// let args: Vec<String> = env::args().collect();
38///
39/// // load config from commandline args
40/// let mut config = TestConfig::from_env_args(args);
41///
42/// // should output TestConfig { test: true } if --test is in the command
43/// // otherwise, it will print TestConfig { test: false }
44/// println!("{:?}", config);
45/// }
46/// ```
47pub trait CommandlineConfig {
48 /// Initialize a CommandlineConfig struct given the commandline arguments that the program was run with.
49 /// ### Example
50 /// ```rust
51 /// # use rsconfig::CommandlineConfig;
52 /// # struct T { test: bool }
53 /// # impl CommandlineConfig for T {
54 /// fn from_env_args(args: Vec<String>) -> Self {
55 /// // check if commandline args contains --test
56 /// Self { test: args.contains(&"--test".to_string()) }
57 /// }
58 /// # }
59 /// ```
60 fn from_env_args(args: Vec<String>) -> Self;
61}
62
63/// Represents a configuration struct that can be created from a YAML (YML) file.
64/// ### Example
65/// ```rust
66/// use yaml_rust;
67/// use rsconfig::YamlConfig;
68///
69/// use std::{fs, io::Result};
70///
71/// struct TestConfig {
72/// test: bool
73/// }
74///
75/// impl YamlConfig for TestConfig {
76/// fn from_yaml(yaml: Vec<yaml_rust::Yaml>) -> Self {
77/// // fetch "test" value of the first yaml document using yaml_rust crate
78/// // NOTE: this code is not error-safe, will panic if the correct file formatting is not used
79/// Self { test: *&yaml[0]["test"].as_bool().unwrap() }
80/// }
81///
82/// fn save_yaml(&self, path: &str) -> Result<()> {
83/// // might want to do this differently for config with more fields
84/// let mut data = "test: ".to_string();
85///
86/// // add the value to the file data
87/// data.push_str(self.test.to_string().as_str());
88///
89/// // write to the file
90/// fs::write(path, data).unwrap();
91///
92/// // return an Ok result
93/// // required because fs::write could fail, which would pass on an Err(()).
94/// Ok(())
95/// }
96/// }
97/// ```
98pub trait YamlConfig {
99 /// Initialize a YamlConfig struct given a list of Yaml documents from a parsed file.
100 /// ### Example
101 /// ```rust
102 /// # use yaml_rust;
103 /// # use rsconfig::YamlConfig;
104 /// # use std::io::Result;
105 ///
106 /// # struct T { test: bool }
107 /// # impl YamlConfig for T {
108 /// fn from_yaml(yaml: Vec<yaml_rust::Yaml>) -> Self {
109 /// // fetch "test" value of the first yaml document using yaml_rust crate
110 /// // NOTE: this code is not error-safe, will panic if the file does not contain a bool named "test"
111 /// Self { test: *&yaml[0]["test"].as_bool().unwrap() }
112 /// }
113 /// # fn save_yaml(&self, path: &str) -> Result<()> {Ok(())}
114 /// # }
115 /// ```
116 fn from_yaml(yaml: Vec<Yaml>) -> Self;
117
118 /// Save a YamlConfig struct's contents to a YAML (YML) file.
119 /// ### Example
120 /// ```rust
121 /// # use std::{fs, io::Result};
122 /// # use rsconfig::YamlConfig;
123 /// # use rust_yaml::Yaml;
124 ///
125 /// # struct T { test: bool }
126 /// # impl YamlConfig for T {
127 /// # fn from_yaml(yaml: Vec<Yaml>) -> Self {Self{test: false}}
128 /// fn save_yaml(&self, path: &str) -> Result<()> {
129 /// // might want to do this differently for config with more fields
130 ///
131 /// let mut data = "test: ".to_string();
132 ///
133 /// // add the value to the file data
134 /// data.push_str(self.test.to_string().as_str());
135 ///
136 /// // write to the file
137 /// fs::write(path, data).unwrap();
138 ///
139 /// // return an Ok result
140 /// // required because fs::write could fail, which would pass on an Err(()).
141 /// Ok(())
142 /// }
143 /// # }
144 /// ```
145 fn save_yaml(&self, path: &str) -> io::Result<()>;
146}
147
148/// Represents a configuration struct that can be created from a JSON file.
149/// ### Example
150/// ```rust
151/// use serde_json;
152///
153/// use rsconfig::JsonConfig;
154///
155/// use std::fs;
156///
157/// #[derive(Debug)]
158/// struct TestConfig {
159/// test: bool
160/// }
161///
162/// impl JsonConfig for TestConfig {
163/// fn from_json(val: serde_json::Value) -> Self {
164/// // look for "test" val
165/// // NOTE: this code is not error-safe, will panic if the json does not contain a bool named "test"
166/// Self { test: val["test"].as_bool().unwrap() }
167/// }
168///
169/// fn save_json(&self, path: &str) -> io::Result<()> {
170/// // convert to json pretty format and save
171/// let mut m: Hashmap<&str, Value> = Hashmap::new();
172/// m.insert("test", &Value::from(self.test));
173/// let data = serde_json::to_string_pretty(m).unwrap();
174/// fs::write(path, data).unwrap();
175///
176/// Ok(())
177/// }
178/// }
179/// ```
180pub trait JsonConfig {
181 /// Initialize a JsonConfig struct from a given json value.
182 /// ### Example
183 /// ```rust
184 /// # use serde_json;
185 /// # use rsconfig::JsonConfig;
186 /// # use std::io::Result;
187 ///
188 /// # struct T { test: bool }
189 /// # impl JsonConfig for T {
190 /// fn from_json(val: serde_json::Value) -> Self {
191 /// // look for "test" val
192 /// // NOTE: this code is not error-safe, will panic if the json does not contain a bool named "test"
193 /// Self { test: val["test"].as_bool().unwrap() }
194 /// }
195 /// # fn save_json(&self, path: &str) -> Result<()> {Ok(())}
196 /// # }
197 /// ```
198 fn from_json(val: Value) -> Self;
199
200 /// Save a JsonConfig struct's contents to a JSON file.
201 /// ### Example
202 /// ```rust
203 /// # use std::{fs, io::Result, collections::HashMap};
204 /// # use serde_json::Value;
205 /// # use rsconfig::JsonConfig;
206 ///
207 /// # struct T { test: bool }
208 /// # impl JsonConfig for T {
209 /// # fn from_json(val: Value) -> Self{Self{test: true}}
210 /// fn save_json(&self, path: &str) -> Result<()> {
211 /// // convert to json pretty format and save
212 /// let mut m: HashMap<&str, Value> = HashMap::new();
213 /// m.insert("test", Value::from(self.test));
214 /// let data = serde_json::to_string_pretty(&m).unwrap();
215 /// fs::write(path, data).unwrap();
216 ///
217 /// Ok(())
218 /// }
219 /// # }
220 /// ```
221 fn save_json(&self, path: &str) -> io::Result<()>;
222}
223
224/// Represents a configuration struct that can be created from a number of file types.
225/// ### Example
226/// ```rust
227/// use rsconfig::{YamlConfig, JsonConfig, FileConfig};
228///
229/// use serde_json;
230/// use yaml_rust;
231///
232/// // rsconfig-macros crate has a derive macro for this trait
233/// #[derive(Debug)]
234/// struct TestConfig {
235/// test: bool
236/// }
237///
238/// impl YamlConfig for TestConfig {
239/// fn from_yaml(yaml: Vec<yaml_rust::Yaml>) -> Self {
240/// Self { test: *&yaml[0]["test"].as_bool().unwrap() }
241/// }
242///
243/// fn save_yaml(&self, path: &str) -> Result<()> {
244/// let mut data = "test: ".to_string();
245/// data.push_str(self.test.to_string().as_str());
246///
247/// fs::write(path, data).unwrap();
248///
249/// Ok(())
250/// }
251/// }
252///
253/// impl JsonConfig for TestConfig {
254/// fn from_json(val: Value) -> Self {
255/// Self { test: val["test"].as_bool().unwrap() }
256/// }
257///
258/// fn save_json(&self, path: &str) -> io::Result<()> {
259/// // convert to json pretty format and save
260/// let mut m: Hashmap<&str, Value> = Hashmap::new();
261/// m.insert("test", &Value::from(self.test));
262/// let data = serde_json::to_string_pretty(m).unwrap();
263/// fs::write(path, data).unwrap();
264///
265/// Ok(())
266/// }
267/// }
268/// impl FileConfig for TestConfig {}
269/// ```
270
271pub trait FileConfig: YamlConfig + JsonConfig {}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use rsconfig_macros::*;
277
278 use std::{collections::HashMap, env, fs, io::Result};
279
280 // config class that we can expand upon to add different values
281 #[derive(Debug)]
282 struct TestConfig {
283 test: bool,
284 }
285
286 impl CommandlineConfig for TestConfig {
287 fn from_env_args(args: Vec<String>) -> Self {
288 Self {
289 test: args.contains(&"test".to_string()),
290 }
291 }
292 }
293
294 impl YamlConfig for TestConfig {
295 fn from_yaml(yaml: Vec<yaml_rust::Yaml>) -> Self {
296 Self {
297 test: *&yaml[0]["test"].as_bool().unwrap(),
298 }
299 }
300
301 fn save_yaml(&self, path: &str) -> Result<()> {
302 let mut data = "test: ".to_string();
303 data.push_str(self.test.to_string().as_str());
304
305 fs::write(path, data).unwrap();
306
307 Ok(())
308 }
309 }
310
311 impl JsonConfig for TestConfig {
312 fn from_json(val: Value) -> Self {
313 Self {
314 test: val["test"].as_bool().unwrap(),
315 }
316 }
317
318 fn save_json(&self, path: &str) -> io::Result<()> {
319 // convert to json pretty format and save
320 let mut m: HashMap<&str, Value> = HashMap::new();
321 m.insert("test", Value::from(self.test));
322 let data = serde_json::to_string_pretty(&m).unwrap();
323 fs::write(path, data).unwrap();
324
325 Ok(())
326 }
327 }
328
329 impl FileConfig for TestConfig {}
330
331 // path to test files
332 const YAML_PATH: &str = "testing\\test.yml";
333 const JSON_PATH: &str = "testing\\test.json";
334
335 #[test]
336 fn args_test() {
337 // under normal test command (cargo test --package rsconfig --lib -- tests --nocapture),
338 // this will always create `config` with `test` as false
339
340 let args: Vec<String> = env::args().collect();
341
342 let mut config = TestConfig::from_env_args(args);
343
344 println!("{:?}", config);
345
346 change_config(&mut config);
347 }
348
349 #[test]
350 fn yaml_test() {
351 // loads from yaml; could use files::load_from_file(),
352 // but since we already know the filetype, it's better to just do this
353
354 let mut config: TestConfig = files::load_from_yaml(YAML_PATH);
355
356 println!("{:?}", config);
357
358 change_config(&mut config);
359 }
360
361 #[test]
362 fn json_test() {
363 // loads from json; could use files::load_from_file(),
364 // but since we already know the filetype, it's better to just do this
365
366 let mut config: TestConfig = files::load_from_json(JSON_PATH);
367
368 println!("{:?}", config);
369
370 change_config(&mut config);
371
372 // saving both yaml and json but idc don't want to copy one line of code
373 config.save_json(JSON_PATH).expect("Unable to save");
374 }
375
376 #[test]
377 fn file_test() {
378 let mut config: TestConfig =
379 files::load_from_file(YAML_PATH).expect("Unable to load from file");
380
381 println!("{:?}", config);
382
383 change_config(&mut config);
384 }
385
386 // swaps the `test` variable value and saves
387 fn change_config(config: &mut TestConfig) {
388 config.test = !config.test;
389
390 config.save_yaml(YAML_PATH).expect("Unable to save");
391
392 println!("{:?}", config);
393 }
394}