use super::auth::Auth;
use super::retry::{self, RetryPolicy};
use crate::errors::MarketDataError;
use crate::tls::{build_rustls_config, TlsConfig};
pub struct RestClient {
agent: ureq::Agent,
auth: Auth,
base_url: String,
retry_policy: Option<RetryPolicy>,
}
impl RestClient {
pub fn new(auth: Auth) -> Self {
Self::with_tls(auth, TlsConfig::default())
.expect("default rustls config should build on supported platforms")
}
pub fn with_tls(auth: Auth, tls: TlsConfig) -> Result<Self, MarketDataError> {
let tls_config = build_rustls_config(&tls)?;
let builder = ureq::AgentBuilder::new()
.timeout_read(std::time::Duration::from_secs(30))
.timeout_write(std::time::Duration::from_secs(30))
.tls_config(tls_config);
Ok(Self {
agent: builder.build(),
auth,
base_url: crate::urls::REST_BASE.to_string(),
retry_policy: None,
})
}
pub fn with_retry(mut self, policy: RetryPolicy) -> Self {
self.retry_policy = Some(policy);
self
}
pub(crate) fn execute(
&self,
request: ureq::Request,
) -> Result<ureq::Response, MarketDataError> {
match self.retry_policy {
Some(policy) => retry::run(&policy, || {
let req = request.clone();
req.call().map_err(MarketDataError::from)
}),
None => request.call().map_err(MarketDataError::from),
}
}
pub fn base_url(mut self, url: &str) -> Self {
self.base_url = url.to_string();
self
}
pub fn stock(&self) -> StockClient<'_> {
StockClient { client: self }
}
pub fn futopt(&self) -> super::futopt::FutOptClient<'_> {
super::futopt::FutOptClient { client: self }
}
pub(crate) fn agent(&self) -> &ureq::Agent {
&self.agent
}
pub(crate) fn auth(&self) -> &Auth {
&self.auth
}
pub(crate) fn get_base_url(&self) -> &str {
&self.base_url
}
}
impl Clone for RestClient {
fn clone(&self) -> Self {
Self {
agent: self.agent.clone(),
auth: self.auth.clone(),
base_url: self.base_url.clone(),
retry_policy: self.retry_policy,
}
}
}
pub struct StockClient<'a> {
client: &'a RestClient,
}
impl<'a> StockClient<'a> {
pub fn intraday(&self) -> IntradayClient<'a> {
IntradayClient {
client: self.client,
}
}
pub fn historical(&self) -> HistoricalClient<'a> {
HistoricalClient {
client: self.client,
}
}
pub fn technical(&self) -> crate::rest::stock::technical::TechnicalClient<'a> {
crate::rest::stock::technical::TechnicalClient::new(self.client)
}
pub fn snapshot(&self) -> crate::rest::stock::snapshot::SnapshotClient<'a> {
crate::rest::stock::snapshot::SnapshotClient::new(self.client)
}
pub fn corporate_actions(&self) -> CorporateActionsClient<'a> {
CorporateActionsClient {
client: self.client,
}
}
}
pub struct CorporateActionsClient<'a> {
client: &'a RestClient,
}
impl<'a> CorporateActionsClient<'a> {
pub fn capital_changes(&self) -> crate::rest::stock::corporate_actions::CapitalChangesRequestBuilder<'_> {
crate::rest::stock::corporate_actions::CapitalChangesRequestBuilder::new(self.client)
}
pub fn dividends(&self) -> crate::rest::stock::corporate_actions::DividendsRequestBuilder<'_> {
crate::rest::stock::corporate_actions::DividendsRequestBuilder::new(self.client)
}
pub fn listing_applicants(&self) -> crate::rest::stock::corporate_actions::ListingApplicantsRequestBuilder<'_> {
crate::rest::stock::corporate_actions::ListingApplicantsRequestBuilder::new(self.client)
}
}
pub struct HistoricalClient<'a> {
client: &'a RestClient,
}
impl<'a> HistoricalClient<'a> {
pub fn candles(&self) -> crate::rest::stock::historical::HistoricalCandlesRequestBuilder<'_> {
crate::rest::stock::historical::HistoricalCandlesRequestBuilder::new(self.client)
}
pub fn stats(&self) -> crate::rest::stock::historical::StatsRequestBuilder<'_> {
crate::rest::stock::historical::StatsRequestBuilder::new(self.client)
}
}
pub struct IntradayClient<'a> {
client: &'a RestClient,
}
impl<'a> IntradayClient<'a> {
pub fn quote(&self) -> crate::rest::stock::intraday::QuoteRequestBuilder<'_> {
crate::rest::stock::intraday::QuoteRequestBuilder::new(self.client)
}
pub fn ticker(&self) -> crate::rest::stock::intraday::TickerRequestBuilder<'_> {
crate::rest::stock::intraday::TickerRequestBuilder::new(self.client)
}
pub fn tickers(&self) -> crate::rest::stock::intraday::TickersRequestBuilder<'_> {
crate::rest::stock::intraday::TickersRequestBuilder::new(self.client)
}
pub fn candles(&self) -> crate::rest::stock::intraday::CandlesRequestBuilder<'_> {
crate::rest::stock::intraday::CandlesRequestBuilder::new(self.client)
}
pub fn trades(&self) -> crate::rest::stock::intraday::TradesRequestBuilder<'_> {
crate::rest::stock::intraday::TradesRequestBuilder::new(self.client)
}
pub fn volumes(&self) -> crate::rest::stock::intraday::VolumesRequestBuilder<'_> {
crate::rest::stock::intraday::VolumesRequestBuilder::new(self.client)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rest_client_creation() {
let client = RestClient::new(Auth::SdkToken("test-token".to_string()));
assert_eq!(client.get_base_url(), "https://api.fugle.tw/marketdata/v1.0");
}
#[test]
fn test_rest_client_custom_base_url() {
let client = RestClient::new(Auth::SdkToken("test-token".to_string()))
.base_url("https://custom.example.com");
assert_eq!(client.get_base_url(), "https://custom.example.com");
}
#[test]
fn test_stock_client_creation() {
let client = RestClient::new(Auth::ApiKey("test-key".to_string()));
let stock_client = client.stock();
assert_eq!(stock_client.client.get_base_url(), "https://api.fugle.tw/marketdata/v1.0");
}
#[test]
fn test_intraday_client_creation() {
let client = RestClient::new(Auth::BearerToken("test-bearer".to_string()));
let intraday = client.stock().intraday();
assert_eq!(intraday.client.get_base_url(), "https://api.fugle.tw/marketdata/v1.0");
}
#[test]
fn test_chained_client_access() {
let client = RestClient::new(Auth::SdkToken("test".to_string()));
let _intraday = client.stock().intraday();
}
#[test]
fn test_auth_types() {
let _client1 = RestClient::new(Auth::ApiKey("key".to_string()));
let _client2 = RestClient::new(Auth::BearerToken("token".to_string()));
let _client3 = RestClient::new(Auth::SdkToken("sdk".to_string()));
}
#[test]
fn test_client_clone() {
let client = RestClient::new(Auth::SdkToken("test".to_string()));
let cloned = client.clone();
assert_eq!(client.get_base_url(), cloned.get_base_url());
}
#[test]
fn test_connection_pool_sharing() {
let client = RestClient::new(Auth::SdkToken("test".to_string()));
let cloned = client.clone();
let _stock1 = client.stock().intraday();
let _stock2 = cloned.stock().intraday();
}
#[test]
fn test_custom_base_url_preserved_in_clone() {
let client = RestClient::new(Auth::SdkToken("test".to_string()))
.base_url("https://custom.example.com");
let cloned = client.clone();
assert_eq!(cloned.get_base_url(), "https://custom.example.com");
}
#[test]
fn test_futopt_client_creation() {
let client = RestClient::new(Auth::SdkToken("test".to_string()));
let futopt = client.futopt();
assert_eq!(futopt.client.get_base_url(), "https://api.fugle.tw/marketdata/v1.0");
}
#[test]
fn test_futopt_intraday_client_creation() {
let client = RestClient::new(Auth::SdkToken("test".to_string()));
let intraday = client.futopt().intraday();
assert_eq!(intraday.client.get_base_url(), "https://api.fugle.tw/marketdata/v1.0");
}
#[test]
fn test_futopt_chained_client_access() {
let client = RestClient::new(Auth::SdkToken("test".to_string()));
let _intraday = client.futopt().intraday();
}
#[test]
fn test_both_stock_and_futopt() {
let client = RestClient::new(Auth::SdkToken("test".to_string()));
let _stock = client.stock().intraday();
let _futopt = client.futopt().intraday();
}
#[test]
fn test_corporate_actions_client_creation() {
let client = RestClient::new(Auth::SdkToken("test".to_string()));
let corporate_actions = client.stock().corporate_actions();
assert_eq!(corporate_actions.client.get_base_url(), "https://api.fugle.tw/marketdata/v1.0");
}
#[test]
fn test_corporate_actions_chained_access() {
let client = RestClient::new(Auth::SdkToken("test".to_string()));
let _capital_changes = client.stock().corporate_actions().capital_changes();
let _dividends = client.stock().corporate_actions().dividends();
let _listing_applicants = client.stock().corporate_actions().listing_applicants();
}
}