valid_toml/
lib.rs

1/* Copyright 2016 Joshua Gentry
2 *
3 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4 * http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5 * <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6 * option. This file may not be copied, modified, or distributed
7 * except according to those terms.
8 */
9
10//*************************************************************************************************
11//! This crate provides validation on a TOML file to ensure that the values read from the file are
12//! correct for the application.
13//!
14//! Currently the crate only supports reading strings, usize, u16, and durations elements from
15//! the TOML file.  The usize and u16 elements are basically a customization of a TOML number
16//! where the range is restricted to valid values.  Likewise a duration element is a specialized
17//! string in the format "##d ##h ##m ##s ##ms".
18//!
19//! There are plans for adding more data types, but so far I've only created the data types that
20//! I need for to read from the configuration file.
21//!
22//! Given a TOML file like:
23//!
24//! ```text
25//! threads = 16
26//!
27//! [database]
28//! host = "localhost"
29//! port = 5432
30//! ```
31//!
32//! You can access the data using the following code:
33//!
34//! ```
35//! use valid_toml::{TomlDef, ItemStr, ItemUsize, ItemU16};
36//!
37//! # fn main() {
38//! # let file = r#"
39//! #    threads = 16
40//! #    [database]
41//! #    host = "localhost"
42//! #    port = 5432
43//! #    "#;
44//! let mut def = TomlDef::new()
45//!     .add(ItemUsize::with_name("threads").min(1).max(32).default(4))
46//!     .add(TomlDef::with_name("database")
47//!         .add(ItemStr::with_name("host").default("localhost"))
48//!         .add(ItemU16::with_name("port").default(5432)));
49//!
50//! // Load the contents of the TOML file into the string "file" and call
51//! // TomlDef::parse_toml::<T:AsRef<str>>(input : T ) to parse it.  Or just call
52//! // TomlDef::load_toml::<P : AsRef<Path>>(file : P ) to have the crate load it.
53//! match def.parse_toml(file) {
54//!     Ok(data) => {
55//!         assert_eq!(data.get_usize("threads"), 16);
56//!         assert_eq!(data.get_str("database.host"), "localhost");
57//!         assert_eq!(data.get_u16("database.port"), 5432);
58//!     },
59//!     Err(issues) => {
60//!         println!("Error reading the configuration file: ");
61//!         for err in issues.errors.iter() {
62//!             println!("    {}", err);
63//!         }
64//!     }
65//! }
66//! # }
67//! ```
68#[macro_use]
69extern crate log;
70extern crate toml;
71extern crate shareable;
72
73#[macro_use]
74mod test_macros;
75
76mod data;
77mod value;
78
79mod issues;
80mod item_def;
81mod item_value;
82mod enums;
83mod notify;
84mod toml_builder;
85mod toml_data;
86mod toml_def;
87mod toml_iter;
88
89pub use toml_data::TomlData;
90pub use toml_def::TomlDef;
91pub use data::{ItemDuration, ItemStr, ItemU16, ItemUsize};
92pub use issues::Issues;
93pub use enums::{Error, FormatDesc, Warning, ValidationError};
94
95#[cfg(test)]
96mod tests {
97    use super::{TomlDef, ItemDuration, ItemStr, ItemU16, ItemUsize};
98
99    //*********************************************************************************************
100    /// Simple test.
101    #[test]
102    fn simple() {
103        let file = r#"
104            delay = "3d 4h 5m 6s 7ms"
105            name  = "foo"
106            short = 16
107            large = 123456789
108        "#;
109
110        let data = TomlDef::new()
111            .add(ItemDuration::with_name("delay").min("6h").max("7d"))
112            .add(ItemStr::with_name("name").min(1).max(16))
113            .add(ItemU16::with_name("short").min(6).max(41))
114            .add(ItemUsize::with_name("large").min(3))
115            .parse_toml(file).unwrap();
116
117        assert_eq!(data.get_duration("delay"), 3 * 24 * 60 * 60 * 1000 + 4 * 60 * 60 * 1000 + 5 * 60 * 1000 + 6 * 1000 + 7);
118        assert_eq!(data.get_str("name"), "foo");
119        assert_eq!(data.get_u16("short"), 16);
120        assert_eq!(data.get_usize("large"), 123456789);
121    }
122
123    //*********************************************************************************************
124    /// Test with groups.
125    #[test]
126    fn complex() {
127        let file = r#"
128            delay = "3d 4h 5m 6s 7ms"
129            name  = "foo"
130            short = 16
131            large = 123456789
132
133            [dup]
134            delay = "7d 6h 5m 4s 3ms"
135            name  = "bar"
136            short = 89
137            large = 987654321
138
139            [other]
140            test = "testing"
141        "#;
142
143        let data = TomlDef::new()
144            .add(ItemDuration::with_name("delay").min("6h").max("7d"))
145            .add(ItemStr::with_name("name").min(1).max(16))
146            .add(ItemU16::with_name("short").min(6).max(41))
147            .add(ItemUsize::with_name("large").max(200000000))
148            .add(TomlDef::with_name("dup")
149                .add(ItemDuration::with_name("delay").min("4d").max("8d"))
150                .add(ItemStr::with_name("name").min(1).max(16))
151                .add(ItemU16::with_name("short").min(22).max(100))
152                .add(ItemUsize::with_name("large").min(300000000)))
153            .add(TomlDef::with_name("other")
154                .add(ItemStr::with_name("test").min(1).max(16)))
155            .parse_toml(file).unwrap();
156
157        assert_eq!(data.get_duration("delay"), 3 * 24 * 60 * 60 * 1000 + 4 * 60 * 60 * 1000 + 5 * 60 * 1000 + 6 * 1000 + 7);
158        assert_eq!(data.get_str("name"), "foo");
159        assert_eq!(data.get_u16("short"), 16);
160        assert_eq!(data.get_usize("large"), 123456789);
161
162        assert_eq!(data.get_duration("dup.delay"), 7 * 24 * 60 * 60 * 1000 + 6 * 60 * 60 * 1000 + 5 * 60 * 1000 + 4 * 1000 + 3);
163        assert_eq!(data.get_str("dup.name"), "bar");
164        assert_eq!(data.get_u16("dup.short"), 89);
165        assert_eq!(data.get_usize("dup.large"), 987654321);
166
167        assert_eq!(data.get_str("other.test"), "testing");
168    }
169
170    //*********************************************************************************************
171    /// Test with optional elements that are missing.
172    #[test]
173    fn optional_missing() {
174        let file = r#"
175            delay = "3d 4h 5m 6s 7ms"
176            short = 16
177            large = 123456789
178
179            [dup]
180            delay = "7d 6h 5m 4s 3ms"
181            name  = "bar"
182            short = 89
183        "#;
184
185        let data = TomlDef::new()
186            .add(ItemDuration::with_name("delay").min("6h").max("7d"))
187            .add(ItemStr::with_name("name").min(1).max(16).optional())
188            .add(ItemU16::with_name("short").min(6).max(41))
189            .add(ItemUsize::with_name("large").max(200000000))
190            .add(TomlDef::with_name("dup")
191                .add(ItemDuration::with_name("delay").min("4d").max("8d"))
192                .add(ItemStr::with_name("name").min(1).max(16))
193                .add(ItemU16::with_name("short").min(22).max(100))
194                .add(ItemUsize::with_name("large").min(300000000).optional()))
195            .add(TomlDef::with_name("other").optional()
196                .add(ItemStr::with_name("test").min(1).max(16)))
197            .parse_toml(file).unwrap();
198
199        assert_eq!(data.get_duration("delay"), 3 * 24 * 60 * 60 * 1000 + 4 * 60 * 60 * 1000 + 5 * 60 * 1000 + 6 * 1000 + 7);
200        assert   !(data.optional_str("name").is_none());
201        assert_eq!(data.get_u16("short"), 16);
202        assert_eq!(data.get_usize("large"), 123456789);
203
204        assert_eq!(data.get_duration("dup.delay"), 7 * 24 * 60 * 60 * 1000 + 6 * 60 * 60 * 1000 + 5 * 60 * 1000 + 4 * 1000 + 3);
205        assert_eq!(data.get_str("dup.name"), "bar");
206        assert_eq!(data.get_u16("dup.short"), 89);
207        assert   !(data.optional_usize("dup.large").is_none());
208
209        assert   !(data.optional_str("other.test").is_none());
210    }
211
212    //*********************************************************************************************
213    /// Test with optional elements that exist.
214    #[test]
215    fn optional_exist() {
216        let file = r#"
217            delay = "3d 4h 5m 6s 7ms"
218            name  = "foo"
219            short = 16
220            large = 123456789
221
222            [dup]
223            delay = "7d 6h 5m 4s 3ms"
224            name  = "bar"
225            short = 89
226            large = 987654321
227
228            [other]
229            test = "testing"
230        "#;
231
232        let data = TomlDef::new()
233            .add(ItemDuration::with_name("delay").min("6h").max("7d"))
234            .add(ItemStr::with_name("name").min(1).max(16).optional())
235            .add(ItemU16::with_name("short").min(6).max(41))
236            .add(ItemUsize::with_name("large").max(200000000))
237            .add(TomlDef::with_name("dup")
238                .add(ItemDuration::with_name("delay").min("4d").max("8d"))
239                .add(ItemStr::with_name("name").min(1).max(16))
240                .add(ItemU16::with_name("short").min(22).max(100))
241                .add(ItemUsize::with_name("large").min(300000000).optional()))
242            .add(TomlDef::with_name("other").optional()
243                .add(ItemStr::with_name("test").min(1).max(16)))
244            .parse_toml(file).unwrap();
245
246        assert_eq!(data.get_duration("delay"), 3 * 24 * 60 * 60 * 1000 + 4 * 60 * 60 * 1000 + 5 * 60 * 1000 + 6 * 1000 + 7);
247        assert_eq!(data.optional_str("name").unwrap(), "foo");
248        assert_eq!(data.get_u16("short"), 16);
249        assert_eq!(data.get_usize("large"), 123456789);
250
251        assert_eq!(data.get_duration("dup.delay"), 7 * 24 * 60 * 60 * 1000 + 6 * 60 * 60 * 1000 + 5 * 60 * 1000 + 4 * 1000 + 3);
252        assert_eq!(data.get_str("dup.name"), "bar");
253        assert_eq!(data.get_u16("dup.short"), 89);
254        assert_eq!(data.optional_usize("dup.large").unwrap(), 987654321);
255
256        assert_eq!(data.optional_str("other.test").unwrap(), "testing");
257    }
258
259    //*********************************************************************************************
260    /// Test with default values.
261    #[test]
262    fn default() {
263        let file = r#"
264            delay = "3d 4h 5m 6s 7ms"
265            short = 16
266            large = 123456789
267
268            [dup]
269            delay = "7d 6h 5m 4s 3ms"
270            name  = "bar"
271            short = 89
272        "#;
273
274        let data = TomlDef::new()
275            .add(ItemDuration::with_name("delay").min("6h").max("7d"))
276            .add(ItemStr::with_name("name").min(1).max(16).default("foo"))
277            .add(ItemU16::with_name("short").min(6).max(41))
278            .add(ItemUsize::with_name("large").max(200000000))
279            .add(TomlDef::with_name("dup")
280                .add(ItemDuration::with_name("delay").min("4d").max("8d"))
281                .add(ItemStr::with_name("name").min(1).max(16))
282                .add(ItemU16::with_name("short").min(22).max(100))
283                .add(ItemUsize::with_name("large").min(300000000).default(987654321)))
284            .add(TomlDef::with_name("other").optional()
285                .add(ItemStr::with_name("test").min(1).max(16).default("testing")))
286            .parse_toml(file).unwrap();
287
288        assert_eq!(data.get_duration("delay"), 3 * 24 * 60 * 60 * 1000 + 4 * 60 * 60 * 1000 + 5 * 60 * 1000 + 6 * 1000 + 7);
289        assert_eq!(data.get_str("name"), "foo");
290        assert_eq!(data.get_u16("short"), 16);
291        assert_eq!(data.get_usize("large"), 123456789);
292
293        assert_eq!(data.get_duration("dup.delay"), 7 * 24 * 60 * 60 * 1000 + 6 * 60 * 60 * 1000 + 5 * 60 * 1000 + 4 * 1000 + 3);
294        assert_eq!(data.get_str("dup.name"), "bar");
295        assert_eq!(data.get_u16("dup.short"), 89);
296        assert_eq!(data.get_usize("dup.large"), 987654321);
297
298        assert!(data.optional_str("other.test").is_none());
299    }
300}