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.
 */
use toml::Value;

use data::Container;
use enums::ExtractResult;
use item_def::ItemDef;
use item_value::ItemValue;
use toml_def::TomlDef;
use value::StringValue;

//*************************************************************************************************
/// This item is used to read an string value from the TOML file
pub struct ItemStr
{
    //---------------------------------------------------------------------------------------------
    /// The name of the item.
    name : String,

    //---------------------------------------------------------------------------------------------
    /// The min length of the string.
    min : Option<usize>,

    //---------------------------------------------------------------------------------------------
    /// The max length of the string.
    max : Option<usize>,

    //---------------------------------------------------------------------------------------------
    /// Flag indicating if the item is optional.
    optional : bool,

    //---------------------------------------------------------------------------------------------
    /// The default value for the value.
    default : Option<String>
}

impl ItemStr
{
    //*********************************************************************************************
    /// Constructs a new instance of the ItemUsize object.
    pub fn with_name<T:AsRef<str>>(name : T) -> ItemStr
    {
        ItemStr {
            name     : String::from(name.as_ref()),
            min      : None,
            max      : None,
            optional : false,
            default  : None
        }
    }

    //*********************************************************************************************
    /// Adds the item to a group and returns an option that will receive the item's value when
    /// the file is loaded.  Basically this allows a program to receive a value laoded from
    /// the file without getting the TomlData object that was created.
    ///
    /// # Examples
    ///
    /// ```
    /// use valid_toml::{TomlDef, ItemStr, ItemUsize};
    ///
    /// # fn main() {
    /// let file = r#"
    ///     password = "secret"
    ///     count   = 15
    ///     "#;
    ///
    /// let mut def = TomlDef::new();
    ///
    /// let password = ItemStr::with_name("password").min(3).max(8).add_to(&mut def);
    /// let count    = ItemUsize::with_name("count").optional().add_to(&mut def);
    ///
    /// let file = def.parse_toml(file).unwrap();
    ///
    /// assert_eq!(password.get(), "secret");
    /// assert_eq!(count.get(), 15);
    /// # }
    /// ```
    pub fn add_to(
        self,
        group : &mut TomlDef
        ) -> StringValue
    {
        let notify = StringValue::new();

        group.add_notify(self.name.clone(), Box::new(notify.clone()));
        group.ref_add(self);

        notify
    }

    //*********************************************************************************************
    /// Defines the minimum length the string can be.
    pub fn min(
        mut self,
        min : usize
        ) -> Self
    {
        self.min = Some(min);

        self
    }

    //*********************************************************************************************
    /// Defines the maximum length the string can be.
    pub fn max(
        mut self,
        max : usize
        ) -> Self
    {
        self.max = Some(max);

        self
    }

    //*********************************************************************************************
    /// Marks the item as optional without providing a default value.
    pub fn optional(
        mut self,
        ) -> Self
    {
        self.optional = true;

        self
    }

    //*********************************************************************************************
    /// Defines the default value for the item.  This method makes the item optional.  The default
    /// value will only be used if the parent group exists.
    pub fn default<T:AsRef<str>>(
        mut self,
        default : T
        ) -> Self
    {
        self.default = Some(String::from(default.as_ref()));

        self
    }
}

impl ItemDef for ItemStr
{
    //*********************************************************************************************
    /// Returns the name of the item.
    fn name(&self) -> &str
    {
        &self.name
    }

    //*********************************************************************************************
    /// Validates and extracts the value.
    fn extract(
        &self,
        value : &Value
        ) -> ExtractResult
    {
        //-----------------------------------------------------------------------------------------
        // Extract the string.
        if let Some(value) = value.as_str()
        {
            //---------------------------------------------------------------------------------
            // If there is a min value, make sure we're over or equal to it.
            if let Some(min) = self.min
            {
                if value.len() < min
                {
                    return ExtractResult::short(self.min.clone(), self.max.clone())
                }
            }

            //---------------------------------------------------------------------------------
            // If there is a max value, make sure we're under or equal to it.
            if let Some(max) = self.max
            {
                if value.len() > max
                {
                    return ExtractResult::long(self.min.clone(), self.max.clone())
                }
            }

            //---------------------------------------------------------------------------------
            // The value looks good.
            ExtractResult::Item(ItemValue::String(String::from(value)))
        }
        //-----------------------------------------------------------------------------------------
        // The value is not a string.
        else
        {
            ExtractResult::incorrect_type("number")
        }
    }

    //*********************************************************************************************
    /// Returns true if the item is optional.
    fn is_optional(&self) -> bool
    {
        self.optional
    }

    //*********************************************************************************************
    /// Returns the default value for the item if it exists, otherwise return Err.
    fn default(&self) -> Option<ItemValue>
    {
        self.default.as_ref().map(|x|ItemValue::String(x.clone()))
    }
}

#[cfg(test)]
mod tests
{
    use toml::Value;
    use item_def::ItemDef;
    use item_value::ItemValue;
    use enums::{ExtractResult, ValidationError};

    macro_rules! test {
        ($item:expr, $val:expr) => ($item.extract(&Value::String(String::from($val))))
    }

    //*********************************************************************************************
    /// Test setting the values don't panic.
    #[test]
    fn set()
    {
        super::ItemStr::with_name("a").min(12);
        super::ItemStr::with_name("b").max(67);
        super::ItemStr::with_name("c").default("abc");
    }

    //*********************************************************************************************
    /// Test that the min value is honored.
    #[test]
    fn min()
    {
        let test = super::ItemStr::with_name("b").min(4);

        assert_short!(test!(test, "ab"), 4);
        assert_string!(test!(test, "abcd"), "abcd");
        assert_string!(test!(test, "abcdef"), "abcdef");
    }

    //*********************************************************************************************
    /// Test that the max value is honored.
    #[test]
    fn max()
    {
        let test = super::ItemStr::with_name("b").max(4);

        assert_string!(test!(test, "abcd"), "abcd");
        assert_string!(test!(test, ""), "");
        assert_long!(test!(test, "abcdef"), 4);
    }

    //*********************************************************************************************
    /// Test that the min and max values are honored.
    #[test]
    fn min_max()
    {
        let test = super::ItemStr::with_name("b").min(2).max(4);

        assert_short!(test!(test, ""), 2, 4);
        assert_string!(test!(test, "ab"), "ab");
        assert_string!(test!(test, "abc"), "abc");
        assert_string!(test!(test, "abcd"), "abcd");
        assert_long!(test!(test, "abcde"), 2, 4);
    }
}