<commit_before>use std::{error,fmt};
use std::num::Float;

use super::{EvaluationContext,Functions,Value};

pub trait Function {
    fn evaluate<'a, 'd>(&self,
                        context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>;
}

#[derive(Copy,Clone,Debug,PartialEq,Hash)]
pub enum ArgumentType {
    Nodeset,
    Boolean,
    Number,
    String,
}

#[derive(Copy,Clone,Debug,PartialEq,Hash)]
pub enum Error {
    TooManyArguments{ expected: usize, actual: usize },
    NotEnoughArguments{ expected: usize, actual: usize },
    WrongType{ expected: ArgumentType, actual: ArgumentType },
}

impl Error {
    fn wrong_type(actual: &Value, expected: ArgumentType) -> Error {
        let actual = match *actual {
            Value::Nodes(..)   => ArgumentType::Nodeset,
            Value::String(..)  => ArgumentType::String,
            Value::Number(..)  => ArgumentType::Number,
            Value::Boolean(..) => ArgumentType::Boolean,
        };

        Error::WrongType {
            expected: expected,
            actual: actual
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        use self::Error::*;
        match *self {
            TooManyArguments{..}   => "too many arguments",
            NotEnoughArguments{..} => "not enough arguments",
            WrongType{..}          => "argument of wrong type",
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        use self::Error::*;
        match *self {
            TooManyArguments{expected, actual} => {
                write!(fmt, "too many arguments, expected {} but had {}", expected, actual)
            },
            NotEnoughArguments{expected, actual} => {
                write!(fmt, "not enough arguments, expected {} but had {}", expected, actual)
            },
            WrongType{expected, actual} => {
                write!(fmt, "argument was the wrong type, expected {:?} but had {:?}", expected, actual)
            },
        }
    }
}

fn minimum_arg_count<T>(args: &Vec<T>, minimum: usize) -> Result<(), Error> {
    let actual = args.len();
    if actual < minimum {
        Err(Error::NotEnoughArguments{expected: minimum, actual: actual})
    } else {
        Ok(())
    }
}

fn exact_arg_count<T>(args: &Vec<T>, expected: usize) -> Result<(), Error> {
    let actual = args.len();
    if actual < expected {
        Err(Error::NotEnoughArguments{ expected: expected, actual: actual })
    } else if actual > expected {
        Err(Error::TooManyArguments{ expected: expected, actual: actual })
    } else {
        Ok(())
    }
}

fn string_args(args: Vec<Value>) -> Result<Vec<String>, Error> {
    fn string_arg(v: Value) -> Result<String, Error> {
        match v {
            Value::String(s) => Ok(s),
            _ => Err(Error::wrong_type(&v, ArgumentType::String)),
        }
    }

    args.into_iter().map(string_arg).collect()
}

fn one_number(args: Vec<Value>) -> Result<f64, Error> {
    match &args[0] {
        &Value::Number(v) => Ok(v),
        a => Err(Error::wrong_type(a, ArgumentType::Number)),
    }
}

struct Last;

impl Function for Last {
    fn evaluate<'a, 'd>(&self,
                        context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Number(context.size() as f64))
    }
}

struct Position;

impl Function for Position {
    fn evaluate<'a, 'd>(&self,
                        context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Number(context.position() as f64))
    }
}

struct Count;

impl Function for Count {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 1));
        let arg = &args[0];
        match arg {
            &Value::Nodes(ref nodeset) => Ok(Value::Number(nodeset.size() as f64)),
            _ => Err(Error::wrong_type(arg, ArgumentType::Nodeset)),
        }
    }
}

struct Concat;

impl Function for Concat {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(minimum_arg_count(&args, 2));
        let args = try!(string_args(args));
        Ok(Value::String(args.concat()))
    }
}

struct StartsWith;

impl Function for StartsWith {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let v = args[0].starts_with(&*args[1]);
        Ok(Value::Boolean(v))
    }
}

struct Contains;

impl Function for Contains {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let v = args[0].contains(&*args[1]);
        Ok(Value::Boolean(v))
    }
}

struct SubstringBefore;

impl Function for SubstringBefore {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let haystack = &args[0];

        let s = match haystack.find_str(&*args[1]) {
            Some(pos) => &haystack[..pos],
            None => "",
        };

        Ok(Value::String(s.to_string()))
    }
}

struct SubstringAfter;

impl Function for SubstringAfter {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let haystack = &args[0];
        let needle = &*args[1];

        let s = match haystack.find_str(needle) {
            Some(pos) => &haystack[pos + needle.len()..],
            None => "",
        };

        Ok(Value::String(s.to_string()))
    }
}

struct Not;

impl Function for Not {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 1));
        let arg = &args[0];
        match arg {
            &Value::Boolean(v) => Ok(Value::Boolean(!v)),
            _ => Err(Error::wrong_type(arg, ArgumentType::Boolean)),
        }
    }
}

struct True;

impl Function for True {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Boolean(true))
    }
}

struct False;

impl Function for False {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Boolean(false))
    }
}

struct Floor;

impl Function for Floor {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 1));
        let arg = try!(one_number(args));
        Ok(Value::Number(arg.floor()))
    }
}

struct Ceiling;

impl Function for Ceiling {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 1));
        let arg = try!(one_number(args));
        Ok(Value::Number(arg.ceil()))
    }
}

pub fn register_core_functions(functions: &mut Functions) {
    functions.insert("last".to_string(), box Last);
    functions.insert("position".to_string(), box Position);
    functions.insert("count".to_string(), box Count);
    functions.insert("concat".to_string(), box Concat);
    functions.insert("starts-with".to_string(), box StartsWith);
    functions.insert("contains".to_string(), box Contains);
    functions.insert("substring-before".to_string(), box SubstringBefore);
    functions.insert("substring-after".to_string(), box SubstringAfter);
    functions.insert("not".to_string(), box Not);
    functions.insert("true".to_string(), box True);
    functions.insert("false".to_string(), box False);
    functions.insert("floor".to_string(), box Floor);
    functions.insert("ceiling".to_string(), box Ceiling);
}

#[cfg(test)]
mod test {
    use std::collections::HashMap;
    use document::Package;
    use super::super::{EvaluationContext,LiteralValue,Value,Functions,Variables,Namespaces};
    use super::super::nodeset::ToNode;
    use super::{
        Function,
        Error,
        Last,
        Position,
        Count,
        Concat,
        StartsWith,
        Contains,
        SubstringBefore,
        SubstringAfter,
        Floor,
        Ceiling,
    };

    struct Setup<'d> {
        functions: Functions,
        variables: Variables<'d>,
        namespaces: Namespaces,
    }

    impl<'d> Setup<'d> {
        fn new() -> Setup<'d> {
            Setup {
                functions: HashMap::new(),
                variables: HashMap::new(),
                namespaces: HashMap::new(),
            }
        }

        fn evaluate<N, F>(&self, node: N, f: F, args: Vec<Value<'d>>)
            -> Result<Value<'d>, Error>
            where N: ToNode<'d>,
                  F: Function
        {
            let context = EvaluationContext::new(
                node, &self.functions, &self.variables, &self.namespaces
            );
            f.evaluate(&context, args)
        }
    }

    fn evaluate_literal<F>(f: F, args: Vec<LiteralValue>) -> Result<LiteralValue, Error>
        where F: Function
    {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let args = args.into_iter().map(|a| a.into_value()).collect();

        let r = setup.evaluate(doc.root(), f, args);

        r.map(|r| r.into_literal_value())
    }

    #[test]
    fn last_returns_context_size() {
        let r = evaluate_literal(Last, vec![]);
        assert_eq!(Ok(LiteralValue::Number(1.0)), r);
    }

    #[test]
    fn position_returns_context_position() {
        let r = evaluate_literal(Position, vec![]);

        assert_eq!(Ok(LiteralValue::Number(1.0)), r);
    }

    #[test]
    fn count_counts_nodes_in_nodeset() {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let nodeset = nodeset![doc.root()];
        let r = setup.evaluate(doc.root(), Count, vec![Value::Nodes(nodeset)]);

        assert_eq!(Ok(Value::Number(1.0)), r);
    }

    #[test]
    fn concat_combines_strings() {
        let args = vec![LiteralValue::String("hello".to_string()),
                        LiteralValue::String(" ".to_string()),
                        LiteralValue::String("world".to_string())];
        let r = evaluate_literal(Concat, args);

        assert_eq!(Ok(LiteralValue::String("hello world".to_string())), r);
    }

    #[test]
    fn starts_with_checks_prefixes() {
        let args = vec![LiteralValue::String("hello".to_string()),
                        LiteralValue::String("he".to_string())];
        let r = evaluate_literal(StartsWith, args);

        assert_eq!(Ok(LiteralValue::Boolean(true)), r);
    }

    #[test]
    fn contains_looks_for_a_needle() {
        let args = vec![LiteralValue::String("astronomer".to_string()),
                        LiteralValue::String("ono".to_string())];
        let r = evaluate_literal(Contains, args);

        assert_eq!(Ok(LiteralValue::Boolean(true)), r);
    }

    #[test]
    fn substring_before_slices_before() {
        let args = vec![LiteralValue::String("1999/04/01".to_string()),
                        LiteralValue::String("/".to_string())];
        let r = evaluate_literal(SubstringBefore, args);

        assert_eq!(Ok(LiteralValue::String("1999".to_string())), r);
    }

    #[test]
    fn substring_after_slices_after() {
        let args = vec![LiteralValue::String("1999/04/01".to_string()),
                        LiteralValue::String("/".to_string())];
        let r = evaluate_literal(SubstringAfter, args);

        assert_eq!(Ok(LiteralValue::String("04/01".to_string())), r);
    }

    #[test]
    fn floor_rounds_down() {
        let r = evaluate_literal(Floor, vec![LiteralValue::Number(199.99)]);

        assert_eq!(Ok(LiteralValue::Number(199.0)), r);
    }

    #[test]
    fn ceiling_rounds_up() {
        let r = evaluate_literal(Ceiling, vec![LiteralValue::Number(199.99)]);

        assert_eq!(Ok(LiteralValue::Number(200.0)), r);
    }
}
<commit_msg>Extract common function infrastructure<commit_after>use std::{error,fmt};
use std::num::Float;

use super::{EvaluationContext,Functions,Value};

pub trait Function {
    fn evaluate<'a, 'd>(&self,
                        context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>;
}

#[derive(Copy,Clone,Debug,PartialEq,Hash)]
pub enum ArgumentType {
    Nodeset,
    Boolean,
    Number,
    String,
}

#[derive(Copy,Clone,Debug,PartialEq,Hash)]
pub enum Error {
    TooManyArguments{ expected: usize, actual: usize },
    NotEnoughArguments{ expected: usize, actual: usize },
    WrongType{ expected: ArgumentType, actual: ArgumentType },
}

impl Error {
    fn wrong_type(actual: &Value, expected: ArgumentType) -> Error {
        let actual = match *actual {
            Value::Nodes(..)   => ArgumentType::Nodeset,
            Value::String(..)  => ArgumentType::String,
            Value::Number(..)  => ArgumentType::Number,
            Value::Boolean(..) => ArgumentType::Boolean,
        };

        Error::WrongType {
            expected: expected,
            actual: actual
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        use self::Error::*;
        match *self {
            TooManyArguments{..}   => "too many arguments",
            NotEnoughArguments{..} => "not enough arguments",
            WrongType{..}          => "argument of wrong type",
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        use self::Error::*;
        match *self {
            TooManyArguments{expected, actual} => {
                write!(fmt, "too many arguments, expected {} but had {}", expected, actual)
            },
            NotEnoughArguments{expected, actual} => {
                write!(fmt, "not enough arguments, expected {} but had {}", expected, actual)
            },
            WrongType{expected, actual} => {
                write!(fmt, "argument was the wrong type, expected {:?} but had {:?}", expected, actual)
            },
        }
    }
}

fn minimum_arg_count<T>(args: &Vec<T>, minimum: usize) -> Result<(), Error> {
    let actual = args.len();
    if actual < minimum {
        Err(Error::NotEnoughArguments{expected: minimum, actual: actual})
    } else {
        Ok(())
    }
}

fn exact_arg_count<T>(args: &Vec<T>, expected: usize) -> Result<(), Error> {
    let actual = args.len();
    if actual < expected {
        Err(Error::NotEnoughArguments{ expected: expected, actual: actual })
    } else if actual > expected {
        Err(Error::TooManyArguments{ expected: expected, actual: actual })
    } else {
        Ok(())
    }
}

fn string_args(args: Vec<Value>) -> Result<Vec<String>, Error> {
    fn string_arg(v: Value) -> Result<String, Error> {
        match v {
            Value::String(s) => Ok(s),
            _ => Err(Error::wrong_type(&v, ArgumentType::String)),
        }
    }

    args.into_iter().map(string_arg).collect()
}

fn one_number(args: Vec<Value>) -> Result<f64, Error> {
    match &args[0] {
        &Value::Number(v) => Ok(v),
        a => Err(Error::wrong_type(a, ArgumentType::Number)),
    }
}

struct Last;

impl Function for Last {
    fn evaluate<'a, 'd>(&self,
                        context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Number(context.size() as f64))
    }
}

struct Position;

impl Function for Position {
    fn evaluate<'a, 'd>(&self,
                        context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Number(context.position() as f64))
    }
}

struct Count;

impl Function for Count {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 1));
        let arg = &args[0];
        match arg {
            &Value::Nodes(ref nodeset) => Ok(Value::Number(nodeset.size() as f64)),
            _ => Err(Error::wrong_type(arg, ArgumentType::Nodeset)),
        }
    }
}

struct Concat;

impl Function for Concat {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(minimum_arg_count(&args, 2));
        let args = try!(string_args(args));
        Ok(Value::String(args.concat()))
    }
}

struct TwoStringPredicate(fn(&str, &str) -> bool);

impl Function for TwoStringPredicate {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let v = self.0(&*args[0], &*args[1]);
        Ok(Value::Boolean(v))
    }
}

fn starts_with() -> TwoStringPredicate { TwoStringPredicate(StrExt::starts_with) }
fn contains() -> TwoStringPredicate { TwoStringPredicate(StrExt::contains) }

struct Substring(for<'s> fn(&'s str, &'s str) -> &'s str);

impl Function for Substring {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 2));
        let args = try!(string_args(args));
        let s = self.0(&*args[0], &*args[1]);
        Ok(Value::String(s.to_string()))
    }
}

fn substring_before() -> Substring {
    fn inner<'a>(haystack: &'a str, needle: &'a str) -> &'a str {
        match haystack.find_str(needle) {
            Some(pos) => &haystack[..pos],
            None => "",
        }
    }
    Substring(inner)
}

fn substring_after() -> Substring {
    fn inner<'a>(haystack: &'a str, needle: &'a str) -> &'a str {
        match haystack.find_str(needle) {
            Some(pos) => &haystack[pos + needle.len()..],
            None => "",
        }
    }
    Substring(inner)
}

struct Not;

impl Function for Not {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 1));
        let arg = &args[0];
        match arg {
            &Value::Boolean(v) => Ok(Value::Boolean(!v)),
            _ => Err(Error::wrong_type(arg, ArgumentType::Boolean)),
        }
    }
}

struct BooleanLiteral(bool);

impl Function for BooleanLiteral {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 0));
        Ok(Value::Boolean(self.0))
    }
}

fn true_fn() -> BooleanLiteral { BooleanLiteral(true) }
fn false_fn() -> BooleanLiteral { BooleanLiteral(false) }

struct NumberConvert(fn(f64) -> f64);

impl Function for NumberConvert {
    fn evaluate<'a, 'd>(&self,
                        _context: &EvaluationContext<'a, 'd>,
                        args: Vec<Value<'d>>) -> Result<Value<'d>, Error>
    {
        try!(exact_arg_count(&args, 1));
        let arg = try!(one_number(args));
        Ok(Value::Number(self.0(arg)))
    }
}

fn floor() -> NumberConvert { NumberConvert(Float::floor) }
fn ceiling() -> NumberConvert { NumberConvert(Float::ceil) }

pub fn register_core_functions(functions: &mut Functions) {
    functions.insert("last".to_string(), box Last);
    functions.insert("position".to_string(), box Position);
    functions.insert("count".to_string(), box Count);
    functions.insert("concat".to_string(), box Concat);
    functions.insert("starts-with".to_string(), box starts_with());
    functions.insert("contains".to_string(), box contains());
    functions.insert("substring-before".to_string(), box substring_before());
    functions.insert("substring-after".to_string(), box substring_after());
    functions.insert("not".to_string(), box Not);
    functions.insert("true".to_string(), box true_fn());
    functions.insert("false".to_string(), box false_fn());
    functions.insert("floor".to_string(), box floor());
    functions.insert("ceiling".to_string(), box ceiling());
}

#[cfg(test)]
mod test {
    use std::collections::HashMap;
    use document::Package;
    use super::super::{EvaluationContext,LiteralValue,Value,Functions,Variables,Namespaces};
    use super::super::nodeset::ToNode;
    use super::{
        Function,
        Error,
        Last,
        Position,
        Count,
        Concat,
    };

    struct Setup<'d> {
        functions: Functions,
        variables: Variables<'d>,
        namespaces: Namespaces,
    }

    impl<'d> Setup<'d> {
        fn new() -> Setup<'d> {
            Setup {
                functions: HashMap::new(),
                variables: HashMap::new(),
                namespaces: HashMap::new(),
            }
        }

        fn evaluate<N, F>(&self, node: N, f: F, args: Vec<Value<'d>>)
            -> Result<Value<'d>, Error>
            where N: ToNode<'d>,
                  F: Function
        {
            let context = EvaluationContext::new(
                node, &self.functions, &self.variables, &self.namespaces
            );
            f.evaluate(&context, args)
        }
    }

    fn evaluate_literal<F>(f: F, args: Vec<LiteralValue>) -> Result<LiteralValue, Error>
        where F: Function
    {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let args = args.into_iter().map(|a| a.into_value()).collect();

        let r = setup.evaluate(doc.root(), f, args);

        r.map(|r| r.into_literal_value())
    }

    #[test]
    fn last_returns_context_size() {
        let r = evaluate_literal(Last, vec![]);
        assert_eq!(Ok(LiteralValue::Number(1.0)), r);
    }

    #[test]
    fn position_returns_context_position() {
        let r = evaluate_literal(Position, vec![]);

        assert_eq!(Ok(LiteralValue::Number(1.0)), r);
    }

    #[test]
    fn count_counts_nodes_in_nodeset() {
        let package = Package::new();
        let doc = package.as_document();
        let setup = Setup::new();

        let nodeset = nodeset![doc.root()];
        let r = setup.evaluate(doc.root(), Count, vec![Value::Nodes(nodeset)]);

        assert_eq!(Ok(Value::Number(1.0)), r);
    }

    #[test]
    fn concat_combines_strings() {
        let args = vec![LiteralValue::String("hello".to_string()),
                        LiteralValue::String(" ".to_string()),
                        LiteralValue::String("world".to_string())];
        let r = evaluate_literal(Concat, args);

        assert_eq!(Ok(LiteralValue::String("hello world".to_string())), r);
    }

    #[test]
    fn starts_with_checks_prefixes() {
        let args = vec![LiteralValue::String("hello".to_string()),
                        LiteralValue::String("he".to_string())];
        let r = evaluate_literal(super::starts_with(), args);

        assert_eq!(Ok(LiteralValue::Boolean(true)), r);
    }

    #[test]
    fn contains_looks_for_a_needle() {
        let args = vec![LiteralValue::String("astronomer".to_string()),
                        LiteralValue::String("ono".to_string())];
        let r = evaluate_literal(super::contains(), args);

        assert_eq!(Ok(LiteralValue::Boolean(true)), r);
    }

    #[test]
    fn substring_before_slices_before() {
        let args = vec![LiteralValue::String("1999/04/01".to_string()),
                        LiteralValue::String("/".to_string())];
        let r = evaluate_literal(super::substring_before(), args);

        assert_eq!(Ok(LiteralValue::String("1999".to_string())), r);
    }

    #[test]
    fn substring_after_slices_after() {
        let args = vec![LiteralValue::String("1999/04/01".to_string()),
                        LiteralValue::String("/".to_string())];
        let r = evaluate_literal(super::substring_after(), args);

        assert_eq!(Ok(LiteralValue::String("04/01".to_string())), r);
    }

    #[test]
    fn floor_rounds_down() {
        let r = evaluate_literal(super::floor(), vec![LiteralValue::Number(199.99)]);

        assert_eq!(Ok(LiteralValue::Number(199.0)), r);
    }

    #[test]
    fn ceiling_rounds_up() {
        let r = evaluate_literal(super::ceiling(), vec![LiteralValue::Number(199.99)]);

        assert_eq!(Ok(LiteralValue::Number(200.0)), r);
    }
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Utilities to support Stratis.
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};

use libudev;

use super::device::is_stratis_device;
use stratis::StratisResult;

/// Takes a libudev device entry and returns the properties as a HashMap.
fn device_as_map(device: &libudev::Device) -> HashMap<String, String> {
    let rc: HashMap<_, _> = device
        .properties()
        .map(|i| {
            (
                String::from(i.name().to_string_lossy()),
                String::from(i.value().to_string_lossy()),
            )
        })
        .collect();
    rc
}

/// Common function used to retrieve the udev db entry for a block device as a HashMap when found
pub fn get_udev_block_device(
    dev_node_search: &Path,
) -> StratisResult<Option<HashMap<String, String>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = libudev::Enumerator::new(&context)?;
    enumerator.match_subsystem("block")?;

    // Get canonical form to ensure we do correct lookup in udev db
    let canonical = fs::canonicalize(dev_node_search)?;

    let result = enumerator
        .scan_devices()?
        .find(|x| x.devnode().map_or(false, |d| canonical == d))
        .and_then(|dev| Some(device_as_map(&dev)));
    Ok(result)
}

/// Lookup the WWN from the udev db using the device node eg. /dev/sda
pub fn hw_lookup(dev_node_search: &Path) -> StratisResult<Option<String>> {
    let dev = get_udev_block_device(dev_node_search)?;
    Ok(dev.and_then(|dev| dev.get("ID_WWN").and_then(|i| Some(i.clone()))))
}

/// Collect paths for all the block devices which are not individual multipath paths and which
/// appear to be empty from a udev perspective.
fn get_all_empty_devices() -> StratisResult<Vec<PathBuf>> {
    let context = libudev::Context::new()?;
    let mut enumerator = libudev::Enumerator::new(&context)?;
    enumerator.match_subsystem("block")?;

    Ok(enumerator
        .scan_devices()?
        .filter(|dev| {
            dev.property_value("DM_MULTIPATH_DEVICE_PATH").is_none()
                && !((dev.property_value("ID_PART_TABLE_TYPE").is_some()
                    && dev.property_value("ID_PART_ENTRY_DISK").is_none())
                    || dev.property_value("ID_FS_USAGE").is_some())
        })
        .filter_map(|i| i.devnode().map(|d| d.into()))
        .collect())
}

/// Retrieve all the block devices on the system that have a Stratis signature.
pub fn get_stratis_block_devices() -> StratisResult<Vec<PathBuf>> {
    let context = libudev::Context::new()?;
    let mut enumerator = libudev::Enumerator::new(&context)?;
    enumerator.match_subsystem("block")?;
    enumerator.match_property("ID_FS_TYPE", "stratis")?;

    let devices: Vec<PathBuf> = enumerator
        .scan_devices()?
        .filter(|dev| dev.property_value("DM_MULTIPATH_DEVICE_PATH").is_none())
        .filter_map(|i| i.devnode().map(|d| d.into()))
        .collect();

    if devices.is_empty() {
        // Either we don't have any stratis devices or we are using a distribution that doesn't
        // have a version of libblkid that supports stratis, lets make sure.
        // TODO: At some point in the future we can remove this and just return the devices.

        Ok(get_all_empty_devices()?
            .into_iter()
            .filter(|x| is_stratis_device(&x).ok().is_some())
            .collect())
    } else {
        Ok(devices)
    }
}
<commit_msg>Bug fix: correct is_stratis_device<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Utilities to support Stratis.
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};

use libudev;

use super::device::is_stratis_device;
use stratis::StratisResult;

/// Takes a libudev device entry and returns the properties as a HashMap.
fn device_as_map(device: &libudev::Device) -> HashMap<String, String> {
    let rc: HashMap<_, _> = device
        .properties()
        .map(|i| {
            (
                String::from(i.name().to_string_lossy()),
                String::from(i.value().to_string_lossy()),
            )
        })
        .collect();
    rc
}

/// Common function used to retrieve the udev db entry for a block device as a HashMap when found
pub fn get_udev_block_device(
    dev_node_search: &Path,
) -> StratisResult<Option<HashMap<String, String>>> {
    let context = libudev::Context::new()?;
    let mut enumerator = libudev::Enumerator::new(&context)?;
    enumerator.match_subsystem("block")?;

    // Get canonical form to ensure we do correct lookup in udev db
    let canonical = fs::canonicalize(dev_node_search)?;

    let result = enumerator
        .scan_devices()?
        .find(|x| x.devnode().map_or(false, |d| canonical == d))
        .and_then(|dev| Some(device_as_map(&dev)));
    Ok(result)
}

/// Lookup the WWN from the udev db using the device node eg. /dev/sda
pub fn hw_lookup(dev_node_search: &Path) -> StratisResult<Option<String>> {
    let dev = get_udev_block_device(dev_node_search)?;
    Ok(dev.and_then(|dev| dev.get("ID_WWN").and_then(|i| Some(i.clone()))))
}

/// Collect paths for all the block devices which are not individual multipath paths and which
/// appear to be empty from a udev perspective.
fn get_all_empty_devices() -> StratisResult<Vec<PathBuf>> {
    let context = libudev::Context::new()?;
    let mut enumerator = libudev::Enumerator::new(&context)?;
    enumerator.match_subsystem("block")?;

    Ok(enumerator
        .scan_devices()?
        .filter(|dev| {
            dev.property_value("DM_MULTIPATH_DEVICE_PATH").is_none()
                && !((dev.property_value("ID_PART_TABLE_TYPE").is_some()
                    && dev.property_value("ID_PART_ENTRY_DISK").is_none())
                    || dev.property_value("ID_FS_USAGE").is_some())
        })
        .filter_map(|i| i.devnode().map(|d| d.into()))
        .collect())
}

/// Retrieve all the block devices on the system that have a Stratis signature.
pub fn get_stratis_block_devices() -> StratisResult<Vec<PathBuf>> {
    let context = libudev::Context::new()?;
    let mut enumerator = libudev::Enumerator::new(&context)?;
    enumerator.match_subsystem("block")?;
    enumerator.match_property("ID_FS_TYPE", "stratis")?;

    let devices: Vec<PathBuf> = enumerator
        .scan_devices()?
        .filter(|dev| dev.property_value("DM_MULTIPATH_DEVICE_PATH").is_none())
        .filter_map(|i| i.devnode().map(|d| d.into()))
        .collect();

    if devices.is_empty() {
        // Either we don't have any stratis devices or we are using a distribution that doesn't
        // have a version of libblkid that supports stratis, lets make sure.
        // TODO: At some point in the future we can remove this and just return the devices.

        Ok(get_all_empty_devices()?
            .into_iter()
            .filter(|x| is_stratis_device(&x).unwrap_or(None).is_some())
            .collect())
    } else {
        Ok(devices)
    }
}
<|endoftext|>


<commit_before>//! The HTTP module which provides functions for performing requests to
//! endpoints in Discord's API.
//!
//! An important function of the REST API is ratelimiting. Requests to endpoints
//! are ratelimited to prevent spam, and once ratelimited Discord will stop
//! performing requests. The library implements protection to pre-emptively
//! ratelimit, to ensure that no wasted requests are made.
//!
//! The HTTP module comprises of two types of requests:
//!
//! - REST API requests, which require an authorization token;
//! - Other requests, which do not require an authorization token.
//!
//! The former require a [`Client`] to have logged in, while the latter may be
//! made regardless of any other usage of the library.
//!
//! If a request spuriously fails, it will be retried once.
//!
//! Note that you may want to perform requests through a [model]s'
//! instance methods where possible, as they each offer different
//! levels of a high-level interface to the HTTP module.
//!
//! [`Client`]: crate::Client
//! [model]: crate::model

pub mod client;
pub mod error;
pub mod ratelimiting;
pub mod request;
pub mod routing;
pub mod typing;
pub mod utils;

use std::{
    borrow::Cow,
    path::{Path, PathBuf},
    sync::Arc,
};

use reqwest::Method;
pub use reqwest::StatusCode;
use tokio::fs::File;

pub use self::client::*;
pub use self::error::Error as HttpError;
use self::request::Request;
pub use self::typing::*;
#[cfg(feature = "cache")]
use crate::cache::Cache;
#[cfg(feature = "client")]
use crate::client::Context;
use crate::model::prelude::*;
#[cfg(feature = "client")]
use crate::CacheAndHttp;

/// This trait will be required by functions that need [`Http`] and can
/// optionally use a [`Cache`] to potentially avoid REST-requests.
///
/// The types [`Context`], [`Cache`], and [`Http`] implement this trait
/// and thus passing these to functions expecting `impl CacheHttp` is possible.
///
/// In a situation where you have the `cache`-feature enabled but you do not
/// pass a cache, the function will behave as if no `cache`-feature is active.
///
/// If you are calling a function that expects `impl CacheHttp` as argument
/// and you wish to utilise the `cache`-feature but you got no access to a
/// [`Context`], you can pass a tuple of `(CacheRwLock, Http)`.
pub trait CacheHttp: Send + Sync {
    fn http(&self) -> &Http;
    #[cfg(feature = "cache")]
    fn cache(&self) -> Option<&Arc<Cache>> {
        None
    }
}

impl<T> CacheHttp for &T
where
    T: CacheHttp,
{
    fn http(&self) -> &Http {
        (*self).http()
    }
    #[cfg(feature = "cache")]
    fn cache(&self) -> Option<&Arc<Cache>> {
        (*self).cache()
    }
}

#[cfg(feature = "client")]
impl CacheHttp for Context {
    fn http(&self) -> &Http {
        &self.http
    }
    #[cfg(feature = "cache")]
    fn cache(&self) -> Option<&Arc<Cache>> {
        Some(&self.cache)
    }
}

#[cfg(feature = "client")]
impl CacheHttp for CacheAndHttp {
    fn http(&self) -> &Http {
        &self.http
    }
    #[cfg(feature = "cache")]
    fn cache(&self) -> Option<&Arc<Cache>> {
        Some(&self.cache)
    }
}

#[cfg(feature = "client")]
impl CacheHttp for Arc<CacheAndHttp> {
    fn http(&self) -> &Http {
        &self.http
    }
    #[cfg(feature = "cache")]
    fn cache(&self) -> Option<&Arc<Cache>> {
        Some(&self.cache)
    }
}

#[cfg(feature = "cache")]
impl CacheHttp for (&Arc<Cache>, &Http) {
    fn cache(&self) -> Option<&Arc<Cache>> {
        Some(&self.0)
    }
    fn http(&self) -> &Http {
        &self.1
    }
}

impl CacheHttp for Arc<Http> {
    fn http(&self) -> &Http {
        &*self
    }
}

#[cfg(feature = "cache")]
impl AsRef<Cache> for (&Arc<Cache>, &Http) {
    fn as_ref(&self) -> &Cache {
        &**self.0
    }
}

#[cfg(feature = "cache")]
impl AsRef<Http> for (&Arc<Cache>, &Http) {
    fn as_ref(&self) -> &Http {
        self.1
    }
}

/// An method used for ratelimiting special routes.
///
/// This is needed because [`reqwest`]'s [`Method`] enum does not derive Copy.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum LightMethod {
    /// Indicates that a route is for the `DELETE` method only.
    Delete,
    /// Indicates that a route is for the `GET` method only.
    Get,
    /// Indicates that a route is for the `PATCH` method only.
    Patch,
    /// Indicates that a route is for the `POST` method only.
    Post,
    /// Indicates that a route is for the `PUT` method only.
    Put,
}

impl LightMethod {
    pub fn reqwest_method(self) -> Method {
        match self {
            LightMethod::Delete => Method::DELETE,
            LightMethod::Get => Method::GET,
            LightMethod::Patch => Method::PATCH,
            LightMethod::Post => Method::POST,
            LightMethod::Put => Method::PUT,
        }
    }
}

/// Enum that allows a user to pass a [`Path`] or a [`File`] type to [`send_files`]
///
/// [`send_files`]: crate::model::id::ChannelId::send_files
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum AttachmentType<'a> {
    /// Indicates that the [`AttachmentType`] is a byte slice with a filename.
    Bytes { data: Cow<'a, [u8]>, filename: String },
    /// Indicates that the [`AttachmentType`] is a [`File`]
    File { file: &'a File, filename: String },
    /// Indicates that the [`AttachmentType`] is a [`Path`]
    Path(&'a Path),
    /// Indicates that the [`AttachmentType`] is an image URL.
    Image(&'a str),
}

impl<'a> From<(&'a [u8], &str)> for AttachmentType<'a> {
    fn from(params: (&'a [u8], &str)) -> AttachmentType<'a> {
        AttachmentType::Bytes {
            data: Cow::Borrowed(params.0),
            filename: params.1.to_string(),
        }
    }
}

impl<'a> From<&'a str> for AttachmentType<'a> {
    /// Constructs an [`AttachmentType`] from a string.
    /// This string may refer to the path of a file on disk, or the http url to an image on the internet.
    fn from(s: &'a str) -> AttachmentType<'_> {
        if s.starts_with("http://") || s.starts_with("https://") {
            AttachmentType::Image(s)
        } else {
            AttachmentType::Path(Path::new(s))
        }
    }
}

impl<'a> From<&'a Path> for AttachmentType<'a> {
    fn from(path: &'a Path) -> AttachmentType<'_> {
        AttachmentType::Path(path)
    }
}

impl<'a> From<&'a PathBuf> for AttachmentType<'a> {
    fn from(pathbuf: &'a PathBuf) -> AttachmentType<'_> {
        AttachmentType::Path(pathbuf.as_path())
    }
}

impl<'a> From<(&'a File, &str)> for AttachmentType<'a> {
    fn from(f: (&'a File, &str)) -> AttachmentType<'a> {
        AttachmentType::File {
            file: f.0,
            filename: f.1.to_string(),
        }
    }
}

/// Representation of the method of a query to send for the [`get_guilds`]
/// function.
///
/// [`get_guilds`]: Http::get_guilds
#[non_exhaustive]
pub enum GuildPagination {
    /// The Id to get the guilds after.
    After(GuildId),
    /// The Id to get the guilds before.
    Before(GuildId),
}

#[cfg(test)]
mod test {
    use std::path::Path;

    use super::AttachmentType;

    #[test]
    fn test_attachment_type() {
        assert!(matches!(
            AttachmentType::from(Path::new("./dogs/corgis/kona.png")),
            AttachmentType::Path(_)
        ));
        assert!(matches!(
            AttachmentType::from(Path::new("./cats/copycat.png")),
            AttachmentType::Path(_)
        ));
    }
}
<commit_msg>Implement `CacheHttp` for `Http`<commit_after>//! The HTTP module which provides functions for performing requests to
//! endpoints in Discord's API.
//!
//! An important function of the REST API is ratelimiting. Requests to endpoints
//! are ratelimited to prevent spam, and once ratelimited Discord will stop
//! performing requests. The library implements protection to pre-emptively
//! ratelimit, to ensure that no wasted requests are made.
//!
//! The HTTP module comprises of two types of requests:
//!
//! - REST API requests, which require an authorization token;
//! - Other requests, which do not require an authorization token.
//!
//! The former require a [`Client`] to have logged in, while the latter may be
//! made regardless of any other usage of the library.
//!
//! If a request spuriously fails, it will be retried once.
//!
//! Note that you may want to perform requests through a [model]s'
//! instance methods where possible, as they each offer different
//! levels of a high-level interface to the HTTP module.
//!
//! [`Client`]: crate::Client
//! [model]: crate::model

pub mod client;
pub mod error;
pub mod ratelimiting;
pub mod request;
pub mod routing;
pub mod typing;
pub mod utils;

use std::{
    borrow::Cow,
    path::{Path, PathBuf},
    sync::Arc,
};

use reqwest::Method;
pub use reqwest::StatusCode;
use tokio::fs::File;

pub use self::client::*;
pub use self::error::Error as HttpError;
use self::request::Request;
pub use self::typing::*;
#[cfg(feature = "cache")]
use crate::cache::Cache;
#[cfg(feature = "client")]
use crate::client::Context;
use crate::model::prelude::*;
#[cfg(feature = "client")]
use crate::CacheAndHttp;

/// This trait will be required by functions that need [`Http`] and can
/// optionally use a [`Cache`] to potentially avoid REST-requests.
///
/// The types [`Context`], [`Cache`], and [`Http`] implement this trait
/// and thus passing these to functions expecting `impl CacheHttp` is possible.
///
/// In a situation where you have the `cache`-feature enabled but you do not
/// pass a cache, the function will behave as if no `cache`-feature is active.
///
/// If you are calling a function that expects `impl CacheHttp` as argument
/// and you wish to utilise the `cache`-feature but you got no access to a
/// [`Context`], you can pass a tuple of `(CacheRwLock, Http)`.
pub trait CacheHttp: Send + Sync {
    fn http(&self) -> &Http;

    #[cfg(feature = "cache")]
    fn cache(&self) -> Option<&Arc<Cache>> {
        None
    }
}

impl<T> CacheHttp for &T
where
    T: CacheHttp,
{
    fn http(&self) -> &Http {
        (*self).http()
    }
    #[cfg(feature = "cache")]
    fn cache(&self) -> Option<&Arc<Cache>> {
        (*self).cache()
    }
}

#[cfg(feature = "client")]
impl CacheHttp for Context {
    fn http(&self) -> &Http {
        &self.http
    }
    #[cfg(feature = "cache")]
    fn cache(&self) -> Option<&Arc<Cache>> {
        Some(&self.cache)
    }
}

#[cfg(feature = "client")]
impl CacheHttp for CacheAndHttp {
    fn http(&self) -> &Http {
        &self.http
    }
    #[cfg(feature = "cache")]
    fn cache(&self) -> Option<&Arc<Cache>> {
        Some(&self.cache)
    }
}

#[cfg(feature = "client")]
impl CacheHttp for Arc<CacheAndHttp> {
    fn http(&self) -> &Http {
        &self.http
    }
    #[cfg(feature = "cache")]
    fn cache(&self) -> Option<&Arc<Cache>> {
        Some(&self.cache)
    }
}

#[cfg(feature = "cache")]
impl CacheHttp for (&Arc<Cache>, &Http) {
    fn cache(&self) -> Option<&Arc<Cache>> {
        Some(&self.0)
    }
    fn http(&self) -> &Http {
        &self.1
    }
}

impl CacheHttp for Http {
    fn http(&self) -> &Http {
        self
    }
}

impl CacheHttp for Arc<Http> {
    fn http(&self) -> &Http {
        &*self
    }
}

#[cfg(feature = "cache")]
impl AsRef<Cache> for (&Arc<Cache>, &Http) {
    fn as_ref(&self) -> &Cache {
        &**self.0
    }
}

#[cfg(feature = "cache")]
impl AsRef<Http> for (&Arc<Cache>, &Http) {
    fn as_ref(&self) -> &Http {
        self.1
    }
}

/// An method used for ratelimiting special routes.
///
/// This is needed because [`reqwest`]'s [`Method`] enum does not derive Copy.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum LightMethod {
    /// Indicates that a route is for the `DELETE` method only.
    Delete,
    /// Indicates that a route is for the `GET` method only.
    Get,
    /// Indicates that a route is for the `PATCH` method only.
    Patch,
    /// Indicates that a route is for the `POST` method only.
    Post,
    /// Indicates that a route is for the `PUT` method only.
    Put,
}

impl LightMethod {
    pub fn reqwest_method(self) -> Method {
        match self {
            LightMethod::Delete => Method::DELETE,
            LightMethod::Get => Method::GET,
            LightMethod::Patch => Method::PATCH,
            LightMethod::Post => Method::POST,
            LightMethod::Put => Method::PUT,
        }
    }
}

/// Enum that allows a user to pass a [`Path`] or a [`File`] type to [`send_files`]
///
/// [`send_files`]: crate::model::id::ChannelId::send_files
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum AttachmentType<'a> {
    /// Indicates that the [`AttachmentType`] is a byte slice with a filename.
    Bytes { data: Cow<'a, [u8]>, filename: String },
    /// Indicates that the [`AttachmentType`] is a [`File`]
    File { file: &'a File, filename: String },
    /// Indicates that the [`AttachmentType`] is a [`Path`]
    Path(&'a Path),
    /// Indicates that the [`AttachmentType`] is an image URL.
    Image(&'a str),
}

impl<'a> From<(&'a [u8], &str)> for AttachmentType<'a> {
    fn from(params: (&'a [u8], &str)) -> AttachmentType<'a> {
        AttachmentType::Bytes {
            data: Cow::Borrowed(params.0),
            filename: params.1.to_string(),
        }
    }
}

impl<'a> From<&'a str> for AttachmentType<'a> {
    /// Constructs an [`AttachmentType`] from a string.
    /// This string may refer to the path of a file on disk, or the http url to an image on the internet.
    fn from(s: &'a str) -> AttachmentType<'_> {
        if s.starts_with("http://") || s.starts_with("https://") {
            AttachmentType::Image(s)
        } else {
            AttachmentType::Path(Path::new(s))
        }
    }
}

impl<'a> From<&'a Path> for AttachmentType<'a> {
    fn from(path: &'a Path) -> AttachmentType<'_> {
        AttachmentType::Path(path)
    }
}

impl<'a> From<&'a PathBuf> for AttachmentType<'a> {
    fn from(pathbuf: &'a PathBuf) -> AttachmentType<'_> {
        AttachmentType::Path(pathbuf.as_path())
    }
}

impl<'a> From<(&'a File, &str)> for AttachmentType<'a> {
    fn from(f: (&'a File, &str)) -> AttachmentType<'a> {
        AttachmentType::File {
            file: f.0,
            filename: f.1.to_string(),
        }
    }
}

/// Representation of the method of a query to send for the [`get_guilds`]
/// function.
///
/// [`get_guilds`]: Http::get_guilds
#[non_exhaustive]
pub enum GuildPagination {
    /// The Id to get the guilds after.
    After(GuildId),
    /// The Id to get the guilds before.
    Before(GuildId),
}

#[cfg(test)]
mod test {
    use std::path::Path;

    use super::AttachmentType;

    #[test]
    fn test_attachment_type() {
        assert!(matches!(
            AttachmentType::from(Path::new("./dogs/corgis/kona.png")),
            AttachmentType::Path(_)
        ));
        assert!(matches!(
            AttachmentType::from(Path::new("./cats/copycat.png")),
            AttachmentType::Path(_)
        ));
    }
}
<|endoftext|>


<commit_before>//! The module implements the framing layer of HTTP/2 and exposes an API for using it.
use std::io;
use std::fmt;
use std::borrow::Cow;
use std::convert::From;
use std::error::Error;

use hpack::decoder::DecoderError;

pub mod frame;
pub mod transport;
pub mod connection;
pub mod session;
pub mod priority;

pub mod client;
pub mod server;

/// An alias for the type that represents the ID of an HTTP/2 stream
pub type StreamId = u32;
/// An alias for the type that represents an HTTP/2 header where both the name and the value is
/// owned.
pub type OwnedHeader = (Vec<u8>, Vec<u8>);

/// A convenience struct representing a part of a header (either the name or the value) that can be
/// either an owned or a borrowed byte sequence.
pub struct HeaderPart<'a>(Cow<'a, [u8]>);
impl<'a> From<Vec<u8>> for HeaderPart<'a> {
    fn from(vec: Vec<u8>) -> HeaderPart<'a> {
        HeaderPart(Cow::Owned(vec))
    }
}

impl<'a> From<&'a [u8]> for HeaderPart<'a> {
    fn from(buf: &'a [u8]) -> HeaderPart<'a> {
        HeaderPart(Cow::Borrowed(buf))
    }
}

impl<'a> From<Cow<'a, [u8]>> for HeaderPart<'a> {
    fn from(cow: Cow<'a, [u8]>) -> HeaderPart<'a> {
        HeaderPart(cow)
    }
}

macro_rules! from_static_size_array {
    ($N:expr) => (
        impl<'a> From<&'a [u8; $N]> for HeaderPart<'a> {
            fn from(buf: &'a [u8; $N]) -> HeaderPart<'a> {
                buf[..].into()
            }
        }
    );
}

macro_rules! impl_from_static_size_array {
    ($($N:expr,)+) => {
        $(
            from_static_size_array!($N);
        )+
    }
}

impl_from_static_size_array!(
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
);

impl<'n, 'v> PartialEq<Header<'n, 'v>> for OwnedHeader {
    fn eq(&self, other: &Header<'n, 'v>) -> bool {
        &self.0[..] == other.name() && &self.1[..] == other.value()
    }
}

impl<'n, 'v> PartialEq<OwnedHeader> for Header<'n, 'v> {
    fn eq(&self, other: &OwnedHeader) -> bool {
        &other.0[..] == self.name() && &other.1[..] == self.value()
    }
}

/// Represents an HTTP/2 header. Allows both the name and the value to be either an owned or a
/// borrowed byte sequence.
///
/// # Examples
///
/// A new `Header` can be created by providing an owned or borrowed name, as well as value:
///
/// ```rust
/// use solicit::http::Header;
/// // Name and value both borrowed (static) slices.
/// let h1 = Header::new(b":method", b"GET");
/// assert_eq!(h1.name(), &b":method"[..]);
/// assert_eq!(h1.value(), &b"GET"[..]);
/// // A borrowed slice with a scope-bound lifetime as the value; static name
/// {
///     let value = vec![1];
///     let header = Header::new(&b"x-test-head"[..], &value[..]);
///     assert_eq!(header.name(), &b"x-test-head"[..]);
///     assert_eq!(header.value(), &[1][..]);
/// }
/// // An owned value, static name
/// {
///     let value = vec![1];
///     let header = Header::new(&b"x-test-head"[..], value);
///     assert_eq!(header.name(), &b"x-test-head"[..]);
///     assert_eq!(header.value(), &[1][..]);
/// }
/// // An owned name, as well as value
/// {
///     let value = vec![1];
///     let name = b"x-test-head".to_vec();
///     let header = Header::new(name, value);
///     assert_eq!(header.name(), &b"x-test-head"[..]);
///     assert_eq!(header.value(), &[1][..]);
/// }
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct Header<'n, 'v> {
    name: Cow<'n, [u8]>,
    value: Cow<'v, [u8]>,
}

/// A type alias for a `Header` where both the name, as well as the value must have a `'static`
/// lifetime if it is borrowed. Owned parts are allowed.
pub type StaticHeader = Header<'static, 'static>;

impl<'n, 'v> Header<'n, 'v> {
    /// Creates a new `Header` with the given name and value.
    ///
    /// The name and value need to be convertible into a `HeaderPart`.
    pub fn new<N: Into<HeaderPart<'n>>, V: Into<HeaderPart<'v>>>(name: N, value: V)
            -> Header<'n, 'v> {
        Header {
            name: name.into().0,
            value: value.into().0,
        }
    }

    /// Return a borrowed representation of the `Header` name.
    pub fn name(&self) -> &[u8] { &self.name }
    /// Return a borrowed representation of the `Header` value.
    pub fn value(&self) -> &[u8] { &self.value }
}

impl<'n, 'v> Into<OwnedHeader> for Header<'n, 'v> {
    fn into(self) -> OwnedHeader {
        (self.name.into_owned(), self.value.into_owned())
    }
}

impl<'n, 'v> Into<Header<'n, 'v>> for OwnedHeader {
    fn into(self) -> Header<'n, 'v> {
        Header::new(self.0, self.1)
    }
}

/// A set of protocol names that the library should use to indicate that HTTP/2
/// is supported during protocol negotiation (NPN or ALPN).
/// We include some of the drafts' protocol names, since there is basically no
/// difference for all intents and purposes (and some servers out there still
/// only officially advertise draft support).
/// TODO: Eventually only use "h2".
pub const ALPN_PROTOCOLS: &'static [&'static [u8]] = &[
    b"h2",
    b"h2-16",
    b"h2-15",
    b"h2-14",
];

/// An enum representing errors that can arise when performing operations
/// involving an HTTP/2 connection.
#[derive(Debug)]
pub enum HttpError {
    IoError(io::Error),
    InvalidFrame,
    CompressionError(DecoderError),
    UnknownStreamId,
    UnableToConnect,
    // TODO This variant should be split into actual reasons for the response being malformed
    MalformedResponse,
    Other(Box<Error + Send + Sync>),
}

/// Implement the trait that allows us to automatically convert `io::Error`s
/// into an `HttpError` by wrapping the given `io::Error` into an `HttpError::IoError` variant.
impl From<io::Error> for HttpError {
    fn from(err: io::Error) -> HttpError {
        HttpError::IoError(err)
    }
}

impl fmt::Display for HttpError {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "HTTP/2 Error: {}", self.description())
    }
}

impl Error for HttpError {
    fn description(&self) -> &str {
        match *self {
            HttpError::IoError(_) => "Encountered an IO error",
            HttpError::InvalidFrame => "Encountered an invalid HTTP/2 frame",
            HttpError::CompressionError(_) => "Encountered an error with HPACK compression",
            HttpError::UnknownStreamId => "Attempted an operation with an unknown HTTP/2 stream ID",
            HttpError::UnableToConnect => "An error attempting to establish an HTTP/2 connection",
            HttpError::MalformedResponse => "The received response was malformed",
            HttpError::Other(_) => "An unknown error",
        }
    }

    fn cause(&self) -> Option<&Error> {
        match *self {
            HttpError::Other(ref e) => Some(&**e),
            HttpError::IoError(ref e) => Some(e),
            _ => None,
        }
    }
}

/// Implementation of the `PartialEq` trait as a convenience for tests.
#[cfg(test)]
impl PartialEq for HttpError {
    fn eq(&self, other: &HttpError) -> bool {
        match (self, other) {
            (&HttpError::IoError(ref e1), &HttpError::IoError(ref e2)) => {
                e1.kind() == e2.kind() && e1.description() == e2.description()
            },
            (&HttpError::InvalidFrame, &HttpError::InvalidFrame) => true,
            (&HttpError::CompressionError(ref e1), &HttpError::CompressionError(ref e2)) => {
                e1 == e2
            },
            (&HttpError::UnknownStreamId, &HttpError::UnknownStreamId) => true,
            (&HttpError::UnableToConnect, &HttpError::UnableToConnect) => true,
            (&HttpError::MalformedResponse, &HttpError::MalformedResponse) => true,
            (&HttpError::Other(ref e1), &HttpError::Other(ref e2)) => {
                e1.description() == e2.description()
            },
            _ => false,
        }
    }
}

/// A convenience `Result` type that has the `HttpError` type as the error
/// type and a generic Ok result type.
pub type HttpResult<T> = Result<T, HttpError>;

/// An enum representing the two possible HTTP schemes.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum HttpScheme {
    /// The variant corresponding to `http://`
    Http,
    /// The variant corresponding to `https://`
    Https,
}

impl HttpScheme {
    /// Returns a byte string representing the scheme.
    #[inline]
    pub fn as_bytes(&self) -> &'static [u8] {
        match *self {
            HttpScheme::Http => b"http",
            HttpScheme::Https => b"https",
        }
    }
}

/// A struct representing the full raw response received on an HTTP/2 connection.
///
/// The full body of the response is included, regardless how large it may be.
/// The headers contain both the meta-headers, as well as the actual headers.
#[derive(Clone)]
pub struct Response<'n, 'v> {
    /// The ID of the stream to which the response is associated. HTTP/1.1 does
    /// not really have an equivalent to this.
    pub stream_id: StreamId,
    /// Exposes *all* the raw response headers, including the meta-headers.
    /// (For now the only meta header allowed in HTTP/2 responses is the
    /// `:status`.)
    pub headers: Vec<Header<'n, 'v>>,
    /// The full body of the response as an uninterpreted sequence of bytes.
    pub body: Vec<u8>,
}

/// A type alias for a `Response` where all headers' names and values must have a `'static`
/// lifetime if they are borrowed. This means that the parts can also be owned.
pub type StaticResponse = Response<'static, 'static>;

impl<'n, 'v> Response<'n, 'v> {
    /// Creates a new `Response` with all the components already provided.
    pub fn new(stream_id: StreamId, headers: Vec<OwnedHeader>, body: Vec<u8>)
            -> Response<'n, 'v> {
        Response {
            stream_id: stream_id,
            headers: headers.into_iter().map(|h| Header::new(h.0, h.1)).collect(),
            body: body,
        }
    }

    /// Gets the response status code from the pseudo-header. If the response
    /// does not contain the response as the first pseuo-header, an error is
    /// returned as such a response is malformed.
    pub fn status_code(&self) -> HttpResult<u16> {
        // Since pseudo-headers MUST be found before any regular header fields
        // and the *only* pseudo-header defined for responses is the `:status`
        // field, the `:status` MUST be the first header; otherwise, the
        // response is malformed.
        if self.headers.len() < 1 {
            return Err(HttpError::MalformedResponse)
        }
        if &self.headers[0].name[..] != &b":status"[..] {
            Err(HttpError::MalformedResponse)
        } else {
            Ok(try!(Response::parse_status_code(&self.headers[0].value)))
        }
    }

    /// A helper function that parses a given buffer as a status code and
    /// returns it as a `u16`, if it is valid.
    fn parse_status_code(buf: &[u8]) -> HttpResult<u16> {
        // "The status-code element is a three-digit integer code [...]"
        if buf.len() != 3 {
            return Err(HttpError::MalformedResponse);
        }

        // "There are five values for the first digit"
        if buf[0] < b'1' || buf[0] > b'5' {
            return Err(HttpError::MalformedResponse);
        }

        // The rest of them just have to be digits
        if buf[1] < b'0' || buf[1] > b'9' || buf[2] < b'0' || buf[2] > b'9' {
            return Err(HttpError::MalformedResponse);
        }

        // Finally, we can merge them into an integer
        Ok(100 * ((buf[0] - b'0') as u16) +
           10 * ((buf[1] - b'0') as u16) +
           1 * ((buf[2] - b'0') as u16))
    }
}

/// A struct representing a full HTTP/2 request, along with the full body, as a
/// sequence of bytes.
#[derive(Clone)]
pub struct Request<'n, 'v> {
    pub stream_id: u32,
    pub headers: Vec<Header<'n, 'v>>,
    pub body: Vec<u8>,
}

#[cfg(test)]
pub mod tests;
<commit_msg>Add HTTP/2 Error Codes enum<commit_after>//! The module implements the framing layer of HTTP/2 and exposes an API for using it.
use std::io;
use std::fmt;
use std::borrow::Cow;
use std::convert::From;
use std::error::Error;

use hpack::decoder::DecoderError;

pub mod frame;
pub mod transport;
pub mod connection;
pub mod session;
pub mod priority;

pub mod client;
pub mod server;

/// An alias for the type that represents the ID of an HTTP/2 stream
pub type StreamId = u32;
/// An alias for the type that represents an HTTP/2 header where both the name and the value is
/// owned.
pub type OwnedHeader = (Vec<u8>, Vec<u8>);

/// A convenience struct representing a part of a header (either the name or the value) that can be
/// either an owned or a borrowed byte sequence.
pub struct HeaderPart<'a>(Cow<'a, [u8]>);
impl<'a> From<Vec<u8>> for HeaderPart<'a> {
    fn from(vec: Vec<u8>) -> HeaderPart<'a> {
        HeaderPart(Cow::Owned(vec))
    }
}

impl<'a> From<&'a [u8]> for HeaderPart<'a> {
    fn from(buf: &'a [u8]) -> HeaderPart<'a> {
        HeaderPart(Cow::Borrowed(buf))
    }
}

impl<'a> From<Cow<'a, [u8]>> for HeaderPart<'a> {
    fn from(cow: Cow<'a, [u8]>) -> HeaderPart<'a> {
        HeaderPart(cow)
    }
}

macro_rules! from_static_size_array {
    ($N:expr) => (
        impl<'a> From<&'a [u8; $N]> for HeaderPart<'a> {
            fn from(buf: &'a [u8; $N]) -> HeaderPart<'a> {
                buf[..].into()
            }
        }
    );
}

macro_rules! impl_from_static_size_array {
    ($($N:expr,)+) => {
        $(
            from_static_size_array!($N);
        )+
    }
}

impl_from_static_size_array!(
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8,
    9,
    10,
);

impl<'n, 'v> PartialEq<Header<'n, 'v>> for OwnedHeader {
    fn eq(&self, other: &Header<'n, 'v>) -> bool {
        &self.0[..] == other.name() && &self.1[..] == other.value()
    }
}

impl<'n, 'v> PartialEq<OwnedHeader> for Header<'n, 'v> {
    fn eq(&self, other: &OwnedHeader) -> bool {
        &other.0[..] == self.name() && &other.1[..] == self.value()
    }
}

/// Represents an HTTP/2 header. Allows both the name and the value to be either an owned or a
/// borrowed byte sequence.
///
/// # Examples
///
/// A new `Header` can be created by providing an owned or borrowed name, as well as value:
///
/// ```rust
/// use solicit::http::Header;
/// // Name and value both borrowed (static) slices.
/// let h1 = Header::new(b":method", b"GET");
/// assert_eq!(h1.name(), &b":method"[..]);
/// assert_eq!(h1.value(), &b"GET"[..]);
/// // A borrowed slice with a scope-bound lifetime as the value; static name
/// {
///     let value = vec![1];
///     let header = Header::new(&b"x-test-head"[..], &value[..]);
///     assert_eq!(header.name(), &b"x-test-head"[..]);
///     assert_eq!(header.value(), &[1][..]);
/// }
/// // An owned value, static name
/// {
///     let value = vec![1];
///     let header = Header::new(&b"x-test-head"[..], value);
///     assert_eq!(header.name(), &b"x-test-head"[..]);
///     assert_eq!(header.value(), &[1][..]);
/// }
/// // An owned name, as well as value
/// {
///     let value = vec![1];
///     let name = b"x-test-head".to_vec();
///     let header = Header::new(name, value);
///     assert_eq!(header.name(), &b"x-test-head"[..]);
///     assert_eq!(header.value(), &[1][..]);
/// }
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct Header<'n, 'v> {
    name: Cow<'n, [u8]>,
    value: Cow<'v, [u8]>,
}

/// A type alias for a `Header` where both the name, as well as the value must have a `'static`
/// lifetime if it is borrowed. Owned parts are allowed.
pub type StaticHeader = Header<'static, 'static>;

impl<'n, 'v> Header<'n, 'v> {
    /// Creates a new `Header` with the given name and value.
    ///
    /// The name and value need to be convertible into a `HeaderPart`.
    pub fn new<N: Into<HeaderPart<'n>>, V: Into<HeaderPart<'v>>>(name: N, value: V)
            -> Header<'n, 'v> {
        Header {
            name: name.into().0,
            value: value.into().0,
        }
    }

    /// Return a borrowed representation of the `Header` name.
    pub fn name(&self) -> &[u8] { &self.name }
    /// Return a borrowed representation of the `Header` value.
    pub fn value(&self) -> &[u8] { &self.value }
}

impl<'n, 'v> Into<OwnedHeader> for Header<'n, 'v> {
    fn into(self) -> OwnedHeader {
        (self.name.into_owned(), self.value.into_owned())
    }
}

impl<'n, 'v> Into<Header<'n, 'v>> for OwnedHeader {
    fn into(self) -> Header<'n, 'v> {
        Header::new(self.0, self.1)
    }
}

/// A set of protocol names that the library should use to indicate that HTTP/2
/// is supported during protocol negotiation (NPN or ALPN).
/// We include some of the drafts' protocol names, since there is basically no
/// difference for all intents and purposes (and some servers out there still
/// only officially advertise draft support).
/// TODO: Eventually only use "h2".
pub const ALPN_PROTOCOLS: &'static [&'static [u8]] = &[
    b"h2",
    b"h2-16",
    b"h2-15",
    b"h2-14",
];

/// The enum represents an error code that are used in `RST_STREAM` and `GOAWAY` frames.
/// These are defined in [Section 7](http://http2.github.io/http2-spec/#ErrorCodes) of the HTTP/2
/// spec.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ErrorCode {
    /// The associated condition is not a result of an error. For example, a GOAWAY might include
    /// this code to indicate graceful shutdown of a connection.
    NoError = 0x0,
    /// The endpoint detected an unspecific protocol error. This error is for use when a more
    /// specific error code is not available.
    ProtocolError = 0x1,
    /// The endpoint encountered an unexpected internal error.
    InternalError = 0x2,
    /// The endpoint detected that its peer violated the flow-control protocol.
    FlowControlError = 0x3,
    /// The endpoint sent a SETTINGS frame but did not receive a response in a timely manner. See
    /// Section 6.5.3 ("Settings Synchronization").
    SettingsTimeout = 0x4,
    /// The endpoint received a frame after a stream was half-closed.
    StreamClosed = 0x5,
    /// The endpoint received a frame with an invalid size.
    FrameSizeError = 0x6,
    /// The endpoint refused the stream prior to performing any application processing (see Section
    /// 8.1.4 for details).
    RefusedStream = 0x7,
    /// Used by the endpoint to indicate that the stream is no longer needed.
    Cancel = 0x8,
    /// The endpoint is unable to maintain the header compression context for the connection.
    CompressionError = 0x9,
    /// The connection established in response to a CONNECT request (Section 8.3) was reset or
    /// abnormally closed.
    ConnectError = 0xa,
    /// The endpoint detected that its peer is exhibiting a behavior that might be generating
    /// excessive load.
    EnhanceYourCalm = 0xb,
    /// The underlying transport has properties that do not meet minimum security requirements (see
    /// Section 9.2).
    InadequateSecurity = 0xc,
    /// The endpoint requires that HTTP/1.1 be used instead of HTTP/2.
    Http11Required = 0xd,
}

impl From<u32> for ErrorCode {
    /// Converts the given `u32` number to the appropriate `ErrorCode` variant.
    fn from(code: u32) -> ErrorCode {
        match code {
            0x0 => ErrorCode::NoError,
            0x1 => ErrorCode::ProtocolError,
            0x2 => ErrorCode::InternalError,
            0x3 => ErrorCode::FlowControlError,
            0x4 => ErrorCode::SettingsTimeout,
            0x5 => ErrorCode::StreamClosed,
            0x6 => ErrorCode::FrameSizeError,
            0x7 => ErrorCode::RefusedStream,
            0x8 => ErrorCode::Cancel,
            0x9 => ErrorCode::CompressionError,
            0xa => ErrorCode::ConnectError,
            0xb => ErrorCode::EnhanceYourCalm,
            0xc => ErrorCode::InadequateSecurity,
            0xd => ErrorCode::Http11Required,
            // According to the spec, unknown error codes MAY be treated as equivalent to
            // INTERNAL_ERROR.
            _ => ErrorCode::InternalError,
        }
    }
}

impl AsRef<str> for ErrorCode {
    fn as_ref(&self) -> &str {
        match *self {
            ErrorCode::NoError => "NoError",
            ErrorCode::ProtocolError => "ProtocolError",
            ErrorCode::InternalError => "InternalError",
            ErrorCode::FlowControlError => "FlowControlError",
            ErrorCode::SettingsTimeout => "SettingsTimeout",
            ErrorCode::StreamClosed => "StreamClosed",
            ErrorCode::FrameSizeError => "FrameSizeError",
            ErrorCode::RefusedStream => "RefusedStream",
            ErrorCode::Cancel => "Cancel",
            ErrorCode::CompressionError => "CompressionError",
            ErrorCode::ConnectError => "ConnectError",
            ErrorCode::EnhanceYourCalm => "EnhanceYourCalm",
            ErrorCode::InadequateSecurity => "InadequateSecurity",
            ErrorCode::Http11Required => "Http11Required",
        }
    }
}

impl Into<u32> for ErrorCode {
    #[inline]
    fn into(self) -> u32 {
        self as u32
    }
}

/// An enum representing errors that can arise when performing operations
/// involving an HTTP/2 connection.
#[derive(Debug)]
pub enum HttpError {
    IoError(io::Error),
    InvalidFrame,
    CompressionError(DecoderError),
    UnknownStreamId,
    UnableToConnect,
    // TODO This variant should be split into actual reasons for the response being malformed
    MalformedResponse,
    Other(Box<Error + Send + Sync>),
}

/// Implement the trait that allows us to automatically convert `io::Error`s
/// into an `HttpError` by wrapping the given `io::Error` into an `HttpError::IoError` variant.
impl From<io::Error> for HttpError {
    fn from(err: io::Error) -> HttpError {
        HttpError::IoError(err)
    }
}

impl fmt::Display for HttpError {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "HTTP/2 Error: {}", self.description())
    }
}

impl Error for HttpError {
    fn description(&self) -> &str {
        match *self {
            HttpError::IoError(_) => "Encountered an IO error",
            HttpError::InvalidFrame => "Encountered an invalid HTTP/2 frame",
            HttpError::CompressionError(_) => "Encountered an error with HPACK compression",
            HttpError::UnknownStreamId => "Attempted an operation with an unknown HTTP/2 stream ID",
            HttpError::UnableToConnect => "An error attempting to establish an HTTP/2 connection",
            HttpError::MalformedResponse => "The received response was malformed",
            HttpError::Other(_) => "An unknown error",
        }
    }

    fn cause(&self) -> Option<&Error> {
        match *self {
            HttpError::Other(ref e) => Some(&**e),
            HttpError::IoError(ref e) => Some(e),
            _ => None,
        }
    }
}

/// Implementation of the `PartialEq` trait as a convenience for tests.
#[cfg(test)]
impl PartialEq for HttpError {
    fn eq(&self, other: &HttpError) -> bool {
        match (self, other) {
            (&HttpError::IoError(ref e1), &HttpError::IoError(ref e2)) => {
                e1.kind() == e2.kind() && e1.description() == e2.description()
            },
            (&HttpError::InvalidFrame, &HttpError::InvalidFrame) => true,
            (&HttpError::CompressionError(ref e1), &HttpError::CompressionError(ref e2)) => {
                e1 == e2
            },
            (&HttpError::UnknownStreamId, &HttpError::UnknownStreamId) => true,
            (&HttpError::UnableToConnect, &HttpError::UnableToConnect) => true,
            (&HttpError::MalformedResponse, &HttpError::MalformedResponse) => true,
            (&HttpError::Other(ref e1), &HttpError::Other(ref e2)) => {
                e1.description() == e2.description()
            },
            _ => false,
        }
    }
}

/// A convenience `Result` type that has the `HttpError` type as the error
/// type and a generic Ok result type.
pub type HttpResult<T> = Result<T, HttpError>;

/// An enum representing the two possible HTTP schemes.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum HttpScheme {
    /// The variant corresponding to `http://`
    Http,
    /// The variant corresponding to `https://`
    Https,
}

impl HttpScheme {
    /// Returns a byte string representing the scheme.
    #[inline]
    pub fn as_bytes(&self) -> &'static [u8] {
        match *self {
            HttpScheme::Http => b"http",
            HttpScheme::Https => b"https",
        }
    }
}

/// A struct representing the full raw response received on an HTTP/2 connection.
///
/// The full body of the response is included, regardless how large it may be.
/// The headers contain both the meta-headers, as well as the actual headers.
#[derive(Clone)]
pub struct Response<'n, 'v> {
    /// The ID of the stream to which the response is associated. HTTP/1.1 does
    /// not really have an equivalent to this.
    pub stream_id: StreamId,
    /// Exposes *all* the raw response headers, including the meta-headers.
    /// (For now the only meta header allowed in HTTP/2 responses is the
    /// `:status`.)
    pub headers: Vec<Header<'n, 'v>>,
    /// The full body of the response as an uninterpreted sequence of bytes.
    pub body: Vec<u8>,
}

/// A type alias for a `Response` where all headers' names and values must have a `'static`
/// lifetime if they are borrowed. This means that the parts can also be owned.
pub type StaticResponse = Response<'static, 'static>;

impl<'n, 'v> Response<'n, 'v> {
    /// Creates a new `Response` with all the components already provided.
    pub fn new(stream_id: StreamId, headers: Vec<OwnedHeader>, body: Vec<u8>)
            -> Response<'n, 'v> {
        Response {
            stream_id: stream_id,
            headers: headers.into_iter().map(|h| Header::new(h.0, h.1)).collect(),
            body: body,
        }
    }

    /// Gets the response status code from the pseudo-header. If the response
    /// does not contain the response as the first pseuo-header, an error is
    /// returned as such a response is malformed.
    pub fn status_code(&self) -> HttpResult<u16> {
        // Since pseudo-headers MUST be found before any regular header fields
        // and the *only* pseudo-header defined for responses is the `:status`
        // field, the `:status` MUST be the first header; otherwise, the
        // response is malformed.
        if self.headers.len() < 1 {
            return Err(HttpError::MalformedResponse)
        }
        if &self.headers[0].name[..] != &b":status"[..] {
            Err(HttpError::MalformedResponse)
        } else {
            Ok(try!(Response::parse_status_code(&self.headers[0].value)))
        }
    }

    /// A helper function that parses a given buffer as a status code and
    /// returns it as a `u16`, if it is valid.
    fn parse_status_code(buf: &[u8]) -> HttpResult<u16> {
        // "The status-code element is a three-digit integer code [...]"
        if buf.len() != 3 {
            return Err(HttpError::MalformedResponse);
        }

        // "There are five values for the first digit"
        if buf[0] < b'1' || buf[0] > b'5' {
            return Err(HttpError::MalformedResponse);
        }

        // The rest of them just have to be digits
        if buf[1] < b'0' || buf[1] > b'9' || buf[2] < b'0' || buf[2] > b'9' {
            return Err(HttpError::MalformedResponse);
        }

        // Finally, we can merge them into an integer
        Ok(100 * ((buf[0] - b'0') as u16) +
           10 * ((buf[1] - b'0') as u16) +
           1 * ((buf[2] - b'0') as u16))
    }
}

/// A struct representing a full HTTP/2 request, along with the full body, as a
/// sequence of bytes.
#[derive(Clone)]
pub struct Request<'n, 'v> {
    pub stream_id: u32,
    pub headers: Vec<Header<'n, 'v>>,
    pub body: Vec<u8>,
}

#[cfg(test)]
pub mod tests;
<|endoftext|>


<commit_before>use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::{env, str};
use std::error::Error;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use toml;

use dependency::Dependency;

/// Enumeration of errors which can occur when working with a rust manifest.
quick_error! {
    #[derive(Debug)]
    pub enum ManifestError {
        /// Cargo.toml could not be found.
        MissingManifest {
            description("missing manifest")
            display("Your Cargo.toml is missing.")
        }
        /// The TOML table could not be found.
        NonExistentTable(table: String) {
            description("non existent table")
            display("The table `{}` could not be found.", table)
        }
        /// The dependency could not be found.
        NonExistentDependency(name: String, table: String) {
            description("non existent dependency")
            display("The dependency `{}` could not be found in `{}`.", name, table)
        }
        ParseError(error: String, loline: usize, locol: usize, hiline: usize, hicol: usize) {
            description("parse error")
            display("{line}:{col}{upto} {error_msg}",
                line = loline + 1,
                col = locol + 1,
                upto = if loline != hiline || locol != hicol {
                    format!("-{}:{}", hiline + 1, hicol + 1)
                } else {
                    "".to_string()
                },
                error_msg = error)
        }
    }
}

enum CargoFile {
    Config,
    Lock,
}

/// A Cargo Manifest
#[derive(Debug, Clone, PartialEq)]
pub struct Manifest {
    /// Manifest contents as TOML data
    pub data: toml::Table,
}

/// If a manifest is specified, return that one, otherise perform a manifest search starting from
/// the current directory.
/// If a manifest is specified, return that one. If a path is specified, perform a manifest search
/// starting from there. If nothing is specified, start searching from the current directory
/// (`cwd`).
fn find(specified: &Option<&str>, file: CargoFile) -> Result<PathBuf, Box<Error>> {
    let file_path = specified.map(PathBuf::from);

    if let Some(path) = file_path {
        if try!(fs::metadata(&path)).is_file() {
            Ok(path)
        } else {
            search(&path, file).map_err(From::from)
        }
    } else {
        env::current_dir()
            .map_err(From::from)
            .and_then(|ref dir| search(dir, file).map_err(From::from))
    }
}

/// Search for Cargo.toml in this directory and recursively up the tree until one is found.
fn search(dir: &Path, file: CargoFile) -> Result<PathBuf, ManifestError> {
    let manifest = match file {
        CargoFile::Config => dir.join("Cargo.toml"),
        CargoFile::Lock => dir.join("Cargo.lock"),
    };

    fs::metadata(&manifest)
        .map(|_| manifest)
        .or(dir.parent().ok_or(ManifestError::MissingManifest).and_then(|dir| search(dir, file)))
}

impl Manifest {
    /// Look for a `Cargo.toml` file
    ///
    /// Starts at the given path an goes into its parent directories until the manifest file is
    /// found. If no path is given, the process's working directory is used as a starting point.
    pub fn find_file(path: &Option<&str>) -> Result<File, Box<Error>> {
        find(path, CargoFile::Config).and_then(|path| {
            OpenOptions::new()
                .read(true)
                .write(true)
                .open(path)
                .map_err(From::from)
        })
    }

    /// Look for a `Cargo.lock` file
    ///
    /// Starts at the given path an goes into its parent directories until the manifest file is
    /// found. If no path is given, the process' working directory is used as a starting point.
    pub fn find_lock_file(path: &Option<&str>) -> Result<File, Box<Error>> {
        find(path, CargoFile::Lock).and_then(|path| {
            OpenOptions::new()
                .read(true)
                .write(true)
                .open(path)
                .map_err(From::from)
        })
    }

    /// Open the `Cargo.toml` for a path (or the process' `cwd`)
    pub fn open(path: &Option<&str>) -> Result<Manifest, Box<Error>> {
        let mut file = try!(Manifest::find_file(path));
        let mut data = String::new();
        try!(file.read_to_string(&mut data));

        data.parse()
    }

    /// Open the `Cargo.lock` for a path (or the process' `cwd`)
    pub fn open_lock_file(path: &Option<&str>) -> Result<Manifest, Box<Error>> {
        let mut file = try!(Manifest::find_lock_file(path));
        let mut data = String::new();
        try!(file.read_to_string(&mut data));

        data.parse()
    }

    /// Overwrite a file with TOML data.
    pub fn write_to_file(&self, file: &mut File) -> Result<(), Box<Error>> {
        let mut toml = self.data.clone();

        let (proj_header, proj_data) = try!(toml.remove("package")
                                                .map(|data| ("package", data))
                                                .or_else(|| {
                                                    toml.remove("project")
                                                        .map(|data| ("project", data))
                                                })
                                                .ok_or(ManifestError::MissingManifest));

        let new_contents = format!("[{}]\n{}{}",
                                   proj_header,
                                   proj_data,
                                   toml::Value::Table(toml));
        let new_contents_bytes = new_contents.as_bytes();

        // We need to truncate the file, otherwise the new contents
        // will be mixed up with the old ones.
        try!(file.set_len(new_contents_bytes.len() as u64));
        file.write_all(new_contents_bytes).map_err(From::from)
    }

    /// Add entry to a Cargo.toml.
    #[cfg_attr(feature = "dev", allow(toplevel_ref_arg))]
    pub fn insert_into_table(&mut self,
                             table: &str,
                             dep: &Dependency)
                             -> Result<(), ManifestError> {
        let (ref name, ref data) = dep.to_toml();
        let ref mut manifest = self.data;
        let entry = manifest.entry(String::from(table))
                            .or_insert_with(|| toml::Value::Table(BTreeMap::new()));
        match *entry {
            toml::Value::Table(ref mut deps) => {
                deps.insert(name.clone(), data.clone());
                Ok(())
            }
            _ => Err(ManifestError::NonExistentTable(table.into())),
        }
    }

    /// Remove entry from a Cargo.toml.
    ///
    /// # Examples
    ///
    /// ```
    /// # extern crate cargo_edit;
    /// # extern crate toml;
    /// # fn main() {
    ///     use cargo_edit::{Dependency, Manifest};
    ///     use toml;
    ///
    ///     let mut manifest = Manifest { data: toml::Table::new() };
    ///     let dep = Dependency::new("cargo-edit").set_version("0.1.0");
    ///     let _ = manifest.insert_into_table("dependencies", &dep);
    ///     assert!(manifest.remove_from_table("dependencies", &dep.name).is_ok());
    ///     assert!(manifest.remove_from_table("dependencies", &dep.name).is_err());
    ///     assert!(manifest.data.is_empty());
    /// # }
    /// ```
    #[cfg_attr(feature = "dev", allow(toplevel_ref_arg))]
    pub fn remove_from_table(&mut self, table: &str, name: &str) -> Result<(), ManifestError> {
        let ref mut manifest = self.data;
        let entry = manifest.entry(String::from(table));

        match entry {
            Entry::Vacant(_) => Err(ManifestError::NonExistentTable(table.into())),
            Entry::Occupied(mut section) => {
                let result = match *section.get_mut() {
                    toml::Value::Table(ref mut deps) => {
                        deps.remove(name)
                            .map(|_| ())
                            .ok_or_else(|| ManifestError::NonExistentDependency(name.into(), table.into()))
                    }
                    _ => Err(ManifestError::NonExistentTable(table.into())),
                };
                if let Some(empty) = section.get().as_table().and_then(|x| Some(x.is_empty())) {
                    if empty {
                        section.remove();
                    }
                }
                result
            }
        }
    }

    /// Add multiple dependencies to manifest
    pub fn add_deps(&mut self, table: &str, deps: &[Dependency]) -> Result<(), Box<Error>> {
        deps.iter()
            .map(|dep| self.insert_into_table(table, &dep))
            .collect::<Result<Vec<_>, _>>()
            .map_err(From::from)
            .map(|_| ())
    }
}

impl str::FromStr for Manifest {
    type Err = Box<Error>;

    /// Read manifest data from string
    fn from_str(input: &str) -> Result<Self, Self::Err> {
        let mut parser = toml::Parser::new(&input);

        parser.parse()
              .ok_or(format_parse_error(parser))
              .map_err(Option::unwrap)
              .map_err(From::from)
              .map(|data| Manifest { data: data })
    }
}

fn format_parse_error(mut parser: toml::Parser) -> Option<ManifestError> {
    parser.errors.pop().map(|error| {
        let (loline, locol) = parser.to_linecol(error.lo);
        let (hiline, hicol) = parser.to_linecol(error.hi);
        ManifestError::ParseError(error.desc.clone(), loline, locol, hiline, hicol)
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use dependency::Dependency;
    use toml;

    #[test]
    fn add_remove_dependency() {
        let mut manifest = Manifest { data: toml::Table::new() };
        let clone = manifest.clone();
        let dep = Dependency::new("cargo-edit").set_version("0.1.0");
        let _ = manifest.insert_into_table("dependencies", &dep);
        assert!(manifest.remove_from_table("dependencies", &dep.name).is_ok());
        assert_eq!(manifest, clone);
    }

    #[test]
    fn remove_dependency_no_section() {
        let mut manifest = Manifest { data: toml::Table::new() };
        let dep = Dependency::new("cargo-edit").set_version("0.1.0");
        assert!(manifest.remove_from_table("dependencies", &dep.name).is_err());
    }

    #[test]
    fn remove_dependency_non_existent() {
        let mut manifest = Manifest { data: toml::Table::new() };
        let dep = Dependency::new("cargo-edit").set_version("0.1.0");
        let other_dep = Dependency::new("other-dep").set_version("0.1.0");
        let _ = manifest.insert_into_table("dependencies", &other_dep);
        assert!(manifest.remove_from_table("dependencies", &dep.name).is_err());
    }
}
<commit_msg>Correct reporting of first TOML parser error<commit_after>use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::{env, str};
use std::error::Error;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use toml;

use dependency::Dependency;

/// Enumeration of errors which can occur when working with a rust manifest.
quick_error! {
    #[derive(Debug)]
    pub enum ManifestError {
        /// Cargo.toml could not be found.
        MissingManifest {
            description("missing manifest")
            display("Your Cargo.toml is missing.")
        }
        /// The TOML table could not be found.
        NonExistentTable(table: String) {
            description("non existent table")
            display("The table `{}` could not be found.", table)
        }
        /// The dependency could not be found.
        NonExistentDependency(name: String, table: String) {
            description("non existent dependency")
            display("The dependency `{}` could not be found in `{}`.", name, table)
        }
        ParseError(error: String, loline: usize, locol: usize, hiline: usize, hicol: usize) {
            description("parse error")
            display("{line}:{col}{upto} {error_msg}",
                line = loline + 1,
                col = locol + 1,
                upto = if loline != hiline || locol != hicol {
                    format!("-{}:{}", hiline + 1, hicol + 1)
                } else {
                    "".to_string()
                },
                error_msg = error)
        }
    }
}

enum CargoFile {
    Config,
    Lock,
}

/// A Cargo Manifest
#[derive(Debug, Clone, PartialEq)]
pub struct Manifest {
    /// Manifest contents as TOML data
    pub data: toml::Table,
}

/// If a manifest is specified, return that one, otherise perform a manifest search starting from
/// the current directory.
/// If a manifest is specified, return that one. If a path is specified, perform a manifest search
/// starting from there. If nothing is specified, start searching from the current directory
/// (`cwd`).
fn find(specified: &Option<&str>, file: CargoFile) -> Result<PathBuf, Box<Error>> {
    let file_path = specified.map(PathBuf::from);

    if let Some(path) = file_path {
        if try!(fs::metadata(&path)).is_file() {
            Ok(path)
        } else {
            search(&path, file).map_err(From::from)
        }
    } else {
        env::current_dir()
            .map_err(From::from)
            .and_then(|ref dir| search(dir, file).map_err(From::from))
    }
}

/// Search for Cargo.toml in this directory and recursively up the tree until one is found.
fn search(dir: &Path, file: CargoFile) -> Result<PathBuf, ManifestError> {
    let manifest = match file {
        CargoFile::Config => dir.join("Cargo.toml"),
        CargoFile::Lock => dir.join("Cargo.lock"),
    };

    fs::metadata(&manifest)
        .map(|_| manifest)
        .or(dir.parent().ok_or(ManifestError::MissingManifest).and_then(|dir| search(dir, file)))
}

impl Manifest {
    /// Look for a `Cargo.toml` file
    ///
    /// Starts at the given path an goes into its parent directories until the manifest file is
    /// found. If no path is given, the process's working directory is used as a starting point.
    pub fn find_file(path: &Option<&str>) -> Result<File, Box<Error>> {
        find(path, CargoFile::Config).and_then(|path| {
            OpenOptions::new()
                .read(true)
                .write(true)
                .open(path)
                .map_err(From::from)
        })
    }

    /// Look for a `Cargo.lock` file
    ///
    /// Starts at the given path an goes into its parent directories until the manifest file is
    /// found. If no path is given, the process' working directory is used as a starting point.
    pub fn find_lock_file(path: &Option<&str>) -> Result<File, Box<Error>> {
        find(path, CargoFile::Lock).and_then(|path| {
            OpenOptions::new()
                .read(true)
                .write(true)
                .open(path)
                .map_err(From::from)
        })
    }

    /// Open the `Cargo.toml` for a path (or the process' `cwd`)
    pub fn open(path: &Option<&str>) -> Result<Manifest, Box<Error>> {
        let mut file = try!(Manifest::find_file(path));
        let mut data = String::new();
        try!(file.read_to_string(&mut data));

        data.parse()
    }

    /// Open the `Cargo.lock` for a path (or the process' `cwd`)
    pub fn open_lock_file(path: &Option<&str>) -> Result<Manifest, Box<Error>> {
        let mut file = try!(Manifest::find_lock_file(path));
        let mut data = String::new();
        try!(file.read_to_string(&mut data));

        data.parse()
    }

    /// Overwrite a file with TOML data.
    pub fn write_to_file(&self, file: &mut File) -> Result<(), Box<Error>> {
        let mut toml = self.data.clone();

        let (proj_header, proj_data) = try!(toml.remove("package")
                                                .map(|data| ("package", data))
                                                .or_else(|| {
                                                    toml.remove("project")
                                                        .map(|data| ("project", data))
                                                })
                                                .ok_or(ManifestError::MissingManifest));

        let new_contents = format!("[{}]\n{}{}",
                                   proj_header,
                                   proj_data,
                                   toml::Value::Table(toml));
        let new_contents_bytes = new_contents.as_bytes();

        // We need to truncate the file, otherwise the new contents
        // will be mixed up with the old ones.
        try!(file.set_len(new_contents_bytes.len() as u64));
        file.write_all(new_contents_bytes).map_err(From::from)
    }

    /// Add entry to a Cargo.toml.
    #[cfg_attr(feature = "dev", allow(toplevel_ref_arg))]
    pub fn insert_into_table(&mut self,
                             table: &str,
                             dep: &Dependency)
                             -> Result<(), ManifestError> {
        let (ref name, ref data) = dep.to_toml();
        let ref mut manifest = self.data;
        let entry = manifest.entry(String::from(table))
                            .or_insert_with(|| toml::Value::Table(BTreeMap::new()));
        match *entry {
            toml::Value::Table(ref mut deps) => {
                deps.insert(name.clone(), data.clone());
                Ok(())
            }
            _ => Err(ManifestError::NonExistentTable(table.into())),
        }
    }

    /// Remove entry from a Cargo.toml.
    ///
    /// # Examples
    ///
    /// ```
    /// # extern crate cargo_edit;
    /// # extern crate toml;
    /// # fn main() {
    ///     use cargo_edit::{Dependency, Manifest};
    ///     use toml;
    ///
    ///     let mut manifest = Manifest { data: toml::Table::new() };
    ///     let dep = Dependency::new("cargo-edit").set_version("0.1.0");
    ///     let _ = manifest.insert_into_table("dependencies", &dep);
    ///     assert!(manifest.remove_from_table("dependencies", &dep.name).is_ok());
    ///     assert!(manifest.remove_from_table("dependencies", &dep.name).is_err());
    ///     assert!(manifest.data.is_empty());
    /// # }
    /// ```
    #[cfg_attr(feature = "dev", allow(toplevel_ref_arg))]
    pub fn remove_from_table(&mut self, table: &str, name: &str) -> Result<(), ManifestError> {
        let ref mut manifest = self.data;
        let entry = manifest.entry(String::from(table));

        match entry {
            Entry::Vacant(_) => Err(ManifestError::NonExistentTable(table.into())),
            Entry::Occupied(mut section) => {
                let result = match *section.get_mut() {
                    toml::Value::Table(ref mut deps) => {
                        deps.remove(name)
                            .map(|_| ())
                            .ok_or_else(|| ManifestError::NonExistentDependency(name.into(), table.into()))
                    }
                    _ => Err(ManifestError::NonExistentTable(table.into())),
                };
                if let Some(empty) = section.get().as_table().and_then(|x| Some(x.is_empty())) {
                    if empty {
                        section.remove();
                    }
                }
                result
            }
        }
    }

    /// Add multiple dependencies to manifest
    pub fn add_deps(&mut self, table: &str, deps: &[Dependency]) -> Result<(), Box<Error>> {
        deps.iter()
            .map(|dep| self.insert_into_table(table, &dep))
            .collect::<Result<Vec<_>, _>>()
            .map_err(From::from)
            .map(|_| ())
    }
}

impl str::FromStr for Manifest {
    type Err = Box<Error>;

    /// Read manifest data from string
    fn from_str(input: &str) -> Result<Self, Self::Err> {
        let mut parser = toml::Parser::new(&input);

        parser.parse()
              .ok_or_else(|| format_parse_error(parser))
              .map_err(Option::unwrap)
              .map_err(From::from)
              .map(|data| Manifest { data: data })
    }
}

fn format_parse_error(parser: toml::Parser) -> Option<ManifestError> {
    parser.errors.first().map(|error| {
        let (loline, locol) = parser.to_linecol(error.lo);
        let (hiline, hicol) = parser.to_linecol(error.hi);
        ManifestError::ParseError(error.desc.clone(), loline, locol, hiline, hicol)
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use dependency::Dependency;
    use toml;

    #[test]
    fn add_remove_dependency() {
        let mut manifest = Manifest { data: toml::Table::new() };
        let clone = manifest.clone();
        let dep = Dependency::new("cargo-edit").set_version("0.1.0");
        let _ = manifest.insert_into_table("dependencies", &dep);
        assert!(manifest.remove_from_table("dependencies", &dep.name).is_ok());
        assert_eq!(manifest, clone);
    }

    #[test]
    fn remove_dependency_no_section() {
        let mut manifest = Manifest { data: toml::Table::new() };
        let dep = Dependency::new("cargo-edit").set_version("0.1.0");
        assert!(manifest.remove_from_table("dependencies", &dep.name).is_err());
    }

    #[test]
    fn remove_dependency_non_existent() {
        let mut manifest = Manifest { data: toml::Table::new() };
        let dep = Dependency::new("cargo-edit").set_version("0.1.0");
        let other_dep = Dependency::new("other-dep").set_version("0.1.0");
        let _ = manifest.insert_into_table("dependencies", &other_dep);
        assert!(manifest.remove_from_table("dependencies", &dep.name).is_err());
    }
}
<|endoftext|>