<commit_before>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::counters::{FETCHED_TRANSACTION, UNABLE_TO_FETCH_TRANSACTION};
use aptos_rest_client::{Client as RestClient, State, Transaction};
use futures::channel::mpsc;
use futures::{SinkExt, StreamExt};
use serde_json::Value;
use std::time::Duration;
use tokio::task::JoinHandle;
use url::Url;

// TODO: make this configurable
const RETRY_TIME_MILLIS: u64 = 1000;
const TRANSACTION_FETCH_BATCH_SIZE: u16 = 500;
const TRANSACTION_CHANNEL_SIZE: usize = 35;
const MAX_THREADS: usize = 10;
const MAX_RETRIES: usize = 5;

#[derive(Debug)]
pub struct Fetcher {
    client: RestClient,
    chain_id: u8,
    current_version: u64,
    highest_known_version: u64,
    transactions_sender: mpsc::Sender<Vec<Transaction>>,
}

impl Fetcher {
    pub fn new(
        client: RestClient,
        current_version: u64,
        transactions_sender: mpsc::Sender<Vec<Transaction>>,
    ) -> Self {
        Self {
            client,
            chain_id: 0,
            current_version,
            highest_known_version: current_version,
            transactions_sender,
        }
    }

    pub async fn set_highest_known_version(&mut self) -> anyhow::Result<()> {
        let res = self.client.get_ledger_information().await?;
        let state = res.state();
        self.highest_known_version = state.version;
        self.chain_id = state.chain_id;
        Ok(())
    }

    pub async fn run(&mut self) {
        loop {
            if self.current_version >= self.highest_known_version {
                tokio::time::sleep(Duration::from_millis(200)).await;
                if let Err(err) = self.set_highest_known_version().await {
                    aptos_logger::error!("Failed to set highest known version. Err: {:?}", err);
                    continue;
                }
            }

            let num_missing = self.highest_known_version - self.current_version;
            let num_batches = std::cmp::min(
                (num_missing as f64 / TRANSACTION_FETCH_BATCH_SIZE as f64).ceil() as u64,
                MAX_THREADS as u64,
            ) as usize;
            let mut futures = vec![];
            for i in 0..num_batches {
                futures.push(fetch_nexts(
                    self.client.clone(),
                    self.current_version + (i as u64 * TRANSACTION_FETCH_BATCH_SIZE as u64),
                ));
            }
            let mut res: Vec<Vec<Transaction>> = futures::future::join_all(futures).await;
            // Sort by first transaction of batch's version
            res.sort_by(|a, b| {
                a.first()
                    .unwrap()
                    .version()
                    .unwrap()
                    .cmp(&b.first().unwrap().version().unwrap())
            });

            // Send keeping track of the last version sent by the batch
            for batch in res {
                self.current_version = batch.last().unwrap().version().unwrap();
                self.transactions_sender
                    .send(batch)
                    .await
                    .expect("Should be able to send transaction on channel");
            }
        }
    }
}

/// Fetches the next version based on its internal version counter
/// Under the hood, it fetches TRANSACTION_FETCH_BATCH_SIZE versions in bulk (when needed), and uses that buffer to feed out
/// In the event it can't fetch, it will keep retrying every RETRY_TIME_MILLIS ms
async fn fetch_nexts(client: RestClient, starting_version: u64) -> Vec<Transaction> {
    let mut retries = 0;
    while retries < MAX_RETRIES {
        retries += 1;

        let res = client
            .get_transactions(Some(starting_version), Some(TRANSACTION_FETCH_BATCH_SIZE))
            .await;

        match res {
            Ok(response) => {
                FETCHED_TRANSACTION.inc();
                return remove_null_bytes_from_txns(response.into_inner());
            }
            Err(err) => {
                let err_str = err.to_string();
                // If it's a 404, then we're all caught up; no need to increment the `UNABLE_TO_FETCH_TRANSACTION` counter
                if err_str.contains("404") {
                    aptos_logger::debug!(
                            "Could not fetch {} transactions starting at {}: all caught up. Will check again in {}ms.",
                            TRANSACTION_FETCH_BATCH_SIZE,
                            starting_version,
                            RETRY_TIME_MILLIS,
                        );
                }
                UNABLE_TO_FETCH_TRANSACTION.inc();
                aptos_logger::error!(
                    "Could not fetch {} transactions starting at {}, will retry in {}ms. Err: {:?}",
                    TRANSACTION_FETCH_BATCH_SIZE,
                    starting_version,
                    RETRY_TIME_MILLIS,
                    err
                );
            }
        }
        tokio::time::sleep(Duration::from_millis(RETRY_TIME_MILLIS)).await;
    }

    panic!(
        "Could not fetch {} transactions starting at {} after {} retries!",
        TRANSACTION_FETCH_BATCH_SIZE, starting_version, MAX_RETRIES
    );
}

#[derive(Debug)]
pub struct TransactionFetcher {
    starting_version: u64,
    client: RestClient,
    fetcher_handle: Option<JoinHandle<()>>,
    transactions_sender: Option<mpsc::Sender<Vec<Transaction>>>,
    transaction_receiver: mpsc::Receiver<Vec<Transaction>>,
}

impl TransactionFetcher {
    pub fn new(node_url: Url, starting_version: Option<u64>) -> Self {
        let (transactions_sender, transaction_receiver) =
            mpsc::channel::<Vec<Transaction>>(TRANSACTION_CHANNEL_SIZE);

        let client = RestClient::new(node_url);

        Self {
            starting_version: starting_version.unwrap_or(0),
            client,
            fetcher_handle: None,
            transactions_sender: Some(transactions_sender),
            transaction_receiver,
        }
    }
}

#[async_trait::async_trait]
impl TransactionFetcherTrait for TransactionFetcher {
    /// Fetches the next batch based on its internal version counter
    /// Under the hood, it fetches TRANSACTION_FETCH_BATCH_SIZE versions in bulk (when needed), and uses that buffer to feed out
    /// In the event it can't fetch, it will keep retrying every RETRY_TIME_MILLIS ms
    async fn fetch_next_batch(&mut self) -> Vec<Transaction> {
        self.transaction_receiver
            .next()
            .await
            .expect("No transactions, producer of batches died")
    }

    /// fetches one version; this used for error checking/repair/etc
    /// In the event it can't, it will keep retrying every RETRY_TIME_MILLIS ms
    async fn fetch_version(&self, version: u64) -> Transaction {
        loop {
            let res = self.client.get_transaction_by_version(version).await;
            match res {
                Ok(response) => {
                    FETCHED_TRANSACTION.inc();
                    return response.into_inner();
                }
                Err(err) => {
                    UNABLE_TO_FETCH_TRANSACTION.inc();
                    aptos_logger::error!(
                        "Could not fetch version {}, will retry in {}ms. Err: {:?}",
                        version,
                        RETRY_TIME_MILLIS,
                        err
                    );
                    tokio::time::sleep(Duration::from_millis(RETRY_TIME_MILLIS)).await;
                }
            };
        }
    }

    async fn fetch_ledger_info(&mut self) -> State {
        let mut retries = 0;
        while retries < MAX_RETRIES {
            retries += 1;
            match self.client.get_ledger_information().await {
                Ok(inner) => return inner.into_inner(),
                Err(err) => {
                    aptos_logger::warn!(
                        "Failed to get ledger info, will retry in {}ms. Err: {:?}",
                        RETRY_TIME_MILLIS,
                        err
                    );
                    tokio::time::sleep(Duration::from_secs(RETRY_TIME_MILLIS)).await
                }
            }
        }
        panic!("Failed to get ledger info.");
    }

    async fn set_version(&mut self, version: u64) {
        if self.fetcher_handle.is_some() {
            panic!("TransactionFetcher already started!");
        }
        self.starting_version = version;
    }

    async fn start(&mut self) {
        if self.fetcher_handle.is_some() {
            panic!("TransactionFetcher already started!");
        }
        let client = self.client.clone();
        let transactions_sender = self.transactions_sender.take().unwrap();
        let starting_version = self.starting_version;
        let fetcher_handle = tokio::spawn(async move {
            let mut fetcher = Fetcher::new(client, starting_version, transactions_sender);
            fetcher.run().await;
        });
        self.fetcher_handle = Some(fetcher_handle);
    }
}

pub fn string_null_byte_replacement(value: &mut str) -> String {
    value.replace('\u{0000}', "").replace("\\u0000", "")
}

pub fn recurse_remove_null_bytes_from_json(sub_json: &mut Value) {
    match sub_json {
        Value::Array(array) => {
            for item in array {
                recurse_remove_null_bytes_from_json(item);
            }
        }
        Value::Object(object) => {
            for (_key, value) in object {
                recurse_remove_null_bytes_from_json(value);
            }
        }
        Value::String(str) => {
            if !str.is_empty() {
                let replacement = string_null_byte_replacement(str);
                *str = replacement;
            }
        }
        _ => {}
    }
}

pub fn remove_null_bytes_from_txns(txns: Vec<Transaction>) -> Vec<Transaction> {
    txns.iter()
        .map(|txn| {
            let mut txn_json = serde_json::to_value(txn).unwrap();
            recurse_remove_null_bytes_from_json(&mut txn_json);
            serde_json::from_value::<Transaction>(txn_json).unwrap()
        })
        .collect::<Vec<Transaction>>()
}

/// For mocking TransactionFetcher in tests
#[async_trait::async_trait]
pub trait TransactionFetcherTrait: Send + Sync {
    async fn fetch_next_batch(&mut self) -> Vec<Transaction>;

    async fn fetch_version(&self, version: u64) -> Transaction;

    async fn fetch_ledger_info(&mut self) -> State;

    async fn set_version(&mut self, version: u64);

    async fn start(&mut self);
}
<commit_msg>[indexer] Add exponential backoff to transaction fetcher<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::counters::{FETCHED_TRANSACTION, UNABLE_TO_FETCH_TRANSACTION};
use aptos_rest_client::{Client as RestClient, State, Transaction};
use futures::channel::mpsc;
use futures::{SinkExt, StreamExt};
use serde_json::Value;
use std::time::Duration;
use tokio::task::JoinHandle;
use url::Url;

// TODO: make this configurable
const RETRY_TIME_MILLIS: u64 = 1000;
const TRANSACTION_FETCH_BATCH_SIZE: u16 = 500;
const TRANSACTION_CHANNEL_SIZE: usize = 35;
const MAX_THREADS: usize = 10;
const MAX_RETRIES: usize = 10;

#[derive(Debug)]
pub struct Fetcher {
    client: RestClient,
    chain_id: u8,
    current_version: u64,
    highest_known_version: u64,
    transactions_sender: mpsc::Sender<Vec<Transaction>>,
}

impl Fetcher {
    pub fn new(
        client: RestClient,
        current_version: u64,
        transactions_sender: mpsc::Sender<Vec<Transaction>>,
    ) -> Self {
        Self {
            client,
            chain_id: 0,
            current_version,
            highest_known_version: current_version,
            transactions_sender,
        }
    }

    pub async fn set_highest_known_version(&mut self) -> anyhow::Result<()> {
        let res = self.client.get_ledger_information().await?;
        let state = res.state();
        self.highest_known_version = state.version;
        self.chain_id = state.chain_id;
        Ok(())
    }

    pub async fn run(&mut self) {
        loop {
            if self.current_version >= self.highest_known_version {
                tokio::time::sleep(Duration::from_millis(200)).await;
                if let Err(err) = self.set_highest_known_version().await {
                    aptos_logger::error!("Failed to set highest known version. Err: {:?}", err);
                    continue;
                }
            }

            let num_missing = self.highest_known_version - self.current_version;
            let num_batches = std::cmp::min(
                (num_missing as f64 / TRANSACTION_FETCH_BATCH_SIZE as f64).ceil() as u64,
                MAX_THREADS as u64,
            ) as usize;
            let mut futures = vec![];
            for i in 0..num_batches {
                futures.push(fetch_nexts(
                    self.client.clone(),
                    self.current_version + (i as u64 * TRANSACTION_FETCH_BATCH_SIZE as u64),
                ));
            }
            let mut res: Vec<Vec<Transaction>> = futures::future::join_all(futures).await;
            // Sort by first transaction of batch's version
            res.sort_by(|a, b| {
                a.first()
                    .unwrap()
                    .version()
                    .unwrap()
                    .cmp(&b.first().unwrap().version().unwrap())
            });

            // Send keeping track of the last version sent by the batch
            for batch in res {
                self.current_version = batch.last().unwrap().version().unwrap();
                self.transactions_sender
                    .send(batch)
                    .await
                    .expect("Should be able to send transaction on channel");
            }
        }
    }
}

/// Fetches the next version based on its internal version counter
/// Under the hood, it fetches TRANSACTION_FETCH_BATCH_SIZE versions in bulk (when needed), and uses that buffer to feed out
/// In the event it can't fetch, it will keep retrying every RETRY_TIME_MILLIS ms
async fn fetch_nexts(client: RestClient, starting_version: u64) -> Vec<Transaction> {
    let mut retries = 0;
    let mut backoff_time = Duration::from_millis(RETRY_TIME_MILLIS);
    while retries < MAX_RETRIES {
        retries += 1;

        let res = client
            .get_transactions(Some(starting_version), Some(TRANSACTION_FETCH_BATCH_SIZE))
            .await;

        match res {
            Ok(response) => {
                FETCHED_TRANSACTION.inc();
                return remove_null_bytes_from_txns(response.into_inner());
            }
            Err(err) => {
                let err_str = err.to_string();
                // If it's a 404, then we're all caught up; no need to increment the `UNABLE_TO_FETCH_TRANSACTION` counter
                if err_str.contains("404") {
                    aptos_logger::debug!(
                            "Could not fetch {} transactions starting at {}: all caught up. Will check again in {}ms.",
                            TRANSACTION_FETCH_BATCH_SIZE,
                            starting_version,
                            backoff_time.as_millis(),
                        );
                }
                UNABLE_TO_FETCH_TRANSACTION.inc();
                aptos_logger::error!(
                    "Could not fetch {} transactions starting at {}, will retry in {}ms. Err: {:?}",
                    TRANSACTION_FETCH_BATCH_SIZE,
                    starting_version,
                    backoff_time.as_millis(),
                    err
                );
            }
        }
        tokio::time::sleep(backoff_time).await;
        backoff_time = backoff_time * 3 / 2;
    }

    panic!(
        "Could not fetch {} transactions starting at {} after {} retries!",
        TRANSACTION_FETCH_BATCH_SIZE, starting_version, retries
    );
}

#[derive(Debug)]
pub struct TransactionFetcher {
    starting_version: u64,
    client: RestClient,
    fetcher_handle: Option<JoinHandle<()>>,
    transactions_sender: Option<mpsc::Sender<Vec<Transaction>>>,
    transaction_receiver: mpsc::Receiver<Vec<Transaction>>,
}

impl TransactionFetcher {
    pub fn new(node_url: Url, starting_version: Option<u64>) -> Self {
        let (transactions_sender, transaction_receiver) =
            mpsc::channel::<Vec<Transaction>>(TRANSACTION_CHANNEL_SIZE);

        let client = RestClient::new(node_url);

        Self {
            starting_version: starting_version.unwrap_or(0),
            client,
            fetcher_handle: None,
            transactions_sender: Some(transactions_sender),
            transaction_receiver,
        }
    }
}

#[async_trait::async_trait]
impl TransactionFetcherTrait for TransactionFetcher {
    /// Fetches the next batch based on its internal version counter
    async fn fetch_next_batch(&mut self) -> Vec<Transaction> {
        self.transaction_receiver
            .next()
            .await
            .expect("No transactions, producer of batches died")
    }

    /// fetches one version; this used for error checking/repair/etc
    /// In the event it can't, it will keep retrying every RETRY_TIME_MILLIS ms
    async fn fetch_version(&self, version: u64) -> Transaction {
        loop {
            let res = self.client.get_transaction_by_version(version).await;
            match res {
                Ok(response) => {
                    FETCHED_TRANSACTION.inc();
                    return response.into_inner();
                }
                Err(err) => {
                    UNABLE_TO_FETCH_TRANSACTION.inc();
                    aptos_logger::error!(
                        "Could not fetch version {}, will retry in {}ms. Err: {:?}",
                        version,
                        RETRY_TIME_MILLIS,
                        err
                    );
                    tokio::time::sleep(Duration::from_millis(RETRY_TIME_MILLIS)).await;
                }
            };
        }
    }

    async fn fetch_ledger_info(&mut self) -> State {
        let mut retries = 0;
        while retries < MAX_RETRIES {
            retries += 1;
            match self.client.get_ledger_information().await {
                Ok(inner) => return inner.into_inner(),
                Err(err) => {
                    aptos_logger::warn!(
                        "Failed to get ledger info, will retry in {}ms. Err: {:?}",
                        RETRY_TIME_MILLIS,
                        err
                    );
                    tokio::time::sleep(Duration::from_secs(RETRY_TIME_MILLIS)).await
                }
            }
        }
        panic!("Failed to get ledger info after {} retries", retries);
    }

    async fn set_version(&mut self, version: u64) {
        if self.fetcher_handle.is_some() {
            panic!("TransactionFetcher already started!");
        }
        self.starting_version = version;
    }

    async fn start(&mut self) {
        if self.fetcher_handle.is_some() {
            panic!("TransactionFetcher already started!");
        }
        let client = self.client.clone();
        let transactions_sender = self.transactions_sender.take().unwrap();
        let starting_version = self.starting_version;
        let fetcher_handle = tokio::spawn(async move {
            let mut fetcher = Fetcher::new(client, starting_version, transactions_sender);
            fetcher.run().await;
        });
        self.fetcher_handle = Some(fetcher_handle);
    }
}

pub fn string_null_byte_replacement(value: &mut str) -> String {
    value.replace('\u{0000}', "").replace("\\u0000", "")
}

pub fn recurse_remove_null_bytes_from_json(sub_json: &mut Value) {
    match sub_json {
        Value::Array(array) => {
            for item in array {
                recurse_remove_null_bytes_from_json(item);
            }
        }
        Value::Object(object) => {
            for (_key, value) in object {
                recurse_remove_null_bytes_from_json(value);
            }
        }
        Value::String(str) => {
            if !str.is_empty() {
                let replacement = string_null_byte_replacement(str);
                *str = replacement;
            }
        }
        _ => {}
    }
}

pub fn remove_null_bytes_from_txns(txns: Vec<Transaction>) -> Vec<Transaction> {
    txns.iter()
        .map(|txn| {
            let mut txn_json = serde_json::to_value(txn).unwrap();
            recurse_remove_null_bytes_from_json(&mut txn_json);
            serde_json::from_value::<Transaction>(txn_json).unwrap()
        })
        .collect::<Vec<Transaction>>()
}

/// For mocking TransactionFetcher in tests
#[async_trait::async_trait]
pub trait TransactionFetcherTrait: Send + Sync {
    async fn fetch_next_batch(&mut self) -> Vec<Transaction>;

    async fn fetch_version(&self, version: u64) -> Transaction;

    async fn fetch_ledger_info(&mut self) -> State;

    async fn set_version(&mut self, version: u64);

    async fn start(&mut self);
}
<|endoftext|>


<commit_before>//! Use this module to interact with Bitstamp exchange.
//! Please see examples for more informations.

use hyper_native_tls::NativeTlsClient;
use hyper::Client;
use hyper::header::ContentType;
use hyper::net::HttpsConnector;

use serde_json::Value;
use serde_json::value::Map;

use std::collections::HashMap;
use std::io::Read;
use std::thread;
use std::time::Duration;

use coinnect::Credentials;
use exchange::Exchange;

use error::*;
use helpers;
use types::Pair;
use bitstamp::utils;
use types::*;

header! {
    #[doc(hidden)]
    (KeyHeader, "Key") => [String]
}

header! {
    #[doc(hidden)]
    (SignHeader, "Sign") => [String]
}

header! {
    #[doc(hidden)]
    (ContentHeader, "Content-Type") => [String]
}

#[derive(Debug)]
pub struct BitstampApi {
    last_request: i64, // unix timestamp in ms, to avoid ban
    api_key: String,
    api_secret: String,
    customer_id: String,
    http_client: Client,
    burst: bool,
}

impl BitstampApi {
    /// Create a new BitstampApi by providing an API key & API secret
    pub fn new<C: Credentials>(creds: C) -> Result<BitstampApi> {
        if creds.exchange() != Exchange::Bitstamp {
            return Err(ErrorKind::InvalidConfigType(Exchange::Bitstamp, creds.exchange()).into());
        }

        //TODO: Handle correctly TLS errors with error_chain.
        let ssl = match NativeTlsClient::new() {
            Ok(res) => res,
            Err(_) => return Err(ErrorKind::TlsError.into()),
        };

        let connector = HttpsConnector::new(ssl);

        Ok(BitstampApi {
               last_request: 0,
               api_key: creds.get("api_key").unwrap_or_default(),
               api_secret: creds.get("api_secret").unwrap_or_default(),
               customer_id: creds.get("customer_id").unwrap_or_default(),
               http_client: Client::with_connector(connector),
               burst: false, // No burst by default
           })
    }

    /// The number of calls in a given period is limited. In order to avoid a ban we limit
    /// by default the number of api requests.
    /// This function sets or removes the limitation.
    /// Burst false implies no block.
    /// Burst true implies there is a control over the number of calls allowed to the exchange
    pub fn set_burst(&mut self, burst: bool) {
        self.burst = burst
    }

    fn block_or_continue(&self) {
        if ! self.burst {
            let threshold: u64 = 1000; // 600 requests per 10 mins = 1 request per second
            let offset: u64 = helpers::get_unix_timestamp_ms() as u64 - self.last_request as u64;
            if offset < threshold {
                let wait_ms = Duration::from_millis(threshold - offset);
                thread::sleep(wait_ms);
            }
        }
    }

    fn public_query(&mut self, params: &HashMap<&str, &str>) -> Result<Map<String, Value>> {

        let method: &str = params
            .get("method")
            .ok_or_else(|| "Missing \"method\" field.")?;
        let pair: &str = params.get("pair").ok_or_else(|| "Missing \"pair\" field.")?;
        let url: String = utils::build_url(method, pair);

        self.block_or_continue();
        let mut response = self.http_client.get(&url).send()?;
        self.last_request = helpers::get_unix_timestamp_ms();
        let mut buffer = String::new();
        response.read_to_string(&mut buffer)?;
        utils::deserialize_json(&buffer)
    }

    ///
    ///
    /// #Examples
    ///
    /// ```json
    /// extern crate coinnect;
    /// use coinnect::bitstamp::BitstampApi;
    /// let mut api = BitstampApi::new("", "");
    /// let  result = api.private_query("balance", "btcusd");
    /// assert_eq!(true, true);
    /// ```
    fn private_query(&mut self, params: &HashMap<&str, &str>) -> Result<Map<String, Value>> {

        let method: &str = params
            .get("method")
            .ok_or_else(|| "Missing \"method\" field.")?;
        let pair: &str = params.get("pair").ok_or_else(|| "Missing \"pair\" field.")?;
        let url: String = utils::build_url(method, pair);

        let nonce = utils::generate_nonce(None);
        let signature =
            utils::build_signature(&nonce, &self.customer_id, &self.api_key, &self.api_secret)?;

        let copy_api_key = self.api_key.clone();
        let mut post_params: &mut HashMap<&str, &str> = &mut HashMap::new();
        post_params.insert("key", &copy_api_key);
        post_params.insert("signature", &signature);
        post_params.insert("nonce", &nonce);
        helpers::strip_empties(&mut post_params);
        let post_data = helpers::url_encode_hashmap(post_params);
        let mut response = self.http_client
            .post(&url)
            .header(ContentType::form_url_encoded())
            .body(&post_data)
            .send()?;

        let mut buffer = String::new();
        response.read_to_string(&mut buffer)?;
        utils::deserialize_json(&buffer)
    }

    /// Sample output :
    ///
    /// ```json
    /// {
    /// "BTC_LTC":{
    /// "last":"0.0251","lowestAsk":"0.02589999","highestBid":"0.0251",
    /// "percentChange":"0.02390438","baseVolume":"6.16485315","quoteVolume":"245.82513926"},
    /// "BTC_NXT":{
    /// "last":"0.00005730","lowestAsk":"0.00005710","highestBid":"0.00004903",
    /// "percentChange":"0.16701570","baseVolume":"0.45347489","quoteVolume":"9094"},
    /// ... }
    /// ```
    pub fn return_ticker(&mut self, pair: Pair) -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),
        };

        let mut params: HashMap<&str, &str> = HashMap::new();
        params.insert("pair", pair_name);
        params.insert("method", "ticker");
        self.public_query(&params)
    }

    /// Sample output :
    ///
    /// ```json
    /// {"asks":[[0.00007600,1164],[0.00007620,1300], ... ], "bids":[[0.00006901,200],
    /// [0.00006900,408], ... ], "timestamp": "1234567890"}
    /// ```
    pub fn return_order_book(&mut self, pair: Pair) -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),

        };

        let mut params: HashMap<&str, &str> = HashMap::new();
        params.insert("method", "order_book");
        params.insert("pair", pair_name);
        self.public_query(&params)
    }

    /// Sample output :
    ///
    /// ```json
    /// [{"date":"2014-02-10 04:23:23","type":"buy","rate":"0.00007600","amount":"140",
    /// "total":"0.01064"},
    /// {"date":"2014-02-10 01:19:37","type":"buy","rate":"0.00007600","amount":"655",
    /// "total":"0.04978"}, ... ]
    /// ```
    pub fn return_trade_history(&mut self, pair: Pair) -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),
        };

        let mut params: HashMap<&str, &str> = HashMap::new();
        params.insert("pair", pair_name);
        params.insert("method", "transactions");
        self.public_query(&params)
    }

    /// Returns all of your available balances.
    ///
    /// Sample output:
    ///
    /// ```json
    /// {"BTC":"0.59098578","LTC":"3.31117268", ... }
    /// ```
    pub fn return_balances(&mut self) -> Result<Map<String, Value>> {
        let mut params = HashMap::new();
        params.insert("method", "balance");
        params.insert("pair", "");
        self.private_query(&params)
    }

    /// Add a buy limit order to the exchange
    /// limit_price	: If the order gets executed, a new sell order will be placed,
    /// with "limit_price" as its price.
    /// daily_order (Optional) : Opens buy limit order which will be canceled
    /// at 0:00 UTC unless it already has been executed. Possible value: True
    pub fn buy_limit(&mut self,
                     pair: Pair,
                     amount: Volume,
                     price: Price,
                     price_limit: Option<Price>,
                     daily_order: Option<bool>)
                     -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),
        };

        let amount_string = amount.to_string();
        let price_string = price.to_string();
        let price_limit_string = match price_limit {
            Some(limit) => limit.to_string(),
            None => "".to_string(),
        };

        let mut params = HashMap::new();
        params.insert("method", "buy");
        params.insert("pair", pair_name);

        params.insert("amount", &amount_string);
        params.insert("price", &price_string);
        params.insert("limit_price", &price_limit_string);
        if let Some(order) = daily_order {
            let daily_order_str = if order { "True" } else { "" }; // False is not a possible value
            params.insert("daily_order", daily_order_str);
        }

        self.private_query(&params)
    }

    /// Add a sell limit order to the exchange
    /// limit_price	: If the order gets executed, a new sell order will be placed,
    /// with "limit_price" as its price.
    /// daily_order (Optional) : Opens sell limit order which will be canceled
    /// at 0:00 UTC unless it already has been executed. Possible value: True
    pub fn sell_limit(&mut self,
                      pair: Pair,
                      amount: Volume,
                      price: Price,
                      price_limit: Option<Price>,
                      daily_order: Option<bool>)
                      -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),
        };

        let amount_string = amount.to_string();
        let price_string = price.to_string();
        let price_limit_string = match price_limit {
            Some(limit) => limit.to_string(),
            None => "".to_string(),
        };

        let mut params = HashMap::new();
        params.insert("method", "sell");
        params.insert("pair", pair_name);

        params.insert("amount", &amount_string);
        params.insert("price", &price_string);
        params.insert("limit_price", &price_limit_string);
        if let Some(order) = daily_order {
            let daily_order_str = if order { "True" } else { "" }; // False is not a possible value
            params.insert("daily_order", daily_order_str);
        }

        self.private_query(&params)
    }

    /// Add a market buy order to the exchange
    /// By placing a market order you acknowledge that the execution of your order depends
    /// on the market conditions and that these conditions may be subject to sudden changes
    /// that cannot be foreseen.
    pub fn buy_market(&mut self, pair: Pair, amount: Volume) -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),
        };

        let amount_string = amount.to_string();

        let mut params = HashMap::new();
        params.insert("method", "buy/market");
        params.insert("pair", pair_name);

        params.insert("amount", &amount_string);

        self.private_query(&params)
    }

    /// Add a market sell order to the exchange
    /// By placing a market order you acknowledge that the execution of your order depends
    /// on the market conditions and that these conditions may be subject to sudden changes
    /// that cannot be foreseen.
    pub fn sell_market(&mut self, pair: Pair, amount: Volume) -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),
        };

        let amount_string = amount.to_string();

        let mut params = HashMap::new();
        params.insert("method", "sell/market");
        params.insert("pair", pair_name);

        params.insert("amount", &amount_string);

        self.private_query(&params)
    }
}

#[cfg(test)]
mod bitstamp_api_tests {
    use super::*;

    #[test]
    fn should_block_or_not_block_when_enabled_or_disabled() {
        let mut api = BitstampApi {
            last_request: helpers::get_unix_timestamp_ms(),
            api_key: "".to_string(),
            api_secret: "".to_string(),
            customer_id: "".to_string(),
            http_client: Client::new(),
            burst: false,
        };

        let mut counter = 0;
        loop {
            api.set_burst(false);
            let start = helpers::get_unix_timestamp_ms();
            api.block_or_continue();
            api.last_request = helpers::get_unix_timestamp_ms();

            let difference = api.last_request - start;
            assert!(difference >= 999);
            assert!(difference < 10000);

            api.set_burst(true);
            let start = helpers::get_unix_timestamp_ms();
            api.block_or_continue();
            api.last_request = helpers::get_unix_timestamp_ms();

            let difference = api.last_request - start;
            assert!(difference < 10);

            counter = counter + 1;
            if counter >= 3 { break; }
        }
    }
}
<commit_msg>add params to POST params for bitstamp<commit_after>//! Use this module to interact with Bitstamp exchange.
//! Please see examples for more informations.

use hyper_native_tls::NativeTlsClient;
use hyper::Client;
use hyper::header::ContentType;
use hyper::net::HttpsConnector;

use serde_json::Value;
use serde_json::value::Map;

use std::collections::HashMap;
use std::io::Read;
use std::thread;
use std::time::Duration;

use coinnect::Credentials;
use exchange::Exchange;

use error::*;
use helpers;
use types::Pair;
use bitstamp::utils;
use types::*;

header! {
    #[doc(hidden)]
    (KeyHeader, "Key") => [String]
}

header! {
    #[doc(hidden)]
    (SignHeader, "Sign") => [String]
}

header! {
    #[doc(hidden)]
    (ContentHeader, "Content-Type") => [String]
}

#[derive(Debug)]
pub struct BitstampApi {
    last_request: i64, // unix timestamp in ms, to avoid ban
    api_key: String,
    api_secret: String,
    customer_id: String,
    http_client: Client,
    burst: bool,
}

impl BitstampApi {
    /// Create a new BitstampApi by providing an API key & API secret
    pub fn new<C: Credentials>(creds: C) -> Result<BitstampApi> {
        if creds.exchange() != Exchange::Bitstamp {
            return Err(ErrorKind::InvalidConfigType(Exchange::Bitstamp, creds.exchange()).into());
        }

        //TODO: Handle correctly TLS errors with error_chain.
        let ssl = match NativeTlsClient::new() {
            Ok(res) => res,
            Err(_) => return Err(ErrorKind::TlsError.into()),
        };

        let connector = HttpsConnector::new(ssl);

        Ok(BitstampApi {
               last_request: 0,
               api_key: creds.get("api_key").unwrap_or_default(),
               api_secret: creds.get("api_secret").unwrap_or_default(),
               customer_id: creds.get("customer_id").unwrap_or_default(),
               http_client: Client::with_connector(connector),
               burst: false, // No burst by default
           })
    }

    /// The number of calls in a given period is limited. In order to avoid a ban we limit
    /// by default the number of api requests.
    /// This function sets or removes the limitation.
    /// Burst false implies no block.
    /// Burst true implies there is a control over the number of calls allowed to the exchange
    pub fn set_burst(&mut self, burst: bool) {
        self.burst = burst
    }

    fn block_or_continue(&self) {
        if ! self.burst {
            let threshold: u64 = 1000; // 600 requests per 10 mins = 1 request per second
            let offset: u64 = helpers::get_unix_timestamp_ms() as u64 - self.last_request as u64;
            if offset < threshold {
                let wait_ms = Duration::from_millis(threshold - offset);
                thread::sleep(wait_ms);
            }
        }
    }

    fn public_query(&mut self, params: &HashMap<&str, &str>) -> Result<Map<String, Value>> {

        let method: &str = params
            .get("method")
            .ok_or_else(|| "Missing \"method\" field.")?;
        let pair: &str = params.get("pair").ok_or_else(|| "Missing \"pair\" field.")?;
        let url: String = utils::build_url(method, pair);

        self.block_or_continue();
        let mut response = self.http_client.get(&url).send()?;
        self.last_request = helpers::get_unix_timestamp_ms();
        let mut buffer = String::new();
        response.read_to_string(&mut buffer)?;
        utils::deserialize_json(&buffer)
    }

    ///
    ///
    /// #Examples
    ///
    /// ```json
    /// extern crate coinnect;
    /// use coinnect::bitstamp::BitstampApi;
    /// let mut api = BitstampApi::new("", "");
    /// let  result = api.private_query("balance", "btcusd");
    /// assert_eq!(true, true);
    /// ```
    fn private_query(&mut self, params: &HashMap<&str, &str>) -> Result<Map<String, Value>> {

        let method: &str = params
            .get("method")
            .ok_or_else(|| "Missing \"method\" field.")?;
        let pair: &str = params.get("pair").ok_or_else(|| "Missing \"pair\" field.")?;
        let url: String = utils::build_url(method, pair);

        let nonce = utils::generate_nonce(None);
        let signature =
            utils::build_signature(&nonce, &self.customer_id, &self.api_key, &self.api_secret)?;

        let copy_api_key = self.api_key.clone();
        let mut post_params: &mut HashMap<&str, &str> = &mut HashMap::new();
        post_params.insert("key", &copy_api_key);
        post_params.insert("signature", &signature);
        post_params.insert("nonce", &nonce);

        // copy params into post_params .... bit of a hack but will do for now
        params.iter().for_each(|(k,v)| {
            post_params.insert(k,v);
        });

        helpers::strip_empties(&mut post_params);
        let post_data = helpers::url_encode_hashmap(post_params);
        let mut response = self.http_client
            .post(&url)
            .header(ContentType::form_url_encoded())
            .body(&post_data)
            .send()?;

        let mut buffer = String::new();
        response.read_to_string(&mut buffer)?;
        utils::deserialize_json(&buffer)
    }

    /// Sample output :
    ///
    /// ```json
    /// {
    /// "BTC_LTC":{
    /// "last":"0.0251","lowestAsk":"0.02589999","highestBid":"0.0251",
    /// "percentChange":"0.02390438","baseVolume":"6.16485315","quoteVolume":"245.82513926"},
    /// "BTC_NXT":{
    /// "last":"0.00005730","lowestAsk":"0.00005710","highestBid":"0.00004903",
    /// "percentChange":"0.16701570","baseVolume":"0.45347489","quoteVolume":"9094"},
    /// ... }
    /// ```
    pub fn return_ticker(&mut self, pair: Pair) -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),
        };

        let mut params: HashMap<&str, &str> = HashMap::new();
        params.insert("pair", pair_name);
        params.insert("method", "ticker");
        self.public_query(&params)
    }

    /// Sample output :
    ///
    /// ```json
    /// {"asks":[[0.00007600,1164],[0.00007620,1300], ... ], "bids":[[0.00006901,200],
    /// [0.00006900,408], ... ], "timestamp": "1234567890"}
    /// ```
    pub fn return_order_book(&mut self, pair: Pair) -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),

        };

        let mut params: HashMap<&str, &str> = HashMap::new();
        params.insert("method", "order_book");
        params.insert("pair", pair_name);
        self.public_query(&params)
    }

    /// Sample output :
    ///
    /// ```json
    /// [{"date":"2014-02-10 04:23:23","type":"buy","rate":"0.00007600","amount":"140",
    /// "total":"0.01064"},
    /// {"date":"2014-02-10 01:19:37","type":"buy","rate":"0.00007600","amount":"655",
    /// "total":"0.04978"}, ... ]
    /// ```
    pub fn return_trade_history(&mut self, pair: Pair) -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),
        };

        let mut params: HashMap<&str, &str> = HashMap::new();
        params.insert("pair", pair_name);
        params.insert("method", "transactions");
        self.public_query(&params)
    }

    /// Returns all of your available balances.
    ///
    /// Sample output:
    ///
    /// ```json
    /// {"BTC":"0.59098578","LTC":"3.31117268", ... }
    /// ```
    pub fn return_balances(&mut self) -> Result<Map<String, Value>> {
        let mut params = HashMap::new();
        params.insert("method", "balance");
        params.insert("pair", "");
        self.private_query(&params)
    }

    /// Add a buy limit order to the exchange
    /// limit_price	: If the order gets executed, a new sell order will be placed,
    /// with "limit_price" as its price.
    /// daily_order (Optional) : Opens buy limit order which will be canceled
    /// at 0:00 UTC unless it already has been executed. Possible value: True
    pub fn buy_limit(&mut self,
                     pair: Pair,
                     amount: Volume,
                     price: Price,
                     price_limit: Option<Price>,
                     daily_order: Option<bool>)
                     -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),
        };

        let amount_string = amount.to_string();
        let price_string = price.to_string();
        let price_limit_string = match price_limit {
            Some(limit) => limit.to_string(),
            None => "".to_string(),
        };

        let mut params = HashMap::new();
        params.insert("method", "buy");
        params.insert("pair", pair_name);

        params.insert("amount", &amount_string);
        params.insert("price", &price_string);
        params.insert("limit_price", &price_limit_string);
        if let Some(order) = daily_order {
            let daily_order_str = if order { "True" } else { "" }; // False is not a possible value
            params.insert("daily_order", daily_order_str);
        }

        self.private_query(&params)
    }

    /// Add a sell limit order to the exchange
    /// limit_price	: If the order gets executed, a new sell order will be placed,
    /// with "limit_price" as its price.
    /// daily_order (Optional) : Opens sell limit order which will be canceled
    /// at 0:00 UTC unless it already has been executed. Possible value: True
    pub fn sell_limit(&mut self,
                      pair: Pair,
                      amount: Volume,
                      price: Price,
                      price_limit: Option<Price>,
                      daily_order: Option<bool>)
                      -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),
        };

        let amount_string = amount.to_string();
        let price_string = price.to_string();
        let price_limit_string = match price_limit {
            Some(limit) => limit.to_string(),
            None => "".to_string(),
        };

        let mut params = HashMap::new();
        params.insert("method", "sell");
        params.insert("pair", pair_name);

        params.insert("amount", &amount_string);
        params.insert("price", &price_string);
        params.insert("limit_price", &price_limit_string);
        if let Some(order) = daily_order {
            let daily_order_str = if order { "True" } else { "" }; // False is not a possible value
            params.insert("daily_order", daily_order_str);
        }

        self.private_query(&params)
    }

    /// Add a market buy order to the exchange
    /// By placing a market order you acknowledge that the execution of your order depends
    /// on the market conditions and that these conditions may be subject to sudden changes
    /// that cannot be foreseen.
    pub fn buy_market(&mut self, pair: Pair, amount: Volume) -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),
        };

        let amount_string = amount.to_string();

        let mut params = HashMap::new();
        params.insert("method", "buy/market");
        params.insert("pair", pair_name);

        params.insert("amount", &amount_string);

        self.private_query(&params)
    }

    /// Add a market sell order to the exchange
    /// By placing a market order you acknowledge that the execution of your order depends
    /// on the market conditions and that these conditions may be subject to sudden changes
    /// that cannot be foreseen.
    pub fn sell_market(&mut self, pair: Pair, amount: Volume) -> Result<Map<String, Value>> {
        let pair_name = match utils::get_pair_string(&pair) {
            Some(name) => name,
            None => return Err(ErrorKind::PairUnsupported.into()),
        };

        let amount_string = amount.to_string();

        let mut params = HashMap::new();
        params.insert("method", "sell/market");
        params.insert("pair", pair_name);

        params.insert("amount", &amount_string);

        self.private_query(&params)
    }
}

#[cfg(test)]
mod bitstamp_api_tests {
    use super::*;

    #[test]
    fn should_block_or_not_block_when_enabled_or_disabled() {
        let mut api = BitstampApi {
            last_request: helpers::get_unix_timestamp_ms(),
            api_key: "".to_string(),
            api_secret: "".to_string(),
            customer_id: "".to_string(),
            http_client: Client::new(),
            burst: false,
        };

        let mut counter = 0;
        loop {
            api.set_burst(false);
            let start = helpers::get_unix_timestamp_ms();
            api.block_or_continue();
            api.last_request = helpers::get_unix_timestamp_ms();

            let difference = api.last_request - start;
            assert!(difference >= 999);
            assert!(difference < 10000);

            api.set_burst(true);
            let start = helpers::get_unix_timestamp_ms();
            api.block_or_continue();
            api.last_request = helpers::get_unix_timestamp_ms();

            let difference = api.last_request - start;
            assert!(difference < 10);

            counter = counter + 1;
            if counter >= 3 { break; }
        }
    }
}
<|endoftext|>


<commit_before>//
// GLSL Mathematics for Rust.
//
// Copyright (c) 2015 The glm-rs authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// The GLSL Specification, ch 8.5, Geometric Functions.
//
// NOTE:
// - `ftransform` is not implemented.

use basenum::BaseFloat;
use traits::GenFloat;
use vec::traits::GenFloatVec;
use vec::vec::Vector3;
use super::exp::inversesqrt;

/// Returns the dot product of `x` and `y`, i.e.,
/// `x[0] * y[0] + x[1] * y[1] + ...`.
///
/// # Example
///
/// ```
/// use glm::{ dot, vec2 };
///
/// let v1 = vec2(1., 2.);
/// let v2 = vec2(3., 4.);
/// assert_eq!(dot(v1, v2), 11.);
/// ```
#[inline(always)]
pub fn dot<S: BaseFloat, T: GenFloatVec<S>>(x: T, y: T) -> S {
    (x * y).sum()
}

/// Returns the length of vector `x`, i.e., `sqrt(x[0]^2 + x[1]^2 + ...)`.
///
/// # Example
///
/// ```
/// assert_eq!(glm::length(glm::vec2(3., 4.)), 5.);
/// ```
#[inline(always)]
pub fn length<S: BaseFloat, T: GenFloatVec<S>>(x: T) -> S {
    dot(x, x).sqrt()
}

/// Returns a vector in the same direction as `x` but with a length of `1`.
///
/// # Example
///
/// ```
/// use glm::{ normalize, dvec2, ApproxEq };
///
/// assert!(normalize(dvec2(3., 4.)).is_approx_eq(&dvec2(0.6, 0.8)));
/// ```
#[inline(always)]
pub fn normalize<S: BaseFloat + GenFloat<S>, T: GenFloatVec<S>>(x: T) -> T {
    x * inversesqrt(dot(x, x))
}

/// Returns the distance between `p0` and `p1`, i.e., `length(p0 – p1)`.
///
/// # Example
///
/// ```
/// use glm::{ distance, vec2 };
///
/// let v1 = vec2(1., 2.);
/// let v2 = vec2(4., 6.);
/// assert_eq!(distance(v1, v2), 5.);
/// ```
#[inline(always)]
pub fn distance<S: BaseFloat, T: GenFloatVec<S>>(p0: T, p1: T) -> S {
    length(p0 - p1)
}

/// If `dot(Nref, I) < 0` return *N*, otherwise return *-N*.
#[inline]
#[allow(non_snake_case)]
pub fn faceforward<S: BaseFloat, T: GenFloatVec<S>>(N: T, I: T, Nref: T) -> T {
    let ling = S::zero();
    if dot(Nref, I) < ling {
        N
    } else {
        -N
    }
}

/// For the incident vector *I* and surface orientation *N*,
/// returns the reflection direction:
/// ```
/// I – 2 ∗ dot(N, I) ∗ N
/// ```
///
/// *N* must already be normalized in order to achieve the desired result.
#[inline]
#[allow(non_snake_case)]
pub fn reflect<S: BaseFloat, T: GenFloatVec<S>>(I: T, N: T) -> T {
    let d = dot(N, I);
    I - N * (d + d)
}

/// For the incident vector *I* and surface normal *N*, and the ratio of
/// indices of refraction `eta`, return the refraction vector.
///
/// The result is computed by,
/// ```
/// k = 1.0 - eta * eta * (1.0 - dot(N, I) * dot(N, I))
/// if (k < 0.0)
///     return genType(0.0) // or genDType(0.0)
/// else
///     return eta * I - (eta * dot(N, I) + sqrt(k)) * N
/// ```
///
/// The input parameters for the incident vector *I* and the surface normal *N*
/// must already be normalized to get the desired results.
#[inline]
#[allow(non_snake_case)]
pub fn refract<S: BaseFloat, T: GenFloatVec<S>>(I: T, N: T, eta: S) -> T {
    let dot_ni = dot(I, N);
    let yi = S::one();
    let ling = S::zero();

    let k = yi - eta * eta * (yi - dot_ni) * dot_ni;
    if k < ling {
        T::zero()
    } else {
        I * eta - N * (eta * dot_ni + k.sqrt())
    }
}

/// Returns the cross product of `x` and `y`.
///
/// # Example
///
/// ```
/// use glm::vec3;
///
/// let x = vec3(1.0, 0.0, 0.0);
/// let y = vec3(0.0, 1.0, 0.0);
/// let z = vec3(0.0, 0.0, 1.0);
/// assert_eq!(glm::cross(x, y), z);
/// ```
#[inline]
pub fn cross<F: BaseFloat>(x: Vector3<F>, y: Vector3<F>) -> Vector3<F> {
    Vector3::new(
        x.y * y.z - y.y * x.z,
        x.z * y.x - y.z * x.x,
        x.x * y.y - y.x * x.y
    )
}
<commit_msg>Ignore non-test code in documentation.<commit_after>//
// GLSL Mathematics for Rust.
//
// Copyright (c) 2015 The glm-rs authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// The GLSL Specification, ch 8.5, Geometric Functions.
//
// NOTE:
// - `ftransform` is not implemented.

use basenum::BaseFloat;
use traits::GenFloat;
use vec::traits::GenFloatVec;
use vec::vec::Vector3;
use super::exp::inversesqrt;

/// Returns the dot product of `x` and `y`, i.e.,
/// `x[0] * y[0] + x[1] * y[1] + ...`.
///
/// # Example
///
/// ```
/// use glm::{ dot, vec2 };
///
/// let v1 = vec2(1., 2.);
/// let v2 = vec2(3., 4.);
/// assert_eq!(dot(v1, v2), 11.);
/// ```
#[inline(always)]
pub fn dot<S: BaseFloat, T: GenFloatVec<S>>(x: T, y: T) -> S {
    (x * y).sum()
}

/// Returns the length of vector `x`, i.e., `sqrt(x[0]^2 + x[1]^2 + ...)`.
///
/// # Example
///
/// ```
/// assert_eq!(glm::length(glm::vec2(3., 4.)), 5.);
/// ```
#[inline(always)]
pub fn length<S: BaseFloat, T: GenFloatVec<S>>(x: T) -> S {
    dot(x, x).sqrt()
}

/// Returns a vector in the same direction as `x` but with a length of `1`.
///
/// # Example
///
/// ```
/// use glm::{ normalize, dvec2, ApproxEq };
///
/// assert!(normalize(dvec2(3., 4.)).is_approx_eq(&dvec2(0.6, 0.8)));
/// ```
#[inline(always)]
pub fn normalize<S: BaseFloat + GenFloat<S>, T: GenFloatVec<S>>(x: T) -> T {
    x * inversesqrt(dot(x, x))
}

/// Returns the distance between `p0` and `p1`, i.e., `length(p0 – p1)`.
///
/// # Example
///
/// ```
/// use glm::{ distance, vec2 };
///
/// let v1 = vec2(1., 2.);
/// let v2 = vec2(4., 6.);
/// assert_eq!(distance(v1, v2), 5.);
/// ```
#[inline(always)]
pub fn distance<S: BaseFloat, T: GenFloatVec<S>>(p0: T, p1: T) -> S {
    length(p0 - p1)
}

/// If `dot(Nref, I) < 0` return *N*, otherwise return *-N*.
#[inline]
#[allow(non_snake_case)]
pub fn faceforward<S: BaseFloat, T: GenFloatVec<S>>(N: T, I: T, Nref: T) -> T {
    let ling = S::zero();
    if dot(Nref, I) < ling {
        N
    } else {
        -N
    }
}

/// For the incident vector *I* and surface orientation *N*,
/// returns the reflection direction: `I - 2 ∗ dot(N, I) ∗ N`.
///
/// *N* must already be normalized in order to achieve the desired result.
#[inline]
#[allow(non_snake_case)]
pub fn reflect<S: BaseFloat, T: GenFloatVec<S>>(I: T, N: T) -> T {
    let d = dot(N, I);
    I - N * (d + d)
}

/// For the incident vector *I* and surface normal *N*, and the ratio of
/// indices of refraction `eta`, return the refraction vector.
///
/// The result is computed by,
/// ```ignore
/// k = 1.0 - eta * eta * (1.0 - dot(N, I) * dot(N, I))
/// if (k < 0.0)
///     return genType(0.0) // or genDType(0.0)
/// else
///     return eta * I - (eta * dot(N, I) + sqrt(k)) * N
/// ```
///
/// The input parameters for the incident vector *I* and the surface normal *N*
/// must already be normalized to get the desired results.
#[inline]
#[allow(non_snake_case)]
pub fn refract<S: BaseFloat, T: GenFloatVec<S>>(I: T, N: T, eta: S) -> T {
    let dot_ni = dot(I, N);
    let yi = S::one();
    let ling = S::zero();

    let k = yi - eta * eta * (yi - dot_ni) * dot_ni;
    if k < ling {
        T::zero()
    } else {
        I * eta - N * (eta * dot_ni + k.sqrt())
    }
}

/// Returns the cross product of `x` and `y`.
///
/// # Example
///
/// ```
/// use glm::vec3;
///
/// let x = vec3(1.0, 0.0, 0.0);
/// let y = vec3(0.0, 1.0, 0.0);
/// let z = vec3(0.0, 0.0, 1.0);
/// assert_eq!(glm::cross(x, y), z);
/// ```
#[inline]
pub fn cross<F: BaseFloat>(x: Vector3<F>, y: Vector3<F>) -> Vector3<F> {
    Vector3::new(
        x.y * y.z - y.y * x.z,
        x.z * y.x - y.z * x.x,
        x.x * y.y - y.x * x.y
    )
}
<|endoftext|>


<commit_before>use regex::Regex;
use std::sync::mpsc::channel;

use adapter::ChatAdapter;
use handler::MessageHandler;

/// The Chatbot is the central data structure of the chatbot platform. It contains a `run` method
/// which listens for messages from adapters and routes them to handlers. Any program which uses
/// chatbot will need to minimally create a Chatbot, add an adapter, add a handler, and call Chatbot
/// [`run`](chatbot/struct.Chatbot.html#method.run).
pub struct Chatbot {
    name: String,
    adapters: Vec<Box<ChatAdapter>>,
    handlers: Vec<Box<MessageHandler>>,
    addressed_handlers: Vec<Box<MessageHandler>>,
    addresser: Regex
}

impl Chatbot {
    /// Create a new chatbot instance.
    ///
    /// The name provided here will be used for message filtering and in handlers for whatever they
    /// want. You'll want to make your binding mutable so you can call `add_adapter` and
    /// `add_handler`.
    pub fn new(name: &str) -> Chatbot {
        let addresser_str = format!(r"^\s*@?{}[:,\s]\s*", name);
        let addresser = Regex::new(addresser_str.as_ref());

        Chatbot {
            name: name.to_owned(),
            adapters: Vec::new(),
            handlers: Vec::new(),
            addressed_handlers: Vec::new(),
            addresser: addresser.unwrap()
        }
    }

    /// Return the name provided in initialization
    pub fn get_name(&self) -> &str {
        self.name.as_ref()
    }

    /// Return the regular expression of what the bot will be addressed by
    pub fn get_addresser(&self) -> &Regex {
        &self.addresser
    }

    /// Add a ChatAdapter to the bot
    ///
    /// Add as many adapters as you like. The IncomingMessages sent by adapters are made available
    /// to all handlers regardless of how many adapters exist. The IncomingMessage.reply method
    /// makes sure the response is sent back to the adapter from whence the message came.
    pub fn add_adapter<T>(&mut self, adapter: T)
        where T: ChatAdapter + 'static
    {
        println!("Adding adapter {}", adapter.get_name());
        self.adapters.push(Box::new(adapter))
    }

    /// Add a MessageHandler to the bot
    ///
    /// The more handlers you have the more useful your bot becomes (for potentially loose
    /// definitions of useful :P). Check out the handler! macro for making simple handlers and also
    /// see the `MessageHandler` implementors section for a list of built in handlers.
    pub fn add_handler<T>(&mut self, handler: T)
        where T: MessageHandler + 'static
    {
        println!("Adding handler {}", handler.name());
        self.handlers.push(Box::new(handler))
    }

    pub fn add_addressed_handler<T>(&mut self, handler: T)
        where T: MessageHandler + 'static
    {
        println!("Adding handler {}", handler.name());
        self.addressed_handlers.push(Box::new(handler))
    }

    /// Start processing messages
    ///
    /// Call process_events on all of the adapters and `recv` on the `IncomingMessage` channel.
    /// Distribute IncomingMessages to list of handlers.
    pub fn run(&self) {

        assert!(self.adapters.len() > 0);
        assert!(self.handlers.len() > 0);

        println!("Chatbot: {} adapters", self.adapters.len());
        println!("Chatbot: {} handlers", self.handlers.len());

        let (incoming_tx, incoming_rx) = channel();

        for adapter in &self.adapters {
            adapter.process_events(self, incoming_tx.clone());
        }

        loop {
            // Get message from adapter
            let msg = match incoming_rx.recv() {
                Ok(msg) => msg,
                Err(_) => break
            };
            let msg_str = msg.get_contents();

            let handlers = if self.addresser.is_match(msg_str) {
                &self.addressed_handlers
            } else {
                &self.handlers
            };

            // Distribute to handlers
            for handler in handlers {
                if handler.can_handle(msg_str) {
                    match handler.handle(&msg) {
                        Err(e) => {
                            println!("Error in handler `{}`", handler.name());
                            println!("{:?}", e);
                            println!("The incoming message was {}", msg_str);

                            // TODO remove handler?
                        },
                        _ => ()
                    }
                }
            }
        }

        println!("chatbot shutting down");
    }
}

#[cfg(test)]
mod tests {
    use chatbot::Chatbot;
    use adapter::CliAdapter;

    #[test]
    fn test_create_chatbot() {
        let name = "testbot";
        let bot = Chatbot::new("testbot");
        assert_eq!(bot.get_name(), name);
    }

    #[test]
    fn test_chatbot_add_adapter() {
        let mut bot = Chatbot::new("testbot");
        let cli = CliAdapter::new();
        bot.add_adapter(cli);
    }

    #[test]
    fn test_chatbot_add_handler() {
        let mut bot = Chatbot::new("testbot");
        let echo = handler!("EchoHandler", r"echo .+", |_, msg| {
            Some(msg.to_owned())
        });
        bot.add_handler(echo);
    }

    #[test]
    fn test_chatbot_address_by_name() {
        let mut bot = Chatbot::new("testbot");
        bot.address_by_name();
    }

    #[test]
    fn test_chatbot_address_matches() {
        let mut bot = Chatbot::new("testbot");
        bot.address_by_name();
        let addresser = bot.get_addresser().unwrap();

        assert!(!addresser.is_match("testbotping"),
                "Shouldn't match bot name without a break (space, colon, comma)");
        assert!(!addresser.is_match("@testbotping"),
                "Shouldn't match bot name with '@' sign minus a break (space, colon, comma)");

        // Space separation
        assert!(addresser.is_match("testbot ping"), "Should match bot name with space");
        assert!(addresser.is_match("@testbot ping"),
                "Should match bot name with '@' sign plus '@' space");

        // Colon separation
        assert!(addresser.is_match("testbot:ping"), "Should match bot name with colon");
        assert!(addresser.is_match("testbot: ping"), "Should match bot name with colon and space");
        assert!(addresser.is_match("@testbot:ping"),
                "Should match bot name with '@' sign plus colon");
        assert!(addresser.is_match("@testbot: ping"),
                "Should match bot name with '@' sign plus colon and space");

        // Comma separation
        assert!(addresser.is_match("testbot,ping"), "Should match bot name with comma");
        assert!(addresser.is_match("testbot, ping"), "Should match bot name with comma and space");
        assert!(addresser.is_match("@testbot,ping"),
                "Should match bot name with '@' sign plus comma");
        assert!(addresser.is_match("@testbot, ping"),
                "Should match bot name with '@' sign plus comma and space");
    }
}
<commit_msg>Change tests to reflect the new interface<commit_after>use regex::Regex;
use std::sync::mpsc::channel;

use adapter::ChatAdapter;
use handler::MessageHandler;

/// The Chatbot is the central data structure of the chatbot platform. It contains a `run` method
/// which listens for messages from adapters and routes them to handlers. Any program which uses
/// chatbot will need to minimally create a Chatbot, add an adapter, add a handler, and call Chatbot
/// [`run`](chatbot/struct.Chatbot.html#method.run).
pub struct Chatbot {
    name: String,
    adapters: Vec<Box<ChatAdapter>>,
    handlers: Vec<Box<MessageHandler>>,
    addressed_handlers: Vec<Box<MessageHandler>>,
    addresser: Regex
}

impl Chatbot {
    /// Create a new chatbot instance.
    ///
    /// The name provided here will be used for message filtering and in handlers for whatever they
    /// want. You'll want to make your binding mutable so you can call `add_adapter` and
    /// `add_handler`.
    pub fn new(name: &str) -> Chatbot {
        let addresser_str = format!(r"^\s*@?{}[:,\s]\s*", name);
        let addresser = Regex::new(addresser_str.as_ref());

        Chatbot {
            name: name.to_owned(),
            adapters: Vec::new(),
            handlers: Vec::new(),
            addressed_handlers: Vec::new(),
            addresser: addresser.unwrap()
        }
    }

    /// Return the name provided in initialization
    pub fn get_name(&self) -> &str {
        self.name.as_ref()
    }

    /// Return the regular expression of what the bot will be addressed by
    pub fn get_addresser(&self) -> &Regex {
        &self.addresser
    }

    /// Add a ChatAdapter to the bot
    ///
    /// Add as many adapters as you like. The IncomingMessages sent by adapters are made available
    /// to all handlers regardless of how many adapters exist. The IncomingMessage.reply method
    /// makes sure the response is sent back to the adapter from whence the message came.
    pub fn add_adapter<T>(&mut self, adapter: T)
        where T: ChatAdapter + 'static
    {
        println!("Adding adapter {}", adapter.get_name());
        self.adapters.push(Box::new(adapter))
    }

    /// Add a MessageHandler to the bot
    ///
    /// The more handlers you have the more useful your bot becomes (for potentially loose
    /// definitions of useful :P). Check out the handler! macro for making simple handlers and also
    /// see the `MessageHandler` implementors section for a list of built in handlers.
    pub fn add_handler<T>(&mut self, handler: T)
        where T: MessageHandler + 'static
    {
        println!("Adding handler {}", handler.name());
        self.handlers.push(Box::new(handler))
    }

    pub fn add_addressed_handler<T>(&mut self, handler: T)
        where T: MessageHandler + 'static
    {
        println!("Adding handler {}", handler.name());
        self.addressed_handlers.push(Box::new(handler))
    }

    /// Start processing messages
    ///
    /// Call process_events on all of the adapters and `recv` on the `IncomingMessage` channel.
    /// Distribute IncomingMessages to list of handlers.
    pub fn run(&self) {

        assert!(self.adapters.len() > 0);
        assert!(self.handlers.len() > 0);

        println!("Chatbot: {} adapters", self.adapters.len());
        println!("Chatbot: {} handlers", self.handlers.len());

        let (incoming_tx, incoming_rx) = channel();

        for adapter in &self.adapters {
            adapter.process_events(self, incoming_tx.clone());
        }

        loop {
            // Get message from adapter
            let msg = match incoming_rx.recv() {
                Ok(msg) => msg,
                Err(_) => break
            };
            let msg_str = msg.get_contents();

            let handlers = if self.addresser.is_match(msg_str) {
                &self.addressed_handlers
            } else {
                &self.handlers
            };

            // Distribute to handlers
            for handler in handlers {
                if handler.can_handle(msg_str) {
                    match handler.handle(&msg) {
                        Err(e) => {
                            println!("Error in handler `{}`", handler.name());
                            println!("{:?}", e);
                            println!("The incoming message was {}", msg_str);

                            // TODO remove handler?
                        },
                        _ => ()
                    }
                }
            }
        }

        println!("chatbot shutting down");
    }
}

#[cfg(test)]
mod tests {
    use chatbot::Chatbot;
    use adapter::CliAdapter;

    #[test]
    fn test_create_chatbot() {
        let name = "testbot";
        let bot = Chatbot::new("testbot");
        assert_eq!(bot.get_name(), name);
    }

    #[test]
    fn test_chatbot_add_adapter() {
        let mut bot = Chatbot::new("testbot");
        let cli = CliAdapter::new();
        bot.add_adapter(cli);
    }

    #[test]
    fn test_chatbot_add_handler() {
        let mut bot = Chatbot::new("testbot");
        let echo = handler!("EchoHandler", r"echo .+", |_, msg| {
            Some(msg.to_owned())
        });
        bot.add_handler(echo);
    }

    #[test]
    fn test_chatbot_address_matches() {
        let bot = Chatbot::new("testbot");
        let addresser = bot.get_addresser();

        assert!(!addresser.is_match("testbotping"),
                "Shouldn't match bot name without a break (space, colon, comma)");
        assert!(!addresser.is_match("@testbotping"),
                "Shouldn't match bot name with '@' sign minus a break (space, colon, comma)");

        // Space separation
        assert!(addresser.is_match("testbot ping"), "Should match bot name with space");
        assert!(addresser.is_match("@testbot ping"),
                "Should match bot name with '@' sign plus '@' space");

        // Colon separation
        assert!(addresser.is_match("testbot:ping"), "Should match bot name with colon");
        assert!(addresser.is_match("testbot: ping"), "Should match bot name with colon and space");
        assert!(addresser.is_match("@testbot:ping"),
                "Should match bot name with '@' sign plus colon");
        assert!(addresser.is_match("@testbot: ping"),
                "Should match bot name with '@' sign plus colon and space");

        // Comma separation
        assert!(addresser.is_match("testbot,ping"), "Should match bot name with comma");
        assert!(addresser.is_match("testbot, ping"), "Should match bot name with comma and space");
        assert!(addresser.is_match("@testbot,ping"),
                "Should match bot name with '@' sign plus comma");
        assert!(addresser.is_match("@testbot, ping"),
                "Should match bot name with '@' sign plus comma and space");
    }
}
<|endoftext|>


<commit_before>// Copyright 2016 Mozilla Foundation
//
// 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.

use crate::errors::*;
use clap::{Arg, ArgGroup, ErrorKind};
use std::env;
use std::ffi::OsString;
use std::path::PathBuf;
use std::str::FromStr;
use which::which_in;

const ENV_VAR_INTERNAL_START_SERVER: &str = "SCCACHE_START_SERVER";

#[derive(Debug, Clone)]
pub enum StatsFormat {
    Text,
    Json,
}

impl StatsFormat {
    fn as_str(&self) -> &'static str {
        match self {
            Self::Text => "text",
            Self::Json => "json",
        }
    }

    fn values() -> &'static [Self] {
        &[Self::Text, Self::Json]
    }
}

impl FromStr for StatsFormat {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> anyhow::Result<Self> {
        match s {
            "text" => Ok(Self::Text),
            "json" => Ok(Self::Json),
            _ => bail!("Unrecognized stats format: {:?}", s),
        }
    }
}

impl Default for StatsFormat {
    fn default() -> Self {
        Self::Text
    }
}

/// A specific command to run.
pub enum Command {
    /// Show cache statistics and exit.
    ShowStats(StatsFormat),
    /// Run background server.
    InternalStartServer,
    /// Start background server as a subprocess.
    StartServer,
    /// Stop background server.
    StopServer,
    /// Zero cache statistics and exit.
    ZeroStats,
    /// Show the status of the distributed client.
    DistStatus,
    /// Perform a login to authenticate for distributed compilation.
    DistAuth,
    /// Package a toolchain for distributed compilation (executable, out)
    PackageToolchain(PathBuf, PathBuf),
    /// Run a compiler command.
    Compile {
        /// The binary to execute.
        exe: OsString,
        /// The commandline arguments to pass to `exe`.
        cmdline: Vec<OsString>,
        /// The directory in which to execute the command.
        cwd: PathBuf,
        /// The environment variables to use for execution.
        env_vars: Vec<(OsString, OsString)>,
    },
}

fn flag_infer_long_and_short(name: &'static str) -> Arg<'static> {
    flag_infer_long(name).short(name.chars().next().expect("Name needs at least one char"))
}

fn flag_infer_long(name: &'static str) -> Arg<'static> {
    Arg::new(name).long(name)
}

/// Get the [`clap::Command`] used for argument parsing.
fn get_clap_command() -> clap::Command<'static> {
    clap::Command::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .trailing_var_arg(true)
        .max_term_width(110)
        .after_help(concat!(
            "Enabled features:\n",
            "    S3:        ",
            cfg!(feature = "s3"),
            "\n",
            "    Redis:     ",
            cfg!(feature = "redis"),
            "\n",
            "    Memcached: ",
            cfg!(feature = "memcached"),
            "\n",
            "    GCS:       ",
            cfg!(feature = "gcs"),
            "\n",
            "    Azure:     ",
            cfg!(feature = "azure"),
            "\n"
        ))
        .args(&[
            flag_infer_long_and_short("show-stats").help("show cache statistics"),
            flag_infer_long("start-server").help("start background server"),
            flag_infer_long("stop-server").help("stop background server"),
            flag_infer_long_and_short("zero-stats").help("zero statistics counters"),
            flag_infer_long("dist-auth").help("authenticate for distributed compilation"),
            flag_infer_long("dist-status").help("show status of the distributed client"),
            flag_infer_long("package-toolchain")
                .help("package toolchain for distributed compilation")
                .number_of_values(2)
                .value_names(&["EXE", "OUT"]),
            flag_infer_long("stats-format")
                .help("set output format of statistics")
                .value_name("FMT")
                .possible_values(StatsFormat::values().iter().map(StatsFormat::as_str))
                .default_value(StatsFormat::default().as_str()),
            Arg::new("CMD")
                .multiple_occurrences(true)
                .use_value_delimiter(false),
        ])
        .group(
            ArgGroup::new("one_and_only_one")
                .args(&[
                    "dist-auth",
                    "dist-status",
                    "show-stats",
                    "start-server",
                    "stop-server",
                    "zero-stats",
                    "package-toolchain",
                    "CMD",
                ])
                .required(true),
        )
}

/// Parse the commandline args into a `Result<Command>` to execute.
pub fn try_parse() -> Result<Command> {
    trace!("parse");

    let cwd =
        env::current_dir().context("sccache: Couldn't determine current working directory")?;

    // We only care if it's `1`
    let internal_start_server = env::var(ENV_VAR_INTERNAL_START_SERVER).as_deref() == Ok("1");
    let mut args: Vec<_> = env::args_os().collect();

    if !internal_start_server {
        if let Ok(exe) = env::current_exe() {
            match exe
                .file_stem()
                .and_then(|s| s.to_str())
                .map(|s| s.to_lowercase())
            {
                // If the executable has its standard name, do nothing.
                Some(ref e) if e == env!("CARGO_PKG_NAME") => {}
                // Otherwise, if it was copied/hardlinked under a different $name, act
                // as if it were invoked with `sccache $name`, but avoid $name resolving
                // to ourselves again if it's in the PATH.
                _ => {
                    if let (Some(path), Some(exe_filename)) = (env::var_os("PATH"), exe.file_name())
                    {
                        match which_in(exe_filename, Some(&path), &cwd) {
                            Ok(ref full_path)
                                if full_path.canonicalize()? == exe.canonicalize()? =>
                            {
                                if let Some(dir) = full_path.parent() {
                                    let path = env::join_paths(
                                        env::split_paths(&path).filter(|p| p != dir),
                                    )
                                    .ok();
                                    if let Ok(full_path) = which_in(exe_filename, path, &cwd) {
                                        args[0] = full_path.into();
                                    }
                                }
                            }
                            Ok(full_path) => args[0] = full_path.into(),
                            Err(_) => {}
                        }
                        args.insert(0, env!("CARGO_PKG_NAME").into());
                    }
                }
            }
        }
    }

    let matches_result = get_clap_command().try_get_matches_from(args);

    // A command can either be from `ENV_VAR_INTERNAL_START_SERVER` being set or from command-line
    // args. Validate things so that error messages are nice and the returned opts are correct
    match (internal_start_server, matches_result) {
        (true, Err(e)) => {
            // Need to make sure that the error from `clap` is due to a missing command and not
            // some other issue
            if e.kind() == ErrorKind::MissingRequiredArgument {
                Ok(Command::InternalStartServer)
            } else {
                Err(e.into())
            }
        }
        (false, Err(e)) => Err(e.into()),
        (true, Ok(_)) => {
            // `ENV_VAR_INTERNAL_START_SERVER` and a match means that more than one command was
            // provided
            bail!("`{ENV_VAR_INTERNAL_START_SERVER}=1` can't be used with other commands");
        }
        (false, Ok(matches)) => {
            let show_stats = matches.is_present("show-stats");
            let start_server = matches.is_present("start-server");
            let stop_server = matches.is_present("stop-server");
            let zero_stats = matches.is_present("zero-stats");
            let dist_auth = matches.is_present("dist-auth");
            let dist_status = matches.is_present("dist-status");
            let package_toolchain = matches.is_present("package-toolchain");
            let cmd = matches.is_present("CMD");

            if show_stats {
                let fmt = matches
                    .value_of_t("stats-format")
                    .expect("There is a default value");
                Ok(Command::ShowStats(fmt))
            } else if start_server {
                Ok(Command::StartServer)
            } else if stop_server {
                Ok(Command::StopServer)
            } else if zero_stats {
                Ok(Command::ZeroStats)
            } else if dist_auth {
                Ok(Command::DistAuth)
            } else if dist_status {
                Ok(Command::DistStatus)
            } else if package_toolchain {
                let mut toolchain_values: Vec<PathBuf> =
                    matches.values_of_t("package-toolchain")?;
                let maybe_out = toolchain_values.pop();
                let maybe_exe = toolchain_values.pop();
                match (maybe_exe, maybe_out) {
                    (Some(exe), Some(out)) => Ok(Command::PackageToolchain(exe, out)),
                    _ => unreachable!("clap should enforce two values"),
                }
            } else if cmd {
                let mut env_vars = env::vars_os().collect::<Vec<_>>();

                // If we're running under rr, avoid the `LD_PRELOAD` bits, as it will
                // almost surely do the wrong thing, as the compiler gets executed
                // in a different process tree.
                //
                // FIXME: Maybe we should strip out `LD_PRELOAD` always?
                if env::var_os("RUNNING_UNDER_RR").is_some() {
                    env_vars.retain(|(k, _v)| k != "LD_PRELOAD" && k != "RUNNING_UNDER_RR");
                }

                let cmd: Vec<OsString> = matches.values_of_t("CMD")?;
                match cmd.as_slice() {
                    [exe, cmdline @ ..] => Ok(Command::Compile {
                        exe: exe.to_owned(),
                        cmdline: cmdline.to_owned(),
                        cwd,
                        env_vars,
                    }),
                    _ => unreachable!("clap should enforce at least one value in cmd"),
                }
            } else {
                unreachable!("Either the arg group or env variable should provide a command");
            }
        }
    }
}
<commit_msg>Inline `matches.is_present()` usages<commit_after>// Copyright 2016 Mozilla Foundation
//
// 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.

use crate::errors::*;
use clap::{Arg, ArgGroup, ErrorKind};
use std::env;
use std::ffi::OsString;
use std::path::PathBuf;
use std::str::FromStr;
use which::which_in;

const ENV_VAR_INTERNAL_START_SERVER: &str = "SCCACHE_START_SERVER";

#[derive(Debug, Clone)]
pub enum StatsFormat {
    Text,
    Json,
}

impl StatsFormat {
    fn as_str(&self) -> &'static str {
        match self {
            Self::Text => "text",
            Self::Json => "json",
        }
    }

    fn values() -> &'static [Self] {
        &[Self::Text, Self::Json]
    }
}

impl FromStr for StatsFormat {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> anyhow::Result<Self> {
        match s {
            "text" => Ok(Self::Text),
            "json" => Ok(Self::Json),
            _ => bail!("Unrecognized stats format: {:?}", s),
        }
    }
}

impl Default for StatsFormat {
    fn default() -> Self {
        Self::Text
    }
}

/// A specific command to run.
pub enum Command {
    /// Show cache statistics and exit.
    ShowStats(StatsFormat),
    /// Run background server.
    InternalStartServer,
    /// Start background server as a subprocess.
    StartServer,
    /// Stop background server.
    StopServer,
    /// Zero cache statistics and exit.
    ZeroStats,
    /// Show the status of the distributed client.
    DistStatus,
    /// Perform a login to authenticate for distributed compilation.
    DistAuth,
    /// Package a toolchain for distributed compilation (executable, out)
    PackageToolchain(PathBuf, PathBuf),
    /// Run a compiler command.
    Compile {
        /// The binary to execute.
        exe: OsString,
        /// The commandline arguments to pass to `exe`.
        cmdline: Vec<OsString>,
        /// The directory in which to execute the command.
        cwd: PathBuf,
        /// The environment variables to use for execution.
        env_vars: Vec<(OsString, OsString)>,
    },
}

fn flag_infer_long_and_short(name: &'static str) -> Arg<'static> {
    flag_infer_long(name).short(name.chars().next().expect("Name needs at least one char"))
}

fn flag_infer_long(name: &'static str) -> Arg<'static> {
    Arg::new(name).long(name)
}

/// Get the [`clap::Command`] used for argument parsing.
fn get_clap_command() -> clap::Command<'static> {
    clap::Command::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .trailing_var_arg(true)
        .max_term_width(110)
        .after_help(concat!(
            "Enabled features:\n",
            "    S3:        ",
            cfg!(feature = "s3"),
            "\n",
            "    Redis:     ",
            cfg!(feature = "redis"),
            "\n",
            "    Memcached: ",
            cfg!(feature = "memcached"),
            "\n",
            "    GCS:       ",
            cfg!(feature = "gcs"),
            "\n",
            "    Azure:     ",
            cfg!(feature = "azure"),
            "\n"
        ))
        .args(&[
            flag_infer_long_and_short("show-stats").help("show cache statistics"),
            flag_infer_long("start-server").help("start background server"),
            flag_infer_long("stop-server").help("stop background server"),
            flag_infer_long_and_short("zero-stats").help("zero statistics counters"),
            flag_infer_long("dist-auth").help("authenticate for distributed compilation"),
            flag_infer_long("dist-status").help("show status of the distributed client"),
            flag_infer_long("package-toolchain")
                .help("package toolchain for distributed compilation")
                .number_of_values(2)
                .value_names(&["EXE", "OUT"]),
            flag_infer_long("stats-format")
                .help("set output format of statistics")
                .value_name("FMT")
                .possible_values(StatsFormat::values().iter().map(StatsFormat::as_str))
                .default_value(StatsFormat::default().as_str()),
            Arg::new("CMD")
                .multiple_occurrences(true)
                .use_value_delimiter(false),
        ])
        .group(
            ArgGroup::new("one_and_only_one")
                .args(&[
                    "dist-auth",
                    "dist-status",
                    "show-stats",
                    "start-server",
                    "stop-server",
                    "zero-stats",
                    "package-toolchain",
                    "CMD",
                ])
                .required(true),
        )
}

/// Parse the commandline args into a `Result<Command>` to execute.
pub fn try_parse() -> Result<Command> {
    trace!("parse");

    let cwd =
        env::current_dir().context("sccache: Couldn't determine current working directory")?;

    // We only care if it's `1`
    let internal_start_server = env::var(ENV_VAR_INTERNAL_START_SERVER).as_deref() == Ok("1");
    let mut args: Vec<_> = env::args_os().collect();

    if !internal_start_server {
        if let Ok(exe) = env::current_exe() {
            match exe
                .file_stem()
                .and_then(|s| s.to_str())
                .map(|s| s.to_lowercase())
            {
                // If the executable has its standard name, do nothing.
                Some(ref e) if e == env!("CARGO_PKG_NAME") => {}
                // Otherwise, if it was copied/hardlinked under a different $name, act
                // as if it were invoked with `sccache $name`, but avoid $name resolving
                // to ourselves again if it's in the PATH.
                _ => {
                    if let (Some(path), Some(exe_filename)) = (env::var_os("PATH"), exe.file_name())
                    {
                        match which_in(exe_filename, Some(&path), &cwd) {
                            Ok(ref full_path)
                                if full_path.canonicalize()? == exe.canonicalize()? =>
                            {
                                if let Some(dir) = full_path.parent() {
                                    let path = env::join_paths(
                                        env::split_paths(&path).filter(|p| p != dir),
                                    )
                                    .ok();
                                    if let Ok(full_path) = which_in(exe_filename, path, &cwd) {
                                        args[0] = full_path.into();
                                    }
                                }
                            }
                            Ok(full_path) => args[0] = full_path.into(),
                            Err(_) => {}
                        }
                        args.insert(0, env!("CARGO_PKG_NAME").into());
                    }
                }
            }
        }
    }

    let matches_result = get_clap_command().try_get_matches_from(args);

    // A command can either be from `ENV_VAR_INTERNAL_START_SERVER` being set or from command-line
    // args. Validate things so that error messages are nice and the returned opts are correct
    match (internal_start_server, matches_result) {
        (true, Err(e)) => {
            // Need to make sure that the error from `clap` is due to a missing command and not
            // some other issue
            if e.kind() == ErrorKind::MissingRequiredArgument {
                Ok(Command::InternalStartServer)
            } else {
                Err(e.into())
            }
        }
        (false, Err(e)) => Err(e.into()),
        (true, Ok(_)) => {
            // `ENV_VAR_INTERNAL_START_SERVER` and a match means that more than one command was
            // provided
            bail!("`{ENV_VAR_INTERNAL_START_SERVER}=1` can't be used with other commands");
        }
        (false, Ok(matches)) => {
            if matches.is_present("show-stats") {
                let fmt = matches
                    .value_of_t("stats-format")
                    .expect("There is a default value");
                Ok(Command::ShowStats(fmt))
            } else if matches.is_present("start-server") {
                Ok(Command::StartServer)
            } else if matches.is_present("stop-server") {
                Ok(Command::StopServer)
            } else if matches.is_present("zero-stats") {
                Ok(Command::ZeroStats)
            } else if matches.is_present("dist-auth") {
                Ok(Command::DistAuth)
            } else if matches.is_present("dist-status") {
                Ok(Command::DistStatus)
            } else if matches.is_present("package-toolchain") {
                let mut toolchain_values: Vec<PathBuf> =
                    matches.values_of_t("package-toolchain")?;
                let maybe_out = toolchain_values.pop();
                let maybe_exe = toolchain_values.pop();
                match (maybe_exe, maybe_out) {
                    (Some(exe), Some(out)) => Ok(Command::PackageToolchain(exe, out)),
                    _ => unreachable!("clap should enforce two values"),
                }
            } else if matches.is_present("CMD") {
                let mut env_vars = env::vars_os().collect::<Vec<_>>();

                // If we're running under rr, avoid the `LD_PRELOAD` bits, as it will
                // almost surely do the wrong thing, as the compiler gets executed
                // in a different process tree.
                //
                // FIXME: Maybe we should strip out `LD_PRELOAD` always?
                if env::var_os("RUNNING_UNDER_RR").is_some() {
                    env_vars.retain(|(k, _v)| k != "LD_PRELOAD" && k != "RUNNING_UNDER_RR");
                }

                let cmd: Vec<OsString> = matches.values_of_t("CMD")?;
                match cmd.as_slice() {
                    [exe, cmdline @ ..] => Ok(Command::Compile {
                        exe: exe.to_owned(),
                        cmdline: cmdline.to_owned(),
                        cwd,
                        env_vars,
                    }),
                    _ => unreachable!("clap should enforce at least one value in cmd"),
                }
            } else {
                unreachable!("Either the arg group or env variable should provide a command");
            }
        }
    }
}
<|endoftext|>