<commit_before>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! This module defines the structs transported during the network handshake protocol v1.
//! These should serialize as per [link](TODO: Add ref).
//!
//! During the v1 Handshake protocol, both end-points of a connection send a serialized and
//! length-prefixed `HandshakeMsg` to each other. The handshake message contains a map from
//! supported messaging protocol versions to a bit vector representing application protocols
//! supported over that messaging protocol. On receipt, both ends will determine the highest
//! intersecting messaging protocol version and use that for the remainder of the session.

use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, convert::TryInto, iter::Iterator};

#[cfg(test)]
mod test;

/// Unique identifier associated with each application protocol.
/// New application protocols can be added without bumping up the MessagingProtocolVersion.
#[repr(u8)]
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Deserialize, Serialize)]
pub enum ProtocolId {
    ConsensusRpc = 0,
    ConsensusDirectSend = 1,
    MempoolDirectSend = 2,
    StateSynchronizerDirectSend = 3,
    DiscoveryDirectSend = 4,
    HealthCheckerRpc = 5,
    IdentityDirectSend = 6,
    OnchainDiscoveryRpc = 7,
}

#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
pub struct SupportedProtocols(bitvec::BitVec);

/// The HandshakeMsg contains a mapping from MessagingProtocolVersion suppported by the node to a
/// bit-vector specifying application-level protocols supported over that version.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct HandshakeMsg {
    pub supported_protocols: BTreeMap<MessagingProtocolVersion, SupportedProtocols>,
}

/// Enum representing different versions of the Libra network protocol. These should be listed from
/// old to new, old having the smallest value.
/// We derive `PartialOrd` since nodes need to find highest intersecting protocol version.
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug, Hash, Deserialize, Serialize)]
pub enum MessagingProtocolVersion {
    V1 = 0,
}

impl TryInto<Vec<ProtocolId>> for SupportedProtocols {
    type Error = lcs::Error;

    fn try_into(self) -> lcs::Result<Vec<ProtocolId>> {
        let mut protocols = Vec::with_capacity(self.0.count_ones() as usize);
        if let Some(last_bit) = self.0.last_set_bit() {
            for i in 0..=last_bit {
                if self.0.is_set(i) {
                    let protocol: ProtocolId = lcs::from_bytes(&[i])?;
                    protocols.push(protocol);
                }
            }
        }
        Ok(protocols)
    }
}

impl<'a, T: Iterator<Item = &'a ProtocolId>> From<T> for SupportedProtocols {
    fn from(protocols: T) -> Self {
        let mut bv = bitvec::BitVec::default();
        protocols.for_each(|p| bv.set(*p as u8));
        Self(bv)
    }
}

impl SupportedProtocols {
    /// Returns a new SupportedProtocols struct that is an intersection.
    fn intersection(self, other: SupportedProtocols) -> SupportedProtocols {
        SupportedProtocols(self.0 & other.0)
    }
}

impl HandshakeMsg {
    pub fn new() -> Self {
        HandshakeMsg {
            supported_protocols: BTreeMap::default(),
        }
    }

    pub fn add(
        &mut self,
        messaging_protocol: MessagingProtocolVersion,
        application_protocols: SupportedProtocols,
    ) {
        self.supported_protocols
            .insert(messaging_protocol, application_protocols);
    }

    pub fn find_common_protocols(
        &self,
        other: &HandshakeMsg,
    ) -> Option<(MessagingProtocolVersion, SupportedProtocols)> {
        // First, find the highest MessagingProtocolVersion supported by both nodes.
        let mut inner = other.supported_protocols.iter().rev().peekable();
        // Iterate over all supported protocol versions in decreasing order.
        for (k_outer, _) in self.supported_protocols.iter().rev() {
            // Remove all elements from inner iterator that are larger than the current head of the
            // outer iterator.
            match inner.by_ref().find(|(k_inner, _)| *k_inner <= k_outer) {
                None => {
                    return None;
                }
                Some((k_inner, _)) if k_inner == k_outer => {
                    // Find all protocols supported by both nodes for the above protocol version.
                    // Both `self` and `other` shold have entry in map for `key`.
                    let protocols_self = self.supported_protocols.get(k_inner).unwrap();
                    let protocols_other = other.supported_protocols.get(k_inner).unwrap();
                    return Some((
                        *k_inner,
                        protocols_self.clone().intersection(protocols_other.clone()),
                    ));
                }
                _ => {}
            }
        }
        None
    }
}
<commit_msg>[network] fix clippy error<commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! This module defines the structs transported during the network handshake protocol v1.
//! These should serialize as per [link](TODO: Add ref).
//!
//! During the v1 Handshake protocol, both end-points of a connection send a serialized and
//! length-prefixed `HandshakeMsg` to each other. The handshake message contains a map from
//! supported messaging protocol versions to a bit vector representing application protocols
//! supported over that messaging protocol. On receipt, both ends will determine the highest
//! intersecting messaging protocol version and use that for the remainder of the session.

use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, convert::TryInto, iter::Iterator};

#[cfg(test)]
mod test;

/// Unique identifier associated with each application protocol.
/// New application protocols can be added without bumping up the MessagingProtocolVersion.
#[repr(u8)]
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Deserialize, Serialize)]
pub enum ProtocolId {
    ConsensusRpc = 0,
    ConsensusDirectSend = 1,
    MempoolDirectSend = 2,
    StateSynchronizerDirectSend = 3,
    DiscoveryDirectSend = 4,
    HealthCheckerRpc = 5,
    IdentityDirectSend = 6,
    OnchainDiscoveryRpc = 7,
}

#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
pub struct SupportedProtocols(bitvec::BitVec);

/// The HandshakeMsg contains a mapping from MessagingProtocolVersion suppported by the node to a
/// bit-vector specifying application-level protocols supported over that version.
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
pub struct HandshakeMsg {
    pub supported_protocols: BTreeMap<MessagingProtocolVersion, SupportedProtocols>,
}

/// Enum representing different versions of the Libra network protocol. These should be listed from
/// old to new, old having the smallest value.
/// We derive `PartialOrd` since nodes need to find highest intersecting protocol version.
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug, Hash, Deserialize, Serialize)]
pub enum MessagingProtocolVersion {
    V1 = 0,
}

impl TryInto<Vec<ProtocolId>> for SupportedProtocols {
    type Error = lcs::Error;

    fn try_into(self) -> lcs::Result<Vec<ProtocolId>> {
        let mut protocols = Vec::with_capacity(self.0.count_ones() as usize);
        if let Some(last_bit) = self.0.last_set_bit() {
            for i in 0..=last_bit {
                if self.0.is_set(i) {
                    let protocol: ProtocolId = lcs::from_bytes(&[i])?;
                    protocols.push(protocol);
                }
            }
        }
        Ok(protocols)
    }
}

impl<'a, T: Iterator<Item = &'a ProtocolId>> From<T> for SupportedProtocols {
    fn from(protocols: T) -> Self {
        let mut bv = bitvec::BitVec::default();
        protocols.for_each(|p| bv.set(*p as u8));
        Self(bv)
    }
}

impl SupportedProtocols {
    /// Returns a new SupportedProtocols struct that is an intersection.
    fn intersection(self, other: SupportedProtocols) -> SupportedProtocols {
        SupportedProtocols(self.0 & other.0)
    }
}

impl HandshakeMsg {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn add(
        &mut self,
        messaging_protocol: MessagingProtocolVersion,
        application_protocols: SupportedProtocols,
    ) {
        self.supported_protocols
            .insert(messaging_protocol, application_protocols);
    }

    pub fn find_common_protocols(
        &self,
        other: &HandshakeMsg,
    ) -> Option<(MessagingProtocolVersion, SupportedProtocols)> {
        // First, find the highest MessagingProtocolVersion supported by both nodes.
        let mut inner = other.supported_protocols.iter().rev().peekable();
        // Iterate over all supported protocol versions in decreasing order.
        for (k_outer, _) in self.supported_protocols.iter().rev() {
            // Remove all elements from inner iterator that are larger than the current head of the
            // outer iterator.
            match inner.by_ref().find(|(k_inner, _)| *k_inner <= k_outer) {
                None => {
                    return None;
                }
                Some((k_inner, _)) if k_inner == k_outer => {
                    // Find all protocols supported by both nodes for the above protocol version.
                    // Both `self` and `other` shold have entry in map for `key`.
                    let protocols_self = self.supported_protocols.get(k_inner).unwrap();
                    let protocols_other = other.supported_protocols.get(k_inner).unwrap();
                    return Some((
                        *k_inner,
                        protocols_self.clone().intersection(protocols_other.clone()),
                    ));
                }
                _ => {}
            }
        }
        None
    }
}
<|endoftext|>


<commit_before>use dsl::{LpObjective, LpProblem, LpConstraint, LpExpression, LpContinuous, Constraint, LpExprNode};
use std::collections::HashMap;
use solvers::{SolverTrait, Solution, Status};
use dsl::LpExprNode::LitVal;
use dsl::LpExprOp::{Multiplication, Addition, Subtraction};

fn direction_to_minilp(objective: &LpObjective) -> minilp::OptimizationDirection {
    match objective {
        LpObjective::Maximize => minilp::OptimizationDirection::Maximize,
        LpObjective::Minimize => minilp::OptimizationDirection::Minimize,
    }
}

fn add_constraint_to_minilp(
    constraint: &LpConstraint,
    variables: &mut HashMap<String, minilp::Variable>,
    pb: &mut minilp::Problem,
) -> Result<(), String> {
    let LpConstraint(expr, op, constant_arena) = constraint.clone();
    let constant = if let &LitVal(c) = constant_arena.get_root_expr_ref() { c } else {
        return Err("not properly simplified".into());
    };
    let expr_variables = decompose_expression(expr)?;
    let mut expr = minilp::LinearExpr::empty();
    for (name, coefficient) in expr_variables.0 {
        let var = variables.entry(name).or_insert_with(|| {
            pb.add_var(0., (f64::NEG_INFINITY, f64::INFINITY))
        }).clone();
        expr.add(var, coefficient.coefficient.into());
    }
    let op = comparison_to_minilp(op);
    pb.add_constraint(expr, op, f64::from(constant));
    Ok(())
}

fn comparison_to_minilp(op: Constraint) -> minilp::ComparisonOp {
    match op {
        Constraint::GreaterOrEqual => minilp::ComparisonOp::Ge,
        Constraint::LessOrEqual => minilp::ComparisonOp::Le,
        Constraint::Equal => minilp::ComparisonOp::Eq
    }
}

#[derive(Debug, PartialEq)]
struct VarWithCoeff {
    coefficient: f32,
    min: f32,
    max: f32,
}

impl Default for VarWithCoeff {
    fn default() -> Self {
        VarWithCoeff { coefficient: 0., min: f32::NEG_INFINITY, max: f32::INFINITY }
    }
}

#[derive(Debug, Default, PartialEq)]
struct VarList(HashMap<String, VarWithCoeff>);

impl VarList {
    fn add(&mut self, var: LpContinuous, coefficient: f32) {
        let LpContinuous { name, lower_bound, upper_bound } = var;
        let prev = self.0.entry(name).or_default();
        prev.coefficient += coefficient;
        if let Some(lower) = lower_bound {
            prev.min = prev.min.max(lower);
        }
        if let Some(upper) = upper_bound {
            prev.max = prev.max.min(upper);
        }
    }
}

fn decompose_expression(
    mut expr: LpExpression,
) -> Result<VarList, String> {
    fn decompose_expression_recursive(
        expr: &LpExpression,
        idx: usize,
        factor: f32,
        mut decomposed: VarList,
    ) -> Result<VarList, String> {
        match expr.expr_ref_at(idx) {
            LpExprNode::ConsCont(var) => { decomposed.add(var.clone(), factor) }
            &LpExprNode::LpCompExpr(Multiplication, lhs, rhs) => {
                if let &LpExprNode::LitVal(lit) = expr.expr_ref_at(lhs) {
                    decomposed = decompose_expression_recursive(expr, rhs, factor * lit, decomposed)?;
                } else {
                    return Err(format!("Non-simplified multiplication: {:?}", expr.expr_ref_at(idx)));
                }
            }
            &LpExprNode::LpCompExpr(Addition, lhs, rhs) => {
                decomposed = decompose_expression_recursive(expr, lhs, factor, decomposed)?;
                decomposed = decompose_expression_recursive(expr, rhs, factor, decomposed)?;
            }
            &LpExprNode::LpCompExpr(Subtraction, lhs, rhs) => {
                decomposed = decompose_expression_recursive(expr, lhs, factor, decomposed)?;
                decomposed = decompose_expression_recursive(expr, rhs, -factor, decomposed)?;
            }
            x => return Err(format!("Unsupported expression: {:?}", x))
        }
        Ok(decomposed)
    }

    expr.simplify();
    decompose_expression_recursive(&expr, expr.get_root_index(), 1., VarList::default())
}

/// Returns a map from dsl variable name to minilp variable
fn add_objective_to_minilp(
    objective: LpExpression,
    pb: &mut minilp::Problem,
) -> Result<HashMap<String, minilp::Variable>, String> {
    let vars = decompose_expression(objective)?;
    Ok(vars.0.into_iter()
        .map(|(name, VarWithCoeff { coefficient, min, max })| {
            let var = pb.add_var(
                coefficient.into(),
                (min.into(), max.into()),
            );
            (name, var)
        }).collect()
    )
}

fn problem_to_minilp(pb: &LpProblem) -> Result<(minilp::Problem, Vec<Option<String>>), String> {
    let objective = direction_to_minilp(&pb.objective_type);
    let mut minilp_pb = minilp::Problem::new(objective);
    let objective = pb.obj_expr_arena.clone().ok_or("Missing objective")?;
    let mut minilp_variables = add_objective_to_minilp(objective, &mut minilp_pb)?;
    for constraint in &pb.constraints {
        add_constraint_to_minilp(
            constraint,
            &mut minilp_variables,
            &mut minilp_pb,
        )?;
    }
    let mut ordered_vars = vec![None; minilp_variables.len()];
    for (name, var) in minilp_variables {
        ordered_vars[var.idx()] = Some(name);
    }
    Ok((minilp_pb, ordered_vars))
}

pub struct MiniLpSolver;

impl MiniLpSolver {
    pub fn new() -> Self { Self }
}

impl SolverTrait for MiniLpSolver {
    type P = LpProblem;

    fn run<'a>(&self, problem: &'a Self::P) -> Result<Solution<'a>, String> {
        let (minilp_pb, variable_names) = problem_to_minilp(problem)?;
        let minilp_result = minilp_pb.solve();
        solution_from_minilp(minilp_result, variable_names)
    }
}

fn solution_from_minilp(
    result: Result<minilp::Solution, minilp::Error>,
    mut variable_names: Vec<Option<String>>,
) -> Result<Solution<'static>, String> {
    match result {
        Ok(solution) => {
            let results: Option<HashMap<String, f32>> = solution.iter()
                .map(|(var, &value)| {
                    std::mem::take(&mut variable_names[var.idx()]).map(|name| {
                        (name, value as f32)
                    })
                })
                .collect();
            if let Some(results) = results {
                Ok(Solution::new(Status::Optimal, results))
            } else {
                Err("missing variable name".into())
            }
        }
        Err(minilp::Error::Unbounded) => {
            Ok(Solution::new(Status::Unbounded, HashMap::new()))
        }
        Err(minilp::Error::Infeasible) => {
            Ok(Solution::new(Status::Infeasible, HashMap::new()))
        }
    }
}

#[test]
fn test_decompose() {
    let ref a = LpContinuous::new("a");
    let ref b = LpContinuous::new("b");
    let expr = (4 * (3 * a - b * 2 + a)) * 1 + b;
    let decomposed = decompose_expression(expr);
    let mut expected = VarList::default();
    expected.add(a.clone(), 4. * 3. + 4.);
    expected.add(b.clone(), 4. * (-2.) + 1.);
    assert_eq!(decomposed, Ok(expected));
}

#[test]
fn test_solve() {
    use dsl::operations::LpOperations;
    let ref a = LpContinuous::new("a");
    let ref b = LpContinuous::new("b");

    // Define problem and objective sense
    let mut problem = LpProblem::new("One Problem", LpObjective::Maximize);
    problem += 10 * a + 20 * b;
    problem += (500 * a - 1000 * b).ge(10000);
    problem += (a).le(b);

    let expected: HashMap<String, f32> = vec![
        ("a".into(), -20.),
        ("b".into(), -20.)
    ].into_iter().collect();
    let actual = MiniLpSolver::new().run(&problem).expect("could not solve").results;
    assert_eq!(actual, expected);
}<commit_msg>Remove recursivity from the minilp solver<commit_after>use dsl::{LpObjective, LpProblem, LpConstraint, LpExpression, Constraint, LpExprNode, LpContinuous};
use std::collections::HashMap;
use solvers::{SolverTrait, Solution, Status};
use dsl::LpExprNode::LitVal;
use dsl::LpExprOp::{Multiplication, Addition, Subtraction};

fn direction_to_minilp(objective: &LpObjective) -> minilp::OptimizationDirection {
    match objective {
        LpObjective::Maximize => minilp::OptimizationDirection::Maximize,
        LpObjective::Minimize => minilp::OptimizationDirection::Minimize,
    }
}

fn add_constraint_to_minilp(
    constraint: &LpConstraint,
    variables: &mut HashMap<String, minilp::Variable>,
    pb: &mut minilp::Problem,
) -> Result<(), String> {
    let LpConstraint(expr, op, constant_arena) = constraint.clone();
    let constant = if let &LitVal(c) = constant_arena.get_root_expr_ref() { c } else {
        return Err("not properly simplified".into());
    };
    let expr_variables = decompose_expression(expr)?;
    let mut expr = minilp::LinearExpr::empty();
    for (name, coefficient) in expr_variables.0 {
        let var = variables.entry(name).or_insert_with(|| {
            pb.add_var(0., (f64::NEG_INFINITY, f64::INFINITY))
        }).clone();
        expr.add(var, coefficient.coefficient.into());
    }
    let op = comparison_to_minilp(op);
    pb.add_constraint(expr, op, f64::from(constant));
    Ok(())
}

fn comparison_to_minilp(op: Constraint) -> minilp::ComparisonOp {
    match op {
        Constraint::GreaterOrEqual => minilp::ComparisonOp::Ge,
        Constraint::LessOrEqual => minilp::ComparisonOp::Le,
        Constraint::Equal => minilp::ComparisonOp::Eq
    }
}

#[derive(Debug, PartialEq)]
struct VarWithCoeff {
    coefficient: f32,
    min: f32,
    max: f32,
}

impl Default for VarWithCoeff {
    fn default() -> Self {
        VarWithCoeff { coefficient: 0., min: f32::NEG_INFINITY, max: f32::INFINITY }
    }
}

#[derive(Debug, Default, PartialEq)]
struct VarList(HashMap<String, VarWithCoeff>);

impl VarList {
    fn add(&mut self, var: LpContinuous, coefficient: f32) {
        let LpContinuous { name, lower_bound, upper_bound } = var;
        let prev = self.0.entry(name).or_default();
        prev.coefficient += coefficient;
        if let Some(lower) = lower_bound {
            prev.min = prev.min.max(lower);
        }
        if let Some(upper) = upper_bound {
            prev.max = prev.max.min(upper);
        }
    }
}

fn decompose_expression(
    mut expr: LpExpression,
) -> Result<VarList, String> {
    expr.simplify();
    let mut decomposed = VarList::default();
    let mut idxs = vec![(1., expr.get_root_index())];
    while let Some((factor, idx)) = idxs.pop() {
        match expr.expr_ref_at(idx) {
            LpExprNode::ConsCont(var) => { decomposed.add(var.clone(), factor) }
            &LpExprNode::LpCompExpr(Multiplication, lhs, rhs) => {
                if let &LpExprNode::LitVal(lit) = expr.expr_ref_at(lhs) {
                    idxs.push((factor * lit, rhs))
                } else {
                    return Err(format!("Non-simplified multiplication: {:?}", expr.expr_ref_at(idx)));
                }
            }
            &LpExprNode::LpCompExpr(Addition, lhs, rhs) => {
                idxs.push((factor, lhs));
                idxs.push((factor, rhs));
            }
            &LpExprNode::LpCompExpr(Subtraction, lhs, rhs) => {
                idxs.push((factor, lhs));
                idxs.push((-factor, rhs));
            }
            x => return Err(format!("Unsupported expression: {:?}", x))
        }
    }
    Ok(decomposed)
}

/// Returns a map from dsl variable name to minilp variable
fn add_objective_to_minilp(
    objective: LpExpression,
    pb: &mut minilp::Problem,
) -> Result<HashMap<String, minilp::Variable>, String> {
    let vars = decompose_expression(objective)?;
    Ok(vars.0.into_iter()
        .map(|(name, VarWithCoeff { coefficient, min, max })| {
            let var = pb.add_var(
                coefficient.into(),
                (min.into(), max.into()),
            );
            (name, var)
        }).collect()
    )
}

fn problem_to_minilp(pb: &LpProblem) -> Result<(minilp::Problem, Vec<Option<String>>), String> {
    let objective = direction_to_minilp(&pb.objective_type);
    let mut minilp_pb = minilp::Problem::new(objective);
    let objective = pb.obj_expr_arena.clone().ok_or("Missing objective")?;
    let mut minilp_variables = add_objective_to_minilp(objective, &mut minilp_pb)?;
    for constraint in &pb.constraints {
        add_constraint_to_minilp(
            constraint,
            &mut minilp_variables,
            &mut minilp_pb,
        )?;
    }
    let mut ordered_vars = vec![None; minilp_variables.len()];
    for (name, var) in minilp_variables {
        ordered_vars[var.idx()] = Some(name);
    }
    Ok((minilp_pb, ordered_vars))
}

pub struct MiniLpSolver;

impl MiniLpSolver {
    pub fn new() -> Self { Self }
}

impl SolverTrait for MiniLpSolver {
    type P = LpProblem;

    fn run<'a>(&self, problem: &'a Self::P) -> Result<Solution<'a>, String> {
        let (minilp_pb, variable_names) = problem_to_minilp(problem)?;
        let minilp_result = minilp_pb.solve();
        solution_from_minilp(minilp_result, variable_names)
    }
}

fn solution_from_minilp(
    result: Result<minilp::Solution, minilp::Error>,
    mut variable_names: Vec<Option<String>>,
) -> Result<Solution<'static>, String> {
    match result {
        Ok(solution) => {
            let results: Option<HashMap<String, f32>> = solution.iter()
                .map(|(var, &value)| {
                    std::mem::take(&mut variable_names[var.idx()]).map(|name| {
                        (name, value as f32)
                    })
                })
                .collect();
            if let Some(results) = results {
                Ok(Solution::new(Status::Optimal, results))
            } else {
                Err("missing variable name".into())
            }
        }
        Err(minilp::Error::Unbounded) => {
            Ok(Solution::new(Status::Unbounded, HashMap::new()))
        }
        Err(minilp::Error::Infeasible) => {
            Ok(Solution::new(Status::Infeasible, HashMap::new()))
        }
    }
}

#[test]
fn test_decompose() {
    let ref a = LpContinuous::new("a");
    let ref b = LpContinuous::new("b");
    let expr = (4 * (3 * a - b * 2 + a)) * 1 + b;
    let decomposed = decompose_expression(expr);
    let mut expected = VarList::default();
    expected.add(a.clone(), 4. * 3. + 4.);
    expected.add(b.clone(), 4. * (-2.) + 1.);
    assert_eq!(decomposed, Ok(expected));
}

#[test]
fn test_solve() {
    use dsl::operations::LpOperations;
    let ref a = LpContinuous::new("a");
    let ref b = LpContinuous::new("b");

    // Define problem and objective sense
    let mut problem = LpProblem::new("One Problem", LpObjective::Maximize);
    problem += 10 * a + 20 * b;
    problem += (500 * a - 1000 * b).ge(10000);
    problem += (a).le(b);

    let expected: HashMap<String, f32> = vec![
        ("a".into(), -20.),
        ("b".into(), -20.)
    ].into_iter().collect();
    let actual = MiniLpSolver::new().run(&problem).expect("could not solve").results;
    assert_eq!(actual, expected);
}

#[test]
fn decompose_large() {
    use dsl::lp_sum;
    let count = 1000;
    let vars: Vec<LpExpression> = (0..count)
        .map(|i|
            &LpContinuous::new(&format!("v{}", i)) * 2
        )
        .collect();
    let sum = lp_sum(&vars);
    let vars = decompose_expression(sum).expect("decompose failed");
    assert_eq!(vars.0.keys().len(), count);
}<|endoftext|>


<commit_before>//! Utility class to communicate between agents
//!
//! This class provides three structs : Msg, Ports and MsgSender.

extern crate capnp;
#[allow(unused_imports)]
use std::io::Read;

use result;
use result::Result;

use std::collections::HashMap;
use std::mem;

use std::sync::mpsc::{Sender, Receiver, SyncSender};
use std::sync::mpsc::sync_channel;

use scheduler::CompMsg;

/// Represent an Msg
pub struct Msg {
    /// The capn'p representation
    pub vec: Vec<u8>,
    /// is the action of the Msg
    pub action: String,
    reader: Option<capnp::message::Reader<capnp::serialize::OwnedSegments>>,
    builder: Option<capnp::message::Builder<capnp::message::HeapAllocator>>,
}

impl Msg {
    /// Return a new Msg
    ///
    /// # Example
    /// ```rust,ignore
    /// let msg = Msg::new();
    /// ```
    pub fn new() -> Self {
        Msg { vec: vec![],
             action: String::new(),
             reader: None,
             builder: None,
        }
    }

    /// Return a capnp `Reader`
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// let msg = an_initialized_msg;
    /// {
    ///     let reader: generic_text::Reader = try!(msg.read_schema());
    ///     let text = try!(reader.get_text());
    /// }
    /// ```
    pub fn read_schema<'a, T: capnp::traits::FromPointerReader<'a>>(&'a mut self) -> Result<T> {
        let msg = try!(capnp::serialize::read_message(&mut &self.vec[..], capnp::message::ReaderOptions::new()));
        self.reader = Some(msg);
        Ok(try!(self.reader.as_ref().unwrap().get_root()))
    }

    /// Return a capnp `Builder`
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// let mut msg = Msg::new();
    /// // Initialize the Msg
    /// {
    ///     let mut builder: generic_text::Builder = msg.build_schema();
    ///     builder.set_text("Hello Fractalide!");
    /// }
    /// ```
    pub fn build_schema<'a, T: capnp::traits::FromPointerBuilder<'a>>(&'a mut self) -> T {
        let msg = capnp::message::Builder::new_default();
        self.builder = Some(msg);
        self.builder.as_mut().unwrap().init_root()
    }

    /// Return a capnp `Builder` from a capnp `Reader`
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// let mut msg = an_initialized_msg;
    /// {
    ///     let mut builder = try!(edit_edge::<generic_text::Builder, generic_text::Reader>());
    ///     builder.set_text("Hello Fractalide!");
    /// }
    /// ```
    pub fn edit_schema<'a, T: capnp::traits::FromPointerBuilder<'a>,
                                 U: capnp::traits::FromPointerReader<'a> + capnp::traits::SetPointerBuilder<T>>
        (&'a mut self) -> Result<T> {
        let reader = try!(capnp::serialize::read_message(&mut &self.vec[..], capnp::message::ReaderOptions::new()));
        self.reader = Some(reader);
        let reader: U = try!(self.reader.as_ref().unwrap().get_root());

        let mut msg = capnp::message::Builder::new_default();
        try!(msg.set_root(reader));
        self.builder = Some(msg);
        Ok(try!(self.builder.as_mut().unwrap().get_root()))
    }

    /// Write a capnp `Builder` to a `Vec`
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// let mut msg = an_initialized_msg;
    /// {
    ///     let mut builder = try!(edit_edge::<generic_text::Builder, generic_text::Reader>());
    ///     builder.set_text("Hello Fractalide!");
    /// }
    /// try!(msg.before_send());
    /// ```
    pub fn before_send(&mut self) -> Result<()> {
        let mut build = mem::replace(&mut self.builder, None);
        if let Some(ref mut b) = build {
            self.vec.clear();
            try!(capnp::serialize::write_message(&mut self.vec, b))
        }
        Ok(())

    }
}

impl Clone for Msg {
    fn clone(&self) -> Self {
        Msg {
            vec: self.vec.clone(),
            action: self.action.clone(),
            reader: None,
            builder: None,
        }
    }
}

/// A wrapper around `SyncSender<Msg>`
///
/// A specific `SyncSender` for the Msg object. It also sends information to the scheduler.
#[derive(Clone)]
pub struct MsgSender {
    /// The SyncSender, connected to a receiver in another agent
    pub sender: SyncSender<Msg>,
    /// The name of the agent owning the receiver
    pub dest: String,
    /// A Sender to the scheduler, to signal that the receiver must be run
    pub sched: Sender<CompMsg>,
}

impl MsgSender {
    /// Send an Msg to the Receiver
    pub fn send(&self, mut msg: Msg) -> Result<()> {
        try!(msg.before_send());
        try!(self.sender.send(msg));
        if self.dest != "" {
            try!(self.sched.send(CompMsg::Inc(self.dest.clone())));
        }
        Ok(())
    }
}

pub trait OutputSend {
    fn send(&self, msg:Msg) -> Result<()>;
}

impl OutputSend for Option<MsgSender> {
    fn send(&self, msg: Msg) -> Result<()> {
        if let &Some(ref sender) = self {
            sender.send(msg)?;
            Ok(())
        } else {
            Err(result::Error::OutputNotConnected)
        }
    }
}

pub struct MsgReceiver {
    name: String,
    recv: Receiver<Msg>,
    sched: Sender<CompMsg>,
    must_sched: bool,
}

impl MsgReceiver {
    pub fn new(name: String, sched: Sender<CompMsg>, must_sched: bool) -> (MsgReceiver, MsgSender) {
        let (s, r) = sync_channel(25);
        let s = MsgSender {
            sender: s,
            dest: if must_sched { name.clone() } else { "".into() },
            sched: sched.clone(),
        };
        let r = MsgReceiver {
            recv: r,
            name: name,
            sched: sched,
            must_sched: must_sched,
        };
        (r, s)
    }

    pub fn recv(&self) -> Result<Msg> {
        let msg = try!(self.recv.recv());
        if self.must_sched {
            try!(self.sched.send(CompMsg::Dec(self.name.clone())));
        }
        Ok(msg)
    }

    pub fn try_recv(&self) -> Result<Msg> {
        let msg = self.recv.try_recv()?;
        if self.must_sched {
            try!(self.sched.send(CompMsg::Dec(self.name.clone())));
        }
        Ok(msg)
    }
}
<commit_msg>[IMPL] MsgReceiver: missing get_sender()<commit_after>//! Utility class to communicate between agents
//!
//! This class provides three structs : Msg, Ports and MsgSender.

extern crate capnp;
#[allow(unused_imports)]
use std::io::Read;

use result;
use result::Result;

use std::collections::HashMap;
use std::mem;

use std::sync::mpsc::{Sender, Receiver, SyncSender};
use std::sync::mpsc::sync_channel;

use scheduler::CompMsg;

/// Represent an Msg
pub struct Msg {
    /// The capn'p representation
    pub vec: Vec<u8>,
    /// is the action of the Msg
    pub action: String,
    reader: Option<capnp::message::Reader<capnp::serialize::OwnedSegments>>,
    builder: Option<capnp::message::Builder<capnp::message::HeapAllocator>>,
}

impl Msg {
    /// Return a new Msg
    ///
    /// # Example
    /// ```rust,ignore
    /// let msg = Msg::new();
    /// ```
    pub fn new() -> Self {
        Msg { vec: vec![],
             action: String::new(),
             reader: None,
             builder: None,
        }
    }

    /// Return a capnp `Reader`
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// let msg = an_initialized_msg;
    /// {
    ///     let reader: generic_text::Reader = try!(msg.read_schema());
    ///     let text = try!(reader.get_text());
    /// }
    /// ```
    pub fn read_schema<'a, T: capnp::traits::FromPointerReader<'a>>(&'a mut self) -> Result<T> {
        let msg = try!(capnp::serialize::read_message(&mut &self.vec[..], capnp::message::ReaderOptions::new()));
        self.reader = Some(msg);
        Ok(try!(self.reader.as_ref().unwrap().get_root()))
    }

    /// Return a capnp `Builder`
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// let mut msg = Msg::new();
    /// // Initialize the Msg
    /// {
    ///     let mut builder: generic_text::Builder = msg.build_schema();
    ///     builder.set_text("Hello Fractalide!");
    /// }
    /// ```
    pub fn build_schema<'a, T: capnp::traits::FromPointerBuilder<'a>>(&'a mut self) -> T {
        let msg = capnp::message::Builder::new_default();
        self.builder = Some(msg);
        self.builder.as_mut().unwrap().init_root()
    }

    /// Return a capnp `Builder` from a capnp `Reader`
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// let mut msg = an_initialized_msg;
    /// {
    ///     let mut builder = try!(edit_edge::<generic_text::Builder, generic_text::Reader>());
    ///     builder.set_text("Hello Fractalide!");
    /// }
    /// ```
    pub fn edit_schema<'a, T: capnp::traits::FromPointerBuilder<'a>,
                                 U: capnp::traits::FromPointerReader<'a> + capnp::traits::SetPointerBuilder<T>>
        (&'a mut self) -> Result<T> {
        let reader = try!(capnp::serialize::read_message(&mut &self.vec[..], capnp::message::ReaderOptions::new()));
        self.reader = Some(reader);
        let reader: U = try!(self.reader.as_ref().unwrap().get_root());

        let mut msg = capnp::message::Builder::new_default();
        try!(msg.set_root(reader));
        self.builder = Some(msg);
        Ok(try!(self.builder.as_mut().unwrap().get_root()))
    }

    /// Write a capnp `Builder` to a `Vec`
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// let mut msg = an_initialized_msg;
    /// {
    ///     let mut builder = try!(edit_edge::<generic_text::Builder, generic_text::Reader>());
    ///     builder.set_text("Hello Fractalide!");
    /// }
    /// try!(msg.before_send());
    /// ```
    pub fn before_send(&mut self) -> Result<()> {
        let mut build = mem::replace(&mut self.builder, None);
        if let Some(ref mut b) = build {
            self.vec.clear();
            try!(capnp::serialize::write_message(&mut self.vec, b))
        }
        Ok(())

    }
}

impl Clone for Msg {
    fn clone(&self) -> Self {
        Msg {
            vec: self.vec.clone(),
            action: self.action.clone(),
            reader: None,
            builder: None,
        }
    }
}

/// A wrapper around `SyncSender<Msg>`
///
/// A specific `SyncSender` for the Msg object. It also sends information to the scheduler.
#[derive(Clone)]
pub struct MsgSender {
    /// The SyncSender, connected to a receiver in another agent
    pub sender: SyncSender<Msg>,
    /// The name of the agent owning the receiver
    pub dest: String,
    /// A Sender to the scheduler, to signal that the receiver must be run
    pub sched: Sender<CompMsg>,
}

impl MsgSender {
    /// Send an Msg to the Receiver
    pub fn send(&self, mut msg: Msg) -> Result<()> {
        try!(msg.before_send());
        try!(self.sender.send(msg));
        if self.dest != "" {
            try!(self.sched.send(CompMsg::Inc(self.dest.clone())));
        }
        Ok(())
    }
}

pub trait OutputSend {
    fn send(&self, msg:Msg) -> Result<()>;
}

impl OutputSend for Option<MsgSender> {
    fn send(&self, msg: Msg) -> Result<()> {
        if let &Some(ref sender) = self {
            sender.send(msg)?;
            Ok(())
        } else {
            Err(result::Error::OutputNotConnected)
        }
    }
}

pub struct MsgReceiver {
    name: String,
    recv: Receiver<Msg>,
    sender: MsgSender,
    sched: Sender<CompMsg>,
    must_sched: bool,
}

impl MsgReceiver {
    pub fn new(name: String, sched: Sender<CompMsg>, must_sched: bool) -> (MsgReceiver, MsgSender) {
        let (s, r) = sync_channel(25);
        let s = MsgSender {
            sender: s,
            dest: if must_sched { name.clone() } else { "".into() },
            sched: sched.clone(),
        };
        let r = MsgReceiver {
            recv: r,
            name: name,
            sender: s.clone(),
            sched: sched,
            must_sched: must_sched,
        };
        (r, s)
    }

    pub fn recv(&self) -> Result<Msg> {
        let msg = try!(self.recv.recv());
        if self.must_sched {
            try!(self.sched.send(CompMsg::Dec(self.name.clone())));
        }
        Ok(msg)
    }

    pub fn try_recv(&self) -> Result<Msg> {
        let msg = self.recv.try_recv()?;
        if self.must_sched {
            try!(self.sched.send(CompMsg::Dec(self.name.clone())));
        }
        Ok(msg)
    }

    pub fn get_sender(&self) -> MsgSender {
	self.sender.clone()
    }
}
<|endoftext|>


<commit_before>#![cfg_attr(feature = "cargo-clippy", allow(unused_parens))]

extern crate num_traits;
extern crate serde;
#[macro_use]
extern crate serde_derive;
#[cfg(feature = "serde-json")]
extern crate serde_json;
extern crate toml;

use std::env;
use std::fs;
use std::path::{Path, PathBuf};

mod error;
mod escaping;

pub use error::{Error, Result};
pub use escaping::MarkupDisplay;

use std::collections::BTreeMap;

pub mod filters;

#[derive(Debug)]
pub struct Config<'a> {
    pub dirs: Vec<PathBuf>,
    pub syntaxes: BTreeMap<String, Syntax<'a>>,
    pub default_syntax: &'a str,
}

impl<'a> Config<'a> {
    pub fn new(s: &str) -> Config {
        let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        let default = vec![root.join("templates")];

        let mut syntaxes = BTreeMap::new();
        syntaxes.insert(DEFAULT_SYNTAX_NAME.to_string(), Syntax::default());

        let raw: RawConfig =
            toml::from_str(&s).expect(&format!("invalid TOML in {}", CONFIG_FILE_NAME));

        let dirs = match raw.general {
            Some(General { dirs: Some(dirs) }) => {
                dirs.into_iter().map(|dir| root.join(dir)).collect()
            }
            Some(General { dirs: None }) | None => default,
        };

        if let Some(raw_syntaxes) = raw.syntax {
            for raw_s in raw_syntaxes {
                let name = raw_s.name;

                if let Some(_) = syntaxes.insert(name.to_string(), Syntax::from(raw_s)) {
                    panic!("syntax \"{}\" is already defined", name)
                }
            }
        }

        let default_syntax = if let Some(default_syntax) = raw.default_syntax {
            if syntaxes.contains_key(default_syntax) {
                default_syntax
            } else {
                panic!("default syntax \"{}\" not found", default_syntax)
            }
        } else {
            DEFAULT_SYNTAX_NAME
        };

        Config {
            dirs,
            syntaxes,
            default_syntax,
        }
    }

    pub fn find_template(&self, path: &str, start_at: Option<&Path>) -> PathBuf {
        if let Some(root) = start_at {
            let relative = root.with_file_name(path);
            if relative.exists() {
                return relative.to_owned();
            }
        }

        for dir in &self.dirs {
            let rooted = dir.join(path);
            if rooted.exists() {
                return rooted;
            }
        }

        panic!(
            "template {:?} not found in directories {:?}",
            path, self.dirs
        )
    }
}

#[derive(Debug)]
pub struct Syntax<'a> {
    pub block_start: &'a str,
    pub block_end: &'a str,
    pub expr_start: &'a str,
    pub expr_end: &'a str,
    pub comment_start: &'a str,
    pub comment_end: &'a str,
}

impl<'a> Default for Syntax<'a> {
    fn default() -> Self {
        Self {
            block_start: "{%",
            block_end: "%}",
            expr_start: "{{",
            expr_end: "}}",
            comment_start: "{#",
            comment_end: "#}",
        }
    }
}

impl<'a> From<RawSyntax<'a>> for Syntax<'a> {
    fn from(raw: RawSyntax<'a>) -> Self {
        let default = Self::default();
        let syntax = Self {
            block_start: raw.block_start.unwrap_or(default.block_start),
            block_end: raw.block_end.unwrap_or(default.block_end),
            expr_start: raw.expr_start.unwrap_or(default.expr_start),
            expr_end: raw.expr_end.unwrap_or(default.expr_end),
            comment_start: raw.comment_start.unwrap_or(default.comment_start),
            comment_end: raw.comment_end.unwrap_or(default.comment_end),
        };

        if syntax.block_start.len() != 2
            || syntax.block_end.len() != 2
            || syntax.expr_start.len() != 2
            || syntax.expr_end.len() != 2
            || syntax.comment_start.len() != 2
            || syntax.comment_end.len() != 2
        {
            panic!("length of delimiters must be two")
        }

        let bs = syntax.block_start.as_bytes()[0];
        let be = syntax.block_start.as_bytes()[1];
        let cs = syntax.comment_start.as_bytes()[0];
        let ce = syntax.comment_start.as_bytes()[1];
        let es = syntax.block_start.as_bytes()[0];
        let ee = syntax.block_start.as_bytes()[1];
        if !(bs == cs && bs == es) && !(be == ce && be == ee) {
            panic!("bad delimiters block_start: {}, comment_start: {}, expr_start: {}, needs one of the two characters in common", syntax.block_start, syntax.comment_start, syntax.expr_start);
        }

        syntax
    }
}

#[derive(Deserialize)]
struct RawConfig<'d> {
    general: Option<General>,
    syntax: Option<Vec<RawSyntax<'d>>>,
    default_syntax: Option<&'d str>,
}

#[derive(Deserialize)]
struct General {
    dirs: Option<Vec<String>>,
}

#[derive(Deserialize)]
struct RawSyntax<'a> {
    name: &'a str,
    block_start: Option<&'a str>,
    block_end: Option<&'a str>,
    expr_start: Option<&'a str>,
    expr_end: Option<&'a str>,
    comment_start: Option<&'a str>,
    comment_end: Option<&'a str>,
}

pub fn read_config_file() -> String {
    let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let filename = root.join(CONFIG_FILE_NAME);
    if filename.exists() {
        fs::read_to_string(&filename)
            .expect(&format!("unable to read {}", filename.to_str().unwrap()))
    } else {
        "".to_string()
    }
}

static CONFIG_FILE_NAME: &str = "askama.toml";
static DEFAULT_SYNTAX_NAME: &str = "default";

#[cfg(test)]
mod tests {
    use super::*;
    use std::env;
    use std::path::{Path, PathBuf};

    #[test]
    fn test_default_config() {
        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        root.push("templates");
        let config = Config::new("");
        assert_eq!(config.dirs, vec![root]);
    }

    #[test]
    fn test_config_dirs() {
        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        root.push("tpl");
        let config = Config::new("[general]\ndirs = [\"tpl\"]");
        assert_eq!(config.dirs, vec![root]);
    }

    fn assert_eq_rooted(actual: &Path, expected: &str) {
        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        root.push("templates");
        let mut inner = PathBuf::new();
        inner.push(expected);
        assert_eq!(actual.strip_prefix(root).unwrap(), inner);
    }

    #[test]
    fn find_absolute() {
        let config = Config::new("");
        let root = config.find_template("a.html", None);
        let path = config.find_template("sub/b.html", Some(&root));
        assert_eq_rooted(&path, "sub/b.html");
    }

    #[test]
    #[should_panic]
    fn find_relative_nonexistent() {
        let config = Config::new("");
        let root = config.find_template("a.html", None);
        config.find_template("b.html", Some(&root));
    }

    #[test]
    fn find_relative() {
        let config = Config::new("");
        let root = config.find_template("sub/b.html", None);
        let path = config.find_template("c.html", Some(&root));
        assert_eq_rooted(&path, "sub/c.html");
    }

    #[test]
    fn find_relative_sub() {
        let config = Config::new("");
        let root = config.find_template("sub/b.html", None);
        let path = config.find_template("sub1/d.html", Some(&root));
        assert_eq_rooted(&path, "sub/sub1/d.html");
    }

    #[test]
    fn add_syntax() {
        let raw_config = r#"
        default_syntax = "foo"

        [[syntax]]
        name = "foo"
        block_start = "{<"

        [[syntax]]
        name = "bar"
        expr_start = "{!"
        "#;

        let default_syntax = Syntax::default();
        let config = Config::new(raw_config);
        assert_eq!(config.default_syntax, "foo");

        let foo = config.syntaxes.get("foo").unwrap();
        assert_eq!(foo.block_start, "{<");
        assert_eq!(foo.block_end, default_syntax.block_end);
        assert_eq!(foo.expr_start, default_syntax.expr_start);
        assert_eq!(foo.expr_end, default_syntax.expr_end);
        assert_eq!(foo.comment_start, default_syntax.comment_start);
        assert_eq!(foo.comment_end, default_syntax.comment_end);

        let bar = config.syntaxes.get("bar").unwrap();
        assert_eq!(bar.block_start, default_syntax.block_start);
        assert_eq!(bar.block_end, default_syntax.block_end);
        assert_eq!(bar.expr_start, "{!");
        assert_eq!(bar.expr_end, default_syntax.expr_end);
        assert_eq!(bar.comment_start, default_syntax.comment_start);
        assert_eq!(bar.comment_end, default_syntax.comment_end);
    }

    #[test]
    fn add_syntax_two() {
        let raw_config = r#"
        default_syntax = "foo"

        syntax = [{ name = "foo", block_start = "{<" },
                  { name = "bar", expr_start = "{!" } ]
        "#;

        let default_syntax = Syntax::default();
        let config = Config::new(raw_config);
        assert_eq!(config.default_syntax, "foo");

        let foo = config.syntaxes.get("foo").unwrap();
        assert_eq!(foo.block_start, "{<");
        assert_eq!(foo.block_end, default_syntax.block_end);
        assert_eq!(foo.expr_start, default_syntax.expr_start);
        assert_eq!(foo.expr_end, default_syntax.expr_end);
        assert_eq!(foo.comment_start, default_syntax.comment_start);
        assert_eq!(foo.comment_end, default_syntax.comment_end);

        let bar = config.syntaxes.get("bar").unwrap();
        assert_eq!(bar.block_start, default_syntax.block_start);
        assert_eq!(bar.block_end, default_syntax.block_end);
        assert_eq!(bar.expr_start, "{!");
        assert_eq!(bar.expr_end, default_syntax.expr_end);
        assert_eq!(bar.comment_start, default_syntax.comment_start);
        assert_eq!(bar.comment_end, default_syntax.comment_end);
    }

    #[should_panic]
    #[test]
    fn use_default_at_syntax_name() {
        let raw_config = r#"
        syntax = [{ name = "default" }]
        "#;

        let _config = Config::new(raw_config);
    }

    #[should_panic]
    #[test]
    fn duplicated_syntax_name_on_list() {
        let raw_config = r#"
        syntax = [{ name = "foo", block_start = "~<" },
                  { name = "foo", block_start = "%%" } ]
        "#;

        let _config = Config::new(raw_config);
    }

    #[should_panic]
    #[test]
    fn is_not_exist_default_syntax() {
        let raw_config = r#"
        default_syntax = "foo"
        "#;

        let _config = Config::new(raw_config);
    }
}
<commit_msg>Move default_syntax setting into general section<commit_after>#![cfg_attr(feature = "cargo-clippy", allow(unused_parens))]

extern crate num_traits;
extern crate serde;
#[macro_use]
extern crate serde_derive;
#[cfg(feature = "serde-json")]
extern crate serde_json;
extern crate toml;

use std::env;
use std::fs;
use std::path::{Path, PathBuf};

mod error;
mod escaping;

pub use error::{Error, Result};
pub use escaping::MarkupDisplay;

use std::collections::BTreeMap;

pub mod filters;

#[derive(Debug)]
pub struct Config<'a> {
    pub dirs: Vec<PathBuf>,
    pub syntaxes: BTreeMap<String, Syntax<'a>>,
    pub default_syntax: &'a str,
}

impl<'a> Config<'a> {
    pub fn new(s: &str) -> Config {
        let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        let default_dirs = vec![root.join("templates")];

        let mut syntaxes = BTreeMap::new();
        syntaxes.insert(DEFAULT_SYNTAX_NAME.to_string(), Syntax::default());

        let raw: RawConfig =
            toml::from_str(&s).expect(&format!("invalid TOML in {}", CONFIG_FILE_NAME));

        let (dirs, default_syntax) = match raw.general {
            Some(General { dirs, default_syntax }) => (
                dirs.map_or(default_dirs, |v| v.into_iter().map(|dir| root.join(dir)).collect()),
                default_syntax.unwrap_or(DEFAULT_SYNTAX_NAME),
            ),
            None => (default_dirs, DEFAULT_SYNTAX_NAME),
        };

        if let Some(raw_syntaxes) = raw.syntax {
            for raw_s in raw_syntaxes {
                let name = raw_s.name;

                if let Some(_) = syntaxes.insert(name.to_string(), Syntax::from(raw_s)) {
                    panic!("syntax \"{}\" is already defined", name)
                }
            }
        }

        if !syntaxes.contains_key(default_syntax) {
            panic!("default syntax \"{}\" not found", default_syntax)
        }

        Config {
            dirs,
            syntaxes,
            default_syntax,
        }
    }

    pub fn find_template(&self, path: &str, start_at: Option<&Path>) -> PathBuf {
        if let Some(root) = start_at {
            let relative = root.with_file_name(path);
            if relative.exists() {
                return relative.to_owned();
            }
        }

        for dir in &self.dirs {
            let rooted = dir.join(path);
            if rooted.exists() {
                return rooted;
            }
        }

        panic!(
            "template {:?} not found in directories {:?}",
            path, self.dirs
        )
    }
}

#[derive(Debug)]
pub struct Syntax<'a> {
    pub block_start: &'a str,
    pub block_end: &'a str,
    pub expr_start: &'a str,
    pub expr_end: &'a str,
    pub comment_start: &'a str,
    pub comment_end: &'a str,
}

impl<'a> Default for Syntax<'a> {
    fn default() -> Self {
        Self {
            block_start: "{%",
            block_end: "%}",
            expr_start: "{{",
            expr_end: "}}",
            comment_start: "{#",
            comment_end: "#}",
        }
    }
}

impl<'a> From<RawSyntax<'a>> for Syntax<'a> {
    fn from(raw: RawSyntax<'a>) -> Self {
        let default = Self::default();
        let syntax = Self {
            block_start: raw.block_start.unwrap_or(default.block_start),
            block_end: raw.block_end.unwrap_or(default.block_end),
            expr_start: raw.expr_start.unwrap_or(default.expr_start),
            expr_end: raw.expr_end.unwrap_or(default.expr_end),
            comment_start: raw.comment_start.unwrap_or(default.comment_start),
            comment_end: raw.comment_end.unwrap_or(default.comment_end),
        };

        if syntax.block_start.len() != 2
            || syntax.block_end.len() != 2
            || syntax.expr_start.len() != 2
            || syntax.expr_end.len() != 2
            || syntax.comment_start.len() != 2
            || syntax.comment_end.len() != 2
        {
            panic!("length of delimiters must be two")
        }

        let bs = syntax.block_start.as_bytes()[0];
        let be = syntax.block_start.as_bytes()[1];
        let cs = syntax.comment_start.as_bytes()[0];
        let ce = syntax.comment_start.as_bytes()[1];
        let es = syntax.block_start.as_bytes()[0];
        let ee = syntax.block_start.as_bytes()[1];
        if !(bs == cs && bs == es) && !(be == ce && be == ee) {
            panic!("bad delimiters block_start: {}, comment_start: {}, expr_start: {}, needs one of the two characters in common", syntax.block_start, syntax.comment_start, syntax.expr_start);
        }

        syntax
    }
}

#[derive(Deserialize)]
struct RawConfig<'d> {
    #[serde(borrow)]
    general: Option<General<'d>>,
    syntax: Option<Vec<RawSyntax<'d>>>,
}

#[derive(Deserialize)]
struct General<'a> {
    #[serde(borrow)]
    dirs: Option<Vec<&'a str>>,
    default_syntax: Option<&'a str>,
}

#[derive(Deserialize)]
struct RawSyntax<'a> {
    name: &'a str,
    block_start: Option<&'a str>,
    block_end: Option<&'a str>,
    expr_start: Option<&'a str>,
    expr_end: Option<&'a str>,
    comment_start: Option<&'a str>,
    comment_end: Option<&'a str>,
}

pub fn read_config_file() -> String {
    let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let filename = root.join(CONFIG_FILE_NAME);
    if filename.exists() {
        fs::read_to_string(&filename)
            .expect(&format!("unable to read {}", filename.to_str().unwrap()))
    } else {
        "".to_string()
    }
}

static CONFIG_FILE_NAME: &str = "askama.toml";
static DEFAULT_SYNTAX_NAME: &str = "default";

#[cfg(test)]
mod tests {
    use super::*;
    use std::env;
    use std::path::{Path, PathBuf};

    #[test]
    fn test_default_config() {
        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        root.push("templates");
        let config = Config::new("");
        assert_eq!(config.dirs, vec![root]);
    }

    #[test]
    fn test_config_dirs() {
        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        root.push("tpl");
        let config = Config::new("[general]\ndirs = [\"tpl\"]");
        assert_eq!(config.dirs, vec![root]);
    }

    fn assert_eq_rooted(actual: &Path, expected: &str) {
        let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        root.push("templates");
        let mut inner = PathBuf::new();
        inner.push(expected);
        assert_eq!(actual.strip_prefix(root).unwrap(), inner);
    }

    #[test]
    fn find_absolute() {
        let config = Config::new("");
        let root = config.find_template("a.html", None);
        let path = config.find_template("sub/b.html", Some(&root));
        assert_eq_rooted(&path, "sub/b.html");
    }

    #[test]
    #[should_panic]
    fn find_relative_nonexistent() {
        let config = Config::new("");
        let root = config.find_template("a.html", None);
        config.find_template("b.html", Some(&root));
    }

    #[test]
    fn find_relative() {
        let config = Config::new("");
        let root = config.find_template("sub/b.html", None);
        let path = config.find_template("c.html", Some(&root));
        assert_eq_rooted(&path, "sub/c.html");
    }

    #[test]
    fn find_relative_sub() {
        let config = Config::new("");
        let root = config.find_template("sub/b.html", None);
        let path = config.find_template("sub1/d.html", Some(&root));
        assert_eq_rooted(&path, "sub/sub1/d.html");
    }

    #[test]
    fn add_syntax() {
        let raw_config = r#"
        [general]
        default_syntax = "foo"

        [[syntax]]
        name = "foo"
        block_start = "{<"

        [[syntax]]
        name = "bar"
        expr_start = "{!"
        "#;

        let default_syntax = Syntax::default();
        let config = Config::new(raw_config);
        assert_eq!(config.default_syntax, "foo");

        let foo = config.syntaxes.get("foo").unwrap();
        assert_eq!(foo.block_start, "{<");
        assert_eq!(foo.block_end, default_syntax.block_end);
        assert_eq!(foo.expr_start, default_syntax.expr_start);
        assert_eq!(foo.expr_end, default_syntax.expr_end);
        assert_eq!(foo.comment_start, default_syntax.comment_start);
        assert_eq!(foo.comment_end, default_syntax.comment_end);

        let bar = config.syntaxes.get("bar").unwrap();
        assert_eq!(bar.block_start, default_syntax.block_start);
        assert_eq!(bar.block_end, default_syntax.block_end);
        assert_eq!(bar.expr_start, "{!");
        assert_eq!(bar.expr_end, default_syntax.expr_end);
        assert_eq!(bar.comment_start, default_syntax.comment_start);
        assert_eq!(bar.comment_end, default_syntax.comment_end);
    }

    #[test]
    fn add_syntax_two() {
        let raw_config = r#"
        syntax = [{ name = "foo", block_start = "{<" },
                  { name = "bar", expr_start = "{!" } ]

        [general]
        default_syntax = "foo"
        "#;

        let default_syntax = Syntax::default();
        let config = Config::new(raw_config);
        assert_eq!(config.default_syntax, "foo");

        let foo = config.syntaxes.get("foo").unwrap();
        assert_eq!(foo.block_start, "{<");
        assert_eq!(foo.block_end, default_syntax.block_end);
        assert_eq!(foo.expr_start, default_syntax.expr_start);
        assert_eq!(foo.expr_end, default_syntax.expr_end);
        assert_eq!(foo.comment_start, default_syntax.comment_start);
        assert_eq!(foo.comment_end, default_syntax.comment_end);

        let bar = config.syntaxes.get("bar").unwrap();
        assert_eq!(bar.block_start, default_syntax.block_start);
        assert_eq!(bar.block_end, default_syntax.block_end);
        assert_eq!(bar.expr_start, "{!");
        assert_eq!(bar.expr_end, default_syntax.expr_end);
        assert_eq!(bar.comment_start, default_syntax.comment_start);
        assert_eq!(bar.comment_end, default_syntax.comment_end);
    }

    #[should_panic]
    #[test]
    fn use_default_at_syntax_name() {
        let raw_config = r#"
        syntax = [{ name = "default" }]
        "#;

        let _config = Config::new(raw_config);
    }

    #[should_panic]
    #[test]
    fn duplicated_syntax_name_on_list() {
        let raw_config = r#"
        syntax = [{ name = "foo", block_start = "~<" },
                  { name = "foo", block_start = "%%" } ]
        "#;

        let _config = Config::new(raw_config);
    }

    #[should_panic]
    #[test]
    fn is_not_exist_default_syntax() {
        let raw_config = r#"
        [general]
        default_syntax = "foo"
        "#;

        let _config = Config::new(raw_config);
    }
}
<|endoftext|>


<commit_before>use std::fmt::{Debug, Display, Formatter};
use std::fmt;

use clap::ArgMatches;

use runtime::Runtime;
use module::Module;

pub struct BM<'a> {
    rt: &'a Runtime<'a>,
}

impl<'a> BM<'a> {

    pub fn new(rt: &'a Runtime<'a>) -> BM<'a> {
        BM {
            rt: rt,
        }
    }

    fn runtime(&self) -> &Runtime {
        &self.rt
    }

}

impl<'a> Module<'a> for BM<'a> {

    fn exec(&self, matches: &ArgMatches) -> bool {
        unimplemented!()
    }

    fn name(&self) -> &'static str {
        "bm"
    }
}

impl<'a> Debug for BM<'a> {

    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        write!(fmt, "BM");
        Ok(())
    }

}
<commit_msg>Add subcommand calling code<commit_after>use std::fmt::{Debug, Display, Formatter};
use std::fmt;

use clap::ArgMatches;

use runtime::Runtime;
use module::Module;

pub struct BM<'a> {
    rt: &'a Runtime<'a>,
}

impl<'a> BM<'a> {

    pub fn new(rt: &'a Runtime<'a>) -> BM<'a> {
        BM {
            rt: rt,
        }
    }

    fn runtime(&self) -> &Runtime {
        &self.rt
    }

    fn command_add(&self) -> bool {
        unimplemented!()
    }

    fn command_list(&self) -> bool {
        unimplemented!()
    }

    fn command_remove(&self) -> bool {
        unimplemented!()
    }

}

impl<'a> Module<'a> for BM<'a> {

    fn exec(&self, matches: &ArgMatches) -> bool {
        match matches.subcommand_name() {
            Some("add")     => self.command_add(),
            Some("list")    => self.command_list(),
            Some("remove")  => self.command_remove(),
            Some(_) | None  => {
                info!("No command given, doing nothing");
                false
            },
        }
    }

    fn name(&self) -> &'static str {
        "bm"
    }
}

impl<'a> Debug for BM<'a> {

    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        write!(fmt, "BM");
        Ok(())
    }

}

<|endoftext|>


<commit_before>// Copyright 2017 Dmitry Tantsur <divius.inside@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Generic API bits for implementing new services.

use std::marker::PhantomData;

use hyper::{Client, Url};
use hyper::client::{Body, IntoUrl, RequestBuilder as HyperRequestBuilder,
                    Response};
use hyper::header::{Header, HeaderFormat, Headers};
pub use hyper::method::Method;
use serde::{Deserialize, Serialize};
use serde_json;

use super::{ApiError, ApiResult, ApiVersion, ApiVersionRequest, Session};
use super::utils;

/// Request builder with error checking and JSON support.
///
/// Partly copies the interface of hyper::client::RequestBuilder.
#[allow(missing_debug_implementations)]
pub struct RequestBuilder<'a> {
    inner: HyperRequestBuilder<'a>
}

/// Type of query parameters.
#[derive(Clone, Debug)]
pub struct Query(pub Vec<(String, String)>);

/// Information about API endpoint.
#[derive(Clone, Debug)]
pub struct ServiceInfo {
    /// Root endpoint.
    pub root_url: Url,
    /// Current API version (if supported).
    pub current_version: Option<ApiVersion>,
    /// Minimum API version (if supported).
    pub minimum_version: Option<ApiVersion>
}

/// Trait representing a service type.
pub trait ServiceType {
    /// Service type to pass to the catalog.
    fn catalog_type() -> &'static str;

    /// Get basic service information.
    fn service_info(endpoint: Url, session: &Session)
        -> ApiResult<ServiceInfo>;
}

/// Trait representing a service with API version support.
pub trait ApiVersioning {
    /// Return headers to set for this API version.
    fn api_version_headers(version: ApiVersion) -> ApiResult<Headers>;
}

/// A service-specific wrapper around Session.
#[derive(Debug)]
pub struct ServiceWrapper<'session, Srv: ServiceType> {
    session: &'session Session,
    service_type: PhantomData<Srv>,
    endpoint_interface: Option<String>
}

impl Query {
    /// Empty query.
    pub fn new() -> Query {
        Query(Vec::new())
    }

    /// Add an item to the query.
    pub fn push<K, V>(&mut self, param: K, value: V)
            where K: Into<String>, V: ToString {
        self.0.push((param.into(), value.to_string()))
    }

    /// Add a strng item to the query.
    pub fn push_str<K, V>(&mut self, param: K, value: V)
            where K: Into<String>, V: Into<String> {
        self.0.push((param.into(), value.into()))
    }
}

impl<'a> RequestBuilder<'a> {
    /// Wrap a request builder.
    pub fn new<U>(client: &'a Client, method: Method, url: U, headers: Headers)
            -> RequestBuilder<'a> where U: IntoUrl {
        RequestBuilder {
            inner: client.request(method, url).headers(headers)
        }
    }

    /// Send this request.
    pub fn send(self) -> ApiResult<Response> {
        let resp = try!(self.send_unchecked());
        if resp.status.is_success() {
            Ok(resp)
        } else {
            Err(ApiError::HttpError(resp.status, resp))
        }
    }

    /// Send this request without checking on status code.
    pub fn send_unchecked(self) -> ApiResult<Response> {
        self.inner.send().map_err(From::from).map(|resp| {
            trace!("Got {} from {} with {:?}",
                   resp.status, resp.url, resp.headers);
            resp
        })
    }

    /// Send this request and parse JSON response on success.
    pub fn fetch_json<T: Deserialize>(self) -> ApiResult<T> {
        let resp = try!(self.send());
        serde_json::from_reader(resp).map_err(From::from)
    }

    /// Add body to the request.
    pub fn body<B: Into<Body<'a>>>(self, body: B) -> RequestBuilder<'a> {
        RequestBuilder {
            inner: self.inner.body(body)
        }
    }

    /// Add an individual header to the request.
    pub fn header<H: Header + HeaderFormat>(self, header: H)
            -> RequestBuilder<'a> {
        RequestBuilder {
            inner: self.inner.header(header)
        }
    }
}

impl<'session, Srv: ServiceType> ServiceWrapper<'session, Srv> {
    /// Create a new wrapper for the specific service.
    pub fn new(session: &'session Session) -> ServiceWrapper<'session, Srv> {
        ServiceWrapper {
            session: session,
            service_type: PhantomData,
            endpoint_interface: None
        }
    }

    /// Change the endpoint interface used for this wrapper.
    pub fn with_endpoint_interface(self, endpoint_interface: String)
            -> ServiceWrapper<'session, Srv> {
        ServiceWrapper {
            endpoint_interface: Some(endpoint_interface),
            .. self
        }
    }

    /// Construct and endpoint for the given service from the path.
    pub fn get_endpoint<P>(&self, path: P, query: Query) -> ApiResult<Url>
            where P: IntoIterator, P::Item: AsRef<str> {
        let ep = self.endpoint_interface.clone();
        let info = try!(self.session.get_service_info::<Srv>(ep));
        let mut url = utils::url::extend(info.root_url, path);
        let _ = url.query_pairs_mut().extend_pairs(query.0);
        Ok(url)
    }

    /// Make an HTTP request to the given service.
    pub fn request<P>(&'session self, method: Method, path: P, query: Query)
            -> ApiResult<RequestBuilder<'session>>
            where P: IntoIterator, P::Item: AsRef<str> {
        let url = try!(self.get_endpoint(path, query));
        let headers = self.session.service_headers::<Srv>();
        trace!("Sending HTTP {} request to {} with {:?}",
               method, url, headers);
        self.session.request(method, url, headers)
    }

    /// Make an HTTP request with JSON body and JSON response.
    pub fn json<P, Req, Res>(&self, method: Method, path: P, query: Query,
                             body: &Req) -> ApiResult<Res>
            where Req: Serialize, Res: Deserialize,
            P: IntoIterator, P::Item: AsRef<str> {
        let str_body = try!(serde_json::to_string(body));
        let request = try!(self.request(method, path, query));
        request.body(&str_body).fetch_json()
    }

    /// Make a GET request returning a JSON.
    pub fn get_json<P, Res>(&self, path: P, query: Query) -> ApiResult<Res>
            where Res: Deserialize, P: IntoIterator, P::Item: AsRef<str> {
        try!(self.request(Method::Get, path, query)).fetch_json()
    }

    /// Make a POST request sending and returning a JSON.
    pub fn post_json<P, Req, Res>(&self, path: P, query: Query, body: &Req)
            -> ApiResult<Res> where Req: Serialize, Res: Deserialize,
            P: IntoIterator, P::Item: AsRef<str> {
        self.json(Method::Post, path, query, body)
    }

    /// Make a POST request sending and returning a JSON.
    pub fn put_json<P, Req, Res>(&self, path: P, query: Query, body: &Req)
            -> ApiResult<Res> where Req: Serialize, Res: Deserialize,
            P: IntoIterator, P::Item: AsRef<str> {
        self.json(Method::Put, path, query, body)
    }

    /// Make a PATCH request sending and returning a JSON.
    pub fn patch_json<P, Req, Res>(&self, path: P, query: Query, body: &Req)
            -> ApiResult<Res> where Req: Serialize, Res: Deserialize,
            P: IntoIterator, P::Item: AsRef<str> {
        self.json(Method::Patch, path, query, body)
    }

    /// Make a DELETE request.
    pub fn delete<P>(&self, path: P, query: Query) -> ApiResult<Response>
            where P: IntoIterator, P::Item: AsRef<str> {
        try!(self.request(Method::Delete, path, query)).send()
    }
}

impl<'session, Srv: ServiceType> Clone for ServiceWrapper<'session, Srv> {
    fn clone(&self) -> ServiceWrapper<'session, Srv> {
        ServiceWrapper {
            session: self.session,
            service_type: PhantomData,
            endpoint_interface: self.endpoint_interface.clone()
        }
    }
}

impl ServiceInfo {
    /// Pick an API version.
    pub fn pick_api_version(&self, request: ApiVersionRequest)
            -> Option<ApiVersion> {
        match request {
            ApiVersionRequest::Minimum =>
                self.minimum_version,
            ApiVersionRequest::Latest =>
                self.current_version,
            ApiVersionRequest::Exact(req) => {
                self.current_version.and_then(|max| {
                    match self.minimum_version {
                        Some(min) if req >= min && req <= max => Some(req),
                        None if req == max => Some(req),
                        _ => None
                    }
                })
            },
            ApiVersionRequest::Choice(vec) => {
                if vec.is_empty() {
                    return None;
                }

                self.current_version.and_then(|max| {
                    match self.minimum_version {
                        Some(min) => vec.into_iter().filter(|x| {
                            *x >= min && *x <= max
                        }).max(),
                        None =>vec.into_iter().find(|x| *x == max)
                    }
                })
            }
        }
    }
}

#[cfg(test)]
pub mod test {
    use hyper::Url;

    use super::super::{ApiVersion, ApiVersionRequest};
    use super::ServiceInfo;

    fn service_info(min: Option<u16>, max: Option<u16>) -> ServiceInfo {
        ServiceInfo {
            root_url: Url::parse("http://127.0.0.1").unwrap(),
            minimum_version: min.map(|x| ApiVersion(2, x)),
            current_version: max.map(|x| ApiVersion(2, x)),
        }
    }

    #[test]
    fn test_pick_version_exact() {
        let info = service_info(Some(1), Some(24));
        let version = ApiVersion(2, 22);
        let result = info.pick_api_version(ApiVersionRequest::Exact(version))
            .unwrap();
        assert_eq!(result, version);
    }

    #[test]
    fn test_pick_version_exact_mismatch() {
        let info = service_info(Some(1), Some(24));
        let version = ApiVersion(2, 25);
        let res1 = info.pick_api_version(ApiVersionRequest::Exact(version));
        assert!(res1.is_none());
        let version2 = ApiVersion(1, 11);
        let res2 = info.pick_api_version(ApiVersionRequest::Exact(version2));
        assert!(res2.is_none());
    }

    #[test]
    fn test_pick_version_exact_current_only() {
        let info = service_info(None, Some(24));
        let version = ApiVersion(2, 24);
        let result = info.pick_api_version(ApiVersionRequest::Exact(version))
            .unwrap();
        assert_eq!(result, version);
    }

    #[test]
    fn test_pick_version_exact_current_only_mismatch() {
        let info = service_info(None, Some(24));
        let version = ApiVersion(2, 22);
        let result = info.pick_api_version(ApiVersionRequest::Exact(version));
        assert!(result.is_none());
    }

    #[test]
    fn test_pick_version_minimum() {
        let info = service_info(Some(1), Some(24));
        let result = info.pick_api_version(ApiVersionRequest::Minimum)
            .unwrap();
        assert_eq!(result, ApiVersion(2, 1));
    }

    #[test]
    fn test_pick_version_minimum_unknown() {
        let info = service_info(None, Some(24));
        let result = info.pick_api_version(ApiVersionRequest::Minimum);
        assert!(result.is_none());
    }

    #[test]
    fn test_pick_version_latest() {
        let info = service_info(Some(1), Some(24));
        let result = info.pick_api_version(ApiVersionRequest::Latest)
            .unwrap();
        assert_eq!(result, ApiVersion(2, 24));
    }

    #[test]
    fn test_pick_version_latest_unknown() {
        let info = service_info(Some(1), None);
        let result = info.pick_api_version(ApiVersionRequest::Latest);
        assert!(result.is_none());
    }

    #[test]
    fn test_pick_version_choice() {
        let info = service_info(Some(1), Some(24));
        let choice = vec![ApiVersion(2, 0), ApiVersion(2, 2),
                          ApiVersion(2, 22), ApiVersion(2, 25)];
        let result = info.pick_api_version(ApiVersionRequest::Choice(choice))
            .unwrap();
        assert_eq!(result, ApiVersion(2, 22));
    }

    #[test]
    fn test_pick_version_choice_mismatch() {
        let info = service_info(Some(1), Some(24));
        let choice = vec![ApiVersion(2, 0), ApiVersion(2, 25)];
        let result = info.pick_api_version(ApiVersionRequest::Choice(choice));
        assert!(result.is_none());
    }

    #[test]
    fn test_pick_version_choice_current_only() {
        let info = service_info(None, Some(24));
        let choice = vec![ApiVersion(2, 0), ApiVersion(2, 2),
                          ApiVersion(2, 24), ApiVersion(2, 25)];
        let result = info.pick_api_version(ApiVersionRequest::Choice(choice))
            .unwrap();
        assert_eq!(result, ApiVersion(2, 24));
    }

    #[test]
    fn test_pick_version_choice_current_only_mismatch() {
        let info = service_info(None, Some(24));
        let choice = vec![ApiVersion(2, 0), ApiVersion(2, 2),
                          ApiVersion(2, 22), ApiVersion(2, 25)];
        let result = info.pick_api_version(ApiVersionRequest::Choice(choice));
        assert!(result.is_none());
    }
}
<commit_msg>chore(service): stop re-exporting hyper::Method<commit_after>// Copyright 2017 Dmitry Tantsur <divius.inside@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Generic API bits for implementing new services.

use std::marker::PhantomData;

use hyper::{Client, Url};
use hyper::client::{Body, IntoUrl, RequestBuilder as HyperRequestBuilder,
                    Response};
use hyper::header::{Header, HeaderFormat, Headers};
use hyper::method::Method;
use serde::{Deserialize, Serialize};
use serde_json;

use super::{ApiError, ApiResult, ApiVersion, ApiVersionRequest, Session};
use super::utils;

/// Request builder with error checking and JSON support.
///
/// Partly copies the interface of hyper::client::RequestBuilder.
#[allow(missing_debug_implementations)]
pub struct RequestBuilder<'a> {
    inner: HyperRequestBuilder<'a>
}

/// Type of query parameters.
#[derive(Clone, Debug)]
pub struct Query(pub Vec<(String, String)>);

/// Information about API endpoint.
#[derive(Clone, Debug)]
pub struct ServiceInfo {
    /// Root endpoint.
    pub root_url: Url,
    /// Current API version (if supported).
    pub current_version: Option<ApiVersion>,
    /// Minimum API version (if supported).
    pub minimum_version: Option<ApiVersion>
}

/// Trait representing a service type.
pub trait ServiceType {
    /// Service type to pass to the catalog.
    fn catalog_type() -> &'static str;

    /// Get basic service information.
    fn service_info(endpoint: Url, session: &Session)
        -> ApiResult<ServiceInfo>;
}

/// Trait representing a service with API version support.
pub trait ApiVersioning {
    /// Return headers to set for this API version.
    fn api_version_headers(version: ApiVersion) -> ApiResult<Headers>;
}

/// A service-specific wrapper around Session.
#[derive(Debug)]
pub struct ServiceWrapper<'session, Srv: ServiceType> {
    session: &'session Session,
    service_type: PhantomData<Srv>,
    endpoint_interface: Option<String>
}

impl Query {
    /// Empty query.
    pub fn new() -> Query {
        Query(Vec::new())
    }

    /// Add an item to the query.
    pub fn push<K, V>(&mut self, param: K, value: V)
            where K: Into<String>, V: ToString {
        self.0.push((param.into(), value.to_string()))
    }

    /// Add a strng item to the query.
    pub fn push_str<K, V>(&mut self, param: K, value: V)
            where K: Into<String>, V: Into<String> {
        self.0.push((param.into(), value.into()))
    }
}

impl<'a> RequestBuilder<'a> {
    /// Wrap a request builder.
    pub fn new<U>(client: &'a Client, method: Method, url: U, headers: Headers)
            -> RequestBuilder<'a> where U: IntoUrl {
        RequestBuilder {
            inner: client.request(method, url).headers(headers)
        }
    }

    /// Send this request.
    pub fn send(self) -> ApiResult<Response> {
        let resp = try!(self.send_unchecked());
        if resp.status.is_success() {
            Ok(resp)
        } else {
            Err(ApiError::HttpError(resp.status, resp))
        }
    }

    /// Send this request without checking on status code.
    pub fn send_unchecked(self) -> ApiResult<Response> {
        self.inner.send().map_err(From::from).map(|resp| {
            trace!("Got {} from {} with {:?}",
                   resp.status, resp.url, resp.headers);
            resp
        })
    }

    /// Send this request and parse JSON response on success.
    pub fn fetch_json<T: Deserialize>(self) -> ApiResult<T> {
        let resp = try!(self.send());
        serde_json::from_reader(resp).map_err(From::from)
    }

    /// Add body to the request.
    pub fn body<B: Into<Body<'a>>>(self, body: B) -> RequestBuilder<'a> {
        RequestBuilder {
            inner: self.inner.body(body)
        }
    }

    /// Add an individual header to the request.
    pub fn header<H: Header + HeaderFormat>(self, header: H)
            -> RequestBuilder<'a> {
        RequestBuilder {
            inner: self.inner.header(header)
        }
    }
}

impl<'session, Srv: ServiceType> ServiceWrapper<'session, Srv> {
    /// Create a new wrapper for the specific service.
    pub fn new(session: &'session Session) -> ServiceWrapper<'session, Srv> {
        ServiceWrapper {
            session: session,
            service_type: PhantomData,
            endpoint_interface: None
        }
    }

    /// Change the endpoint interface used for this wrapper.
    pub fn with_endpoint_interface(self, endpoint_interface: String)
            -> ServiceWrapper<'session, Srv> {
        ServiceWrapper {
            endpoint_interface: Some(endpoint_interface),
            .. self
        }
    }

    /// Construct and endpoint for the given service from the path.
    pub fn get_endpoint<P>(&self, path: P, query: Query) -> ApiResult<Url>
            where P: IntoIterator, P::Item: AsRef<str> {
        let ep = self.endpoint_interface.clone();
        let info = try!(self.session.get_service_info::<Srv>(ep));
        let mut url = utils::url::extend(info.root_url, path);
        let _ = url.query_pairs_mut().extend_pairs(query.0);
        Ok(url)
    }

    /// Make an HTTP request to the given service.
    pub fn request<P>(&'session self, method: Method, path: P, query: Query)
            -> ApiResult<RequestBuilder<'session>>
            where P: IntoIterator, P::Item: AsRef<str> {
        let url = try!(self.get_endpoint(path, query));
        let headers = self.session.service_headers::<Srv>();
        trace!("Sending HTTP {} request to {} with {:?}",
               method, url, headers);
        self.session.request(method, url, headers)
    }

    /// Make an HTTP request with JSON body and JSON response.
    pub fn json<P, Req, Res>(&self, method: Method, path: P, query: Query,
                             body: &Req) -> ApiResult<Res>
            where Req: Serialize, Res: Deserialize,
            P: IntoIterator, P::Item: AsRef<str> {
        let str_body = try!(serde_json::to_string(body));
        let request = try!(self.request(method, path, query));
        request.body(&str_body).fetch_json()
    }

    /// Make a GET request returning a JSON.
    pub fn get_json<P, Res>(&self, path: P, query: Query) -> ApiResult<Res>
            where Res: Deserialize, P: IntoIterator, P::Item: AsRef<str> {
        try!(self.request(Method::Get, path, query)).fetch_json()
    }

    /// Make a POST request sending and returning a JSON.
    pub fn post_json<P, Req, Res>(&self, path: P, query: Query, body: &Req)
            -> ApiResult<Res> where Req: Serialize, Res: Deserialize,
            P: IntoIterator, P::Item: AsRef<str> {
        self.json(Method::Post, path, query, body)
    }

    /// Make a POST request sending and returning a JSON.
    pub fn put_json<P, Req, Res>(&self, path: P, query: Query, body: &Req)
            -> ApiResult<Res> where Req: Serialize, Res: Deserialize,
            P: IntoIterator, P::Item: AsRef<str> {
        self.json(Method::Put, path, query, body)
    }

    /// Make a PATCH request sending and returning a JSON.
    pub fn patch_json<P, Req, Res>(&self, path: P, query: Query, body: &Req)
            -> ApiResult<Res> where Req: Serialize, Res: Deserialize,
            P: IntoIterator, P::Item: AsRef<str> {
        self.json(Method::Patch, path, query, body)
    }

    /// Make a DELETE request.
    pub fn delete<P>(&self, path: P, query: Query) -> ApiResult<Response>
            where P: IntoIterator, P::Item: AsRef<str> {
        try!(self.request(Method::Delete, path, query)).send()
    }
}

impl<'session, Srv: ServiceType> Clone for ServiceWrapper<'session, Srv> {
    fn clone(&self) -> ServiceWrapper<'session, Srv> {
        ServiceWrapper {
            session: self.session,
            service_type: PhantomData,
            endpoint_interface: self.endpoint_interface.clone()
        }
    }
}

impl ServiceInfo {
    /// Pick an API version.
    pub fn pick_api_version(&self, request: ApiVersionRequest)
            -> Option<ApiVersion> {
        match request {
            ApiVersionRequest::Minimum =>
                self.minimum_version,
            ApiVersionRequest::Latest =>
                self.current_version,
            ApiVersionRequest::Exact(req) => {
                self.current_version.and_then(|max| {
                    match self.minimum_version {
                        Some(min) if req >= min && req <= max => Some(req),
                        None if req == max => Some(req),
                        _ => None
                    }
                })
            },
            ApiVersionRequest::Choice(vec) => {
                if vec.is_empty() {
                    return None;
                }

                self.current_version.and_then(|max| {
                    match self.minimum_version {
                        Some(min) => vec.into_iter().filter(|x| {
                            *x >= min && *x <= max
                        }).max(),
                        None =>vec.into_iter().find(|x| *x == max)
                    }
                })
            }
        }
    }
}

#[cfg(test)]
pub mod test {
    use hyper::Url;

    use super::super::{ApiVersion, ApiVersionRequest};
    use super::ServiceInfo;

    fn service_info(min: Option<u16>, max: Option<u16>) -> ServiceInfo {
        ServiceInfo {
            root_url: Url::parse("http://127.0.0.1").unwrap(),
            minimum_version: min.map(|x| ApiVersion(2, x)),
            current_version: max.map(|x| ApiVersion(2, x)),
        }
    }

    #[test]
    fn test_pick_version_exact() {
        let info = service_info(Some(1), Some(24));
        let version = ApiVersion(2, 22);
        let result = info.pick_api_version(ApiVersionRequest::Exact(version))
            .unwrap();
        assert_eq!(result, version);
    }

    #[test]
    fn test_pick_version_exact_mismatch() {
        let info = service_info(Some(1), Some(24));
        let version = ApiVersion(2, 25);
        let res1 = info.pick_api_version(ApiVersionRequest::Exact(version));
        assert!(res1.is_none());
        let version2 = ApiVersion(1, 11);
        let res2 = info.pick_api_version(ApiVersionRequest::Exact(version2));
        assert!(res2.is_none());
    }

    #[test]
    fn test_pick_version_exact_current_only() {
        let info = service_info(None, Some(24));
        let version = ApiVersion(2, 24);
        let result = info.pick_api_version(ApiVersionRequest::Exact(version))
            .unwrap();
        assert_eq!(result, version);
    }

    #[test]
    fn test_pick_version_exact_current_only_mismatch() {
        let info = service_info(None, Some(24));
        let version = ApiVersion(2, 22);
        let result = info.pick_api_version(ApiVersionRequest::Exact(version));
        assert!(result.is_none());
    }

    #[test]
    fn test_pick_version_minimum() {
        let info = service_info(Some(1), Some(24));
        let result = info.pick_api_version(ApiVersionRequest::Minimum)
            .unwrap();
        assert_eq!(result, ApiVersion(2, 1));
    }

    #[test]
    fn test_pick_version_minimum_unknown() {
        let info = service_info(None, Some(24));
        let result = info.pick_api_version(ApiVersionRequest::Minimum);
        assert!(result.is_none());
    }

    #[test]
    fn test_pick_version_latest() {
        let info = service_info(Some(1), Some(24));
        let result = info.pick_api_version(ApiVersionRequest::Latest)
            .unwrap();
        assert_eq!(result, ApiVersion(2, 24));
    }

    #[test]
    fn test_pick_version_latest_unknown() {
        let info = service_info(Some(1), None);
        let result = info.pick_api_version(ApiVersionRequest::Latest);
        assert!(result.is_none());
    }

    #[test]
    fn test_pick_version_choice() {
        let info = service_info(Some(1), Some(24));
        let choice = vec![ApiVersion(2, 0), ApiVersion(2, 2),
                          ApiVersion(2, 22), ApiVersion(2, 25)];
        let result = info.pick_api_version(ApiVersionRequest::Choice(choice))
            .unwrap();
        assert_eq!(result, ApiVersion(2, 22));
    }

    #[test]
    fn test_pick_version_choice_mismatch() {
        let info = service_info(Some(1), Some(24));
        let choice = vec![ApiVersion(2, 0), ApiVersion(2, 25)];
        let result = info.pick_api_version(ApiVersionRequest::Choice(choice));
        assert!(result.is_none());
    }

    #[test]
    fn test_pick_version_choice_current_only() {
        let info = service_info(None, Some(24));
        let choice = vec![ApiVersion(2, 0), ApiVersion(2, 2),
                          ApiVersion(2, 24), ApiVersion(2, 25)];
        let result = info.pick_api_version(ApiVersionRequest::Choice(choice))
            .unwrap();
        assert_eq!(result, ApiVersion(2, 24));
    }

    #[test]
    fn test_pick_version_choice_current_only_mismatch() {
        let info = service_info(None, Some(24));
        let choice = vec![ApiVersion(2, 0), ApiVersion(2, 2),
                          ApiVersion(2, 22), ApiVersion(2, 25)];
        let result = info.pick_api_version(ApiVersionRequest::Choice(choice));
        assert!(result.is_none());
    }
}
<|endoftext|>