<commit_before>use std::collections::HashMap;

#[cfg(feature = "builtins")]
use chrono::prelude::*;
#[cfg(feature = "builtins")]
use rand::Rng;
use serde_json::value::{from_value, to_value, Value};

use crate::errors::{Error, Result};

/// The global function type definition
pub trait Function: Sync + Send {
    /// The global function type definition
    fn call(&self, args: &HashMap<String, Value>) -> Result<Value>;

    /// Whether the current function's output should be treated as safe, defaults to `false`
    fn is_safe(&self) -> bool {
        false
    }
}

impl<F> Function for F
where
    F: Fn(&HashMap<String, Value>) -> Result<Value> + Sync + Send,
{
    fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
        self(args)
    }
}

pub fn range(args: &HashMap<String, Value>) -> Result<Value> {
    let start = match args.get("start") {
        Some(val) => match from_value::<usize>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `range` received start={} but `start` can only be a number",
                    val
                )));
            }
        },
        None => 0,
    };
    let step_by = match args.get("step_by") {
        Some(val) => match from_value::<usize>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `range` received step_by={} but `step` can only be a number",
                    val
                )));
            }
        },
        None => 1,
    };
    let end = match args.get("end") {
        Some(val) => match from_value::<usize>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `range` received end={} but `end` can only be a number",
                    val
                )));
            }
        },
        None => {
            return Err(Error::msg("Function `range` was called without a `end` argument"));
        }
    };

    if start > end {
        return Err(Error::msg(
            "Function `range` was called with a `start` argument greater than the `end` one",
        ));
    }

    let mut i = start;
    let mut res = vec![];
    while i < end {
        res.push(i);
        i += step_by;
    }
    Ok(to_value(res).unwrap())
}

#[cfg(feature = "builtins")]
pub fn now(args: &HashMap<String, Value>) -> Result<Value> {
    let utc = match args.get("utc") {
        Some(val) => match from_value::<bool>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `now` received utc={} but `utc` can only be a boolean",
                    val
                )));
            }
        },
        None => false,
    };
    let timestamp = match args.get("timestamp") {
        Some(val) => match from_value::<bool>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `now` received timestamp={} but `timestamp` can only be a boolean",
                    val
                )));
            }
        },
        None => false,
    };

    if utc {
        let datetime = Utc::now();
        if timestamp {
            return Ok(to_value(datetime.timestamp()).unwrap());
        }
        Ok(to_value(datetime.to_rfc3339()).unwrap())
    } else {
        let datetime = Local::now();
        if timestamp {
            return Ok(to_value(datetime.timestamp()).unwrap());
        }
        Ok(to_value(datetime.to_rfc3339()).unwrap())
    }
}

pub fn throw(args: &HashMap<String, Value>) -> Result<Value> {
    match args.get("message") {
        Some(val) => match from_value::<String>(val.clone()) {
            Ok(v) => Err(Error::msg(v)),
            Err(_) => Err(Error::msg(format!(
                "Function `throw` received message={} but `message` can only be a string",
                val
            ))),
        },
        None => Err(Error::msg("Function `throw` was called without a `message` argument")),
    }
}

#[cfg(feature = "builtins")]
pub fn get_random(args: &HashMap<String, Value>) -> Result<Value> {
    let start = match args.get("start") {
        Some(val) => match from_value::<i32>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `get_random` received start={} but `start` can only be a number",
                    val
                )));
            }
        },
        None => 0,
    };

    let end = match args.get("end") {
        Some(val) => match from_value::<i32>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `get_random` received end={} but `end` can only be a number",
                    val
                )));
            }
        },
        None => return Err(Error::msg("Function `get_random` didn't receive an `end` argument")),
    };
    let mut rng = rand::thread_rng();
    let res = rng.gen_range(start..end);

    Ok(Value::Number(res.into()))
}

pub fn get_env(args: &HashMap<String, Value>) -> Result<Value> {
    let name = match args.get("name") {
        Some(val) => match from_value::<String>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `get_env` received name={} but `name` can only be a string",
                    val
                )));
            }
        },
        None => return Err(Error::msg("Function `get_env` didn't receive a `name` argument")),
    };

    match std::env::var(&name).ok() {
        Some(res) => Ok(Value::String(res)),
        None => match args.get("default") {
            Some(default) => Ok(default.clone()),
            None => Err(Error::msg(format!("Environment variable `{}` not found", &name))),
        },
    }
}

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

    use serde_json::value::to_value;

    use super::*;

    #[test]
    fn range_default() {
        let mut args = HashMap::new();
        args.insert("end".to_string(), to_value(5).unwrap());

        let res = range(&args).unwrap();
        assert_eq!(res, to_value(vec![0, 1, 2, 3, 4]).unwrap());
    }

    #[test]
    fn range_start() {
        let mut args = HashMap::new();
        args.insert("end".to_string(), to_value(5).unwrap());
        args.insert("start".to_string(), to_value(1).unwrap());

        let res = range(&args).unwrap();
        assert_eq!(res, to_value(vec![1, 2, 3, 4]).unwrap());
    }

    #[test]
    fn range_start_greater_than_end() {
        let mut args = HashMap::new();
        args.insert("end".to_string(), to_value(5).unwrap());
        args.insert("start".to_string(), to_value(6).unwrap());

        assert!(range(&args).is_err());
    }

    #[test]
    fn range_step_by() {
        let mut args = HashMap::new();
        args.insert("end".to_string(), to_value(10).unwrap());
        args.insert("step_by".to_string(), to_value(2).unwrap());

        let res = range(&args).unwrap();
        assert_eq!(res, to_value(vec![0, 2, 4, 6, 8]).unwrap());
    }

    #[cfg(feature = "builtins")]
    #[test]
    fn now_default() {
        let args = HashMap::new();

        let res = now(&args).unwrap();
        assert!(res.is_string());
        assert!(res.as_str().unwrap().contains("T"));
    }

    #[cfg(feature = "builtins")]
    #[test]
    fn now_datetime_utc() {
        let mut args = HashMap::new();
        args.insert("utc".to_string(), to_value(true).unwrap());

        let res = now(&args).unwrap();
        assert!(res.is_string());
        let val = res.as_str().unwrap();
        println!("{}", val);
        assert!(val.contains("T"));
        assert!(val.contains("+00:00"));
    }

    #[cfg(feature = "builtins")]
    #[test]
    fn now_timestamp() {
        let mut args = HashMap::new();
        args.insert("timestamp".to_string(), to_value(true).unwrap());

        let res = now(&args).unwrap();
        assert!(res.is_number());
    }

    #[test]
    fn throw_errors_with_message() {
        let mut args = HashMap::new();
        args.insert("message".to_string(), to_value("Hello").unwrap());

        let res = throw(&args);
        assert!(res.is_err());
        let err = res.unwrap_err();
        assert_eq!(err.to_string(), "Hello");
    }

    #[cfg(feature = "builtins")]
    #[test]
    fn get_random_no_start() {
        let mut args = HashMap::new();
        args.insert("end".to_string(), to_value(10).unwrap());
        let res = get_random(&args).unwrap();
        println!("{}", res);
        assert!(res.is_number());
        assert!(res.as_i64().unwrap() >= 0);
        assert!(res.as_i64().unwrap() < 10);
    }

    #[cfg(feature = "builtins")]
    #[test]
    fn get_random_with_start() {
        let mut args = HashMap::new();
        args.insert("start".to_string(), to_value(5).unwrap());
        args.insert("end".to_string(), to_value(10).unwrap());
        let res = get_random(&args).unwrap();
        println!("{}", res);
        assert!(res.is_number());
        assert!(res.as_i64().unwrap() >= 5);
        assert!(res.as_i64().unwrap() < 10);
    }

    #[test]
    fn get_env_existing() {
        std::env::set_var("TERA_TEST", "true");
        let mut args = HashMap::new();
        args.insert("name".to_string(), to_value("TERA_TEST").unwrap());
        let res = get_env(&args).unwrap();
        assert!(res.is_string());
        assert_eq!(res.as_str().unwrap(), "true");
        std::env::remove_var("TERA_TEST");
    }

    #[test]
    fn get_env_non_existing_no_default() {
        let mut args = HashMap::new();
        args.insert("name".to_string(), to_value("UNKNOWN_VAR").unwrap());
        let res = get_env(&args);
        assert!(res.is_err());
    }

    #[test]
    fn get_env_non_existing_with_default() {
        let mut args = HashMap::new();
        args.insert("name".to_string(), to_value("UNKNOWN_VAR").unwrap());
        args.insert("default".to_string(), to_value("false").unwrap());
        let res = get_env(&args).unwrap();
        assert!(res.is_string());
        assert_eq!(res.as_str().unwrap(), "false");
    }
}
<commit_msg>Use isize for get_random<commit_after>use std::collections::HashMap;

#[cfg(feature = "builtins")]
use chrono::prelude::*;
#[cfg(feature = "builtins")]
use rand::Rng;
use serde_json::value::{from_value, to_value, Value};

use crate::errors::{Error, Result};

/// The global function type definition
pub trait Function: Sync + Send {
    /// The global function type definition
    fn call(&self, args: &HashMap<String, Value>) -> Result<Value>;

    /// Whether the current function's output should be treated as safe, defaults to `false`
    fn is_safe(&self) -> bool {
        false
    }
}

impl<F> Function for F
where
    F: Fn(&HashMap<String, Value>) -> Result<Value> + Sync + Send,
{
    fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
        self(args)
    }
}

pub fn range(args: &HashMap<String, Value>) -> Result<Value> {
    let start = match args.get("start") {
        Some(val) => match from_value::<usize>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `range` received start={} but `start` can only be a number",
                    val
                )));
            }
        },
        None => 0,
    };
    let step_by = match args.get("step_by") {
        Some(val) => match from_value::<usize>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `range` received step_by={} but `step` can only be a number",
                    val
                )));
            }
        },
        None => 1,
    };
    let end = match args.get("end") {
        Some(val) => match from_value::<usize>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `range` received end={} but `end` can only be a number",
                    val
                )));
            }
        },
        None => {
            return Err(Error::msg("Function `range` was called without a `end` argument"));
        }
    };

    if start > end {
        return Err(Error::msg(
            "Function `range` was called with a `start` argument greater than the `end` one",
        ));
    }

    let mut i = start;
    let mut res = vec![];
    while i < end {
        res.push(i);
        i += step_by;
    }
    Ok(to_value(res).unwrap())
}

#[cfg(feature = "builtins")]
pub fn now(args: &HashMap<String, Value>) -> Result<Value> {
    let utc = match args.get("utc") {
        Some(val) => match from_value::<bool>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `now` received utc={} but `utc` can only be a boolean",
                    val
                )));
            }
        },
        None => false,
    };
    let timestamp = match args.get("timestamp") {
        Some(val) => match from_value::<bool>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `now` received timestamp={} but `timestamp` can only be a boolean",
                    val
                )));
            }
        },
        None => false,
    };

    if utc {
        let datetime = Utc::now();
        if timestamp {
            return Ok(to_value(datetime.timestamp()).unwrap());
        }
        Ok(to_value(datetime.to_rfc3339()).unwrap())
    } else {
        let datetime = Local::now();
        if timestamp {
            return Ok(to_value(datetime.timestamp()).unwrap());
        }
        Ok(to_value(datetime.to_rfc3339()).unwrap())
    }
}

pub fn throw(args: &HashMap<String, Value>) -> Result<Value> {
    match args.get("message") {
        Some(val) => match from_value::<String>(val.clone()) {
            Ok(v) => Err(Error::msg(v)),
            Err(_) => Err(Error::msg(format!(
                "Function `throw` received message={} but `message` can only be a string",
                val
            ))),
        },
        None => Err(Error::msg("Function `throw` was called without a `message` argument")),
    }
}

#[cfg(feature = "builtins")]
pub fn get_random(args: &HashMap<String, Value>) -> Result<Value> {
    let start = match args.get("start") {
        Some(val) => match from_value::<isize>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `get_random` received start={} but `start` can only be a number",
                    val
                )));
            }
        },
        None => 0,
    };

    let end = match args.get("end") {
        Some(val) => match from_value::<isize>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `get_random` received end={} but `end` can only be a number",
                    val
                )));
            }
        },
        None => return Err(Error::msg("Function `get_random` didn't receive an `end` argument")),
    };
    let mut rng = rand::thread_rng();
    let res = rng.gen_range(start..end);

    Ok(Value::Number(res.into()))
}

pub fn get_env(args: &HashMap<String, Value>) -> Result<Value> {
    let name = match args.get("name") {
        Some(val) => match from_value::<String>(val.clone()) {
            Ok(v) => v,
            Err(_) => {
                return Err(Error::msg(format!(
                    "Function `get_env` received name={} but `name` can only be a string",
                    val
                )));
            }
        },
        None => return Err(Error::msg("Function `get_env` didn't receive a `name` argument")),
    };

    match std::env::var(&name).ok() {
        Some(res) => Ok(Value::String(res)),
        None => match args.get("default") {
            Some(default) => Ok(default.clone()),
            None => Err(Error::msg(format!("Environment variable `{}` not found", &name))),
        },
    }
}

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

    use serde_json::value::to_value;

    use super::*;

    #[test]
    fn range_default() {
        let mut args = HashMap::new();
        args.insert("end".to_string(), to_value(5).unwrap());

        let res = range(&args).unwrap();
        assert_eq!(res, to_value(vec![0, 1, 2, 3, 4]).unwrap());
    }

    #[test]
    fn range_start() {
        let mut args = HashMap::new();
        args.insert("end".to_string(), to_value(5).unwrap());
        args.insert("start".to_string(), to_value(1).unwrap());

        let res = range(&args).unwrap();
        assert_eq!(res, to_value(vec![1, 2, 3, 4]).unwrap());
    }

    #[test]
    fn range_start_greater_than_end() {
        let mut args = HashMap::new();
        args.insert("end".to_string(), to_value(5).unwrap());
        args.insert("start".to_string(), to_value(6).unwrap());

        assert!(range(&args).is_err());
    }

    #[test]
    fn range_step_by() {
        let mut args = HashMap::new();
        args.insert("end".to_string(), to_value(10).unwrap());
        args.insert("step_by".to_string(), to_value(2).unwrap());

        let res = range(&args).unwrap();
        assert_eq!(res, to_value(vec![0, 2, 4, 6, 8]).unwrap());
    }

    #[cfg(feature = "builtins")]
    #[test]
    fn now_default() {
        let args = HashMap::new();

        let res = now(&args).unwrap();
        assert!(res.is_string());
        assert!(res.as_str().unwrap().contains("T"));
    }

    #[cfg(feature = "builtins")]
    #[test]
    fn now_datetime_utc() {
        let mut args = HashMap::new();
        args.insert("utc".to_string(), to_value(true).unwrap());

        let res = now(&args).unwrap();
        assert!(res.is_string());
        let val = res.as_str().unwrap();
        println!("{}", val);
        assert!(val.contains("T"));
        assert!(val.contains("+00:00"));
    }

    #[cfg(feature = "builtins")]
    #[test]
    fn now_timestamp() {
        let mut args = HashMap::new();
        args.insert("timestamp".to_string(), to_value(true).unwrap());

        let res = now(&args).unwrap();
        assert!(res.is_number());
    }

    #[test]
    fn throw_errors_with_message() {
        let mut args = HashMap::new();
        args.insert("message".to_string(), to_value("Hello").unwrap());

        let res = throw(&args);
        assert!(res.is_err());
        let err = res.unwrap_err();
        assert_eq!(err.to_string(), "Hello");
    }

    #[cfg(feature = "builtins")]
    #[test]
    fn get_random_no_start() {
        let mut args = HashMap::new();
        args.insert("end".to_string(), to_value(10).unwrap());
        let res = get_random(&args).unwrap();
        println!("{}", res);
        assert!(res.is_number());
        assert!(res.as_i64().unwrap() >= 0);
        assert!(res.as_i64().unwrap() < 10);
    }

    #[cfg(feature = "builtins")]
    #[test]
    fn get_random_with_start() {
        let mut args = HashMap::new();
        args.insert("start".to_string(), to_value(5).unwrap());
        args.insert("end".to_string(), to_value(10).unwrap());
        let res = get_random(&args).unwrap();
        println!("{}", res);
        assert!(res.is_number());
        assert!(res.as_i64().unwrap() >= 5);
        assert!(res.as_i64().unwrap() < 10);
    }

    #[test]
    fn get_env_existing() {
        std::env::set_var("TERA_TEST", "true");
        let mut args = HashMap::new();
        args.insert("name".to_string(), to_value("TERA_TEST").unwrap());
        let res = get_env(&args).unwrap();
        assert!(res.is_string());
        assert_eq!(res.as_str().unwrap(), "true");
        std::env::remove_var("TERA_TEST");
    }

    #[test]
    fn get_env_non_existing_no_default() {
        let mut args = HashMap::new();
        args.insert("name".to_string(), to_value("UNKNOWN_VAR").unwrap());
        let res = get_env(&args);
        assert!(res.is_err());
    }

    #[test]
    fn get_env_non_existing_with_default() {
        let mut args = HashMap::new();
        args.insert("name".to_string(), to_value("UNKNOWN_VAR").unwrap());
        args.insert("default".to_string(), to_value("false").unwrap());
        let res = get_env(&args).unwrap();
        assert!(res.is_string());
        assert_eq!(res.as_str().unwrap(), "false");
    }
}
<|endoftext|>


<commit_before>use std::iter::{Iterator, IntoIterator};
use std::error;
use std::cmp;
use std::fmt;
use std::time::{Duration, Instant};

use futures::{Async, Future, Poll};
use tokio_timer::{Delay, Error as TimerError};
use tokio_timer::timer::Handle;

use super::action::Action;
use super::condition::Condition;

/// Represents the errors possible during the execution of the `RetryFuture`.
#[derive(Debug)]
pub enum Error<E> {
    OperationError(E),
    TimerError(TimerError)
}

impl<E: cmp::PartialEq> cmp::PartialEq for Error<E> {
    fn eq(&self, other: &Error<E>) -> bool  {
        match (self, other) {
            (&Error::TimerError(_), _) => false,
            (_, &Error::TimerError(_)) => false,
            (&Error::OperationError(ref left_err), &Error::OperationError(ref right_err)) =>
                left_err.eq(right_err)
        }
    }
}

impl<E: fmt::Display> fmt::Display for Error<E> {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match *self {
            Error::OperationError(ref err) => err.fmt(formatter),
            Error::TimerError(ref err) => err.fmt(formatter)
        }
    }
}

impl<E: error::Error> error::Error for Error<E> {
    fn description(&self) -> &str {
        match *self {
            Error::OperationError(ref err) => err.description(),
            Error::TimerError(ref err) => err.description()
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::OperationError(ref err) => Some(err),
            Error::TimerError(ref err) => Some(err)
        }
    }
}

enum RetryState<A> where A: Action {
    Running(A::Future),
    Sleeping(Delay)
}

impl<A: Action> RetryState<A> {
    fn poll(&mut self) -> RetryFuturePoll<A> {
        match *self {
            RetryState::Running(ref mut future) =>
                RetryFuturePoll::Running(future.poll()),
            RetryState::Sleeping(ref mut future) =>
                RetryFuturePoll::Sleeping(future.poll())
        }
    }
}

enum RetryFuturePoll<A> where A: Action {
    Running(Poll<A::Item, A::Error>),
    Sleeping(Poll<(), TimerError>)
}

/// Future that drives multiple attempts at an action via a retry strategy.
pub struct Retry<I, A> where I: Iterator<Item=Duration>, A: Action {
    retry_if: RetryIf<I, A, fn(&A::Error) -> bool>
}

impl<I, A> Retry<I, A> where I: Iterator<Item=Duration>, A: Action {
    pub fn spawn<T: IntoIterator<IntoIter=I, Item=Duration>>(strategy: T, action: A) -> Retry<I, A> {
        Retry::new(None, strategy, action)
    }

    pub fn spawn_with_handle<T: IntoIterator<IntoIter=I, Item=Duration>>(handle: Handle, strategy: T, action: A) -> Retry<I, A> {
        Retry::new(Some(handle), strategy, action)
    }

    fn new<T: IntoIterator<IntoIter=I, Item=Duration>>(handle: Option<Handle>, strategy: T, action: A) -> Retry<I, A> {
        Retry {
            retry_if: RetryIf::new(handle, strategy, action, (|_| true) as fn(&A::Error) -> bool)
        }
    }
}

impl<I, A> Future for Retry<I, A> where I: Iterator<Item=Duration>, A: Action {
    type Item = A::Item;
    type Error = Error<A::Error>;

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        self.retry_if.poll()
    }
}

/// Future that drives multiple attempts at an action via a retry strategy. Retries are only attempted if
/// the `Error` returned by the future satisfies a given condition.
pub struct RetryIf<I, A, C> where I: Iterator<Item=Duration>, A: Action, C: Condition<A::Error> {
    strategy: I,
    state: RetryState<A>,
    action: A,
    handle: Option<Handle>,
    condition: C
}

impl<I, A, C> RetryIf<I, A, C> where I: Iterator<Item=Duration>, A: Action, C: Condition<A::Error> {
    pub fn spawn<T: IntoIterator<IntoIter=I, Item=Duration>>(
        strategy: T,
        action: A,
        condition: C
    ) -> RetryIf<I, A, C> {
        RetryIf::new(None, strategy, action, condition)
    }

    pub fn spawn_with_handle<T: IntoIterator<IntoIter=I, Item=Duration>>(
        handle: Handle,
        strategy: T,
        action: A,
        condition: C
    ) -> RetryIf<I, A, C> {
        RetryIf::new(Some(handle), strategy, action, condition)
    }

    fn new<T: IntoIterator<IntoIter=I, Item=Duration>>(
        handle: Option<Handle>,
        strategy: T,
        mut action: A,
        condition: C
    ) -> RetryIf<I, A, C> {
        RetryIf {
            strategy: strategy.into_iter(),
            state: RetryState::Running(action.run()),
            action: action,
            handle: handle,
            condition: condition,
        }
    }

    fn attempt(&mut self) -> Poll<A::Item, Error<A::Error>> {
        let future = self.action.run();
        self.state = RetryState::Running(future);
        self.poll()
    }

    fn retry(&mut self, err: A::Error) -> Poll<A::Item, Error<A::Error>> {
        match self.strategy.next() {
            None => Err(Error::OperationError(err)),
            Some(duration) => {
                let deadline = Instant::now() + duration;
                let handle = self.handle.clone().unwrap_or_else(Handle::current);
                let future = handle.delay(deadline);
                self.state = RetryState::Sleeping(future);
                self.poll()
            }
        }
    }
}

impl<I, A, C> Future for RetryIf<I, A, C> where I: Iterator<Item=Duration>, A: Action, C: Condition<A::Error> {
    type Item = A::Item;
    type Error = Error<A::Error>;

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        match self.state.poll() {
            RetryFuturePoll::Running(poll_result) => match poll_result {
                Ok(async) => Ok(async),
                Err(err) => {
                    if self.condition.should_retry(&err) {
                        self.retry(err)
                    } else {
                        Err(Error::OperationError(err))
                    }
                }
            },
            RetryFuturePoll::Sleeping(poll_result) => match poll_result {
                Ok(Async::NotReady) => Ok(Async::NotReady),
                Ok(Async::Ready(_)) => self.attempt(),
                Err(err) => Err(Error::TimerError(err))
            }
        }
    }
}
<commit_msg>remove ::spawn_with_handle methods<commit_after>use std::iter::{Iterator, IntoIterator};
use std::error;
use std::cmp;
use std::fmt;
use std::time::{Duration, Instant};

use futures::{Async, Future, Poll};
use tokio_timer::{Delay, Error as TimerError};

use super::action::Action;
use super::condition::Condition;

/// Represents the errors possible during the execution of the `RetryFuture`.
#[derive(Debug)]
pub enum Error<E> {
    OperationError(E),
    TimerError(TimerError)
}

impl<E: cmp::PartialEq> cmp::PartialEq for Error<E> {
    fn eq(&self, other: &Error<E>) -> bool  {
        match (self, other) {
            (&Error::TimerError(_), _) => false,
            (_, &Error::TimerError(_)) => false,
            (&Error::OperationError(ref left_err), &Error::OperationError(ref right_err)) =>
                left_err.eq(right_err)
        }
    }
}

impl<E: fmt::Display> fmt::Display for Error<E> {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match *self {
            Error::OperationError(ref err) => err.fmt(formatter),
            Error::TimerError(ref err) => err.fmt(formatter)
        }
    }
}

impl<E: error::Error> error::Error for Error<E> {
    fn description(&self) -> &str {
        match *self {
            Error::OperationError(ref err) => err.description(),
            Error::TimerError(ref err) => err.description()
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::OperationError(ref err) => Some(err),
            Error::TimerError(ref err) => Some(err)
        }
    }
}

enum RetryState<A> where A: Action {
    Running(A::Future),
    Sleeping(Delay)
}

impl<A: Action> RetryState<A> {
    fn poll(&mut self) -> RetryFuturePoll<A> {
        match *self {
            RetryState::Running(ref mut future) =>
                RetryFuturePoll::Running(future.poll()),
            RetryState::Sleeping(ref mut future) =>
                RetryFuturePoll::Sleeping(future.poll())
        }
    }
}

enum RetryFuturePoll<A> where A: Action {
    Running(Poll<A::Item, A::Error>),
    Sleeping(Poll<(), TimerError>)
}

/// Future that drives multiple attempts at an action via a retry strategy.
pub struct Retry<I, A> where I: Iterator<Item=Duration>, A: Action {
    retry_if: RetryIf<I, A, fn(&A::Error) -> bool>
}

impl<I, A> Retry<I, A> where I: Iterator<Item=Duration>, A: Action {
    pub fn spawn<T: IntoIterator<IntoIter=I, Item=Duration>>(strategy: T, action: A) -> Retry<I, A> {
        Retry {
            retry_if: RetryIf::spawn(strategy, action, (|_| true) as fn(&A::Error) -> bool)
        }
    }
}

impl<I, A> Future for Retry<I, A> where I: Iterator<Item=Duration>, A: Action {
    type Item = A::Item;
    type Error = Error<A::Error>;

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        self.retry_if.poll()
    }
}

/// Future that drives multiple attempts at an action via a retry strategy. Retries are only attempted if
/// the `Error` returned by the future satisfies a given condition.
pub struct RetryIf<I, A, C> where I: Iterator<Item=Duration>, A: Action, C: Condition<A::Error> {
    strategy: I,
    state: RetryState<A>,
    action: A,
    condition: C
}

impl<I, A, C> RetryIf<I, A, C> where I: Iterator<Item=Duration>, A: Action, C: Condition<A::Error> {
    pub fn spawn<T: IntoIterator<IntoIter=I, Item=Duration>>(
        strategy: T,
        mut action: A,
        condition: C
    ) -> RetryIf<I, A, C> {
        RetryIf {
            strategy: strategy.into_iter(),
            state: RetryState::Running(action.run()),
            action: action,
            condition: condition,
        }
    }

    fn attempt(&mut self) -> Poll<A::Item, Error<A::Error>> {
        let future = self.action.run();
        self.state = RetryState::Running(future);
        self.poll()
    }

    fn retry(&mut self, err: A::Error) -> Poll<A::Item, Error<A::Error>> {
        match self.strategy.next() {
            None => Err(Error::OperationError(err)),
            Some(duration) => {
                let deadline = Instant::now() + duration;
                let future = Delay::new(deadline);
                self.state = RetryState::Sleeping(future);
                self.poll()
            }
        }
    }
}

impl<I, A, C> Future for RetryIf<I, A, C> where I: Iterator<Item=Duration>, A: Action, C: Condition<A::Error> {
    type Item = A::Item;
    type Error = Error<A::Error>;

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        match self.state.poll() {
            RetryFuturePoll::Running(poll_result) => match poll_result {
                Ok(async) => Ok(async),
                Err(err) => {
                    if self.condition.should_retry(&err) {
                        self.retry(err)
                    } else {
                        Err(Error::OperationError(err))
                    }
                }
            },
            RetryFuturePoll::Sleeping(poll_result) => match poll_result {
                Ok(Async::NotReady) => Ok(Async::NotReady),
                Ok(Async::Ready(_)) => self.attempt(),
                Err(err) => Err(Error::TimerError(err))
            }
        }
    }
}
<|endoftext|>


<commit_before>extern crate iron;
extern crate router;
extern crate rustc_serialize;

use iron::{Iron, Request, Response, IronResult};
use iron::status;
use router::Router;
use rustc_serialize::json;

#[derive(RustcDecodable, RustcEncodable)]
struct Message {
    message: String,
}

fn main() {
    let mut router = Router::new();
    router.get("/json", jsonHandler);
    router.get("/plaintext", plaintextHandler);

    Iron::new(router).http("0.0.0.0:8080").unwrap();
}

fn jsonHandler(req: &mut Request) -> IronResult<Response> {
    let message: Message = Message { message: "Hello, World!".to_string() };
    Ok(Response::with((status::Ok, json::encode(&message).unwrap())))
}

fn plaintextHandler(req: &mut Request) -> IronResult<Response> {
    Ok(Response::with((status::Ok, "Hello, World!")))
}
<commit_msg>fix style errors<commit_after>extern crate iron;
extern crate router;
extern crate rustc_serialize;

use iron::{Iron, Request, Response, IronResult};
use iron::status;
use router::Router;
use rustc_serialize::json;

#[derive(RustcDecodable, RustcEncodable)]
struct Message {
    message: String,
}

fn main() {
    let mut router = Router::new();
    router.get("/json", json_handler);
    router.get("/plaintext", plaintext_handler);

    Iron::new(router).http("0.0.0.0:8080").unwrap();
}

fn json_handler(_: &mut Request) -> IronResult<Response> {
    let message: Message = Message { message: "Hello, World!".to_string() };
    Ok(Response::with((status::Ok, json::encode(&message).unwrap())))
}

fn plaintext_handler(_: &mut Request) -> IronResult<Response> {
    Ok(Response::with((status::Ok, "Hello, World!")))
}
<|endoftext|>


<commit_before>
use std::fmt;
use std::io::Read;  // Trait providing read_to_string()
use std::env;

use hyper;
use serde;
use serde_json;

use BuildQuery;
use Groups;

pub const API_VERSION: u16 = 3;

#[derive(Debug)]
pub struct Pagination {
    pub page: u16,
    pub per_page: u16,
}

pub struct GitLab {
    scheme: String,
    domain: String,
    port: u16,
    private_token: String,
    client: hyper::Client,
    pagination: Option<Pagination>,
}

// Explicitly implement Debug trait for GitLab so we can hide the token.
impl fmt::Debug for GitLab {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "GitLab {{ scheme: {}, domain: {}, port: {}, private_token: XXXXXXXXXXXXXXXXXXXX, \
                client: {:?}, pagination: {:?} }}",
               self.scheme,
               self.domain,
               self.port,
               self.client,
               self.pagination)
    }
}

impl GitLab {
    pub fn new(scheme: &str, domain: &str, port: u16, private_token: &str) -> GitLab {
        GitLab {
            scheme: scheme.to_string(),
            domain: domain.to_string(),
            port: port,
            private_token: private_token.to_string(),
            client: match env::var("HTTP_PROXY") {
                Ok(proxy) => {
                    let proxy: Vec<&str> = proxy.trim_left_matches("http://").split(':').collect();
                    let hostname = proxy[0].to_string();
                    let port = proxy[1];

                    hyper::Client::with_http_proxy(hostname, port.parse().unwrap())
                }
                Err(_) => hyper::Client::new(),
            },
            pagination: None,
        }
    }

    pub fn new_http(domain: &str, private_token: &str) -> GitLab {
        GitLab::new("http", domain, 80, private_token)
    }

    pub fn new(domain: &str, private_token: &str) -> GitLab {
        GitLab::new("https", domain, 443, private_token)
    }

    /// Build a URL used to access GitLab instance, including some parameters.
    ///
    /// # Examples
    ///
    /// Example from GitLab: https://docs.gitlab.com/ce/api/#basic-usage
    ///
    /// ```
    /// use gitlab_api::GitLab;
    ///
    /// let expected_url = "https://gitlab.example.com:\
    ///                     443/api/v3/groups?order_by=path&private_token=XXXXXXXXXXXXX";
    ///
    /// let gl = GitLab::new("gitlab.example.com", "XXXXXXXXXXXXX");
    ///
    /// assert_eq!(gl.build_url("groups?order_by=path"), expected_url);
    /// ```
    pub fn build_url(&self, query: &str) -> String {
        let params_splitter = if query.find('?').is_some() { "&" } else { "?" };
        let mut url = format!("{}://{}:{}/api/v{}/{}{}private_token={}",
                              self.scheme,
                              self.domain,
                              self.port,
                              API_VERSION,
                              query,
                              params_splitter,
                              self.private_token);

        self.pagination.as_ref().map(|pagination| {
            url.push_str(&format!("&page={}&per_page={}", pagination.page, pagination.per_page));
        });

        url
    }

    pub fn attempt_connection(&self) -> Result<hyper::client::Response, hyper::Error> {
        let url = self.build_url("version");
        // Close connections after each GET.
        self.client.get(&url).header(hyper::header::Connection::close()).send()
    }

    pub fn set_pagination(&mut self, pagination: Pagination) {
        self.pagination = Some(pagination);
    }

    pub fn get<T>(&self, query: &str) -> Result<T, serde_json::Error>
        where T: serde::Deserialize
    {
        let url = self.build_url(query);
        info!("url: {:?}", url);
        let mut res: hyper::client::Response = self.client
            .get(&url)
            .header(hyper::header::Connection::close())
            .send()
            .unwrap();
        info!("res.status: {:?}", res.status);
        debug!("res.headers: {:?}", res.headers);
        debug!("res.url: {:?}", res.url);

        let mut body = String::new();
        res.read_to_string(&mut body).unwrap();
        debug!("body:\n{:?}", body);

        // FIXME: Properly handle the error. Will require defining our own errors...
        assert_eq!(res.status, hyper::status::StatusCode::Ok);

        serde_json::from_str(body.as_str())
    }

    // pub fn version(&self) -> Result<Version, serde_json::Error> {
    //     self.get("version")
    // }
    //
    // pub fn groups(&self) -> Result<Groups, serde_json::Error> {
    //     self.get("groups")
    // }
    //
    // pub fn projects(&self) -> Result<Projects, serde_json::Error> {
    //     self.get("projects")
    // }

    pub fn groups(&mut self, listing: ::groups::Listing) -> Result<Groups, serde_json::Error> {
        let query = listing.build_query();
        self.get(&query)
    }

    pub fn owned_groups(&mut self) -> Result<Groups, serde_json::Error> {
        let query = ::groups::owned_groups::Listing::new().build_query();
        info!("query: {:?}", query);
        self.get(&query)
    }
}

// #[cfg(test)]
// mod tests {
//
// #[test]
// fn it_works() {
// }
// }
//
<commit_msg>Rename `GitLab::new_http()` to `new_insecure()`<commit_after>
use std::fmt;
use std::io::Read;  // Trait providing read_to_string()
use std::env;

use hyper;
use serde;
use serde_json;

use BuildQuery;
use Groups;

pub const API_VERSION: u16 = 3;

#[derive(Debug)]
pub struct Pagination {
    pub page: u16,
    pub per_page: u16,
}

pub struct GitLab {
    scheme: String,
    domain: String,
    port: u16,
    private_token: String,
    client: hyper::Client,
    pagination: Option<Pagination>,
}

// Explicitly implement Debug trait for GitLab so we can hide the token.
impl fmt::Debug for GitLab {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "GitLab {{ scheme: {}, domain: {}, port: {}, private_token: XXXXXXXXXXXXXXXXXXXX, \
                client: {:?}, pagination: {:?} }}",
               self.scheme,
               self.domain,
               self.port,
               self.client,
               self.pagination)
    }
}

impl GitLab {
    pub fn new(scheme: &str, domain: &str, port: u16, private_token: &str) -> GitLab {
        GitLab {
            scheme: scheme.to_string(),
            domain: domain.to_string(),
            port: port,
            private_token: private_token.to_string(),
            client: match env::var("HTTP_PROXY") {
                Ok(proxy) => {
                    let proxy: Vec<&str> = proxy.trim_left_matches("http://").split(':').collect();
                    let hostname = proxy[0].to_string();
                    let port = proxy[1];

                    hyper::Client::with_http_proxy(hostname, port.parse().unwrap())
                }
                Err(_) => hyper::Client::new(),
            },
            pagination: None,
        }
    }

    pub fn new_insecure(domain: &str, private_token: &str) -> GitLab {
        GitLab::new("http", domain, 80, private_token)
    }

    pub fn new(domain: &str, private_token: &str) -> GitLab {
        GitLab::new("https", domain, 443, private_token)
    }

    /// Build a URL used to access GitLab instance, including some parameters.
    ///
    /// # Examples
    ///
    /// Example from GitLab: https://docs.gitlab.com/ce/api/#basic-usage
    ///
    /// ```
    /// use gitlab_api::GitLab;
    ///
    /// let expected_url = "https://gitlab.example.com:\
    ///                     443/api/v3/groups?order_by=path&private_token=XXXXXXXXXXXXX";
    ///
    /// let gl = GitLab::new("gitlab.example.com", "XXXXXXXXXXXXX");
    ///
    /// assert_eq!(gl.build_url("groups?order_by=path"), expected_url);
    /// ```
    pub fn build_url(&self, query: &str) -> String {
        let params_splitter = if query.find('?').is_some() { "&" } else { "?" };
        let mut url = format!("{}://{}:{}/api/v{}/{}{}private_token={}",
                              self.scheme,
                              self.domain,
                              self.port,
                              API_VERSION,
                              query,
                              params_splitter,
                              self.private_token);

        self.pagination.as_ref().map(|pagination| {
            url.push_str(&format!("&page={}&per_page={}", pagination.page, pagination.per_page));
        });

        url
    }

    pub fn attempt_connection(&self) -> Result<hyper::client::Response, hyper::Error> {
        let url = self.build_url("version");
        // Close connections after each GET.
        self.client.get(&url).header(hyper::header::Connection::close()).send()
    }

    pub fn set_pagination(&mut self, pagination: Pagination) {
        self.pagination = Some(pagination);
    }

    pub fn get<T>(&self, query: &str) -> Result<T, serde_json::Error>
        where T: serde::Deserialize
    {
        let url = self.build_url(query);
        info!("url: {:?}", url);
        let mut res: hyper::client::Response = self.client
            .get(&url)
            .header(hyper::header::Connection::close())
            .send()
            .unwrap();
        info!("res.status: {:?}", res.status);
        debug!("res.headers: {:?}", res.headers);
        debug!("res.url: {:?}", res.url);

        let mut body = String::new();
        res.read_to_string(&mut body).unwrap();
        debug!("body:\n{:?}", body);

        // FIXME: Properly handle the error. Will require defining our own errors...
        assert_eq!(res.status, hyper::status::StatusCode::Ok);

        serde_json::from_str(body.as_str())
    }

    // pub fn version(&self) -> Result<Version, serde_json::Error> {
    //     self.get("version")
    // }
    //
    // pub fn groups(&self) -> Result<Groups, serde_json::Error> {
    //     self.get("groups")
    // }
    //
    // pub fn projects(&self) -> Result<Projects, serde_json::Error> {
    //     self.get("projects")
    // }

    pub fn groups(&mut self, listing: ::groups::Listing) -> Result<Groups, serde_json::Error> {
        let query = listing.build_query();
        self.get(&query)
    }

    pub fn owned_groups(&mut self) -> Result<Groups, serde_json::Error> {
        let query = ::groups::owned_groups::Listing::new().build_query();
        info!("query: {:?}", query);
        self.get(&query)
    }
}

// #[cfg(test)]
// mod tests {
//
// #[test]
// fn it_works() {
// }
// }
//
<|endoftext|>


<commit_before>use ra_syntax::SmolStr;

use crate::macros::tt;

struct MacroRules {
    rules: Vec<Rule>,
}

struct Rule {
    lhs: TokenTree,
    rhs: TokenTree,
}

enum TokenTree {
    Leaf(Leaf),
    Subtree(Subtree),
}

enum Leaf {
    Literal(Literal),
    Punct(Punct),
    Ident(Ident),
    Var(Var),
}

struct Subtree {
    delimiter: Delimiter,
    token_trees: Vec<TokenTree>,
}

enum Delimiter {
    Parenthesis,
    Brace,
    Bracket,
    None,
}

struct Literal {
    text: SmolStr,
}

struct Punct {
    char: char,
}

struct Ident {
    text: SmolStr,
}

struct Var {
    text: SmolStr,
}

fn parse(tt: tt::TokenTree) -> MacroRules {
    MacroRules { rules: Vec::new() }
}
<commit_msg>add repeats to ast<commit_after>use ra_syntax::SmolStr;

use crate::macros::tt;

struct MacroRules {
    rules: Vec<Rule>,
}

struct Rule {
    lhs: TokenTree,
    rhs: TokenTree,
}

enum TokenTree {
    Leaf(Leaf),
    Subtree(Subtree),
    Repeat(Repeat),
}

enum Leaf {
    Literal(Literal),
    Punct(Punct),
    Ident(Ident),
    Var(Var),
}

struct Subtree {
    delimiter: Delimiter,
    token_trees: Vec<TokenTree>,
}

enum Delimiter {
    Parenthesis,
    Brace,
    Bracket,
    None,
}

struct Repeat {
    subtree: Subtree,
    kind: RepeatKind,
}

enum RepeatKind {
    ZeroOrMore,
    OneOrMore,
    ZeroOrOne,
}

struct Literal {
    text: SmolStr,
}

struct Punct {
    char: char,
}

struct Ident {
    text: SmolStr,
}

struct Var {
    text: SmolStr,
}

fn parse(tt: tt::TokenTree) -> MacroRules {
    MacroRules { rules: Vec::new() }
}
<|endoftext|>


<commit_before>//! This module implements syntax validation that the parser doesn't handle.
//!
//! A failed validation emits a diagnostic.

mod block;

use rowan::Direction;
use rustc_lexer::unescape::{
    self, unescape_byte, unescape_byte_literal, unescape_char, unescape_literal, Mode,
};

use crate::{
    algo,
    ast::{self, HasAttrs, HasVisibility},
    match_ast, AstNode, SyntaxError,
    SyntaxKind::{CONST, FN, INT_NUMBER, TYPE_ALIAS},
    SyntaxNode, SyntaxToken, TextSize, T,
};

pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> {
    // FIXME:
    // * Add unescape validation of raw string literals and raw byte string literals
    // * Add validation of doc comments are being attached to nodes

    let mut errors = Vec::new();
    for node in root.descendants() {
        match_ast! {
            match node {
                ast::Literal(it) => validate_literal(it, &mut errors),
                ast::Const(it) => validate_const(it, &mut errors),
                ast::BlockExpr(it) => block::validate_block_expr(it, &mut errors),
                ast::FieldExpr(it) => validate_numeric_name(it.name_ref(), &mut errors),
                ast::RecordExprField(it) => validate_numeric_name(it.name_ref(), &mut errors),
                ast::Visibility(it) => validate_visibility(it, &mut errors),
                ast::RangeExpr(it) => validate_range_expr(it, &mut errors),
                ast::PathSegment(it) => validate_path_keywords(it, &mut errors),
                ast::RefType(it) => validate_trait_object_ref_ty(it, &mut errors),
                ast::PtrType(it) => validate_trait_object_ptr_ty(it, &mut errors),
                ast::FnPtrType(it) => validate_trait_object_fn_ptr_ret_ty(it, &mut errors),
                ast::MacroRules(it) => validate_macro_rules(it, &mut errors),
                ast::LetExpr(it) => validate_let_expr(it, &mut errors),
                _ => (),
            }
        }
    }
    errors
}

fn rustc_unescape_error_to_string(err: unescape::EscapeError) -> &'static str {
    use unescape::EscapeError as EE;

    #[rustfmt::skip]
    let err_message = match err {
        EE::ZeroChars => {
            "Literal must not be empty"
        }
        EE::MoreThanOneChar => {
            "Literal must be one character long"
        }
        EE::LoneSlash => {
            "Character must be escaped: `\\`"
        }
        EE::InvalidEscape => {
            "Invalid escape"
        }
        EE::BareCarriageReturn | EE::BareCarriageReturnInRawString => {
            "Character must be escaped: `\r`"
        }
        EE::EscapeOnlyChar => {
            "Escape character `\\` must be escaped itself"
        }
        EE::TooShortHexEscape => {
            "ASCII hex escape code must have exactly two digits"
        }
        EE::InvalidCharInHexEscape => {
            "ASCII hex escape code must contain only hex characters"
        }
        EE::OutOfRangeHexEscape => {
            "ASCII hex escape code must be at most 0x7F"
        }
        EE::NoBraceInUnicodeEscape => {
            "Missing `{` to begin the unicode escape"
        }
        EE::InvalidCharInUnicodeEscape => {
            "Unicode escape must contain only hex characters and underscores"
        }
        EE::EmptyUnicodeEscape => {
            "Unicode escape must not be empty"
        }
        EE::UnclosedUnicodeEscape => {
            "Missing `}` to terminate the unicode escape"
        }
        EE::LeadingUnderscoreUnicodeEscape => {
            "Unicode escape code must not begin with an underscore"
        }
        EE::OverlongUnicodeEscape => {
            "Unicode escape code must have at most 6 digits"
        }
        EE::LoneSurrogateUnicodeEscape => {
            "Unicode escape code must not be a surrogate"
        }
        EE::OutOfRangeUnicodeEscape => {
            "Unicode escape code must be at most 0x10FFFF"
        }
        EE::UnicodeEscapeInByte => {
            "Byte literals must not contain unicode escapes"
        }
        EE::NonAsciiCharInByte | EE::NonAsciiCharInByteString => {
            "Byte literals must not contain non-ASCII characters"
        }
    };

    err_message
}

fn validate_literal(literal: ast::Literal, acc: &mut Vec<SyntaxError>) {
    // FIXME: move this function to outer scope (https://github.com/rust-lang/rust-analyzer/pull/2834#discussion_r366196658)
    fn unquote(text: &str, prefix_len: usize, end_delimiter: char) -> Option<&str> {
        text.rfind(end_delimiter).and_then(|end| text.get(prefix_len..end))
    }

    let token = literal.token();
    let text = token.text();

    // FIXME: lift this lambda refactor to `fn` (https://github.com/rust-lang/rust-analyzer/pull/2834#discussion_r366199205)
    let mut push_err = |prefix_len, (off, err): (usize, unescape::EscapeError)| {
        let off = token.text_range().start() + TextSize::try_from(off + prefix_len).unwrap();
        acc.push(SyntaxError::new_at_offset(rustc_unescape_error_to_string(err), off));
    };

    match literal.kind() {
        ast::LiteralKind::String(s) => {
            if !s.is_raw() {
                if let Some(without_quotes) = unquote(text, 1, '"') {
                    unescape_literal(without_quotes, Mode::Str, &mut |range, char| {
                        if let Err(err) = char {
                            push_err(1, (range.start, err));
                        }
                    });
                }
            }
        }
        ast::LiteralKind::ByteString(s) => {
            if !s.is_raw() {
                if let Some(without_quotes) = unquote(text, 2, '"') {
                    unescape_byte_literal(without_quotes, Mode::ByteStr, &mut |range, char| {
                        if let Err(err) = char {
                            push_err(2, (range.start, err));
                        }
                    });
                }
            }
        }
        ast::LiteralKind::Char(_) => {
            if let Some(Err(e)) = unquote(text, 1, '\'').map(unescape_char) {
                push_err(1, e);
            }
        }
        ast::LiteralKind::Byte(_) => {
            if let Some(Err(e)) = unquote(text, 2, '\'').map(unescape_byte) {
                push_err(2, e);
            }
        }
        ast::LiteralKind::IntNumber(_)
        | ast::LiteralKind::FloatNumber(_)
        | ast::LiteralKind::Bool(_) => {}
    }
}

pub(crate) fn validate_block_structure(root: &SyntaxNode) {
    let mut stack = Vec::new();
    for node in root.descendants_with_tokens() {
        match node.kind() {
            T!['{'] => stack.push(node),
            T!['}'] => {
                if let Some(pair) = stack.pop() {
                    assert_eq!(
                        node.parent(),
                        pair.parent(),
                        "\nunpaired curlys:\n{}\n{:#?}\n",
                        root.text(),
                        root,
                    );
                    assert!(
                        node.next_sibling_or_token().is_none()
                            && pair.prev_sibling_or_token().is_none(),
                        "\nfloating curlys at {:?}\nfile:\n{}\nerror:\n{}\n",
                        node,
                        root.text(),
                        node,
                    );
                }
            }
            _ => (),
        }
    }
}

fn validate_numeric_name(name_ref: Option<ast::NameRef>, errors: &mut Vec<SyntaxError>) {
    if let Some(int_token) = int_token(name_ref) {
        if int_token.text().chars().any(|c| !c.is_digit(10)) {
            errors.push(SyntaxError::new(
                "Tuple (struct) field access is only allowed through \
                decimal integers with no underscores or suffix",
                int_token.text_range(),
            ));
        }
    }

    fn int_token(name_ref: Option<ast::NameRef>) -> Option<SyntaxToken> {
        name_ref?.syntax().first_child_or_token()?.into_token().filter(|it| it.kind() == INT_NUMBER)
    }
}

fn validate_visibility(vis: ast::Visibility, errors: &mut Vec<SyntaxError>) {
    let path_without_in_token = vis.in_token().is_none()
        && vis.path().and_then(|p| p.as_single_name_ref()).and_then(|n| n.ident_token()).is_some();
    if path_without_in_token {
        errors.push(SyntaxError::new("incorrect visibility restriction", vis.syntax.text_range()));
    }
    let parent = match vis.syntax().parent() {
        Some(it) => it,
        None => return,
    };
    match parent.kind() {
        FN | CONST | TYPE_ALIAS => (),
        _ => return,
    }

    let impl_def = match parent.parent().and_then(|it| it.parent()).and_then(ast::Impl::cast) {
        Some(it) => it,
        None => return,
    };
    // FIXME: disable validation if there's an attribute, since some proc macros use this syntax.
    // ideally the validation would run only on the fully expanded code, then this wouldn't be necessary.
    if impl_def.trait_().is_some() && impl_def.attrs().next().is_none() {
        errors.push(SyntaxError::new("Unnecessary visibility qualifier", vis.syntax.text_range()));
    }
}

fn validate_range_expr(expr: ast::RangeExpr, errors: &mut Vec<SyntaxError>) {
    if expr.op_kind() == Some(ast::RangeOp::Inclusive) && expr.end().is_none() {
        errors.push(SyntaxError::new(
            "An inclusive range must have an end expression",
            expr.syntax().text_range(),
        ));
    }
}

fn validate_path_keywords(segment: ast::PathSegment, errors: &mut Vec<SyntaxError>) {
    let path = segment.parent_path();
    let is_path_start = segment.coloncolon_token().is_none() && path.qualifier().is_none();

    if let Some(token) = segment.self_token() {
        if !is_path_start {
            errors.push(SyntaxError::new(
                "The `self` keyword is only allowed as the first segment of a path",
                token.text_range(),
            ));
        }
    } else if let Some(token) = segment.crate_token() {
        if !is_path_start || use_prefix(path).is_some() {
            errors.push(SyntaxError::new(
                "The `crate` keyword is only allowed as the first segment of a path",
                token.text_range(),
            ));
        }
    }

    fn use_prefix(mut path: ast::Path) -> Option<ast::Path> {
        for node in path.syntax().ancestors().skip(1) {
            match_ast! {
                match node {
                    ast::UseTree(it) => if let Some(tree_path) = it.path() {
                        // Even a top-level path exists within a `UseTree` so we must explicitly
                        // allow our path but disallow anything else
                        if tree_path != path {
                            return Some(tree_path);
                        }
                    },
                    ast::UseTreeList(_) => continue,
                    ast::Path(parent) => path = parent,
                    _ => return None,
                }
            };
        }
        None
    }
}

fn validate_trait_object_ref_ty(ty: ast::RefType, errors: &mut Vec<SyntaxError>) {
    if let Some(ast::Type::DynTraitType(ty)) = ty.ty() {
        if let Some(err) = validate_trait_object_ty(ty) {
            errors.push(err);
        }
    }
}

fn validate_trait_object_ptr_ty(ty: ast::PtrType, errors: &mut Vec<SyntaxError>) {
    if let Some(ast::Type::DynTraitType(ty)) = ty.ty() {
        if let Some(err) = validate_trait_object_ty(ty) {
            errors.push(err);
        }
    }
}

fn validate_trait_object_fn_ptr_ret_ty(ty: ast::FnPtrType, errors: &mut Vec<SyntaxError>) {
    if let Some(ast::Type::DynTraitType(ty)) = ty.ret_type().and_then(|ty| ty.ty()) {
        if let Some(err) = validate_trait_object_ty(ty) {
            errors.push(err);
        }
    }
}

fn validate_trait_object_ty(ty: ast::DynTraitType) -> Option<SyntaxError> {
    let tbl = ty.type_bound_list()?;

    if tbl.bounds().count() > 1 {
        let dyn_token = ty.dyn_token()?;
        let potential_parenthesis =
            algo::skip_trivia_token(dyn_token.prev_token()?, Direction::Prev)?;
        let kind = potential_parenthesis.kind();
        if !matches!(kind, T!['('] | T![<] | T![=]) {
            return Some(SyntaxError::new("ambiguous `+` in a type", ty.syntax().text_range()));
        }
    }
    None
}

fn validate_macro_rules(mac: ast::MacroRules, errors: &mut Vec<SyntaxError>) {
    if let Some(vis) = mac.visibility() {
        errors.push(SyntaxError::new(
            "visibilities are not allowed on `macro_rules!` items",
            vis.syntax().text_range(),
        ));
    }
}

fn validate_const(const_: ast::Const, errors: &mut Vec<SyntaxError>) {
    if let Some(mut_token) = const_
        .const_token()
        .and_then(|t| t.next_token())
        .and_then(|t| algo::skip_trivia_token(t, Direction::Next))
        .filter(|t| t.kind() == T![mut])
    {
        errors.push(SyntaxError::new("const globals cannot be mutable", mut_token.text_range()));
    }
}

fn validate_let_expr(let_: ast::LetExpr, errors: &mut Vec<SyntaxError>) {
    let mut token = let_.syntax().clone();
    loop {
        token = match token.parent() {
            Some(it) => it,
            None => break,
        };

        if ast::ParenExpr::can_cast(token.kind()) {
            continue;
        } else if let Some(it) = ast::BinExpr::cast(token.clone()) {
            if it.op_kind() == Some(ast::BinaryOp::LogicOp(ast::LogicOp::And)) {
                continue;
            }
        } else if ast::IfExpr::can_cast(token.kind())
            || ast::WhileExpr::can_cast(token.kind())
            || ast::MatchGuard::can_cast(token.kind())
        {
            // It must be part of the condition since the expressions are inside a block.
            return;
        }

        break;
    }
    errors.push(SyntaxError::new(
        "`let` expressions are not supported here",
        let_.syntax().text_range(),
    ));
}
<commit_msg>Rollup merge of #103919 - nnethercote:unescaping-cleanups, r=matklad<commit_after>//! This module implements syntax validation that the parser doesn't handle.
//!
//! A failed validation emits a diagnostic.

mod block;

use rowan::Direction;
use rustc_lexer::unescape::{self, unescape_byte, unescape_char, unescape_literal, Mode};

use crate::{
    algo,
    ast::{self, HasAttrs, HasVisibility},
    match_ast, AstNode, SyntaxError,
    SyntaxKind::{CONST, FN, INT_NUMBER, TYPE_ALIAS},
    SyntaxNode, SyntaxToken, TextSize, T,
};

pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> {
    // FIXME:
    // * Add unescape validation of raw string literals and raw byte string literals
    // * Add validation of doc comments are being attached to nodes

    let mut errors = Vec::new();
    for node in root.descendants() {
        match_ast! {
            match node {
                ast::Literal(it) => validate_literal(it, &mut errors),
                ast::Const(it) => validate_const(it, &mut errors),
                ast::BlockExpr(it) => block::validate_block_expr(it, &mut errors),
                ast::FieldExpr(it) => validate_numeric_name(it.name_ref(), &mut errors),
                ast::RecordExprField(it) => validate_numeric_name(it.name_ref(), &mut errors),
                ast::Visibility(it) => validate_visibility(it, &mut errors),
                ast::RangeExpr(it) => validate_range_expr(it, &mut errors),
                ast::PathSegment(it) => validate_path_keywords(it, &mut errors),
                ast::RefType(it) => validate_trait_object_ref_ty(it, &mut errors),
                ast::PtrType(it) => validate_trait_object_ptr_ty(it, &mut errors),
                ast::FnPtrType(it) => validate_trait_object_fn_ptr_ret_ty(it, &mut errors),
                ast::MacroRules(it) => validate_macro_rules(it, &mut errors),
                ast::LetExpr(it) => validate_let_expr(it, &mut errors),
                _ => (),
            }
        }
    }
    errors
}

fn rustc_unescape_error_to_string(err: unescape::EscapeError) -> &'static str {
    use unescape::EscapeError as EE;

    #[rustfmt::skip]
    let err_message = match err {
        EE::ZeroChars => {
            "Literal must not be empty"
        }
        EE::MoreThanOneChar => {
            "Literal must be one character long"
        }
        EE::LoneSlash => {
            "Character must be escaped: `\\`"
        }
        EE::InvalidEscape => {
            "Invalid escape"
        }
        EE::BareCarriageReturn | EE::BareCarriageReturnInRawString => {
            "Character must be escaped: `\r`"
        }
        EE::EscapeOnlyChar => {
            "Escape character `\\` must be escaped itself"
        }
        EE::TooShortHexEscape => {
            "ASCII hex escape code must have exactly two digits"
        }
        EE::InvalidCharInHexEscape => {
            "ASCII hex escape code must contain only hex characters"
        }
        EE::OutOfRangeHexEscape => {
            "ASCII hex escape code must be at most 0x7F"
        }
        EE::NoBraceInUnicodeEscape => {
            "Missing `{` to begin the unicode escape"
        }
        EE::InvalidCharInUnicodeEscape => {
            "Unicode escape must contain only hex characters and underscores"
        }
        EE::EmptyUnicodeEscape => {
            "Unicode escape must not be empty"
        }
        EE::UnclosedUnicodeEscape => {
            "Missing `}` to terminate the unicode escape"
        }
        EE::LeadingUnderscoreUnicodeEscape => {
            "Unicode escape code must not begin with an underscore"
        }
        EE::OverlongUnicodeEscape => {
            "Unicode escape code must have at most 6 digits"
        }
        EE::LoneSurrogateUnicodeEscape => {
            "Unicode escape code must not be a surrogate"
        }
        EE::OutOfRangeUnicodeEscape => {
            "Unicode escape code must be at most 0x10FFFF"
        }
        EE::UnicodeEscapeInByte => {
            "Byte literals must not contain unicode escapes"
        }
        EE::NonAsciiCharInByte | EE::NonAsciiCharInByteString => {
            "Byte literals must not contain non-ASCII characters"
        }
    };

    err_message
}

fn validate_literal(literal: ast::Literal, acc: &mut Vec<SyntaxError>) {
    // FIXME: move this function to outer scope (https://github.com/rust-lang/rust-analyzer/pull/2834#discussion_r366196658)
    fn unquote(text: &str, prefix_len: usize, end_delimiter: char) -> Option<&str> {
        text.rfind(end_delimiter).and_then(|end| text.get(prefix_len..end))
    }

    let token = literal.token();
    let text = token.text();

    // FIXME: lift this lambda refactor to `fn` (https://github.com/rust-lang/rust-analyzer/pull/2834#discussion_r366199205)
    let mut push_err = |prefix_len, (off, err): (usize, unescape::EscapeError)| {
        let off = token.text_range().start() + TextSize::try_from(off + prefix_len).unwrap();
        acc.push(SyntaxError::new_at_offset(rustc_unescape_error_to_string(err), off));
    };

    match literal.kind() {
        ast::LiteralKind::String(s) => {
            if !s.is_raw() {
                if let Some(without_quotes) = unquote(text, 1, '"') {
                    unescape_literal(without_quotes, Mode::Str, &mut |range, char| {
                        if let Err(err) = char {
                            push_err(1, (range.start, err));
                        }
                    });
                }
            }
        }
        ast::LiteralKind::ByteString(s) => {
            if !s.is_raw() {
                if let Some(without_quotes) = unquote(text, 2, '"') {
                    unescape_literal(without_quotes, Mode::ByteStr, &mut |range, char| {
                        if let Err(err) = char {
                            push_err(2, (range.start, err));
                        }
                    });
                }
            }
        }
        ast::LiteralKind::Char(_) => {
            if let Some(Err(e)) = unquote(text, 1, '\'').map(unescape_char) {
                push_err(1, e);
            }
        }
        ast::LiteralKind::Byte(_) => {
            if let Some(Err(e)) = unquote(text, 2, '\'').map(unescape_byte) {
                push_err(2, e);
            }
        }
        ast::LiteralKind::IntNumber(_)
        | ast::LiteralKind::FloatNumber(_)
        | ast::LiteralKind::Bool(_) => {}
    }
}

pub(crate) fn validate_block_structure(root: &SyntaxNode) {
    let mut stack = Vec::new();
    for node in root.descendants_with_tokens() {
        match node.kind() {
            T!['{'] => stack.push(node),
            T!['}'] => {
                if let Some(pair) = stack.pop() {
                    assert_eq!(
                        node.parent(),
                        pair.parent(),
                        "\nunpaired curlys:\n{}\n{:#?}\n",
                        root.text(),
                        root,
                    );
                    assert!(
                        node.next_sibling_or_token().is_none()
                            && pair.prev_sibling_or_token().is_none(),
                        "\nfloating curlys at {:?}\nfile:\n{}\nerror:\n{}\n",
                        node,
                        root.text(),
                        node,
                    );
                }
            }
            _ => (),
        }
    }
}

fn validate_numeric_name(name_ref: Option<ast::NameRef>, errors: &mut Vec<SyntaxError>) {
    if let Some(int_token) = int_token(name_ref) {
        if int_token.text().chars().any(|c| !c.is_digit(10)) {
            errors.push(SyntaxError::new(
                "Tuple (struct) field access is only allowed through \
                decimal integers with no underscores or suffix",
                int_token.text_range(),
            ));
        }
    }

    fn int_token(name_ref: Option<ast::NameRef>) -> Option<SyntaxToken> {
        name_ref?.syntax().first_child_or_token()?.into_token().filter(|it| it.kind() == INT_NUMBER)
    }
}

fn validate_visibility(vis: ast::Visibility, errors: &mut Vec<SyntaxError>) {
    let path_without_in_token = vis.in_token().is_none()
        && vis.path().and_then(|p| p.as_single_name_ref()).and_then(|n| n.ident_token()).is_some();
    if path_without_in_token {
        errors.push(SyntaxError::new("incorrect visibility restriction", vis.syntax.text_range()));
    }
    let parent = match vis.syntax().parent() {
        Some(it) => it,
        None => return,
    };
    match parent.kind() {
        FN | CONST | TYPE_ALIAS => (),
        _ => return,
    }

    let impl_def = match parent.parent().and_then(|it| it.parent()).and_then(ast::Impl::cast) {
        Some(it) => it,
        None => return,
    };
    // FIXME: disable validation if there's an attribute, since some proc macros use this syntax.
    // ideally the validation would run only on the fully expanded code, then this wouldn't be necessary.
    if impl_def.trait_().is_some() && impl_def.attrs().next().is_none() {
        errors.push(SyntaxError::new("Unnecessary visibility qualifier", vis.syntax.text_range()));
    }
}

fn validate_range_expr(expr: ast::RangeExpr, errors: &mut Vec<SyntaxError>) {
    if expr.op_kind() == Some(ast::RangeOp::Inclusive) && expr.end().is_none() {
        errors.push(SyntaxError::new(
            "An inclusive range must have an end expression",
            expr.syntax().text_range(),
        ));
    }
}

fn validate_path_keywords(segment: ast::PathSegment, errors: &mut Vec<SyntaxError>) {
    let path = segment.parent_path();
    let is_path_start = segment.coloncolon_token().is_none() && path.qualifier().is_none();

    if let Some(token) = segment.self_token() {
        if !is_path_start {
            errors.push(SyntaxError::new(
                "The `self` keyword is only allowed as the first segment of a path",
                token.text_range(),
            ));
        }
    } else if let Some(token) = segment.crate_token() {
        if !is_path_start || use_prefix(path).is_some() {
            errors.push(SyntaxError::new(
                "The `crate` keyword is only allowed as the first segment of a path",
                token.text_range(),
            ));
        }
    }

    fn use_prefix(mut path: ast::Path) -> Option<ast::Path> {
        for node in path.syntax().ancestors().skip(1) {
            match_ast! {
                match node {
                    ast::UseTree(it) => if let Some(tree_path) = it.path() {
                        // Even a top-level path exists within a `UseTree` so we must explicitly
                        // allow our path but disallow anything else
                        if tree_path != path {
                            return Some(tree_path);
                        }
                    },
                    ast::UseTreeList(_) => continue,
                    ast::Path(parent) => path = parent,
                    _ => return None,
                }
            };
        }
        None
    }
}

fn validate_trait_object_ref_ty(ty: ast::RefType, errors: &mut Vec<SyntaxError>) {
    if let Some(ast::Type::DynTraitType(ty)) = ty.ty() {
        if let Some(err) = validate_trait_object_ty(ty) {
            errors.push(err);
        }
    }
}

fn validate_trait_object_ptr_ty(ty: ast::PtrType, errors: &mut Vec<SyntaxError>) {
    if let Some(ast::Type::DynTraitType(ty)) = ty.ty() {
        if let Some(err) = validate_trait_object_ty(ty) {
            errors.push(err);
        }
    }
}

fn validate_trait_object_fn_ptr_ret_ty(ty: ast::FnPtrType, errors: &mut Vec<SyntaxError>) {
    if let Some(ast::Type::DynTraitType(ty)) = ty.ret_type().and_then(|ty| ty.ty()) {
        if let Some(err) = validate_trait_object_ty(ty) {
            errors.push(err);
        }
    }
}

fn validate_trait_object_ty(ty: ast::DynTraitType) -> Option<SyntaxError> {
    let tbl = ty.type_bound_list()?;

    if tbl.bounds().count() > 1 {
        let dyn_token = ty.dyn_token()?;
        let potential_parenthesis =
            algo::skip_trivia_token(dyn_token.prev_token()?, Direction::Prev)?;
        let kind = potential_parenthesis.kind();
        if !matches!(kind, T!['('] | T![<] | T![=]) {
            return Some(SyntaxError::new("ambiguous `+` in a type", ty.syntax().text_range()));
        }
    }
    None
}

fn validate_macro_rules(mac: ast::MacroRules, errors: &mut Vec<SyntaxError>) {
    if let Some(vis) = mac.visibility() {
        errors.push(SyntaxError::new(
            "visibilities are not allowed on `macro_rules!` items",
            vis.syntax().text_range(),
        ));
    }
}

fn validate_const(const_: ast::Const, errors: &mut Vec<SyntaxError>) {
    if let Some(mut_token) = const_
        .const_token()
        .and_then(|t| t.next_token())
        .and_then(|t| algo::skip_trivia_token(t, Direction::Next))
        .filter(|t| t.kind() == T![mut])
    {
        errors.push(SyntaxError::new("const globals cannot be mutable", mut_token.text_range()));
    }
}

fn validate_let_expr(let_: ast::LetExpr, errors: &mut Vec<SyntaxError>) {
    let mut token = let_.syntax().clone();
    loop {
        token = match token.parent() {
            Some(it) => it,
            None => break,
        };

        if ast::ParenExpr::can_cast(token.kind()) {
            continue;
        } else if let Some(it) = ast::BinExpr::cast(token.clone()) {
            if it.op_kind() == Some(ast::BinaryOp::LogicOp(ast::LogicOp::And)) {
                continue;
            }
        } else if ast::IfExpr::can_cast(token.kind())
            || ast::WhileExpr::can_cast(token.kind())
            || ast::MatchGuard::can_cast(token.kind())
        {
            // It must be part of the condition since the expressions are inside a block.
            return;
        }

        break;
    }
    errors.push(SyntaxError::new(
        "`let` expressions are not supported here",
        let_.syntax().text_range(),
    ));
}
<|endoftext|>


<commit_before>#[macro_use]
mod common;

use common::util::*;

static UTIL_NAME: &'static str = "base64";

#[test]
fn test_encode() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let input = "hello, world!";
    let result = ucmd.run_piped_stdin(input.as_bytes());

    assert_empty_stderr!(result);
    assert!(result.success);
    assert_eq!(result.stdout, "aGVsbG8sIHdvcmxkIQ==\n");
}

#[test]
fn test_decode() {
    for decode_param in vec!["-d", "--decode"] {
        let (_, mut ucmd) = testing(UTIL_NAME);
        let input = "aGVsbG8sIHdvcmxkIQ==";
        let result = ucmd.arg(decode_param).run_piped_stdin(input.as_bytes());

        assert_empty_stderr!(result);
        assert!(result.success);
        assert_eq!(result.stdout, "hello, world!");
    }
}

#[test]
fn test_garbage() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let input = "aGVsbG8sIHdvcmxkIQ==\0";
    let result = ucmd.arg("-d").run_piped_stdin(input.as_bytes());

    assert!(!result.success);
    assert_eq!(result.stderr,
               "base64: error: invalid character (Invalid character '0' at position 20)\n");
}

#[test]
fn test_ignore_garbage() {
    for ignore_garbage_param in vec!["-i", "--ignore-garbage"] {
        let (_, mut ucmd) = testing(UTIL_NAME);
        let input = "aGVsbG8sIHdvcmxkIQ==\0";
        let result = ucmd.arg("-d").arg(ignore_garbage_param).run_piped_stdin(input.as_bytes());
        assert_empty_stderr!(result);
        assert!(result.success);
        assert_eq!(result.stdout, "hello, world!");
    }
}

#[test]
fn test_wrap() {
    for wrap_param in vec!["-w", "--wrap"] {
        let (_, mut ucmd) = testing(UTIL_NAME);
        let input = "The quick brown fox jumps over the lazy dog.";
        let result = ucmd.arg(wrap_param).arg("20").run_piped_stdin(input.as_bytes());

        assert_empty_stderr!(result);
        assert!(result.success);
        assert_eq!(result.stdout,
                   "VGhlIHF1aWNrIGJyb3du\nIGZveCBqdW1wcyBvdmVy\nIHRoZSBsYXp5IGRvZy4=\n");
    }
}
<commit_msg>base64: tests for incorrect wrap args<commit_after>#[macro_use]
mod common;

use common::util::*;

static UTIL_NAME: &'static str = "base64";

#[test]
fn test_encode() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let input = "hello, world!";
    let result = ucmd.run_piped_stdin(input.as_bytes());

    assert_empty_stderr!(result);
    assert!(result.success);
    assert_eq!(result.stdout, "aGVsbG8sIHdvcmxkIQ==\n");
}

#[test]
fn test_decode() {
    for decode_param in vec!["-d", "--decode"] {
        let (_, mut ucmd) = testing(UTIL_NAME);
        let input = "aGVsbG8sIHdvcmxkIQ==";
        let result = ucmd.arg(decode_param).run_piped_stdin(input.as_bytes());

        assert_empty_stderr!(result);
        assert!(result.success);
        assert_eq!(result.stdout, "hello, world!");
    }
}

#[test]
fn test_garbage() {
    let (_, mut ucmd) = testing(UTIL_NAME);
    let input = "aGVsbG8sIHdvcmxkIQ==\0";
    let result = ucmd.arg("-d").run_piped_stdin(input.as_bytes());

    assert!(!result.success);
    assert_eq!(result.stderr,
               "base64: error: invalid character (Invalid character '0' at position 20)\n");
}

#[test]
fn test_ignore_garbage() {
    for ignore_garbage_param in vec!["-i", "--ignore-garbage"] {
        let (_, mut ucmd) = testing(UTIL_NAME);
        let input = "aGVsbG8sIHdvcmxkIQ==\0";
        let result = ucmd.arg("-d").arg(ignore_garbage_param).run_piped_stdin(input.as_bytes());
        assert_empty_stderr!(result);
        assert!(result.success);
        assert_eq!(result.stdout, "hello, world!");
    }
}

#[test]
fn test_wrap() {
    for wrap_param in vec!["-w", "--wrap"] {
        let (_, mut ucmd) = testing(UTIL_NAME);
        let input = "The quick brown fox jumps over the lazy dog.";
        let result = ucmd.arg(wrap_param).arg("20").run_piped_stdin(input.as_bytes());

        assert_empty_stderr!(result);
        assert!(result.success);
        assert_eq!(result.stdout,
                   "VGhlIHF1aWNrIGJyb3du\nIGZveCBqdW1wcyBvdmVy\nIHRoZSBsYXp5IGRvZy4=\n");
    }
}

#[test]
fn test_wrap_no_arg() {
    for wrap_param in vec!["-w", "--wrap"] {
        let (_, mut ucmd) = testing(UTIL_NAME);
        let result = ucmd.arg(wrap_param).run();

        assert!(!result.success);
        assert!(result.stdout.len() == 0);
        assert_eq!(result.stderr.trim_right(),
                   format!("base64: error: Argument to option '{}' missing.",
                           if wrap_param == "-w" { "w" } else { "wrap" }));
    }
}

#[test]
fn test_wrap_bad_arg() {
    for wrap_param in vec!["-w", "--wrap"] {
        let (_, mut ucmd) = testing(UTIL_NAME);
        let result = ucmd.arg(wrap_param).arg("b").run();

        assert!(!result.success);
        assert!(result.stdout.len() == 0);
        assert_eq!(result.stderr.trim_right(),
                   "base64: error: Argument to option 'wrap' improperly formatted: invalid digit found in string");
    }
}
<|endoftext|>


<commit_before>extern crate staticdir;
extern crate iron;
extern crate mount;
extern crate rustc_serialize;
extern crate staticfile;
extern crate iron_test;

#[derive(RustcDecodable, RustcEncodable)]
pub struct DirEntryState {
    pub file_type: FileType,
    pub file_name: String,
    pub size: u64,
    pub creation_time: Option<u64>,
    pub last_modification_time: u64,
    pub last_access_time: u64,
}

#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug)]
pub enum FileType {
    File, Dir, Symlink
}

mod file {

    use iron::prelude::*;
    use iron::headers::{ Headers, ContentType };
    use iron::mime::{ Mime, TopLevel, SubLevel };
    use iron::status;
    use iron_test::{ request, ProjectBuilder, response };
    use mount::Mount;
    use rustc_serialize::json;
    use std::ops::Deref;
    use staticdir::{ StaticDir, AsJson };
    use super::*;

    #[test]
    fn response_should_be_200() {
        let p = ProjectBuilder::new("example").file("file1.html", "this is file1");
        p.build();

        let static_dir = StaticDir::new(p.root(), AsJson);
        let res = request::get("http://localhost:3000/", Headers::new(), &static_dir).unwrap();
        assert_eq!(res.status.unwrap(), status::Ok);
    }

    #[test]
    fn response_should_be_json() {
        let p = ProjectBuilder::new("example").file("file1.html", "this is file1");
        p.build();

        let static_dir = StaticDir::new(p.root(), AsJson);
        let res = request::get("http://localhost:3000/", Headers::new(), &static_dir).unwrap();
        let &Mime(ref top, ref sub, _) = res.headers.get::<ContentType>().unwrap().deref();
        assert_eq!((top, sub), (&TopLevel::Application, &SubLevel::Json));
    }

    #[test]
    fn response_should_contain_file_entry() {
        let p = ProjectBuilder::new("example").file("file1.html", "this is file1");
        p.build();

        let static_dir = StaticDir::new(p.root(), AsJson);
        let res = request::get("http://localhost:3000/", Headers::new(), &static_dir).unwrap();
        let body = response::extract_body_to_string(res);
        let entries: Vec<DirEntryState> = json::decode(&body).unwrap();
        let ref entry = entries[0];
        assert_eq!(entry.file_name, "file1.html");
        assert_eq!(entry.file_type, FileType::File);
        assert!(entry.size > 0);
        assert!(entry.last_modification_time > 0);
        assert!(entry.last_access_time > 0);
    }

    #[test]
    fn should_work_with_mount() {
        let p = ProjectBuilder::new("example").file("file1.html", "this is file1");
        p.build();

        let static_dir = StaticDir::new(p.root(), AsJson);
        let mut mount = Mount::new();
        mount.mount("/mnt/", static_dir);
        let res = request::get("http://localhost:3000/mnt", Headers::new(), &mount).unwrap();
        assert_eq!(res.status.unwrap(), status::Ok);
    }
}

mod dir {
    use iron::prelude::*;
    use iron::headers::{ Headers, ContentType };
    use iron::mime::{ Mime, TopLevel, SubLevel };
    use iron::status;
    use iron_test::{ request, ProjectBuilder, response };
    use std::ops::Deref;
    use staticdir::{ StaticDir, AsJson };
    use staticfile::Static;

    #[test]
    fn should_work_with_trailing_slash() {
        let p = ProjectBuilder::new("example").file("dir/file1.html", "this is file1");
        p.build();

        let static_dir = StaticDir::new(p.root(), AsJson);
        let res = request::get("http://localhost:3000/dir/", Headers::new(), &static_dir).unwrap();
        assert_eq!(res.status.unwrap(), status::Ok);
    }

    #[test]
    fn should_work_without_trailing_slash() {
        let p = ProjectBuilder::new("example").file("dir/file1.html", "this is file1");
        p.build();

        let static_dir = StaticDir::new(p.root(), AsJson);
        let res = request::get("http://localhost:3000/dir", Headers::new(), &static_dir).unwrap();
        assert_eq!(res.status.unwrap(), status::Ok);
    }

    #[test]
    fn should_work_with_static_file() {
        let p = ProjectBuilder::new("example")
            .file("dir/file1.html", "this is file");
        p.build();

        let handle_statics = {
            let root = p.root();
            let mut chain = Chain::new(Static::new(root));
            chain.link_after(StaticDir::new(root, AsJson));
            chain
        };

        let res = request::get("http://localhost:3000/dir/file1.html", Headers::new(), &handle_statics).unwrap();
        assert_eq!(res.status.unwrap(), status::Ok);
        assert_eq!(response::extract_body_to_string(res), "this is file");

        let res = request::get("http://localhost:3000/dir/", Headers::new(), &handle_statics).unwrap();
        assert_eq!(res.status.unwrap(), status::Ok);
        let &Mime(ref top, ref sub, _) = res.headers.get::<ContentType>().unwrap().deref();
        assert_eq!((top, sub), (&TopLevel::Application, &SubLevel::Json));
    }
}
<commit_msg>added tests for integration with ResponseStrategy<commit_after>extern crate staticdir;
extern crate iron;
extern crate mount;
extern crate rustc_serialize;
extern crate staticfile;
extern crate iron_test;

#[derive(RustcDecodable, RustcEncodable)]
pub struct DirEntryState {
    pub file_type: FileType,
    pub file_name: String,
    pub size: u64,
    pub creation_time: Option<u64>,
    pub last_modification_time: u64,
    pub last_access_time: u64,
}

#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug)]
pub enum FileType {
    File, Dir, Symlink
}

mod file {

    use iron::prelude::*;
    use iron::headers::{ Headers, ContentType };
    use iron::mime::{ Mime, TopLevel, SubLevel };
    use iron::status;
    use iron_test::{ request, ProjectBuilder, response };
    use mount::Mount;
    use rustc_serialize::json;
    use std::ops::Deref;
    use std::fs::ReadDir;
    use staticdir::*;
    use super::*;

    #[test]
    fn response_should_be_200() {
        let p = ProjectBuilder::new("example").file("file1.html", "this is file1");
        p.build();

        let static_dir = StaticDir::new(p.root(), AsJson);
        let res = request::get("http://localhost:3000/", Headers::new(), &static_dir).unwrap();
        assert_eq!(res.status.unwrap(), status::Ok);
    }

    #[test]
    fn response_should_be_json() {
        let p = ProjectBuilder::new("example").file("file1.html", "this is file1");
        p.build();

        let static_dir = StaticDir::new(p.root(), AsJson);
        let res = request::get("http://localhost:3000/", Headers::new(), &static_dir).unwrap();
        let &Mime(ref top, ref sub, _) = res.headers.get::<ContentType>().unwrap().deref();
        assert_eq!((top, sub), (&TopLevel::Application, &SubLevel::Json));
    }

    #[test]
    fn response_should_contain_file_entry() {
        let p = ProjectBuilder::new("example").file("file1.html", "this is file1");
        p.build();

        let static_dir = StaticDir::new(p.root(), AsJson);
        let res = request::get("http://localhost:3000/", Headers::new(), &static_dir).unwrap();
        let body = response::extract_body_to_string(res);
        let entries: Vec<DirEntryState> = json::decode(&body).unwrap();
        let ref entry = entries[0];
        assert_eq!(entry.file_name, "file1.html");
        assert_eq!(entry.file_type, FileType::File);
        assert!(entry.size > 0);
        assert!(entry.last_modification_time > 0);
        assert!(entry.last_access_time > 0);
    }

    #[test]
    fn should_work_with_mount() {
        let p = ProjectBuilder::new("example").file("file1.html", "this is file1");
        p.build();

        let static_dir = StaticDir::new(p.root(), AsJson);
        let mut mount = Mount::new();
        mount.mount("/mnt/", static_dir);
        let res = request::get("http://localhost:3000/mnt", Headers::new(), &mount).unwrap();
        assert_eq!(res.status.unwrap(), status::Ok);
    }

    #[test]
    fn should_support_custom_strategies() {
        struct AsHtml;

        fn build_html(dir: ReadDir) -> String {
            let mut html = String::new();
            for entry in dir {
                let entry = entry.unwrap();
                html = format!("{}
{}
", html, entry.file_name().into_string().unwrap());
            }
            format!("
{}
", html)
        }

        impl ResponseStrategy for AsHtml {
            fn make_response(&self, dir: ReadDir) -> IronResult<Response> {
                let html = build_html(dir);
                let content_type = "text/html; charset=utf-8".parse::<Mime>().unwrap();
                Ok(Response::with((status::Ok, html, content_type)))
            }
        }

        let p = ProjectBuilder::new("example")
            .file("file1.html", "this is file1")
            .file("file2.html", "this is file2");
        p.build();

        let static_dir = StaticDir::new(p.root(), AsHtml);
        let res = request::get("http://localhost:3000/", Headers::new(), &static_dir).unwrap();
        let body = response::extract_body_to_string(res);
        assert_eq!(body, "

file2.html

file1.html

");
    }
}

mod dir {
    use iron::prelude::*;
    use iron::headers::{ Headers, ContentType };
    use iron::mime::{ Mime, TopLevel, SubLevel };
    use iron::status;
    use iron_test::{ request, ProjectBuilder, response };
    use std::ops::Deref;
    use staticdir::{ StaticDir, AsJson };
    use staticfile::Static;

    #[test]
    fn should_work_with_trailing_slash() {
        let p = ProjectBuilder::new("example").file("dir/file1.html", "this is file1");
        p.build();

        let static_dir = StaticDir::new(p.root(), AsJson);
        let res = request::get("http://localhost:3000/dir/", Headers::new(), &static_dir).unwrap();
        assert_eq!(res.status.unwrap(), status::Ok);
    }

    #[test]
    fn should_work_without_trailing_slash() {
        let p = ProjectBuilder::new("example").file("dir/file1.html", "this is file1");
        p.build();

        let static_dir = StaticDir::new(p.root(), AsJson);
        let res = request::get("http://localhost:3000/dir", Headers::new(), &static_dir).unwrap();
        assert_eq!(res.status.unwrap(), status::Ok);
    }

    #[test]
    fn should_work_with_static_file() {
        let p = ProjectBuilder::new("example")
            .file("dir/file1.html", "this is file");
        p.build();

        let handle_statics = {
            let root = p.root();
            let mut chain = Chain::new(Static::new(root));
            chain.link_after(StaticDir::new(root, AsJson));
            chain
        };

        let res = request::get("http://localhost:3000/dir/file1.html", Headers::new(), &handle_statics).unwrap();
        assert_eq!(res.status.unwrap(), status::Ok);
        assert_eq!(response::extract_body_to_string(res), "this is file");

        let res = request::get("http://localhost:3000/dir/", Headers::new(), &handle_statics).unwrap();
        assert_eq!(res.status.unwrap(), status::Ok);
        let &Mime(ref top, ref sub, _) = res.headers.get::<ContentType>().unwrap().deref();
        assert_eq!((top, sub), (&TopLevel::Application, &SubLevel::Json));
    }
}
<|endoftext|>