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 std;

use toml::Value;

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

//*************************************************************************************************
/// This item is used to read an 16 bit unsigned number from the TOML file.
pub struct ItemU16
{
    //---------------------------------------------------------------------------------------------
    /// The name of the item.
    name : String,

    //---------------------------------------------------------------------------------------------
    /// The min value for the value.
    min : Option<i64>,

    //---------------------------------------------------------------------------------------------
    /// The max value for the value.
    max : Option<i64>,

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

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

impl ItemU16
{
    //*********************************************************************************************
    /// Constructs a new instance of the ItemU16 object.
    pub fn with_name<T:AsRef<str>>(name : T) -> ItemU16
    {
        ItemU16 {
            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, ItemU16};
    ///
    /// # 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    = ItemU16::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
        ) -> U16Value
    {
        let notify = U16Value::new();

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

        notify
    }

    //*********************************************************************************************
    /// Defines the minimum value (inclusive) of the value read from the file.
    ///
    /// # Panics
    ///
    /// This method will panic if max is set and is less than min.
    pub fn min(
        mut self,
        min : u16
        ) -> Self
    {
        if let Some(max) = self.max
        {
            if max < min as i64
            {
                panic!("Minimum value [{}] is greater than the maximum value [{}]", min, max);
            }
        }

        self.min = Some(min as i64);

        self
    }

    //*********************************************************************************************
    /// Defines the maximum value (inclusive) of the value read from the file.
    ///
    /// # Panics
    ///
    /// This method will panic if min is set and is greater than max.
    pub fn max(
        mut self,
        max : u16
        ) -> Self
    {
        if let Some(min) = self.min
        {
            if min > max as i64
            {
                panic!("Maximum value [{}] is less than the minimum value [{}]", max, min);
            }
        }

        self.max = Some(max as i64);

        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(
        mut self,
        default : u16
        ) -> Self
    {
        self.default = Some(default);

        self
    }
}

impl ItemDef for ItemU16
{
    //*********************************************************************************************
    /// 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_integer()
        {
            //---------------------------------------------------------------------------------
            // If there is a min value, make sure we're over or equal to it.
            if let Some(min) = self.min
            {
                if value < min as i64
                {
                    return ExtractResult::underrun(self.min.clone(), self.max.clone())
                }
            }
            else if value < std::u16::MIN as i64
            {
                return ExtractResult::underrun(Some(0), 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 > max as i64
                {
                    return ExtractResult::overrun(self.min.clone(), self.max.clone())
                }
            }
            else if value > std::u16::MAX as i64
            {
                return ExtractResult::overrun(self.min.clone(), Some(std::u16::MAX as i64))
            }

            //---------------------------------------------------------------------------------
            // The value looks good.
            ExtractResult::Item(ItemValue::U16(value as u16))
        }
        //-----------------------------------------------------------------------------------------
        // 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.map(|x|ItemValue::U16(x))
    }
}

#[cfg(test)]
mod tests
{
    use std;

    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::Integer($val)))
    }

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

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

        assert_underrun!(test!(test, 17), 53);
        assert_u16!(test!(test, 53), 53);
        assert_u16!(test!(test, 67), 67);
    }

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

        assert_u16!(test!(test, 3), 3);
        assert_u16!(test!(test, 24), 24);
        assert_overflow!(test!(test, 67), 24);
    }

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

        assert_underrun!(test!(test, 0), 11, 17);
        assert_u16!(test!(test, 11), 11);
        assert_u16!(test!(test, 15), 15);
        assert_u16!(test!(test, 17), 17);
        assert_overflow!(test!(test, 27), 11, 17)
    }

    //*********************************************************************************************
    /// Test the extremes.
    #[test]
    fn validate()
    {
        let test = super::ItemU16::with_name("b");

        assert_u16!(test.extract(&Value::Integer(std::u16::MIN as i64)), std::u16::MIN);
        assert_u16!(test.extract(&Value::Integer(std::u16::MAX as i64)), std::u16::MAX);
        assert_underrun!(test!(test, std::u16::MIN as i64 - 1), std::u16::MIN as i64);
        assert_overflow!(test!(test, std::u16::MAX as i64 + 1), std::u16::MAX as i64);
    }

}