valid_toml 0.0.2

Provides the ability to load a TOML file with validation.
Documentation
/* Copyright 2016 Joshua Gentry
 *
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 * http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 * <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 * option. This file may not be copied, modified, or distributed
 * except according to those terms.
 */

//*************************************************************************************************
//! This crate provides validation on a TOML file to ensure that the values read from the file are
//! correct for the application.
//!
//! Currently the crate only supports reading strings, usize, u16, and durations elements from
//! the TOML file.  The usize and u16 elements are basically a customization of a TOML number
//! where the range is restricted to valid values.  Likewise a duration element is a specialized
//! string in the format "##d ##h ##m ##s ##ms".
//!
//! There are plans for adding more data types, but so far I've only created the data types that
//! I need for to read from the configuration file.
//!
//! Given a TOML file like:
//!
//! ```text
//! threads = 16
//!
//! [database]
//! host = "localhost"
//! port = 5432
//! ```
//!
//! You can access the data using the following code:
//!
//! ```
//! use valid_toml::{TomlDef, ItemStr, ItemUsize, ItemU16};
//!
//! # fn main() {
//! # let file = r#"
//! #    threads = 16
//! #    [database]
//! #    host = "localhost"
//! #    port = 5432
//! #    "#;
//! let mut def = TomlDef::new()
//!     .add(ItemUsize::with_name("threads").min(1).max(32).default(4))
//!     .add(TomlDef::with_name("database")
//!         .add(ItemStr::with_name("host").default("localhost"))
//!         .add(ItemU16::with_name("port").default(5432)));
//!
//! // Load the contents of the TOML file into the string "file" and call
//! // TomlDef::parse_toml::<T:AsRef<str>>(input : T ) to parse it.  Or just call
//! // TomlDef::load_toml::<P : AsRef<Path>>(file : P ) to have the crate load it.
//! match def.parse_toml(file) {
//!     Ok(data) => {
//!         assert_eq!(data.get_usize("threads"), 16);
//!         assert_eq!(data.get_str("database.host"), "localhost");
//!         assert_eq!(data.get_u16("database.port"), 5432);
//!     },
//!     Err(issues) => {
//!         println!("Error reading the configuration file: ");
//!         for err in issues.errors.iter() {
//!             println!("    {}", err);
//!         }
//!     }
//! }
//! # }
//! ```
#[macro_use]
extern crate log;
extern crate toml;
extern crate shareable;

#[macro_use]
mod test_macros;

mod data;
mod value;

mod issues;
mod item_def;
mod item_value;
mod enums;
mod notify;
mod toml_builder;
mod toml_data;
mod toml_def;
mod toml_iter;

pub use toml_data::TomlData;
pub use toml_def::TomlDef;
pub use data::{ItemDuration, ItemStr, ItemU16, ItemUsize};
pub use issues::Issues;
pub use enums::{Error, FormatDesc, Warning, ValidationError};

#[cfg(test)]
mod tests {
    use super::{TomlDef, ItemDuration, ItemStr, ItemU16, ItemUsize};

    //*********************************************************************************************
    /// Simple test.
    #[test]
    fn simple() {
        let file = r#"
            delay = "3d 4h 5m 6s 7ms"
            name  = "foo"
            short = 16
            large = 123456789
        "#;

        let data = TomlDef::new()
            .add(ItemDuration::with_name("delay").min("6h").max("7d"))
            .add(ItemStr::with_name("name").min(1).max(16))
            .add(ItemU16::with_name("short").min(6).max(41))
            .add(ItemUsize::with_name("large").min(3))
            .parse_toml(file).unwrap();

        assert_eq!(data.get_duration("delay"), 3 * 24 * 60 * 60 * 1000 + 4 * 60 * 60 * 1000 + 5 * 60 * 1000 + 6 * 1000 + 7);
        assert_eq!(data.get_str("name"), "foo");
        assert_eq!(data.get_u16("short"), 16);
        assert_eq!(data.get_usize("large"), 123456789);
    }

    //*********************************************************************************************
    /// Test with groups.
    #[test]
    fn complex() {
        let file = r#"
            delay = "3d 4h 5m 6s 7ms"
            name  = "foo"
            short = 16
            large = 123456789

            [dup]
            delay = "7d 6h 5m 4s 3ms"
            name  = "bar"
            short = 89
            large = 987654321

            [other]
            test = "testing"
        "#;

        let data = TomlDef::new()
            .add(ItemDuration::with_name("delay").min("6h").max("7d"))
            .add(ItemStr::with_name("name").min(1).max(16))
            .add(ItemU16::with_name("short").min(6).max(41))
            .add(ItemUsize::with_name("large").max(200000000))
            .add(TomlDef::with_name("dup")
                .add(ItemDuration::with_name("delay").min("4d").max("8d"))
                .add(ItemStr::with_name("name").min(1).max(16))
                .add(ItemU16::with_name("short").min(22).max(100))
                .add(ItemUsize::with_name("large").min(300000000)))
            .add(TomlDef::with_name("other")
                .add(ItemStr::with_name("test").min(1).max(16)))
            .parse_toml(file).unwrap();

        assert_eq!(data.get_duration("delay"), 3 * 24 * 60 * 60 * 1000 + 4 * 60 * 60 * 1000 + 5 * 60 * 1000 + 6 * 1000 + 7);
        assert_eq!(data.get_str("name"), "foo");
        assert_eq!(data.get_u16("short"), 16);
        assert_eq!(data.get_usize("large"), 123456789);

        assert_eq!(data.get_duration("dup.delay"), 7 * 24 * 60 * 60 * 1000 + 6 * 60 * 60 * 1000 + 5 * 60 * 1000 + 4 * 1000 + 3);
        assert_eq!(data.get_str("dup.name"), "bar");
        assert_eq!(data.get_u16("dup.short"), 89);
        assert_eq!(data.get_usize("dup.large"), 987654321);

        assert_eq!(data.get_str("other.test"), "testing");
    }

    //*********************************************************************************************
    /// Test with optional elements that are missing.
    #[test]
    fn optional_missing() {
        let file = r#"
            delay = "3d 4h 5m 6s 7ms"
            short = 16
            large = 123456789

            [dup]
            delay = "7d 6h 5m 4s 3ms"
            name  = "bar"
            short = 89
        "#;

        let data = TomlDef::new()
            .add(ItemDuration::with_name("delay").min("6h").max("7d"))
            .add(ItemStr::with_name("name").min(1).max(16).optional())
            .add(ItemU16::with_name("short").min(6).max(41))
            .add(ItemUsize::with_name("large").max(200000000))
            .add(TomlDef::with_name("dup")
                .add(ItemDuration::with_name("delay").min("4d").max("8d"))
                .add(ItemStr::with_name("name").min(1).max(16))
                .add(ItemU16::with_name("short").min(22).max(100))
                .add(ItemUsize::with_name("large").min(300000000).optional()))
            .add(TomlDef::with_name("other").optional()
                .add(ItemStr::with_name("test").min(1).max(16)))
            .parse_toml(file).unwrap();

        assert_eq!(data.get_duration("delay"), 3 * 24 * 60 * 60 * 1000 + 4 * 60 * 60 * 1000 + 5 * 60 * 1000 + 6 * 1000 + 7);
        assert   !(data.optional_str("name").is_none());
        assert_eq!(data.get_u16("short"), 16);
        assert_eq!(data.get_usize("large"), 123456789);

        assert_eq!(data.get_duration("dup.delay"), 7 * 24 * 60 * 60 * 1000 + 6 * 60 * 60 * 1000 + 5 * 60 * 1000 + 4 * 1000 + 3);
        assert_eq!(data.get_str("dup.name"), "bar");
        assert_eq!(data.get_u16("dup.short"), 89);
        assert   !(data.optional_usize("dup.large").is_none());

        assert   !(data.optional_str("other.test").is_none());
    }

    //*********************************************************************************************
    /// Test with optional elements that exist.
    #[test]
    fn optional_exist() {
        let file = r#"
            delay = "3d 4h 5m 6s 7ms"
            name  = "foo"
            short = 16
            large = 123456789

            [dup]
            delay = "7d 6h 5m 4s 3ms"
            name  = "bar"
            short = 89
            large = 987654321

            [other]
            test = "testing"
        "#;

        let data = TomlDef::new()
            .add(ItemDuration::with_name("delay").min("6h").max("7d"))
            .add(ItemStr::with_name("name").min(1).max(16).optional())
            .add(ItemU16::with_name("short").min(6).max(41))
            .add(ItemUsize::with_name("large").max(200000000))
            .add(TomlDef::with_name("dup")
                .add(ItemDuration::with_name("delay").min("4d").max("8d"))
                .add(ItemStr::with_name("name").min(1).max(16))
                .add(ItemU16::with_name("short").min(22).max(100))
                .add(ItemUsize::with_name("large").min(300000000).optional()))
            .add(TomlDef::with_name("other").optional()
                .add(ItemStr::with_name("test").min(1).max(16)))
            .parse_toml(file).unwrap();

        assert_eq!(data.get_duration("delay"), 3 * 24 * 60 * 60 * 1000 + 4 * 60 * 60 * 1000 + 5 * 60 * 1000 + 6 * 1000 + 7);
        assert_eq!(data.optional_str("name").unwrap(), "foo");
        assert_eq!(data.get_u16("short"), 16);
        assert_eq!(data.get_usize("large"), 123456789);

        assert_eq!(data.get_duration("dup.delay"), 7 * 24 * 60 * 60 * 1000 + 6 * 60 * 60 * 1000 + 5 * 60 * 1000 + 4 * 1000 + 3);
        assert_eq!(data.get_str("dup.name"), "bar");
        assert_eq!(data.get_u16("dup.short"), 89);
        assert_eq!(data.optional_usize("dup.large").unwrap(), 987654321);

        assert_eq!(data.optional_str("other.test").unwrap(), "testing");
    }

    //*********************************************************************************************
    /// Test with default values.
    #[test]
    fn default() {
        let file = r#"
            delay = "3d 4h 5m 6s 7ms"
            short = 16
            large = 123456789

            [dup]
            delay = "7d 6h 5m 4s 3ms"
            name  = "bar"
            short = 89
        "#;

        let data = TomlDef::new()
            .add(ItemDuration::with_name("delay").min("6h").max("7d"))
            .add(ItemStr::with_name("name").min(1).max(16).default("foo"))
            .add(ItemU16::with_name("short").min(6).max(41))
            .add(ItemUsize::with_name("large").max(200000000))
            .add(TomlDef::with_name("dup")
                .add(ItemDuration::with_name("delay").min("4d").max("8d"))
                .add(ItemStr::with_name("name").min(1).max(16))
                .add(ItemU16::with_name("short").min(22).max(100))
                .add(ItemUsize::with_name("large").min(300000000).default(987654321)))
            .add(TomlDef::with_name("other").optional()
                .add(ItemStr::with_name("test").min(1).max(16).default("testing")))
            .parse_toml(file).unwrap();

        assert_eq!(data.get_duration("delay"), 3 * 24 * 60 * 60 * 1000 + 4 * 60 * 60 * 1000 + 5 * 60 * 1000 + 6 * 1000 + 7);
        assert_eq!(data.get_str("name"), "foo");
        assert_eq!(data.get_u16("short"), 16);
        assert_eq!(data.get_usize("large"), 123456789);

        assert_eq!(data.get_duration("dup.delay"), 7 * 24 * 60 * 60 * 1000 + 6 * 60 * 60 * 1000 + 5 * 60 * 1000 + 4 * 1000 + 3);
        assert_eq!(data.get_str("dup.name"), "bar");
        assert_eq!(data.get_u16("dup.short"), 89);
        assert_eq!(data.get_usize("dup.large"), 987654321);

        assert!(data.optional_str("other.test").is_none());
    }
}