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

use std::str;
use std::str::FromStr;
use std::time::Instant;

use reqwest::blocking::Client as HttpClient;
use reqwest::blocking::RequestBuilder;
use reqwest::header::HeaderValue;
use reqwest::StatusCode;
use serde::de::DeserializeOwned;
use serde::Serialize;

use crate::errors::{Result, ResultExt};
use crate::{Config, QueryMeta, QueryOptions, WriteMeta, WriteOptions};

fn add_config_options(builder: RequestBuilder, config: &Config) -> RequestBuilder {
    match &config.token {
        Some(val) => builder.header("X-Consul-Token", val),
        None => builder,
    }
}

pub fn get_vec<R: DeserializeOwned>(
    path: &str,
    config: &Config,
    mut params: HashMap<String, String>,
    options: Option<&QueryOptions>,
) -> Result<(Vec<R>, QueryMeta)> {
    let datacenter: Option<&String> = options
        .and_then(|o| o.datacenter.as_ref())
        .or_else(|| config.datacenter.as_ref());

    if let Some(dc) = datacenter {
        params.insert(String::from("dc"), dc.to_owned());
    }
    if let Some(options) = options {
        if let Some(index) = options.wait_index {
            params.insert(String::from("index"), index.to_string());
        }
        if let Some(wait_time) = options.wait_time {
            params.insert(String::from("wait"), format!("{}s", wait_time.as_secs()));
        }
    }

    let url_str = format!("{}{}", config.address, path);
    let url =
        Url::parse_with_params(&url_str, params.iter()).chain_err(|| "Failed to parse URL")?;
    let start = Instant::now();
    let request_builder = add_config_options(config.http_client.get(url), &config);
    let response = request_builder.send();
    response
        .chain_err(|| "HTTP request to consul failed")
        .and_then(|r| {
            let x: Option<Result<u64>> = r
                .headers()
                .get("X-Consul-Index")
                .and_then(|value: &HeaderValue| Some(value.as_bytes()))
                .map(|bytes| {
                    str::from_utf8(bytes)
                        .chain_err(|| "Failed to parse valid UT8 for last index")
                        .and_then(|s| {
                            u64::from_str(s)
                                .chain_err(|| "Failed to parse valid number for last index")
                        })
                });
            let j = if r.status() != StatusCode::NOT_FOUND {
                r.json().chain_err(|| "Failed to parse JSON response")?
            } else {
                Vec::new()
            };
            match x {
                Some(r) => Ok((j, Some(r?))),
                None => Ok((j, None)),
            }
        })
        .map(|x: (Vec<R>, Option<u64>)| {
            (
                x.0,
                QueryMeta {
                    last_index: x.1,
                    request_time: Instant::now() - start,
                },
            )
        })
}

pub fn get<R: DeserializeOwned>(
    path: &str,
    config: &Config,
    mut params: HashMap<String, String>,
    options: Option<&QueryOptions>,
) -> Result<(R, QueryMeta)> {
    let datacenter: Option<&String> = options
        .and_then(|o| o.datacenter.as_ref())
        .or_else(|| config.datacenter.as_ref());

    if let Some(dc) = datacenter {
        params.insert(String::from("dc"), dc.to_owned());
    }
    if let Some(options) = options {
        if let Some(index) = options.wait_index {
            params.insert(String::from("index"), index.to_string());
        }
        if let Some(wait_time) = options.wait_time {
            params.insert(String::from("wait"), format!("{}s", wait_time.as_secs()));
        }
    }

    let url_str = format!("{}{}", config.address, path);
    let url =
        Url::parse_with_params(&url_str, params.iter()).chain_err(|| "Failed to parse URL")?;
    let start = Instant::now();
    let request_builder = add_config_options(config.http_client.get(url), &config);
    let response = request_builder.send();
    response
        .chain_err(|| "HTTP request to consul failed")
        .and_then(|r| {
            let x: Option<Result<u64>> =
                r.headers()
                    .get("X-Consul-Index")
                    .map(|bytes: &HeaderValue| -> Result<u64> {
                        bytes
                            .to_str()
                            .chain_err(|| "Failed to parse valid UT8 for last index")
                            .and_then(|s: &str| -> Result<u64> {
                                u64::from_str(s)
                                    .chain_err(|| "Failed to parse valid number for last index")
                            })
                    });
            let j = r.json().chain_err(|| "Failed to parse JSON response")?;
            match x {
                Some(r) => Ok((j, Some(r?))),
                None => Ok((j, None)),
            }
        })
        .map(|x: (R, Option<u64>)| {
            (
                x.0,
                QueryMeta {
                    last_index: x.1,
                    request_time: Instant::now() - start,
                },
            )
        })
}

pub fn delete<R: DeserializeOwned>(
    path: &str,
    config: &Config,
    params: HashMap<String, String>,
    options: Option<&WriteOptions>,
) -> Result<(R, WriteMeta)> {
    let req = |http_client: &HttpClient, url: Url| -> RequestBuilder { http_client.delete(url) };
    write_with_body(path, None as Option<&()>, config, params, options, req)
}

/*
pub fn post<T: Serialize, R: DeserializeOwned>(path: &str,
                                               body: Option<&T>,
                                               config: &Config,
                                               options: Option<&WriteOptions>)
                                               -> Result<(R, WriteMeta)> {
    let req = |http_client: &HttpClient, url: Url| -> RequestBuilder { http_client.post(url) };
    write_with_body(path, body, config, options, req)
}
*/
pub fn put<T: Serialize, R: DeserializeOwned>(
    path: &str,
    body: Option<&T>,
    config: &Config,
    params: HashMap<String, String>,
    options: Option<&WriteOptions>,
) -> Result<(R, WriteMeta)> {
    let req = |http_client: &HttpClient, url: Url| -> RequestBuilder { http_client.put(url) };
    write_with_body(path, body, config, params, options, req)
}

fn write_with_body<T: Serialize, R: DeserializeOwned, F>(
    path: &str,
    body: Option<&T>,
    config: &Config,
    mut params: HashMap<String, String>,
    options: Option<&WriteOptions>,
    req: F,
) -> Result<(R, WriteMeta)>
where
    F: Fn(&HttpClient, Url) -> RequestBuilder,
{
    let start = Instant::now();
    let datacenter: Option<&String> = options
        .and_then(|o| o.datacenter.as_ref())
        .or_else(|| config.datacenter.as_ref());

    if let Some(dc) = datacenter {
        params.insert(String::from("dc"), dc.to_owned());
    }

    let url_str = format!("{}{}", config.address, path);
    let url =
        Url::parse_with_params(&url_str, params.iter()).chain_err(|| "Failed to parse URL")?;
    let builder = req(&config.http_client, url);
    let builder = if let Some(b) = body {
        builder.json(b)
    } else {
        builder
    };
    let builder = add_config_options(builder, &config);
    builder
        .send()
        .chain_err(|| "HTTP request to consul failed")
        .and_then(|x| x.json().chain_err(|| "Failed to parse JSON"))
        .map(|x| {
            (
                x,
                WriteMeta {
                    request_time: Instant::now() - start,
                },
            )
        })
}
<commit_msg>Simplifying and_then with inner Some() https://rust-lang.github.io/rust-clippy/master/index.html#option_and_then_some<commit_after>use std::collections::HashMap;
use url::Url;

use std::str;
use std::str::FromStr;
use std::time::Instant;

use reqwest::blocking::Client as HttpClient;
use reqwest::blocking::RequestBuilder;
use reqwest::header::HeaderValue;
use reqwest::StatusCode;
use serde::de::DeserializeOwned;
use serde::Serialize;

use crate::errors::{Result, ResultExt};
use crate::{Config, QueryMeta, QueryOptions, WriteMeta, WriteOptions};

fn add_config_options(builder: RequestBuilder, config: &Config) -> RequestBuilder {
    match &config.token {
        Some(val) => builder.header("X-Consul-Token", val),
        None => builder,
    }
}

pub fn get_vec<R: DeserializeOwned>(
    path: &str,
    config: &Config,
    mut params: HashMap<String, String>,
    options: Option<&QueryOptions>,
) -> Result<(Vec<R>, QueryMeta)> {
    let datacenter: Option<&String> = options
        .and_then(|o| o.datacenter.as_ref())
        .or_else(|| config.datacenter.as_ref());

    if let Some(dc) = datacenter {
        params.insert(String::from("dc"), dc.to_owned());
    }
    if let Some(options) = options {
        if let Some(index) = options.wait_index {
            params.insert(String::from("index"), index.to_string());
        }
        if let Some(wait_time) = options.wait_time {
            params.insert(String::from("wait"), format!("{}s", wait_time.as_secs()));
        }
    }

    let url_str = format!("{}{}", config.address, path);
    let url =
        Url::parse_with_params(&url_str, params.iter()).chain_err(|| "Failed to parse URL")?;
    let start = Instant::now();
    let request_builder = add_config_options(config.http_client.get(url), &config);
    let response = request_builder.send();
    response
        .chain_err(|| "HTTP request to consul failed")
        .and_then(|r| {
            let x: Option<Result<u64>> = r
                .headers()
                .get("X-Consul-Index")
                .map(|value: &HeaderValue| value.as_bytes())
                .map(|bytes| {
                    str::from_utf8(bytes)
                        .chain_err(|| "Failed to parse valid UT8 for last index")
                        .and_then(|s| {
                            u64::from_str(s)
                                .chain_err(|| "Failed to parse valid number for last index")
                        })
                });
            let j = if r.status() != StatusCode::NOT_FOUND {
                r.json().chain_err(|| "Failed to parse JSON response")?
            } else {
                Vec::new()
            };
            match x {
                Some(r) => Ok((j, Some(r?))),
                None => Ok((j, None)),
            }
        })
        .map(|x: (Vec<R>, Option<u64>)| {
            (
                x.0,
                QueryMeta {
                    last_index: x.1,
                    request_time: Instant::now() - start,
                },
            )
        })
}

pub fn get<R: DeserializeOwned>(
    path: &str,
    config: &Config,
    mut params: HashMap<String, String>,
    options: Option<&QueryOptions>,
) -> Result<(R, QueryMeta)> {
    let datacenter: Option<&String> = options
        .and_then(|o| o.datacenter.as_ref())
        .or_else(|| config.datacenter.as_ref());

    if let Some(dc) = datacenter {
        params.insert(String::from("dc"), dc.to_owned());
    }
    if let Some(options) = options {
        if let Some(index) = options.wait_index {
            params.insert(String::from("index"), index.to_string());
        }
        if let Some(wait_time) = options.wait_time {
            params.insert(String::from("wait"), format!("{}s", wait_time.as_secs()));
        }
    }

    let url_str = format!("{}{}", config.address, path);
    let url =
        Url::parse_with_params(&url_str, params.iter()).chain_err(|| "Failed to parse URL")?;
    let start = Instant::now();
    let request_builder = add_config_options(config.http_client.get(url), &config);
    let response = request_builder.send();
    response
        .chain_err(|| "HTTP request to consul failed")
        .and_then(|r| {
            let x: Option<Result<u64>> =
                r.headers()
                    .get("X-Consul-Index")
                    .map(|bytes: &HeaderValue| -> Result<u64> {
                        bytes
                            .to_str()
                            .chain_err(|| "Failed to parse valid UT8 for last index")
                            .and_then(|s: &str| -> Result<u64> {
                                u64::from_str(s)
                                    .chain_err(|| "Failed to parse valid number for last index")
                            })
                    });
            let j = r.json().chain_err(|| "Failed to parse JSON response")?;
            match x {
                Some(r) => Ok((j, Some(r?))),
                None => Ok((j, None)),
            }
        })
        .map(|x: (R, Option<u64>)| {
            (
                x.0,
                QueryMeta {
                    last_index: x.1,
                    request_time: Instant::now() - start,
                },
            )
        })
}

pub fn delete<R: DeserializeOwned>(
    path: &str,
    config: &Config,
    params: HashMap<String, String>,
    options: Option<&WriteOptions>,
) -> Result<(R, WriteMeta)> {
    let req = |http_client: &HttpClient, url: Url| -> RequestBuilder { http_client.delete(url) };
    write_with_body(path, None as Option<&()>, config, params, options, req)
}

/*
pub fn post<T: Serialize, R: DeserializeOwned>(path: &str,
                                               body: Option<&T>,
                                               config: &Config,
                                               options: Option<&WriteOptions>)
                                               -> Result<(R, WriteMeta)> {
    let req = |http_client: &HttpClient, url: Url| -> RequestBuilder { http_client.post(url) };
    write_with_body(path, body, config, options, req)
}
*/
pub fn put<T: Serialize, R: DeserializeOwned>(
    path: &str,
    body: Option<&T>,
    config: &Config,
    params: HashMap<String, String>,
    options: Option<&WriteOptions>,
) -> Result<(R, WriteMeta)> {
    let req = |http_client: &HttpClient, url: Url| -> RequestBuilder { http_client.put(url) };
    write_with_body(path, body, config, params, options, req)
}

fn write_with_body<T: Serialize, R: DeserializeOwned, F>(
    path: &str,
    body: Option<&T>,
    config: &Config,
    mut params: HashMap<String, String>,
    options: Option<&WriteOptions>,
    req: F,
) -> Result<(R, WriteMeta)>
where
    F: Fn(&HttpClient, Url) -> RequestBuilder,
{
    let start = Instant::now();
    let datacenter: Option<&String> = options
        .and_then(|o| o.datacenter.as_ref())
        .or_else(|| config.datacenter.as_ref());

    if let Some(dc) = datacenter {
        params.insert(String::from("dc"), dc.to_owned());
    }

    let url_str = format!("{}{}", config.address, path);
    let url =
        Url::parse_with_params(&url_str, params.iter()).chain_err(|| "Failed to parse URL")?;
    let builder = req(&config.http_client, url);
    let builder = if let Some(b) = body {
        builder.json(b)
    } else {
        builder
    };
    let builder = add_config_options(builder, &config);
    builder
        .send()
        .chain_err(|| "HTTP request to consul failed")
        .and_then(|x| x.json().chain_err(|| "Failed to parse JSON"))
        .map(|x| {
            (
                x,
                WriteMeta {
                    request_time: Instant::now() - start,
                },
            )
        })
}
<|endoftext|>


<commit_before>pub mod builtins;

use std::collections::HashMap;
use std::path::PathBuf;
use std::io;
use std::io::{Read, Write};
use std::collections::hash_map::Entry;

#[derive(PartialEq, Debug, Clone)]
pub struct State {
    cwd: PathBuf,
    environment: HashMap<String, String>,
    aliases: HashMap<String, String>,
    argv: Vec<String>,
    argc: usize,
}

impl State {
    pub fn new(cwd: String) -> State {
        State {
            cwd: PathBuf::from(cwd),
            environment: HashMap::new(),
            aliases: HashMap::new(),
            argv: Vec::new(),
            argc: 0,
        }
    }
}

pub fn run(initial_state: State) {
    let mut builtins = builtins::load();
    let mut s = initial_state.clone();

    println!("Welcome to rsh! {:?}", s);

    loop {

        print!("\n");
        print!("{} -> ", s.cwd.to_str().unwrap());

        // this forces the prompt to print
        io::stdout().flush();

        // read the user input
        let mut input = String::new();
        io::stdin().read_line(&mut input).unwrap();

        s.argv = parse_args(&input);
        s.argc = s.argv.len();

        print!("\n");
        println!("Input: {}\nState: {:?}", input, s);

        let first_arg = s.argv.get(0).unwrap().clone();
        if let Entry::Occupied(f) = builtins.entry(String::from(first_arg)) {
            let bn = f.get();
            s = bn(s.clone());
        }
    }
}

#[derive(PartialEq)]
enum BuildType {
    None,
    Single,
    Double,
}

struct ParseResult {
    result: Vec<String>,
    completed: bool,
    build_string: String,
    build_type: BuildType,
}

fn parse_args(args: &String) -> Vec<String> {
    let mut parse_result: ParseResult = ParseResult {
        result: Vec::new(),
        completed: false,
        build_string: String::from(""),
        build_type: BuildType::None,
    };

    if args.len() == 0 {
        return parse_result.result;
    }

    parse_string_into_vec(&args, &mut parse_result);

    let result: Vec<String> = parse_result.result
        .into_iter()
        .filter(|s| s.len() > 0)
        .collect();

    result
}

fn parse_string_into_vec(string: &String, parse_result: &mut ParseResult) {
    let &mut ParseResult { ref mut build_string,
                           ref mut build_type,
                           ref mut completed,
                           ref mut result } = parse_result;
    let mut iter = string.chars().peekable();

    while let Some(c) = iter.next() {
        match c {
            '\'' => {
                match *build_type {
                    BuildType::Single => {
                        *build_type = BuildType::None;
                        if iter.peek() == Some(&' ') {
                            result.push(build_string.clone());
                            *build_string = String::from("");
                        }
                    }

                    BuildType::None => {
                        *build_type = BuildType::Single;
                    }

                    _ => {
                        build_string.push(c);
                    }
                }
            }

            '\"' => {
                match *build_type {
                    BuildType::Double => {
                        *build_type = BuildType::None;
                        if iter.peek() == Some(&' ') {
                            result.push(build_string.clone());
                            *build_string = String::from("");
                        }
                    }

                    BuildType::None => {
                        *build_type = BuildType::Double;
                    }

                    _ => {
                        build_string.push(c);
                    }
                }
            }

            ' ' => {
                match *build_type {
                    BuildType::None => {
                        result.push(build_string.clone());
                        *build_string = String::from("");
                    }

                    _ => {
                        build_string.push(c);
                    }
                }
            }

            _ => {
                build_string.push(c);
            }
        }
    }

    if *build_type == BuildType::None {
        result.push(build_string.clone());
        *completed = true;
    }
}

#[test]
fn parse_args_test() {
    // parse empty string
    {
        let expected: Vec<String> = Vec::new();
        let result = parse_args(&String::from(""));
        assert_eq!(result, expected);
    }

    // parse single-word string
    {
        let expected: Vec<String> = vec!["echo".to_string()];
        let result = parse_args(&String::from("echo"));
        assert_eq!(result, expected);
    }

    // parse single-word string inside quotes
    {
        let expected = vec!["echo".to_string()];
        let result = parse_args(&String::from("\"echo\""));
        assert_eq!(result, expected);
    }

    // parse multi-word string with closed quotes section
    {
        let expected = vec!["echo".to_string(), "-n".into(), "Hello Dear World".into()];
        let result = parse_args(&String::from("echo -n \"Hello Dear World\""));
        assert_eq!(result, expected);
    }

    // parse multi-word string with multiple closed quotes sections
    {
        let expected = vec!["echo".to_string(), "Hello".into(), "Dear World".into()];
        let result = parse_args(&String::from("echo \"Hello\" \"Dear World\""));
        assert_eq!(result, expected);
    }

    // parse multi-word string with no spaces around single quotes
    {
        let expected = vec!["echo".to_string(), "helloworld".into()];
        let result = parse_args(&String::from("echo 'hello'world"));
        assert_eq!(result, expected);
    }

    // parse multi-word string with no spaces around double quotes
    {
        let expected = vec!["echo".to_string(), "helloworld".into()];
        let result = parse_args(&String::from("echo \"hello\"world"));
        assert_eq!(result, expected);
    }

    // allow double quotes inside single quotes
    {
        let expected = vec!["\"".to_string()];
        let result = parse_args(&String::from("'\"'"));
        assert_eq!(result, expected);
    }

    // allow single quotes inside double quotes
    {
        let expected = vec!["\'".to_string()];
        let result = parse_args(&String::from("\"\'\""));
        assert_eq!(result, expected);
    }
}
<commit_msg>Add while loop check to continue parsing if not complete<commit_after>pub mod builtins;

use std::collections::HashMap;
use std::path::PathBuf;
use std::io;
use std::io::{Read, Write};
use std::collections::hash_map::Entry;

#[derive(PartialEq, Debug, Clone)]
pub struct State {
    cwd: PathBuf,
    environment: HashMap<String, String>,
    aliases: HashMap<String, String>,
    argv: Vec<String>,
    argc: usize,
}

impl State {
    pub fn new(cwd: String) -> State {
        State {
            cwd: PathBuf::from(cwd),
            environment: HashMap::new(),
            aliases: HashMap::new(),
            argv: Vec::new(),
            argc: 0,
        }
    }
}

pub fn run(initial_state: State) {
    let mut builtins = builtins::load();
    let mut s = initial_state.clone();

    println!("Welcome to rsh! {:?}", s);

    loop {

        print!("\n");
        print!("{} -> ", s.cwd.to_str().unwrap());

        // this forces the prompt to print
        io::stdout().flush();

        // read the user input
        let mut input = String::new();
        io::stdin().read_line(&mut input).unwrap();

        s.argv = parse_args(&input);
        s.argc = s.argv.len();

        print!("\n");
        println!("Input: {}\nState: {:?}", input, s);

        let first_arg = s.argv.get(0).unwrap().clone();
        if let Entry::Occupied(f) = builtins.entry(String::from(first_arg)) {
            let bn = f.get();
            s = bn(s.clone());
        }
    }
}

#[derive(PartialEq)]
enum BuildType {
    None,
    Single,
    Double,
}

struct ParseResult {
    result: Vec<String>,
    completed: bool,
    build_string: String,
    build_type: BuildType,
}

fn parse_args(args: &String) -> Vec<String> {
    let mut parse_result: ParseResult = ParseResult {
        result: Vec::new(),
        completed: false,
        build_string: String::from(""),
        build_type: BuildType::None,
    };

    if args.len() == 0 {
        return parse_result.result;
    }

    loop {
        parse_string_into_vec(&args, &mut parse_result);

        if (parse_result.completed) {
            break;
        }
    }

    let result: Vec<String> = parse_result.result
        .into_iter()
        .filter(|s| s.len() > 0)
        .collect();

    result
}

fn parse_string_into_vec(string: &String, parse_result: &mut ParseResult) {
    let &mut ParseResult { ref mut build_string,
                           ref mut build_type,
                           ref mut completed,
                           ref mut result } = parse_result;
    let mut iter = string.chars().peekable();

    while let Some(c) = iter.next() {
        match c {
            '\'' => {
                match *build_type {
                    BuildType::Single => {
                        *build_type = BuildType::None;
                        if iter.peek() == Some(&' ') {
                            result.push(build_string.clone());
                            *build_string = String::from("");
                        }
                    }

                    BuildType::None => {
                        *build_type = BuildType::Single;
                    }

                    _ => {
                        build_string.push(c);
                    }
                }
            }

            '\"' => {
                match *build_type {
                    BuildType::Double => {
                        *build_type = BuildType::None;
                        if iter.peek() == Some(&' ') {
                            result.push(build_string.clone());
                            *build_string = String::from("");
                        }
                    }

                    BuildType::None => {
                        *build_type = BuildType::Double;
                    }

                    _ => {
                        build_string.push(c);
                    }
                }
            }

            ' ' => {
                match *build_type {
                    BuildType::None => {
                        result.push(build_string.clone());
                        *build_string = String::from("");
                    }

                    _ => {
                        build_string.push(c);
                    }
                }
            }

            _ => {
                build_string.push(c);
            }
        }
    }

    if *build_type == BuildType::None {
        result.push(build_string.clone());
        *completed = true;
    }
}

#[test]
fn parse_args_test() {
    // parse empty string
    {
        let expected: Vec<String> = Vec::new();
        let result = parse_args(&String::from(""));
        assert_eq!(result, expected);
    }

    // parse single-word string
    {
        let expected: Vec<String> = vec!["echo".to_string()];
        let result = parse_args(&String::from("echo"));
        assert_eq!(result, expected);
    }

    // parse single-word string inside quotes
    {
        let expected = vec!["echo".to_string()];
        let result = parse_args(&String::from("\"echo\""));
        assert_eq!(result, expected);
    }

    // parse multi-word string with closed quotes section
    {
        let expected = vec!["echo".to_string(), "-n".into(), "Hello Dear World".into()];
        let result = parse_args(&String::from("echo -n \"Hello Dear World\""));
        assert_eq!(result, expected);
    }

    // parse multi-word string with multiple closed quotes sections
    {
        let expected = vec!["echo".to_string(), "Hello".into(), "Dear World".into()];
        let result = parse_args(&String::from("echo \"Hello\" \"Dear World\""));
        assert_eq!(result, expected);
    }

    // parse multi-word string with no spaces around single quotes
    {
        let expected = vec!["echo".to_string(), "helloworld".into()];
        let result = parse_args(&String::from("echo 'hello'world"));
        assert_eq!(result, expected);
    }

    // parse multi-word string with no spaces around double quotes
    {
        let expected = vec!["echo".to_string(), "helloworld".into()];
        let result = parse_args(&String::from("echo \"hello\"world"));
        assert_eq!(result, expected);
    }

    // allow double quotes inside single quotes
    {
        let expected = vec!["\"".to_string()];
        let result = parse_args(&String::from("'\"'"));
        assert_eq!(result, expected);
    }

    // allow single quotes inside double quotes
    {
        let expected = vec!["\'".to_string()];
        let result = parse_args(&String::from("\"\'\""));
        assert_eq!(result, expected);
    }
}
<|endoftext|>


<commit_before>// Copyright 2016 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

use raftstore::store::{PeerStorage, keys};
use raftstore::store::engine::Iterable;
use util::worker::Runnable;

use rocksdb::{DB, WriteBatch, Writable};
use std::sync::Arc;
use std::fmt::{self, Formatter, Display};
use std::error;

/// Compact task.
pub struct Task {
    engine: Arc<DB>,
    region_id: u64,
    compact_idx: u64,
}

impl Task {
    pub fn new(ps: &PeerStorage, compact_idx: u64) -> Task {
        Task {
            engine: ps.get_engine().clone(),
            region_id: ps.get_region_id(),
            compact_idx: compact_idx,
        }
    }
}

impl Display for Task {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f,
               "Compact Task [region: {}, to: {}]",
               self.region_id,
               self.compact_idx)
    }
}

quick_error! {
    #[derive(Debug)]
    enum Error {
        Other(err: Box<error::Error + Sync + Send>) {
            from()
            cause(err.as_ref())
            description(err.description())
            display("compact failed {:?}", err)
        }
    }
}

pub struct Runner;

impl Runner {
    /// Do the compact job and return the count of log compacted.
    fn compact(&mut self, task: Task) -> Result<u64, Error> {
        let start_key = keys::raft_log_key(task.region_id, 0);
        let mut first_idx = task.compact_idx;
        if let Some((k, _)) = box_try!(task.engine.seek(&start_key)) {
            first_idx = box_try!(keys::raft_log_index(&k));
        }
        if first_idx >= task.compact_idx {
            info!("no need to compact");
            return Ok(0);
        }
        let wb = WriteBatch::new();
        for idx in first_idx..task.compact_idx {
            let key = keys::raft_log_key(task.region_id, idx);
            box_try!(wb.delete(&key));
        }
        box_try!(task.engine.write(wb));
        Ok(task.compact_idx - first_idx)
    }
}

impl Runnable<Task> for Runner {
    fn run(&mut self, task: Task) {
        debug!("executing task {}", task);
        let region_id = task.region_id;
        match self.compact(task) {
            Err(e) => error!("failed to compact: {:?}", e),
            Ok(n) => {
                info!("{} log entries have been compacted for region {}",
                      n,
                      region_id)
            }
        }
    }
}
<commit_msg>worker: disable WAL for compact log. (#732)<commit_after>// Copyright 2016 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

use raftstore::store::{PeerStorage, keys};
use raftstore::store::engine::Iterable;
use util::worker::Runnable;

use rocksdb::{DB, WriteBatch, Writable};
use std::sync::Arc;
use std::fmt::{self, Formatter, Display};
use std::error;

/// Compact task.
pub struct Task {
    engine: Arc<DB>,
    region_id: u64,
    compact_idx: u64,
}

impl Task {
    pub fn new(ps: &PeerStorage, compact_idx: u64) -> Task {
        Task {
            engine: ps.get_engine().clone(),
            region_id: ps.get_region_id(),
            compact_idx: compact_idx,
        }
    }
}

impl Display for Task {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f,
               "Compact Task [region: {}, to: {}]",
               self.region_id,
               self.compact_idx)
    }
}

quick_error! {
    #[derive(Debug)]
    enum Error {
        Other(err: Box<error::Error + Sync + Send>) {
            from()
            cause(err.as_ref())
            description(err.description())
            display("compact failed {:?}", err)
        }
    }
}

pub struct Runner;

impl Runner {
    /// Do the compact job and return the count of log compacted.
    fn compact(&mut self, task: Task) -> Result<u64, Error> {
        let start_key = keys::raft_log_key(task.region_id, 0);
        let mut first_idx = task.compact_idx;
        if let Some((k, _)) = box_try!(task.engine.seek(&start_key)) {
            first_idx = box_try!(keys::raft_log_index(&k));
        }
        if first_idx >= task.compact_idx {
            info!("no need to compact");
            return Ok(0);
        }
        let wb = WriteBatch::new();
        for idx in first_idx..task.compact_idx {
            let key = keys::raft_log_key(task.region_id, idx);
            box_try!(wb.delete(&key));
        }
        // It is safe to disable WAL here. If crashed, we can still
        // compact the log after restart.
        box_try!(task.engine.write_without_wal(wb));
        Ok(task.compact_idx - first_idx)
    }
}

impl Runnable<Task> for Runner {
    fn run(&mut self, task: Task) {
        debug!("executing task {}", task);
        let region_id = task.region_id;
        match self.compact(task) {
            Err(e) => error!("failed to compact: {:?}", e),
            Ok(n) => {
                info!("{} log entries have been compacted for region {}",
                      n,
                      region_id)
            }
        }
    }
}
<|endoftext|>


<commit_before>//! Sensor related stuff.

use super::api;
use super::datastore::SafeDataStore;

/// A specification of a sensor.
///
/// The ``template`` field contains the static data of a sensor and
/// the ``data_key`` says how to find the sensor value in the datastore.
pub struct SensorSpec {
    pub template: Box<api::SensorTemplate>,
    pub data_key: String,
}

impl SensorSpec {

    /// Retrieve sensor value from the datastore.
    pub fn get_sensor_value(&self, datastore: &SafeDataStore) -> Option<String> {
        let datastore_clone = datastore.clone();
        let datastore_lock = datastore_clone.lock().unwrap();
        match datastore_lock.retrieve(&self.data_key) {
            Ok(v) => Some(v),
            Err(_) => None,
        }
    }

}

<commit_msg>server: Do not clone the SaveDataStore before locking<commit_after>//! Sensor related stuff.

use super::api;
use super::datastore::SafeDataStore;

/// A specification of a sensor.
///
/// The ``template`` field contains the static data of a sensor and
/// the ``data_key`` says how to find the sensor value in the datastore.
pub struct SensorSpec {
    pub template: Box<api::SensorTemplate>,
    pub data_key: String,
}

impl SensorSpec {

    /// Retrieve sensor value from the datastore.
    pub fn get_sensor_value(&self, datastore: &SafeDataStore) -> Option<String> {
        let datastore_lock = datastore.lock().unwrap();
        match datastore_lock.retrieve(&self.data_key) {
            Ok(v) => Some(v),
            Err(_) => None,
        }
    }

}

<|endoftext|>


<commit_before>extern crate path_slash;
extern crate reqwest;
extern crate url;

use crate::config::Config;
use crate::error::{Error, Result};
use crate::github_api::Client;
use crate::page::{DiffOp, Line, Page};
use crate::pull_request;
use std::borrow::Cow;
use std::mem;
use url::Url;

#[cfg(target_os = "windows")]
fn to_slash<S: AsRef<str>>(s: &S) -> String {
    use path_slash::PathExt;
    use std::path::Path;
    Path::new(s.as_ref()).to_slash_lossy()
}

// Do nothing on Windows
#[cfg(not(target_os = "windows"))]
fn to_slash<S: AsRef<str>>(s: &S) -> &str {
    s.as_ref()
}

// TODO: Omit fallback and return Result<String>
fn first_available_url<T: AsRef<str>>(
    candidates: &mut [String],
    fallback: String,
    https_proxy: &Option<T>,
) -> String {
    let mut builder = reqwest::Client::builder();
    if let Some(ref p) = https_proxy {
        if let Ok(p) = reqwest::Proxy::https(p.as_ref()) {
            builder = builder.proxy(p);
        } else {
            return fallback;
        }
    }
    if let Ok(client) = builder.build() {
        for mut candidate in candidates.iter_mut() {
            let req = client.head(candidate.as_str());
            if let Ok(res) = req.send() {
                let status = res.status();
                if status == reqwest::StatusCode::OK {
                    return mem::replace(&mut candidate, String::new());
                }
            }
        }
    }
    fallback
}

fn build_github_like_url<S: AsRef<str>>(
    host: &str,
    user: &str,
    repo: &str,
    api_endpoint: Option<S>,
    cfg: &Config,
    page: &Page,
) -> Result<String> {
    match page {
        Page::Open { website: true, .. } => {
            match host {
                "github.com" => {
                    if let Some(endpoint) = api_endpoint {
                        if let Ok(client) = Client::build(
                            endpoint.as_ref(),
                            cfg.env.github_token.as_ref(),
                            &cfg.env.https_proxy,
                        ) {
                            if let Ok(Some(homepage)) = client.repo_homepage(user, repo) {
                                return Ok(homepage);
                            }
                        }
                    }
                    let host = &host[0..host.len() - 4];
                    Ok(format!("https://{}.{}.io/{}", user, host, repo))
                }
                "gitlab.com" => Ok(format!("https://{}.gitlab.io/{}", user, repo)),
                host if host.starts_with("gitlab.") => {
                    Ok(format!("https://{}.{}/{}", user, host, repo))
                }
                // For GitHub Enterprise:
                //   https://help.github.com/enterprise/user/articles/user-organization-and-project-pages/
                host => {
                    // Token is always required for GitHub Enterprise
                    if let (Some(ref endpoint), Some(ref token)) =
                        (api_endpoint, &cfg.env.ghe_token)
                    {
                        if let Ok(client) =
                            Client::build(endpoint.as_ref(), Some(token), &cfg.env.https_proxy)
                        {
                            if let Ok(Some(homepage)) = client.repo_homepage(user, repo) {
                                return Ok(homepage);
                            }
                        }
                    }
                    let with_subdomain = format!("https://pages.{}/{}/{}", host, user, repo);
                    let without_subdomain = format!("https://{}/pages/{}/{}", host, user, repo);
                    Ok(first_available_url(
                        &mut [with_subdomain],
                        without_subdomain,
                        &cfg.env.https_proxy,
                    ))
                }
            }
        }
        Page::Open {
            pull_request: true, ..
        } => {
            if let Some(endpoint) = api_endpoint {
                match pull_request::find_page(endpoint.as_ref(), user, repo, cfg)? {
                    pull_request::Page::Existing { url } => Ok(url),
                    pull_request::Page::New {
                        author,
                        repo,
                        default_branch,
                        branch,
                    } => Ok(format!(
                        "https://{}/{}/{}/compare/{}...{}",
                        host, author, repo, default_branch, branch,
                    )),
                }
            } else {
                Err(Error::PullReqNotSupported {
                    service: host.to_string(),
                })
            }
        }
        Page::Open { .. } => {
            if let Some(ref b) = cfg.branch {
                Ok(format!("https://{}/{}/{}/tree/{}", host, user, repo, b))
            } else {
                Ok(format!("https://{}/{}/{}", host, user, repo))
            }
        }
        Page::Tag { ref tagname, .. } => Ok(format!(
            "https://{}/{}/{}/tree/{}",
            host, user, repo, tagname,
        )),
        Page::Diff {
            ref lhs,
            ref rhs,
            ref op,
        } => Ok(format!(
            "https://{}/{}/{}/compare/{}{}{}",
            host, user, repo, lhs, op, rhs,
        )),
        Page::Commit { ref hash } => Ok(format!(
            "https://{}/{}/{}/commit/{}",
            host, user, repo, hash
        )),
        Page::FileOrDir {
            ref relative_path,
            ref hash,
            line: None,
            blame,
        } => Ok(format!(
            "https://{}/{}/{}/{}/{}/{}",
            host,
            user,
            repo,
            if *blame { "blame" } else { "blob" },
            hash,
            to_slash(relative_path),
        )),
        Page::FileOrDir {
            ref relative_path,
            ref hash,
            line: Some(Line::At(line)),
            blame,
        } => Ok(format!(
            "https://{}/{}/{}/{}/{}/{}#L{}",
            host,
            user,
            repo,
            if *blame { "blame" } else { "blob" },
            hash,
            to_slash(relative_path),
            line,
        )),
        Page::FileOrDir {
            ref relative_path,
            ref hash,
            line: Some(Line::Range(start, end)),
            blame,
        } => Ok(format!(
            "https://{}/{}/{}/{}/{}/{}#L{}-L{}",
            host,
            user,
            repo,
            if *blame { "blame" } else { "blob" },
            hash,
            to_slash(relative_path),
            start,
            end,
        )),
        Page::Issue { number } => Ok(format!(
            "https://{}/{}/{}/issues/{}",
            host, user, repo, number
        )),
    }
}

fn build_gitlab_url(
    host: &str,
    user: &str,
    repo: &str,
    cfg: &Config,
    page: &Page,
) -> Result<String> {
    if let Page::Diff { op, .. } = page {
        if *op == DiffOp::TwoDots {
            return Err(Error::GitLabDiffNotSupported);
        }
    }
    build_github_like_url::<&str>(host, user, repo, None, cfg, page)
}

fn build_bitbucket_url(user: &str, repo: &str, cfg: &Config, page: &Page) -> Result<String> {
    match page {
        Page::Open { website: true, .. } => {
            // Build bitbucket cloud URL:
            //   https://confluence.atlassian.com/bitbucket/publishing-a-website-on-bitbucket-cloud-221449776.html
            let with_user = format!("https://{}.bitbucket.io/{}", user, repo);
            let without_user = format!("https://{}.bitbucket.io", user);
            Ok(first_available_url(
                &mut [with_user],
                without_user,
                &cfg.env.https_proxy,
            ))
        }
        Page::Open {
            pull_request: true, ..
        } => Err(Error::PullReqNotSupported {
            service: "bitbucket.org".to_string(),
        }),
        Page::Open { .. } => {
            if let Some(ref b) = cfg.branch {
                Ok(format!(
                    "https://bitbucket.org/{}/{}/branch/{}",
                    user, repo, b,
                ))
            } else {
                Ok(format!("https://bitbucket.org/{}/{}", user, repo))
            }
        }
        Page::Diff { .. } => Err(Error::BitbucketDiffNotSupported),
        Page::Commit { ref hash } => Ok(format!(
            "https://bitbucket.org/{}/{}/commits/{}",
            user, repo, hash,
        )),
        // On Bitbucket, there is no tag-specific page. However, unlike GitHub, bitbucket supports
        // tag commit. Open the tag commit page instead.
        Page::Tag { ref commit, .. } => Ok(format!(
            "https://bitbucket.org/{}/{}/commits/{}",
            user, repo, commit,
        )),
        Page::FileOrDir {
            ref relative_path,
            ref hash,
            line: None,
            blame,
        } => Ok(format!(
            "https://bitbucket.org/{}/{}/{}/{}/{}",
            user,
            repo,
            if *blame { "annotate" } else { "src" },
            hash,
            to_slash(relative_path),
        )),
        Page::FileOrDir {
            ref relative_path,
            ref hash,
            line: Some(Line::At(line)),
            blame,
        } => Ok(format!(
            "https://bitbucket.org/{}/{}/{}/{}/{}#lines-{}",
            user,
            repo,
            if *blame { "annotate" } else { "src" },
            hash,
            to_slash(relative_path),
            line,
        )),
        Page::FileOrDir {
            ref relative_path,
            ref hash,
            line: Some(Line::Range(start, end)),
            blame,
        } => Ok(format!(
            "https://bitbucket.org/{}/{}/{}/{}/{}#lines-{}:{}",
            user,
            repo,
            if *blame { "annotate" } else { "src" },
            hash,
            to_slash(relative_path),
            start,
            end,
        )),
        Page::Issue { number } => Ok(format!(
            "https://bitbucket.org/{}/{}/issues/{}",
            user, repo, number,
        )),
    }
}

// Note: Parse '/user/repo.git' or '/user/repo' or 'user/repo' into 'user' and 'repo'
pub fn slug_from_path<'a>(path: &'a str) -> Result<(&'a str, &'a str)> {
    let mut split = path.split('/').skip_while(|s| s.is_empty());
    let user = split.next().ok_or_else(|| Error::NoUserInPath {
        path: path.to_string(),
    })?;
    let mut repo = split.next().ok_or_else(|| Error::NoRepoInPath {
        path: path.to_string(),
    })?;
    if repo.ends_with(".git") {
        // Slice '.git' from 'repo.git'
        repo = &repo[0..repo.len() - 4];
    }
    Ok((user, repo))
}

// Known URL formats
//  1. https://hosting_service.com/user/repo.git
//  2. git@hosting_service.com:user/repo.git (-> ssh://git@hosting_service.com:22/user/repo.git)
pub fn build_page_url(page: &Page, cfg: &Config) -> Result<String> {
    let repo_url = &cfg.repo;
    let env = &cfg.env;

    let url = Url::parse(repo_url).map_err(|e| Error::BrokenUrl {
        url: repo_url.to_string(),
        msg: format!("{}", e),
    })?;
    let path = url.path();
    let (user, repo_name) = slug_from_path(path)?;
    let host = url.host_str().ok_or_else(|| Error::BrokenUrl {
        url: repo_url.to_string(),
        msg: "No host in URL".to_string(),
    })?;

    match host {
        "github.com" => {
            build_github_like_url(host, user, repo_name, Some("api.github.com"), cfg, page)
        }
        "gitlab.com" => build_gitlab_url(host, user, repo_name, cfg, page),
        "bitbucket.org" => build_bitbucket_url(user, repo_name, cfg, page),
        _ => {
            let is_gitlab = host.starts_with("gitlab.");
            let port = if host.starts_with("github.") {
                env.ghe_ssh_port
            } else if is_gitlab {
                env.gitlab_ssh_port
            } else {
                match env.ghe_url_host {
                    Some(ref v) if v == host => env.ghe_ssh_port,
                    _ => {
                        return Err(Error::UnknownHostingService {
                            url: repo_url.to_string(),
                        });
                    }
                }
            };

            let host = match port {
                Some(port) => Cow::Owned(format!("{}:{}", host, port)),
                None => Cow::Borrowed(host),
            };

            if is_gitlab {
                build_gitlab_url(&host, user, repo_name, cfg, page)
            } else {
                build_github_like_url(
                    &host,
                    user,
                    repo_name,
                    Some(format!("{}/api/v3", host)),
                    cfg,
                    page,
                )
            }
        }
    }
}
<commit_msg>refactor building URL for file or directory path<commit_after>extern crate path_slash;
extern crate reqwest;
extern crate url;

use crate::config::Config;
use crate::error::{Error, Result};
use crate::github_api::Client;
use crate::page::{DiffOp, Line, Page};
use crate::pull_request;
use std::borrow::Cow;
use std::mem;
use url::Url;

#[cfg(target_os = "windows")]
fn to_slash<S: AsRef<str>>(s: &S) -> String {
    use path_slash::PathExt;
    use std::path::Path;
    Path::new(s.as_ref()).to_slash_lossy()
}

// Do nothing on Windows
#[cfg(not(target_os = "windows"))]
fn to_slash<S: AsRef<str>>(s: &S) -> &str {
    s.as_ref()
}

// TODO: Omit fallback and return Result<String>
fn first_available_url<T: AsRef<str>>(
    candidates: &mut [String],
    fallback: String,
    https_proxy: &Option<T>,
) -> String {
    let mut builder = reqwest::Client::builder();
    if let Some(ref p) = https_proxy {
        if let Ok(p) = reqwest::Proxy::https(p.as_ref()) {
            builder = builder.proxy(p);
        } else {
            return fallback;
        }
    }
    if let Ok(client) = builder.build() {
        for mut candidate in candidates.iter_mut() {
            let req = client.head(candidate.as_str());
            if let Ok(res) = req.send() {
                let status = res.status();
                if status == reqwest::StatusCode::OK {
                    return mem::replace(&mut candidate, String::new());
                }
            }
        }
    }
    fallback
}

fn build_github_like_url<S: AsRef<str>>(
    host: &str,
    user: &str,
    repo: &str,
    api_endpoint: Option<S>,
    cfg: &Config,
    page: &Page,
) -> Result<String> {
    match page {
        Page::Open { website: true, .. } => {
            match host {
                "github.com" => {
                    if let Some(endpoint) = api_endpoint {
                        if let Ok(client) = Client::build(
                            endpoint.as_ref(),
                            cfg.env.github_token.as_ref(),
                            &cfg.env.https_proxy,
                        ) {
                            if let Ok(Some(homepage)) = client.repo_homepage(user, repo) {
                                return Ok(homepage);
                            }
                        }
                    }
                    let host = &host[0..host.len() - 4];
                    Ok(format!("https://{}.{}.io/{}", user, host, repo))
                }
                "gitlab.com" => Ok(format!("https://{}.gitlab.io/{}", user, repo)),
                host if host.starts_with("gitlab.") => {
                    Ok(format!("https://{}.{}/{}", user, host, repo))
                }
                // For GitHub Enterprise:
                //   https://help.github.com/enterprise/user/articles/user-organization-and-project-pages/
                host => {
                    // Token is always required for GitHub Enterprise
                    if let (Some(ref endpoint), Some(ref token)) =
                        (api_endpoint, &cfg.env.ghe_token)
                    {
                        if let Ok(client) =
                            Client::build(endpoint.as_ref(), Some(token), &cfg.env.https_proxy)
                        {
                            if let Ok(Some(homepage)) = client.repo_homepage(user, repo) {
                                return Ok(homepage);
                            }
                        }
                    }
                    let with_subdomain = format!("https://pages.{}/{}/{}", host, user, repo);
                    let without_subdomain = format!("https://{}/pages/{}/{}", host, user, repo);
                    Ok(first_available_url(
                        &mut [with_subdomain],
                        without_subdomain,
                        &cfg.env.https_proxy,
                    ))
                }
            }
        }
        Page::Open {
            pull_request: true, ..
        } => {
            if let Some(endpoint) = api_endpoint {
                match pull_request::find_page(endpoint.as_ref(), user, repo, cfg)? {
                    pull_request::Page::Existing { url } => Ok(url),
                    pull_request::Page::New {
                        author,
                        repo,
                        default_branch,
                        branch,
                    } => Ok(format!(
                        "https://{}/{}/{}/compare/{}...{}",
                        host, author, repo, default_branch, branch,
                    )),
                }
            } else {
                Err(Error::PullReqNotSupported {
                    service: host.to_string(),
                })
            }
        }
        Page::Open { .. } => {
            if let Some(ref b) = cfg.branch {
                Ok(format!("https://{}/{}/{}/tree/{}", host, user, repo, b))
            } else {
                Ok(format!("https://{}/{}/{}", host, user, repo))
            }
        }
        Page::Tag { ref tagname, .. } => Ok(format!(
            "https://{}/{}/{}/tree/{}",
            host, user, repo, tagname,
        )),
        Page::Diff {
            ref lhs,
            ref rhs,
            ref op,
        } => Ok(format!(
            "https://{}/{}/{}/compare/{}{}{}",
            host, user, repo, lhs, op, rhs,
        )),
        Page::Commit { ref hash } => Ok(format!(
            "https://{}/{}/{}/commit/{}",
            host, user, repo, hash
        )),
        Page::FileOrDir {
            ref relative_path,
            ref hash,
            line,
            blame,
        } => Ok(format!(
            "https://{host}/{user}/{repo}/{feat}/{hash}/{path}{anchor}",
            host = host,
            user = user,
            repo = repo,
            feat = if *blame { "blame" } else { "blob" },
            hash = hash,
            path = to_slash(relative_path),
            anchor = match line {
                None => "".to_string(),
                Some(Line::At(line)) => format!("#L{}", line),
                Some(Line::Range(start, end)) => format!("#L{}-L{}", start, end),
            },
        )),
        Page::Issue { number } => Ok(format!(
            "https://{}/{}/{}/issues/{}",
            host, user, repo, number
        )),
    }
}

fn build_gitlab_url(
    host: &str,
    user: &str,
    repo: &str,
    cfg: &Config,
    page: &Page,
) -> Result<String> {
    if let Page::Diff { op, .. } = page {
        if *op == DiffOp::TwoDots {
            return Err(Error::GitLabDiffNotSupported);
        }
    }
    build_github_like_url::<&str>(host, user, repo, None, cfg, page)
}

fn build_bitbucket_url(user: &str, repo: &str, cfg: &Config, page: &Page) -> Result<String> {
    match page {
        Page::Open { website: true, .. } => {
            // Build bitbucket cloud URL:
            //   https://confluence.atlassian.com/bitbucket/publishing-a-website-on-bitbucket-cloud-221449776.html
            let with_user = format!("https://{}.bitbucket.io/{}", user, repo);
            let without_user = format!("https://{}.bitbucket.io", user);
            Ok(first_available_url(
                &mut [with_user],
                without_user,
                &cfg.env.https_proxy,
            ))
        }
        Page::Open {
            pull_request: true, ..
        } => Err(Error::PullReqNotSupported {
            service: "bitbucket.org".to_string(),
        }),
        Page::Open { .. } => {
            if let Some(ref b) = cfg.branch {
                Ok(format!(
                    "https://bitbucket.org/{}/{}/branch/{}",
                    user, repo, b,
                ))
            } else {
                Ok(format!("https://bitbucket.org/{}/{}", user, repo))
            }
        }
        Page::Diff { .. } => Err(Error::BitbucketDiffNotSupported),
        Page::Commit { ref hash } => Ok(format!(
            "https://bitbucket.org/{}/{}/commits/{}",
            user, repo, hash,
        )),
        // On Bitbucket, there is no tag-specific page. However, unlike GitHub, bitbucket supports
        // tag commit. Open the tag commit page instead.
        Page::Tag { ref commit, .. } => Ok(format!(
            "https://bitbucket.org/{}/{}/commits/{}",
            user, repo, commit,
        )),
        Page::FileOrDir {
            ref relative_path,
            ref hash,
            line,
            blame,
        } => Ok(format!(
            "https://bitbucket.org/{user}/{repo}/{feat}/{hash}/{path}{anchor}",
            user = user,
            repo = repo,
            feat = if *blame { "annotate" } else { "src" },
            hash = hash,
            path = to_slash(relative_path),
            anchor = match line {
                None => "".to_string(),
                Some(Line::At(line)) => format!("#lines-{}", line),
                Some(Line::Range(start, end)) => format!("#lines-{}:{}", start, end),
            },
        )),
        Page::Issue { number } => Ok(format!(
            "https://bitbucket.org/{}/{}/issues/{}",
            user, repo, number,
        )),
    }
}

// Note: Parse '/user/repo.git' or '/user/repo' or 'user/repo' into 'user' and 'repo'
pub fn slug_from_path<'a>(path: &'a str) -> Result<(&'a str, &'a str)> {
    let mut split = path.split('/').skip_while(|s| s.is_empty());
    let user = split.next().ok_or_else(|| Error::NoUserInPath {
        path: path.to_string(),
    })?;
    let mut repo = split.next().ok_or_else(|| Error::NoRepoInPath {
        path: path.to_string(),
    })?;
    if repo.ends_with(".git") {
        // Slice '.git' from 'repo.git'
        repo = &repo[0..repo.len() - 4];
    }
    Ok((user, repo))
}

// Known URL formats
//  1. https://hosting_service.com/user/repo.git
//  2. git@hosting_service.com:user/repo.git (-> ssh://git@hosting_service.com:22/user/repo.git)
pub fn build_page_url(page: &Page, cfg: &Config) -> Result<String> {
    let repo_url = &cfg.repo;
    let env = &cfg.env;

    let url = Url::parse(repo_url).map_err(|e| Error::BrokenUrl {
        url: repo_url.to_string(),
        msg: format!("{}", e),
    })?;
    let path = url.path();
    let (user, repo_name) = slug_from_path(path)?;
    let host = url.host_str().ok_or_else(|| Error::BrokenUrl {
        url: repo_url.to_string(),
        msg: "No host in URL".to_string(),
    })?;

    match host {
        "github.com" => {
            build_github_like_url(host, user, repo_name, Some("api.github.com"), cfg, page)
        }
        "gitlab.com" => build_gitlab_url(host, user, repo_name, cfg, page),
        "bitbucket.org" => build_bitbucket_url(user, repo_name, cfg, page),
        _ => {
            let is_gitlab = host.starts_with("gitlab.");
            let port = if host.starts_with("github.") {
                env.ghe_ssh_port
            } else if is_gitlab {
                env.gitlab_ssh_port
            } else {
                match env.ghe_url_host {
                    Some(ref v) if v == host => env.ghe_ssh_port,
                    _ => {
                        return Err(Error::UnknownHostingService {
                            url: repo_url.to_string(),
                        });
                    }
                }
            };

            let host = match port {
                Some(port) => Cow::Owned(format!("{}:{}", host, port)),
                None => Cow::Borrowed(host),
            };

            if is_gitlab {
                build_gitlab_url(&host, user, repo_name, cfg, page)
            } else {
                build_github_like_url(
                    &host,
                    user,
                    repo_name,
                    Some(format!("{}/api/v3", host)),
                    cfg,
                    page,
                )
            }
        }
    }
}
<|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.

//! Session structure definition.
//!
//! The Session object serves as a wrapper around an HTTP(s) client, handling
//! authentication, accessing the service catalog and token refresh.

use std::fmt;
use std::marker::PhantomData;

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

use super::{ApiError, ApiResult, ServiceType};
use super::auth::Method as AuthMethod;
use super::identity::protocol;
use super::utils;

/// Request builder with authentication.
///
/// Essentially copies the interface of hyper::client::RequestBuilder.
#[allow(missing_debug_implementations)]
pub struct AuthenticatedRequestBuilder<'a, A: AuthMethod + 'a> {
    parent: &'a Session<A>,
    inner: RequestBuilder<'a>
}

/// An OpenStack API session.
///
/// Owns a token and an underlying client.
#[derive(Debug)]
pub struct Session<Auth: AuthMethod> {
    auth_method: Auth,
    client: Client,
    cached_token: utils::ValueCache<Auth::TokenType>
}

impl<'a, Auth: AuthMethod> AuthenticatedRequestBuilder<'a, Auth> {
    /// 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> {
        let token = try!(self.parent.auth_token());
        let hdr = protocol::AuthTokenHeader(token.into());
        self.inner.header(hdr).send().map_err(From::from)
    }

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

    /// Add additional headers to the request.
    pub fn headers(self, headers: Headers)
            -> AuthenticatedRequestBuilder<'a, Auth> {
        AuthenticatedRequestBuilder {
            inner: self.inner.headers(headers),
            .. self
        }
    }

    /// Add an individual header to the request.
    ///
    /// Note that X-Auth-Token is always overwritten with a token in use.
    pub fn header<H: Header + HeaderFormat>(self, header: H)
            -> AuthenticatedRequestBuilder<'a, Auth> {
        AuthenticatedRequestBuilder {
            inner: self.inner.header(header),
            .. self
        }
    }
}

impl<'a, Auth: AuthMethod + 'a> Session<Auth> {
    /// Create a new session with a given authentication plugin.
    pub fn new(auth_method: Auth) -> Session<Auth> {
        Session {
            auth_method: auth_method,
            client: utils::http_client(),
            cached_token: utils::ValueCache::new(None)
        }
    }

    /// Get a clone of the authentication token.
    pub fn auth_token(&self) -> ApiResult<Auth::TokenType> {
        try!(self.refresh_token());
        Ok(self.cached_token.get().unwrap())
    }

    /// Get an endpoint URL.
    pub fn get_endpoint(&self, service_type: &str,
                        endpoint_interface: Option<&str>,
                        region: Option<&str>) -> ApiResult<Url> {
        self.auth_method.get_endpoint(service_type, endpoint_interface,
                                      region, &self)
    }

    /// A wrapper for HTTP request.
    pub fn request<U: IntoUrl>(&'a self, method: Method, url: U)
            -> AuthenticatedRequestBuilder<'a, Auth> {
        AuthenticatedRequestBuilder {
            parent: self,
            inner: self.client.request(method, url)
        }
    }

    // Private and test-only

    #[cfg(test)]
    pub fn new_with_params(auth_method: Auth, client: Client,
                           token: Auth::TokenType) -> Session<Auth> {
        Session {
            auth_method: auth_method,
            client: client,
            cached_token: utils::ValueCache::new(Some(token))
        }
    }

    fn refresh_token(&self) -> ApiResult<()> {
        self.cached_token.ensure_value(|| {
            self.auth_method.get_token(&self.client)
        })
    }
}

/// API version (major, minor).
#[derive(Copy, Clone, Debug)]
pub struct ApiVersion(pub u16, pub Option<u16>);

impl fmt::Display for ApiVersion {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if let Some(minor) = self.1 {
            write!(f, "{}.{}", self.0, minor)
        } else {
            write!(f, "{}", self.0)
        }
    }
}

/// Low-level API calls.
#[derive(Debug)]
pub struct ServiceApi<'a, Auth: AuthMethod + 'a, S> {
    session: &'a Session<Auth>,
    service_type: PhantomData<S>,
    endpoint_interface: Option<String>,
    region: Option<String>
}

impl<'a, Auth: AuthMethod + 'a, S: ServiceType> ServiceApi<'a, Auth, S> {
    /// Create a new API instance using the given session.
    pub fn new(session: &'a Session<Auth>) -> ServiceApi<'a, Auth, S> {
        ServiceApi::new_with_endpoint_params(session, None, None)
    }

    /// Create a new API instance using the given session.
    ///
    /// This variant allows passing an endpoint type (defaults to public),
    /// and region (defaults to any).
    pub fn new_with_endpoint_params(session: &'a Session<Auth>,
                                    endpoint_interface: Option<&str>,
                                    region: Option<&str>)
            -> ServiceApi<'a, Auth, S> {
        ServiceApi {
            session: session,
            service_type: PhantomData,
            endpoint_interface: endpoint_interface.map(String::from),
            region: region.map(String::from)
        }
    }

    /// Get an endpoint with version suffix and given path appended.
    pub fn get_endpoint(&self, path: &str) -> ApiResult<Url> {
        let endpoint = try!(self.session.get_endpoint(
                S::catalog_type(),
                self.endpoint_interface.as_ref().map(String::as_str),
                self.region.as_ref().map(String::as_str)));

        let with_version = if let Some(suffix) = S::version_suffix() {
            if endpoint.path().ends_with(suffix) {
                endpoint
            } else {
                try!(endpoint.join(suffix))
            }
        } else {
            endpoint
        };

        with_version.join(path).map_err(From::from)
    }

    /// List entities.
    pub fn list<R: Deserialize>(&self, path: &str) -> ApiResult<R> {
        // TODO: filtering
        let url = try!(self.get_endpoint(path));
        debug!("Listing entities from {}", url);
        let resp = try!(self.session.request(Get, url).send());
        let root = try!(serde_json::from_reader(resp));
        Ok(root)
    }

    /// Get one entity.
    pub fn get<R: Deserialize, Id: utils::IntoId>(&self, path: &str, id: Id)
            -> ApiResult<R> {
        // Url expects trailing /
        let root_path = if path.ends_with("/") {
            String::from(path)
        } else {
            format!("{}/", path)
        };
        let url = try!(self.get_endpoint(&root_path));
        let url_with_id = try!(url.join(&id.into_id()));
        debug!("Get one entity from {}", url);
        let resp = try!(self.session.request(Get, url_with_id).send());
        let root = try!(serde_json::from_reader(resp));
        Ok(root)
    }
}

#[cfg(test)]
pub mod test {
    #![allow(missing_debug_implementations)]
    #![allow(unused_results)]

    use std::io::Read;

    use hyper;
    use hyper::header::ContentLength;
    use hyper::status::StatusCode;

    use super::{ApiError, Session};
    use super::super::auth::{Token, NoAuth, SimpleToken};
    use super::super::utils;

    pub fn new_session(token: &str) -> Session<NoAuth> {
        let token = SimpleToken(String::from(token));
        Session::new_with_params(NoAuth::new("http://127.0.0.1/").unwrap(),
                                 utils::http_client(), token)
    }

    mock_connector!(MockHttp {
        "http://127.0.0.1" => "HTTP/1.1 200 OK\r\n\
                               Server: Mock.Mock\r\n\
                               \r\n\
                               {}"
        "http://127.0.0.2" => "HTTP/1.1 404 NOT FOUND\r\n\
                               Server: Mock.Mock\r\n\
                               \r\n\
                               {}"
    });

    #[test]
    fn test_session_new() {
        let s = new_session("foo");
        let token = s.auth_token().unwrap();
        assert_eq!(token.value(), "foo");
        assert!(token.expires_at().is_none());
    }

    #[test]
    fn test_session_get_endpoint() {
        let s = new_session("foo");
        let e = s.get_endpoint("foo", None, None).unwrap();
        assert_eq!(&e.to_string(), "http://127.0.0.1/");
    }

    #[test]
    fn test_session_request() {
        let cli = hyper::Client::with_connector(MockHttp::default());
        let s = Session {
            auth_method: NoAuth::new("http://127.0.0.1/").unwrap(),
            client: cli,
            cached_token: utils::ValueCache::new(None)
        };

        let mut resp = s.request(hyper::Post, "http://127.0.0.1/")
            .body("body").header(ContentLength(4u64)).send().unwrap();

        let mut s = String::new();
        resp.read_to_string(&mut s).unwrap();
        assert_eq!(&s, "{}");
    }

    #[test]
    fn test_session_request_error() {
        let cli = hyper::Client::with_connector(MockHttp::default());
        let s = Session {
            auth_method: NoAuth::new("http://127.0.0.2/").unwrap(),
            client: cli,
            cached_token: utils::ValueCache::new(None)
        };

        let err = s.request(hyper::Post, "http://127.0.0.2/")
            .body("body").header(ContentLength(4u64)).send().err().unwrap();

        match err {
            ApiError::HttpError(StatusCode::NotFound, ..) => (),
            other => panic!("Unexpected {}", other)
        }
    }

    #[test]
    fn test_session_request_unchecked_error() {
        let cli = hyper::Client::with_connector(MockHttp::default());
        let s = Session {
            auth_method: NoAuth::new("http://127.0.0.2/").unwrap(),
            client: cli,
            cached_token: utils::ValueCache::new(None)
        };

        let mut resp = s.request(hyper::Post, "http://127.0.0.2/")
            .body("body").header(ContentLength(4u64)).send_unchecked()
            .unwrap();

        assert_eq!(resp.status, StatusCode::NotFound);

        let mut s = String::new();
        resp.read_to_string(&mut s).unwrap();
        assert_eq!(&s, "{}");
    }
}
<commit_msg>feat(session): cache and expose root endpoint on ServiceApi<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.

//! Session structure definition.
//!
//! The Session object serves as a wrapper around an HTTP(s) client, handling
//! authentication, accessing the service catalog and token refresh.

use std::fmt;
use std::marker::PhantomData;

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

use super::{ApiError, ApiResult, ServiceType};
use super::auth::Method as AuthMethod;
use super::identity::protocol;
use super::utils;

/// Request builder with authentication.
///
/// Essentially copies the interface of hyper::client::RequestBuilder.
#[allow(missing_debug_implementations)]
pub struct AuthenticatedRequestBuilder<'a, A: AuthMethod + 'a> {
    parent: &'a Session<A>,
    inner: RequestBuilder<'a>
}

/// An OpenStack API session.
///
/// Owns a token and an underlying client.
#[derive(Debug)]
pub struct Session<Auth: AuthMethod> {
    auth_method: Auth,
    client: Client,
    cached_token: utils::ValueCache<Auth::TokenType>
}

impl<'a, Auth: AuthMethod> AuthenticatedRequestBuilder<'a, Auth> {
    /// 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> {
        let token = try!(self.parent.auth_token());
        let hdr = protocol::AuthTokenHeader(token.into());
        self.inner.header(hdr).send().map_err(From::from)
    }

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

    /// Add additional headers to the request.
    pub fn headers(self, headers: Headers)
            -> AuthenticatedRequestBuilder<'a, Auth> {
        AuthenticatedRequestBuilder {
            inner: self.inner.headers(headers),
            .. self
        }
    }

    /// Add an individual header to the request.
    ///
    /// Note that X-Auth-Token is always overwritten with a token in use.
    pub fn header<H: Header + HeaderFormat>(self, header: H)
            -> AuthenticatedRequestBuilder<'a, Auth> {
        AuthenticatedRequestBuilder {
            inner: self.inner.header(header),
            .. self
        }
    }
}

impl<'a, Auth: AuthMethod + 'a> Session<Auth> {
    /// Create a new session with a given authentication plugin.
    pub fn new(auth_method: Auth) -> Session<Auth> {
        Session {
            auth_method: auth_method,
            client: utils::http_client(),
            cached_token: utils::ValueCache::new(None)
        }
    }

    /// Get a clone of the authentication token.
    pub fn auth_token(&self) -> ApiResult<Auth::TokenType> {
        try!(self.refresh_token());
        Ok(self.cached_token.get().unwrap())
    }

    /// Get an endpoint URL.
    pub fn get_endpoint(&self, service_type: &str,
                        endpoint_interface: Option<&str>,
                        region: Option<&str>) -> ApiResult<Url> {
        self.auth_method.get_endpoint(service_type, endpoint_interface,
                                      region, &self)
    }

    /// A wrapper for HTTP request.
    pub fn request<U: IntoUrl>(&'a self, method: Method, url: U)
            -> AuthenticatedRequestBuilder<'a, Auth> {
        AuthenticatedRequestBuilder {
            parent: self,
            inner: self.client.request(method, url)
        }
    }

    // Private and test-only

    #[cfg(test)]
    pub fn new_with_params(auth_method: Auth, client: Client,
                           token: Auth::TokenType) -> Session<Auth> {
        Session {
            auth_method: auth_method,
            client: client,
            cached_token: utils::ValueCache::new(Some(token))
        }
    }

    fn refresh_token(&self) -> ApiResult<()> {
        self.cached_token.ensure_value(|| {
            self.auth_method.get_token(&self.client)
        })
    }
}

/// API version (major, minor).
#[derive(Copy, Clone, Debug)]
pub struct ApiVersion(pub u16, pub Option<u16>);

impl fmt::Display for ApiVersion {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if let Some(minor) = self.1 {
            write!(f, "{}.{}", self.0, minor)
        } else {
            write!(f, "{}", self.0)
        }
    }
}

/// Low-level API calls.
#[derive(Debug)]
pub struct ServiceApi<'a, Auth: AuthMethod + 'a, S> {
    session: &'a Session<Auth>,
    service_type: PhantomData<S>,
    endpoint_interface: Option<String>,
    region: Option<String>,
    cached_endpoint: utils::ValueCache<Url>
}

impl<'a, Auth: AuthMethod + 'a, S: ServiceType> ServiceApi<'a, Auth, S> {
    /// Create a new API instance using the given session.
    pub fn new(session: &'a Session<Auth>) -> ServiceApi<'a, Auth, S> {
        ServiceApi::new_with_endpoint_params(session, None, None)
    }

    /// Create a new API instance using the given session.
    ///
    /// This variant allows passing an endpoint type (defaults to public),
    /// and region (defaults to any).
    pub fn new_with_endpoint_params(session: &'a Session<Auth>,
                                    endpoint_interface: Option<&str>,
                                    region: Option<&str>)
            -> ServiceApi<'a, Auth, S> {
        ServiceApi {
            session: session,
            service_type: PhantomData,
            endpoint_interface: endpoint_interface.map(String::from),
            region: region.map(String::from),
            cached_endpoint: utils::ValueCache::new(None)
        }
    }

    /// Get the root endpoint with or without the major version.
    ///
    /// The resulting endpoint is cached on the current ServiceApi object.
    pub fn get_root_endpoint(&self, include_version: bool) -> ApiResult<Url> {
        try!(self.cached_endpoint.ensure_value(|| {
            self.session.get_endpoint(
                S::catalog_type(),
                self.endpoint_interface.as_ref().map(String::as_str),
                self.region.as_ref().map(String::as_str))
        }));

        let endpoint = self.cached_endpoint.get().unwrap();
        if include_version {
            if let Some(suffix) = S::version_suffix() {
                if !endpoint.path().ends_with(suffix) {
                    return endpoint.join(suffix).map_err(From::from);
                }
            }
        }

        Ok(endpoint)
    }

    /// Get an endpoint with version suffix and given path appended.
    pub fn get_endpoint(&self, path: &str) -> ApiResult<Url> {
        let endpoint = try!(self.get_root_endpoint(true));
        endpoint.join(path).map_err(From::from)
    }

    /// List entities.
    pub fn list<R: Deserialize>(&self, path: &str) -> ApiResult<R> {
        // TODO: filtering
        let url = try!(self.get_endpoint(path));
        debug!("Listing entities from {}", url);
        let resp = try!(self.session.request(Get, url).send());
        let root = try!(serde_json::from_reader(resp));
        Ok(root)
    }

    /// Get one entity.
    pub fn get<R: Deserialize, Id: utils::IntoId>(&self, path: &str, id: Id)
            -> ApiResult<R> {
        // Url expects trailing /
        let root_path = if path.ends_with("/") {
            String::from(path)
        } else {
            format!("{}/", path)
        };
        let url = try!(self.get_endpoint(&root_path));
        let url_with_id = try!(url.join(&id.into_id()));
        debug!("Get one entity from {}", url);
        let resp = try!(self.session.request(Get, url_with_id).send());
        let root = try!(serde_json::from_reader(resp));
        Ok(root)
    }
}

#[cfg(test)]
pub mod test {
    #![allow(missing_debug_implementations)]
    #![allow(unused_results)]

    use std::io::Read;

    use hyper;
    use hyper::header::ContentLength;
    use hyper::status::StatusCode;

    use super::{ApiError, Session};
    use super::super::auth::{Token, NoAuth, SimpleToken};
    use super::super::utils;

    pub fn new_session(token: &str) -> Session<NoAuth> {
        let token = SimpleToken(String::from(token));
        Session::new_with_params(NoAuth::new("http://127.0.0.1/").unwrap(),
                                 utils::http_client(), token)
    }

    mock_connector!(MockHttp {
        "http://127.0.0.1" => "HTTP/1.1 200 OK\r\n\
                               Server: Mock.Mock\r\n\
                               \r\n\
                               {}"
        "http://127.0.0.2" => "HTTP/1.1 404 NOT FOUND\r\n\
                               Server: Mock.Mock\r\n\
                               \r\n\
                               {}"
    });

    #[test]
    fn test_session_new() {
        let s = new_session("foo");
        let token = s.auth_token().unwrap();
        assert_eq!(token.value(), "foo");
        assert!(token.expires_at().is_none());
    }

    #[test]
    fn test_session_get_endpoint() {
        let s = new_session("foo");
        let e = s.get_endpoint("foo", None, None).unwrap();
        assert_eq!(&e.to_string(), "http://127.0.0.1/");
    }

    #[test]
    fn test_session_request() {
        let cli = hyper::Client::with_connector(MockHttp::default());
        let s = Session {
            auth_method: NoAuth::new("http://127.0.0.1/").unwrap(),
            client: cli,
            cached_token: utils::ValueCache::new(None)
        };

        let mut resp = s.request(hyper::Post, "http://127.0.0.1/")
            .body("body").header(ContentLength(4u64)).send().unwrap();

        let mut s = String::new();
        resp.read_to_string(&mut s).unwrap();
        assert_eq!(&s, "{}");
    }

    #[test]
    fn test_session_request_error() {
        let cli = hyper::Client::with_connector(MockHttp::default());
        let s = Session {
            auth_method: NoAuth::new("http://127.0.0.2/").unwrap(),
            client: cli,
            cached_token: utils::ValueCache::new(None)
        };

        let err = s.request(hyper::Post, "http://127.0.0.2/")
            .body("body").header(ContentLength(4u64)).send().err().unwrap();

        match err {
            ApiError::HttpError(StatusCode::NotFound, ..) => (),
            other => panic!("Unexpected {}", other)
        }
    }

    #[test]
    fn test_session_request_unchecked_error() {
        let cli = hyper::Client::with_connector(MockHttp::default());
        let s = Session {
            auth_method: NoAuth::new("http://127.0.0.2/").unwrap(),
            client: cli,
            cached_token: utils::ValueCache::new(None)
        };

        let mut resp = s.request(hyper::Post, "http://127.0.0.2/")
            .body("body").header(ContentLength(4u64)).send_unchecked()
            .unwrap();

        assert_eq!(resp.status, StatusCode::NotFound);

        let mut s = String::new();
        resp.read_to_string(&mut s).unwrap();
        assert_eq!(&s, "{}");
    }
}
<|endoftext|>


<commit_before>#![allow(non_snake_case)]

#[derive(Serialize, Deserialize, Debug)]
pub struct GameSettings {
    pub maxNoofPlayers: u32,
    pub startSnakeLength: u32,
    pub timeInMsPerTick: u32,
    pub obstaclesEnabled: bool,
    pub foodEnabled: bool,
    pub headToTailConsumes: bool,
    pub tailConsumeGrows: bool,
    pub addFoodLikelihood: u32,
    pub removeFoodLikelihood: u32,
    pub spontaneousGrowthEveryNWorldTick: u32,
    pub trainingGame: bool,
    pub pointsPerLength: u32,
    pub pointsPerFood: u32,
    pub pointsPerCausedDeath: u32,
    pub pointsPerNibble: u32,
    pub noofRoundsTailProtectedAfterNibble: u32,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct PlayRegistration {
    #[serde(rename="type")]
    pub type_: String,
    pub playerName: String,
    pub gameSettings: GameSettings,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct RegisterMove {
    #[serde(rename="type")]
    pub type_: String,
    pub direction: String,
    pub gameTick: u32,
    pub receivingPlayerId: String,
    pub gameId: String
}

#[derive(Serialize, Deserialize, Debug)]
pub struct StartGame {
    #[serde(rename="type")]
    pub type_: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct HeartBeatRequest {
    #[serde(rename="type")]
    pub type_: String,
    pub receivingPlayerId: String
}

#[derive(Serialize, Deserialize, Debug)]
pub struct PlayerRegistered {
    #[serde(rename="type")]
    pub type_: String,
    pub gameId: String,
    pub gameMode: String,
    pub receivingPlayerId: String,
    pub name: String,
    pub gameSettings: GameSettings
}

#[derive(Serialize, Deserialize, Debug)]
pub struct MapUpdate {
    #[serde(rename="type")]
    pub type_: String,
    pub receivingPlayerId: String,
    pub gameId: String,
    pub gameTick: u32,
    pub map: Map,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct InvalidPlayerName {
    #[serde(rename="type")]
    pub type_: String,
    pub reasonCode: u32,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct GameEnded {
    #[serde(rename="type")]
    pub type_: String,
    pub receivingPlayerId: String,
    pub playerWinnerId: String,
    pub gameId: String,
    pub gameTick: u32,
    pub map: Map,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct SnakeDead {
    #[serde(rename="type")]
    pub type_: String,
    pub playerId: String,
    pub x: u32,
    pub y: u32,
    pub gameId: String,
    pub gameTick: u32,
    pub deathReason: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct GameStarting {
    #[serde(rename="type")]
    pub type_: String,
    pub receivingPlayerId: String,
    pub gameId: String,
    pub noofPlayers: u32,
    pub width: u32,
    pub height: u32,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct HeartBeatResponse {
    #[serde(rename="type")]
    pub type_: String,
    pub receivingPlayerId: Option<String>
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct GameLink {
    #[serde(rename="type")]
    pub type_: String,
    pub receivingPlayerId: String,
    pub gameId: String,
    pub url: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct TournamentEnded {
    #[serde(rename="type")]
    pub type_: String,
    pub playerWinnerId: String,
    pub gameId: String,
    pub gameResult: String,
    pub tournamentName: String,
    pub tournamentId: String,
    pub gameTick: Option<i32>
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Map {
    #[serde(rename="type")]
    pub type_: String,
    pub width: i32,
    pub height: i32,
    pub worldTick: u32,
    pub snakeInfos: Vec<SnakeInfo>,
    pub foodPositions: Vec<i32>,
    pub obstaclePositions: Vec<i32>,
    pub receivingPlayerId: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct SnakeInfo {
    pub name: String,
    pub points: i32,
    pub positions: Vec<i32>,
    pub tailProtectedForGameTicks: u32,
    pub id: String
}
<commit_msg>Minor fixes to messages<commit_after>#![allow(non_snake_case)]

#[derive(Serialize, Deserialize, Debug)]
pub struct GameSettings {
    pub maxNoofPlayers: u32,
    pub startSnakeLength: u32,
    pub timeInMsPerTick: u32,
    pub obstaclesEnabled: bool,
    pub foodEnabled: bool,
    pub headToTailConsumes: bool,
    pub tailConsumeGrows: bool,
    pub addFoodLikelihood: u32,
    pub removeFoodLikelihood: u32,
    pub spontaneousGrowthEveryNWorldTick: u32,
    pub trainingGame: bool,
    pub pointsPerLength: u32,
    pub pointsPerFood: u32,
    pub pointsPerCausedDeath: u32,
    pub pointsPerNibble: u32,
    pub noofRoundsTailProtectedAfterNibble: u32,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct PlayRegistration {
    #[serde(rename="type")]
    pub type_: String,
    pub playerName: String,
    pub gameSettings: GameSettings,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct RegisterMove {
    #[serde(rename="type")]
    pub type_: String,
    pub direction: String,
    pub gameTick: u32,
    pub receivingPlayerId: String,
    pub gameId: String
}

#[derive(Serialize, Deserialize, Debug)]
pub struct StartGame {
    #[serde(rename="type")]
    pub type_: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct HeartBeatRequest {
    #[serde(rename="type")]
    pub type_: String,
    pub receivingPlayerId: String
}

#[derive(Serialize, Deserialize, Debug)]
pub struct PlayerRegistered {
    #[serde(rename="type")]
    pub type_: String,
    pub gameId: String,
    pub gameMode: String,
    pub receivingPlayerId: String,
    pub name: String,
    pub gameSettings: GameSettings
}

#[derive(Serialize, Deserialize, Debug)]
pub struct MapUpdate {
    #[serde(rename="type")]
    pub type_: String,
    pub receivingPlayerId: String,
    pub gameId: String,
    pub gameTick: u32,
    pub map: Map,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct InvalidPlayerName {
    #[serde(rename="type")]
    pub type_: String,
    pub reasonCode: u32,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct GameEnded {
    #[serde(rename="type")]
    pub type_: String,
    pub receivingPlayerId: String,
    pub playerWinnerId: String,
    pub gameId: String,
    pub gameTick: u32,
    pub map: Map,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct SnakeDead {
    #[serde(rename="type")]
    pub type_: String,
    pub playerId: String,
    pub x: u32,
    pub y: u32,
    pub gameId: String,
    pub gameTick: u32,
    pub deathReason: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct GameStarting {
    #[serde(rename="type")]
    pub type_: String,
    pub receivingPlayerId: String,
    pub gameId: String,
    pub noofPlayers: u32,
    pub width: u32,
    pub height: u32,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct HeartBeatResponse {
    #[serde(rename="type")]
    pub type_: String,
    pub receivingPlayerId: String
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct GameLink {
    #[serde(rename="type")]
    pub type_: String,
    pub receivingPlayerId: String,
    pub gameId: String,
    pub url: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct TournamentEnded {
    #[serde(rename="type")]
    pub type_: String,
    pub playerWinnerId: String,
    pub gameId: String,
    pub gameResult: String,
    pub tournamentName: String,
    pub tournamentId: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Map {
    #[serde(rename="type")]
    pub type_: String,
    pub width: i32,
    pub height: i32,
    pub worldTick: u32,
    pub snakeInfos: Vec<SnakeInfo>,
    pub foodPositions: Vec<i32>,
    pub obstaclePositions: Vec<i32>
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct SnakeInfo {
    pub name: String,
    pub points: i32,
    pub positions: Vec<i32>,
    pub tailProtectedForGameTicks: u32,
    pub id: String
}
<|endoftext|>