txlog_client 0.2.3

txlog client
Documentation
//! txlog client implementation
//!
//! provides functions to read and write bitcoin transactions from a txlog server

use errors::TxLogErrors;
use hex;
use serde::{Deserialize, Serialize};

mod errors;

#[derive(Clone)]
pub struct TxlogClient {
    pub url: String,
    secret: String,
}

#[derive(Serialize, Deserialize)]
pub struct SubmitResponse {
    pub success: bool,
}

#[derive(Serialize, Deserialize)]
pub struct TxResponse {
    pub txid: String,
    pub status: u32,
    pub created: String,
    pub updated: String,
}

// create an instance of a txlog client
pub fn new(url: String, secret: String) -> TxlogClient {
    // TODO validate input
    return TxlogClient { url, secret };
}

pub struct SubmitMetadata {
    pub metadata: Option<String>,
    pub merchant: Option<String>,
    pub network: Option<String>,
}

impl TxlogClient {
    // fetch a raw transaction
    pub async fn rawtx(&self, txid: &String) -> Result<Vec<u8>, TxLogErrors> {
        let client = reqwest::Client::new();
        let url = format!("{}/tx/{}/raw", &self.url, txid);
        let response = client
            .get(&url)
            .header("Authorization", format!("Bearer {}", self.secret))
            .send()
            .await?;

        let body = match response.text().await {
            Err(e) => return Err(TxLogErrors::BodyDecodeError(e)),
            Ok(v) => v,
        };

        let decoded = match hex::decode(&body) {
            Ok(v) => v,
            Err(e) => return Err(TxLogErrors::HexDecode(e)),
        };

        return Ok(decoded);
    }

    // fetch transaction status
    pub async fn tx(&self, txid: &String) -> Result<TxResponse, TxLogErrors> {
        let client = reqwest::Client::new();
        let url = format!("{}/tx/{}", &self.url, txid);
        let response = client
            .get(&url)
            .header("Authorization", format!("Bearer {}", self.secret))
            .send()
            .await?;

        let res = match response.json::<TxResponse>().await {
            Ok(v) => v,
            Err(e) => return Err(TxLogErrors::BodyDecodeError(e)),
        };

        Ok(res)
    }

    // submit a transaction
    pub async fn submit(
        &self,
        rawtx: Vec<u8>,
        params: Option<SubmitMetadata>,
    ) -> Result<SubmitResponse, TxLogErrors> {
        let client = reqwest::Client::new();
        let url = format!("{}/tx", &self.url);
        let mut query_params: Vec<(String, String)> = vec![];

        match params {
            Some(p) => {
                match p.metadata {
                    Some(v) => query_params.push(("metadata".to_string(), v)),
                    None => (),
                };
                match p.merchant {
                    Some(v) => query_params.push(("merchant".to_string(), v)),
                    None => (),
                };
                match p.network {
                    Some(v) => query_params.push(("network".to_string(), v)),
                    None => (),
                };
            }
            None => (),
        }

        let response = client
            .post(&url)
            .query(&query_params)
            .header("Authorization", format!("Bearer {}", self.secret))
            .header("Content-Type", "application/octet-stream")
            .body(rawtx)
            .send()
            .await?;

        let res = match response.json::<SubmitResponse>().await {
            Ok(v) => v,
            Err(e) => return Err(TxLogErrors::BodyDecodeError(e)),
        };

        if !res.success {
            return Err(TxLogErrors::SubmitResponseFailed);
        }

        Ok(res)
    }
}

#[cfg(test)]
mod tests {
    macro_rules! aw {
        ($e:expr) => {
            tokio_test::block_on($e)
        };
    }

    #[test]
    fn test_rawtx() {
        // Add variables to .env file
        let secret = dotenv::var("TXLOG_SECRET").unwrap();
        let url = dotenv::var("TXLOG_URL").unwrap();
        let rawtx = dotenv::var("RAWTX").unwrap();
        let txid = dotenv::var("TXID").unwrap();

        let client = crate::new(url, secret);

        let response = aw!(client.rawtx(&txid)).unwrap();

        assert_eq!(response, hex::decode(&rawtx).unwrap());
    }

    #[test]
    fn test_submit() {
        // Add variables to .env file
        let secret = dotenv::var("TXLOG_SECRET").unwrap();
        let url = dotenv::var("TXLOG_URL").unwrap();
        let rawtx = dotenv::var("RAWTX").unwrap();

        let client = crate::new(url, secret);

        let response = aw!(client.submit(hex::decode(&rawtx).unwrap(), None)).unwrap();

        assert_eq!(response.success, true);
    }

    #[test]
    fn test_tx() {
        // Add variables to .env file
        let secret = dotenv::var("TXLOG_SECRET").unwrap();
        let url = dotenv::var("TXLOG_URL").unwrap();
        let txid = dotenv::var("TXID").unwrap();

        let client = crate::new(url, secret);

        let response = aw!(client.tx(&txid)).unwrap();

        assert_eq!(response.txid, txid);
    }
}