alpha_vantage 0.9.0

Rust Wrapper/Crate built for AlphaVantage API
Documentation
//! Module for searching specific symbol or companies
//!
//! Looking for some specific symbols or companies? Trying to build a search box
//! similar to the one below?
//!
//! You can read about [Symbol][symbol_search] API and what it returns
//! on alphavantage documentation
//!
//! [symbol_search]: https://www.alphavantage.co/documentation/#symbolsearch

use serde::Deserialize;

use crate::api::ApiClient;
use crate::deserialize::from_str;
use crate::error::{detect_common_helper_error, Error, Result};

/// Struct which stores matches data for search keyword
#[derive(Debug, Clone, Deserialize, Default)]
pub struct Match {
    #[serde(rename = "1. symbol")]
    symbol: String,
    #[serde(rename = "2. name")]
    name: String,
    #[serde(rename = "3. type")]
    stock_type: String,
    #[serde(rename = "4. region")]
    region: String,
    #[serde(rename = "5. marketOpen")]
    market_open: String,
    #[serde(rename = "6. marketClose")]
    market_close: String,
    #[serde(rename = "7. timezone")]
    time_zone: String,
    #[serde(rename = "8. currency")]
    currency: String,
    #[serde(rename = "9. matchScore", deserialize_with = "from_str")]
    match_score: f64,
}

impl Match {
    /// Return symbol
    ///
    /// ```
    /// #[tokio::main]
    /// async fn main() {
    ///     let api = alpha_vantage::set_api("demo", reqwest::Client::new());
    ///     let search = api.search("BA").json().await.unwrap();
    ///     let symbol = search.matches()[0].symbol();
    ///     assert_eq!(symbol, "BA");
    /// }
    /// ```
    #[must_use]
    pub fn symbol(&self) -> &str {
        &self.symbol
    }

    /// Return name for symbol
    ///
    /// ```
    /// #[tokio::main]
    /// async fn main() {
    ///     let api = alpha_vantage::set_api("demo", reqwest::Client::new());
    ///     let search = api.search("BA").json().await.unwrap();
    ///     let name = search.matches()[0].name();
    ///     assert_eq!(name, "Boeing Company");
    /// }
    /// ```
    #[must_use]
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Return stock type
    ///
    /// ```
    /// #[tokio::main]
    /// async fn main() {
    ///     let api = alpha_vantage::set_api("demo", reqwest::Client::new());
    ///     let search = api.search("BA").json().await.unwrap();
    ///     let stock_type = search.matches()[0].stock_type();
    ///     assert_eq!(stock_type, "Equity");
    /// }
    #[must_use]
    pub fn stock_type(&self) -> &str {
        &self.stock_type
    }

    /// Return region of search data
    ///
    /// ```
    /// #[tokio::main]
    /// async fn main() {
    ///     let api = alpha_vantage::set_api("demo", reqwest::Client::new());
    ///     let search = api.search("BA").json().await.unwrap();
    ///     let region = search.matches()[0].region();
    ///     assert_eq!(region, "United States");
    /// }
    #[must_use]
    pub fn region(&self) -> &str {
        &self.region
    }

    /// Return market open time
    ///
    /// ```
    /// #[tokio::main]
    /// async fn main() {
    ///     let api = alpha_vantage::set_api("demo", reqwest::Client::new());
    ///     let search = api.search("BA").json().await.unwrap();
    ///     let market_open = search.matches()[0].market_open();
    ///     assert_eq!(market_open, "09:30");
    /// }
    #[must_use]
    pub fn market_open(&self) -> &str {
        &self.market_open
    }

    /// Return market close time
    ///
    /// ```
    /// #[tokio::main]
    /// async fn main() {
    ///     let api = alpha_vantage::set_api("demo", reqwest::Client::new());
    ///     let search = api.search("BA").json().await.unwrap();
    ///     let market_close = search.matches()[0].market_close();
    ///     assert_eq!(market_close, "16:00");
    /// }
    #[must_use]
    pub fn market_close(&self) -> &str {
        &self.market_close
    }

    /// Return time zone of symbol
    #[must_use]
    pub fn time_zone(&self) -> &str {
        &self.time_zone
    }

    /// Return currency
    ///
    /// ```
    /// #[tokio::main]
    /// async fn main() {
    ///     let api = alpha_vantage::set_api("demo", reqwest::Client::new());
    ///     let search = api.search("BA").json().await.unwrap();
    ///     let currency = search.matches()[0].currency();
    ///     assert_eq!(currency, "USD");
    /// }
    #[must_use]
    pub fn currency(&self) -> &str {
        &self.currency
    }

    /// Return match score
    ///
    /// ```
    /// #[tokio::main]
    /// async fn main() {
    ///     let api = alpha_vantage::set_api("demo", reqwest::Client::new());
    ///     let search = api.search("BA").json().await.unwrap();
    ///     let match_score = search.matches()[0].match_score();
    ///     assert_eq!(match_score, 1.0);
    /// }
    #[must_use]
    pub fn match_score(&self) -> f64 {
        self.match_score
    }
}

/// struct for storing search method data
#[derive(Default)]
pub struct Search {
    matches: Vec<Match>,
}

impl Search {
    /// Return result of search
    #[must_use]
    pub fn matches(&self) -> &Vec<Match> {
        &self.matches
    }
}

/// struct for helping creation of search struct
#[derive(Debug, Deserialize)]
pub(crate) struct SearchHelper {
    #[serde(rename = "Information")]
    information: Option<String>,
    #[serde(rename = "Note")]
    note: Option<String>,
    #[serde(rename = "bestMatches")]
    matches: Option<Vec<Match>>,
}

impl SearchHelper {
    pub(crate) fn convert(self) -> Result<Search> {
        let mut search = Search::default();
        detect_common_helper_error(self.information, None, self.note)?;
        if self.matches.is_none() {
            return Err(Error::EmptyResponse);
        }
        search.matches = self.matches.unwrap();
        Ok(search)
    }
}

/// Builder to create new `Search`
pub struct SearchBuilder<'a> {
    api_client: &'a ApiClient,
    keywords: &'a str,
}

impl<'a> SearchBuilder<'a> {
    /// Create new `SearchBuilder` from `APIClient`
    #[must_use]
    pub fn new(api_client: &'a ApiClient, keywords: &'a str) -> Self {
        Self {
            api_client,
            keywords,
        }
    }

    fn create_url(&self) -> String {
        format!("query?function=SYMBOL_SEARCH&keywords={}", self.keywords)
    }

    /// Returns JSON data struct
    ///
    /// # Errors
    /// Raise error if data obtained cannot be properly converted to struct or
    /// API returns any 4 possible known errors
    pub async fn json(&self) -> Result<Search> {
        let url = self.create_url();
        let search_helper: SearchHelper = self.api_client.get_json(&url).await?;
        search_helper.convert()
    }
}