use serde::Serialize;
use serde_json::Value;
use crate::client::api_request::ApiRequest;
use crate::client::decode::decode_value;
use crate::client::http_client::HttpClient;
use crate::config::client_config::ClientConfig;
use crate::error::TigerError;
use crate::model::quote::{
Brief, CapitalDistribution, CapitalFlow, CorporateAction, CorporateActionRequest,
CorporateDelisting, CorporateIPO, CorporateSymbolChange, Depth, ExchangeRate,
FinancialCurrency, FinancialDailyItem, FinancialDailyRequest, FinancialReportItem,
FinancialReportRequest, FundContractInfo, FundHistoryQuote, FundQuote, FutureContractInfo,
FutureDepth, FutureExchange, FutureKline, FutureKlineItem, FutureMainContractHistory,
FutureQuote, FutureTradeTickItem, FutureTradingTime, IndustryItem, IndustryStock, Kline,
KlineItem, KlineQuota, MarketScannerRequest, MarketScannerTagGroup, MarketState,
OptionAnalysis, OptionBrief, OptionChain, OptionExpiration, OptionKline, OptionSymbol,
QuoteOvernight, QuotePermission, ScannerResult, ShortInterest, StockBroker, StockDetail,
StockIndustry, SymbolName, Timeline, TradeRankItem, TradeTick, TradingCalendarItem,
WarrantBrief, WarrantFilterResult,
};
use crate::model::quote_requests::{
AllFutureContractsRequest, BriefRequest, DelayedQuoteRequest, FinancialCurrencyRequest,
FinancialExchangeRateRequest, FundContractsRequest, FundHistoryQuoteRequest, FundQuoteRequest,
FundSymbolsRequest, FutureContinuousContractsRequest, FutureContractSingleRequest,
FutureDepthRequest, FutureHistoryMainContractRequest, FutureKlineByPageRequest,
FutureKlineRequest, FutureRealTimeQuoteRequest, FutureTradeTicksRequest,
FutureTradingTimesRequest, IndustryListRequest, IndustryStocksRequest, KlineByPageRequest,
KlineQuotaRequest, KlineRequest, MarketScannerTagsRequest, OptionAnalysisRequest,
OptionChainRequest, OptionContractItem, OptionDepthRequest, OptionKlineRequest,
OptionQuoteRequest, OptionSymbolsRequest, OptionTimelineRequest, OptionTradeTicksRequest,
QuoteDepthRequest, QuoteOvernightRequest, QuotePermissionRequest, ShortInterestRequest,
StockBrokerRequest, StockDetailsRequest, StockFundamentalRequest, StockIndustryRequest,
SymbolsRequest, TimelineHistoryRequest, TradeMetasRequest, TradeRankRequest, TradeTickRequest,
TradingCalendarRequest, WarrantFilterRequest, WarrantQuoteRequest,
};
const VERSION_V1: &str = "1.0";
const VERSION_V2: &str = "2.0";
const VERSION_V3: &str = "3.0";
pub struct QuoteClient {
http_client: HttpClient,
}
impl QuoteClient {
pub fn from_config(config: ClientConfig) -> Self {
Self {
http_client: HttpClient::with_quote_server(config),
}
}
pub fn new(http_client: HttpClient) -> Self {
Self { http_client }
}
pub async fn query_token(&self) -> Result<String, crate::error::TigerError> {
self.http_client.query_token().await
}
pub async fn refresh_token(
&self,
token_manager: Option<&crate::config::TokenManager>,
) -> Result<(), crate::error::TigerError> {
self.http_client.refresh_token(token_manager).await
}
#[must_use = "retain the TokenManager handle to call stop_auto_refresh() when done; the background task runs until explicitly stopped"]
pub fn start_token_auto_refresh(
&self,
refresh_duration_secs: i64,
check_interval_secs: u64,
token_writer: Option<Box<dyn Fn(String) + Send + Sync + 'static>>,
) -> std::sync::Arc<crate::config::TokenManager> {
self.http_client.start_token_auto_refresh(
None,
refresh_duration_secs,
check_interval_secs,
token_writer,
)
}
pub async fn call_into<T, P>(&self, method: &str, params: P) -> Result<T, TigerError>
where
T: serde::de::DeserializeOwned + Default,
P: Serialize,
{
self.call_into_versioned(method, params, None).await
}
pub async fn call_into_versioned<T, P>(
&self,
method: &str,
params: P,
version: Option<&str>,
) -> Result<T, TigerError>
where
T: serde::de::DeserializeOwned + Default,
P: Serialize,
{
let biz = serde_json::to_string(¶ms)
.map_err(|e| TigerError::Config(format!("serialize biz params failed: {}", e)))?;
let req = match version {
Some(v) => ApiRequest::with_version(method, biz, v),
None => ApiRequest::new(method, biz),
};
let resp = self.http_client.execute_request(&req).await?;
unmarshal_data(resp.data)
}
pub async fn call_into_items<T, P>(&self, method: &str, params: P) -> Result<Vec<T>, TigerError>
where
T: serde::de::DeserializeOwned,
P: Serialize,
{
let raw: serde_json::Value = self.call_into(method, params).await?;
let wrap = match raw {
serde_json::Value::String(s) => serde_json::from_str::<serde_json::Value>(&s)
.map_err(|e| TigerError::Config(format!("decode items string failed: {}", e)))?,
v => v,
};
match wrap.get("items") {
None | Some(Value::Null) => Ok(vec![]),
Some(items) => serde_json::from_value(items.clone())
.map_err(|e| TigerError::Config(format!("decode items failed: {}", e))),
}
}
pub async fn call_into_list_or_object<T, P>(
&self,
method: &str,
params: P,
) -> Result<Vec<T>, TigerError>
where
T: serde::de::DeserializeOwned,
P: Serialize,
{
let biz = serde_json::to_string(¶ms)
.map_err(|e| TigerError::Config(format!("serialize biz params failed: {}", e)))?;
let req = ApiRequest::new(method, biz);
let resp = self.http_client.execute_request(&req).await?;
let raw = match resp.data {
None | Some(Value::Null) => return Ok(vec![]),
Some(v) => v,
};
if let Ok(list) = serde_json::from_value::<Vec<T>>(raw.clone()) {
return Ok(list);
}
let inner = if let Value::String(s) = &raw {
serde_json::from_str::<Value>(s)
.map_err(|e| TigerError::Config(format!("decode double-encoded data: {}", e)))?
} else {
raw
};
let obj: T = serde_json::from_value(inner)
.map_err(|e| TigerError::Config(format!("decode single object failed: {}", e)))?;
Ok(vec![obj])
}
pub async fn get_market_state(&self, market: &str) -> Result<Vec<MarketState>, TigerError> {
self.call_into("market_state", serde_json::json!({ "market": market }))
.await
}
pub async fn get_real_time_quote(&self, req: BriefRequest) -> Result<Vec<Brief>, TigerError> {
self.call_into("quote_real_time", req).await
}
#[deprecated(since = "0.5.1", note = "Use get_real_time_quote instead")]
pub async fn get_brief(&self, req: BriefRequest) -> Result<Vec<Brief>, TigerError> {
self.get_real_time_quote(req).await
}
pub async fn get_kline(&self, req: KlineRequest) -> Result<Vec<Kline>, TigerError> {
self.call_into("kline", req).await
}
pub async fn get_timeline(&self, symbols: &[&str]) -> Result<Vec<Timeline>, TigerError> {
self.call_into_versioned(
"timeline",
serde_json::json!({ "symbols": symbols }),
Some(VERSION_V3),
)
.await
}
pub async fn get_trade_tick(
&self,
req: TradeTickRequest,
) -> Result<Vec<TradeTick>, TigerError> {
self.call_into("trade_tick", req).await
}
pub async fn get_quote_depth(&self, req: QuoteDepthRequest) -> Result<Vec<Depth>, TigerError> {
self.call_into("quote_depth", req).await
}
pub async fn get_symbols(&self, req: SymbolsRequest) -> Result<Vec<String>, TigerError> {
self.call_into("all_symbols", req).await
}
pub async fn get_symbol_names(
&self,
req: SymbolsRequest,
) -> Result<Vec<SymbolName>, TigerError> {
self.call_into("all_symbol_names", req).await
}
pub async fn get_trade_metas(
&self,
req: TradeMetasRequest,
) -> Result<Vec<crate::model::quote::TradeMeta>, TigerError> {
self.call_into("quote_stock_trade", req).await
}
pub async fn get_stock_details(
&self,
req: StockDetailsRequest,
) -> Result<Vec<StockDetail>, TigerError> {
self.call_into_items("stock_detail", req).await
}
pub async fn get_delayed_quote(
&self,
req: DelayedQuoteRequest,
) -> Result<Vec<Brief>, TigerError> {
self.call_into("quote_delay", req).await
}
#[deprecated(since = "0.5.1", note = "Use get_delayed_quote instead")]
pub async fn get_stock_delay_briefs(
&self,
req: DelayedQuoteRequest,
) -> Result<Vec<Brief>, TigerError> {
self.get_delayed_quote(req).await
}
pub async fn get_kline_by_page(
&self,
req: KlineByPageRequest,
) -> Result<Vec<KlineItem>, TigerError> {
let page_size = req.page_size.unwrap_or(200);
let total_size = req.total_size.unwrap_or(1000);
let mut acc: Vec<KlineItem> = Vec::new();
let mut end_time = req.end_time;
let begin_time = req.begin_time;
while (acc.len() as i32) < total_size {
let sub = KlineRequest {
symbols: req.symbols.clone(),
period: req.period.clone(),
right: req.right.clone(),
begin_time,
end_time,
limit: Some(page_size),
trade_session: req.trade_session.clone(),
lang: req.lang.clone(),
..Default::default()
};
let page_out: Vec<Kline> = self.call_into("kline", sub).await?;
if page_out.is_empty() || page_out.iter().all(|k| k.items.is_empty()) {
break;
}
let mut any_full_page = false;
let mut oldest_across_symbols: Option<i64> = None;
for kline in &page_out {
if kline.items.is_empty() {
continue;
}
let len = kline.items.len() as i32;
if len >= page_size {
any_full_page = true;
}
acc.extend(kline.items.clone());
let oldest = kline.items.iter().map(|i| i.time).min().unwrap_or(0);
oldest_across_symbols = Some(match oldest_across_symbols {
None => oldest,
Some(prev) => prev.min(oldest),
});
}
if !any_full_page {
break;
}
end_time = oldest_across_symbols.map(|t| t - 1);
}
Ok(acc)
}
pub async fn get_timeline_history(
&self,
req: TimelineHistoryRequest,
) -> Result<Vec<Timeline>, TigerError> {
self.call_into("history_timeline", req).await
}
pub async fn get_trade_rank(
&self,
req: TradeRankRequest,
) -> Result<Vec<TradeRankItem>, TigerError> {
self.call_into("trade_rank", req).await
}
pub async fn get_short_interest(
&self,
req: ShortInterestRequest,
) -> Result<Vec<ShortInterest>, TigerError> {
self.call_into("quote_shortable_stocks", req).await
}
pub async fn get_stock_broker(
&self,
req: StockBrokerRequest,
) -> Result<Option<StockBroker>, TigerError> {
self.call_optional("stock_broker", req).await
}
pub async fn get_stock_fundamental(
&self,
req: StockFundamentalRequest,
) -> Result<std::collections::BTreeMap<String, serde_json::Value>, TigerError> {
self.call_into("stock_fundamental", req).await
}
pub async fn get_stock_industry(
&self,
req: StockIndustryRequest,
) -> Result<Vec<StockIndustry>, TigerError> {
self.call_into("stock_industry", req).await
}
pub async fn get_quote_permission(
&self,
req: QuotePermissionRequest,
) -> Result<Vec<QuotePermission>, TigerError> {
self.call_into("get_quote_permission", req).await
}
pub async fn get_kline_quota(
&self,
req: KlineQuotaRequest,
) -> Result<Vec<KlineQuota>, TigerError> {
self.call_into("kline_quota", req).await
}
pub async fn get_option_expiration(
&self,
symbols: &[&str],
market: Option<&str>,
) -> Result<Vec<OptionExpiration>, TigerError> {
let mut payload = serde_json::json!({ "symbols": symbols });
if let Some(m) = market {
payload["market"] = serde_json::Value::String(m.to_string());
}
self.call_into("option_expiration", payload).await
}
pub async fn get_option_chain(
&self,
req: OptionChainRequest,
) -> Result<Vec<OptionChain>, TigerError> {
self.call_into_versioned("option_chain", req, Some(VERSION_V3))
.await
}
pub async fn get_option_quote(
&self,
req: OptionQuoteRequest,
) -> Result<Vec<OptionBrief>, TigerError> {
self.call_into_versioned("option_brief", req, Some(VERSION_V2))
.await
}
#[deprecated(since = "0.5.1", note = "Use get_option_quote instead")]
pub async fn get_option_brief(
&self,
identifiers: &[&str],
) -> Result<Vec<OptionBrief>, TigerError> {
let items: Result<Vec<_>, _> = identifiers
.iter()
.map(|id| OptionContractItem::from_occ(id))
.collect();
self.get_option_quote(OptionQuoteRequest::new(items?)).await
}
pub async fn get_option_kline(
&self,
req: OptionKlineRequest,
) -> Result<Vec<OptionKline>, TigerError> {
self.call_into_versioned("option_kline", req, Some(VERSION_V2))
.await
}
pub async fn get_option_trade_ticks(
&self,
req: OptionTradeTicksRequest,
) -> Result<Vec<TradeTick>, TigerError> {
self.call_into("option_trade_tick", req).await
}
pub async fn get_option_timeline(
&self,
req: OptionTimelineRequest,
) -> Result<Vec<Timeline>, TigerError> {
self.call_into("option_timeline", req).await
}
pub async fn get_option_depth(
&self,
req: OptionDepthRequest,
) -> Result<Vec<Depth>, TigerError> {
self.call_into("option_depth", req).await
}
pub async fn get_option_symbols(
&self,
req: OptionSymbolsRequest,
) -> Result<Vec<OptionSymbol>, TigerError> {
self.call_into("all_hk_option_symbols", req).await
}
pub async fn get_option_analysis(
&self,
req: OptionAnalysisRequest,
) -> Result<Vec<OptionAnalysis>, TigerError> {
self.call_into("option_analysis", req).await
}
pub async fn get_future_exchange(&self) -> Result<Vec<FutureExchange>, TigerError> {
self.call_into("future_exchange", serde_json::json!({ "sec_type": "FUT" }))
.await
}
pub async fn get_future_contracts(
&self,
exchange_code: &str,
) -> Result<Vec<FutureContractInfo>, TigerError> {
self.call_into(
"future_contract_by_exchange_code",
serde_json::json!({ "exchange_code": exchange_code }),
)
.await
}
pub async fn get_future_real_time_quote(
&self,
req: FutureRealTimeQuoteRequest,
) -> Result<Vec<FutureQuote>, TigerError> {
self.call_into("future_real_time_quote", req).await
}
pub async fn get_future_kline(
&self,
mut req: FutureKlineRequest,
) -> Result<Vec<FutureKline>, TigerError> {
if req.begin_time == Some(0) {
req.begin_time = Some(-1);
}
if req.end_time == Some(0) {
req.end_time = Some(-1);
}
self.call_into("future_kline", req).await
}
pub async fn get_future_contract(
&self,
req: FutureContractSingleRequest,
) -> Result<Vec<FutureContractInfo>, TigerError> {
self.call_into_list_or_object("future_contract_by_contract_code", req)
.await
}
pub async fn get_all_future_contracts(
&self,
req: AllFutureContractsRequest,
) -> Result<Vec<FutureContractInfo>, TigerError> {
self.call_into("future_contracts", req).await
}
pub async fn get_current_future_contract(
&self,
req: FutureContractSingleRequest,
) -> Result<Option<FutureContractInfo>, TigerError> {
self.call_optional("future_current_contract", req).await
}
pub async fn get_future_continuous_contracts(
&self,
req: FutureContinuousContractsRequest,
) -> Result<Vec<FutureContractInfo>, TigerError> {
self.call_into_list_or_object("future_continuous_contracts", req)
.await
}
pub async fn get_future_history_main_contract(
&self,
req: FutureHistoryMainContractRequest,
) -> Result<Vec<FutureMainContractHistory>, TigerError> {
self.call_into("future_main_contract", req).await
}
pub async fn get_future_kline_by_page(
&self,
req: FutureKlineByPageRequest,
) -> Result<Vec<FutureKlineItem>, TigerError> {
let page_size = req.page_size.unwrap_or(200);
let total_size = req.total_size.unwrap_or(1000);
let mut acc: Vec<FutureKlineItem> = Vec::new();
let mut end_time = if req.end_time == Some(0) || req.end_time.is_none() {
Some(-1i64)
} else {
req.end_time
};
let begin_time = if req.begin_time == Some(0) || req.begin_time.is_none() {
Some(-1i64)
} else {
req.begin_time
};
while (acc.len() as i32) < total_size {
let sub = FutureKlineRequest {
contract_code: req.contract_code.clone(),
period: req.period.clone(),
begin_time,
end_time,
limit: Some(page_size),
lang: req.lang.clone(),
..Default::default()
};
let page_out: Vec<FutureKline> = self.call_into("future_kline", sub).await?;
if page_out.is_empty() || page_out[0].items.is_empty() {
break;
}
let items = page_out[0].items.clone();
let len = items.len() as i32;
acc.extend(items.clone());
if len < page_size {
break;
}
let oldest = items.iter().map(|i| i.time).min().unwrap_or(0);
end_time = Some(oldest - 1);
}
Ok(acc)
}
pub async fn get_future_trade_ticks(
&self,
req: FutureTradeTicksRequest,
) -> Result<Vec<FutureTradeTickItem>, TigerError> {
let mut req = req;
if req.end_index.is_none() {
req.end_index = Some(30);
}
#[derive(serde::Deserialize, Default)]
#[serde(rename_all = "camelCase")]
struct FutureTickWrap {
#[serde(default)]
contract_code: String,
#[serde(default)]
items: Vec<FutureTradeTickItem>,
}
let wrap: FutureTickWrap = self
.call_into_versioned("future_tick", req, Some(VERSION_V3))
.await?;
let mut items = wrap.items;
for item in &mut items {
if item.contract_code.is_empty() {
item.contract_code = wrap.contract_code.clone();
}
}
Ok(items)
}
pub async fn get_future_depth(
&self,
req: FutureDepthRequest,
) -> Result<Vec<FutureDepth>, TigerError> {
self.call_into("future_depth", req).await
}
pub async fn get_future_trading_times(
&self,
req: FutureTradingTimesRequest,
) -> Result<Option<FutureTradingTime>, TigerError> {
self.call_optional("future_trading_date", req).await
}
pub async fn get_fund_symbols(
&self,
req: FundSymbolsRequest,
) -> Result<Vec<String>, TigerError> {
self.call_into("fund_all_symbols", req).await
}
pub async fn get_fund_contracts(
&self,
req: FundContractsRequest,
) -> Result<Vec<FundContractInfo>, TigerError> {
self.call_into("fund_contracts", req).await
}
pub async fn get_fund_quote(
&self,
req: FundQuoteRequest,
) -> Result<Vec<FundQuote>, TigerError> {
self.call_into("fund_quote", req).await
}
pub async fn get_fund_history_quote(
&self,
req: FundHistoryQuoteRequest,
) -> Result<Vec<FundHistoryQuote>, TigerError> {
self.call_into("fund_history_quote", req).await
}
pub async fn get_warrant_quote(
&self,
req: WarrantQuoteRequest,
) -> Result<Vec<WarrantBrief>, TigerError> {
self.call_into("warrant_briefs", req).await
}
#[deprecated(since = "0.5.1", note = "Use get_warrant_quote instead")]
pub async fn get_warrant_briefs(
&self,
req: WarrantQuoteRequest,
) -> Result<Vec<WarrantBrief>, TigerError> {
self.get_warrant_quote(req).await
}
pub async fn get_warrant_filter(
&self,
req: WarrantFilterRequest,
) -> Result<Option<WarrantFilterResult>, TigerError> {
self.call_optional("warrant_filter", req).await
}
pub async fn get_industry_list(
&self,
req: IndustryListRequest,
) -> Result<Vec<IndustryItem>, TigerError> {
self.call_into("industry_list", req).await
}
pub async fn get_industry_stocks(
&self,
req: IndustryStocksRequest,
) -> Result<Vec<IndustryStock>, TigerError> {
self.call_into("industry_stock_list", req).await
}
pub async fn get_corporate_action(
&self,
req: CorporateActionRequest,
) -> Result<Vec<CorporateAction>, TigerError> {
let grouped: std::collections::BTreeMap<String, Vec<CorporateAction>> =
self.call_into("corporate_action", req).await?;
let mut out = Vec::new();
for (_, mut list) in grouped {
out.append(&mut list);
}
Ok(out)
}
pub async fn get_corporate_split(
&self,
mut req: CorporateActionRequest,
) -> Result<Vec<CorporateAction>, TigerError> {
req.action_type = "split".into();
self.get_corporate_action(req).await
}
pub async fn get_corporate_dividend(
&self,
mut req: CorporateActionRequest,
) -> Result<Vec<CorporateAction>, TigerError> {
req.action_type = "dividend".into();
self.get_corporate_action(req).await
}
pub async fn get_corporate_earnings_calendar(
&self,
mut req: CorporateActionRequest,
) -> Result<Vec<CorporateAction>, TigerError> {
req.action_type = "earning".into();
self.get_corporate_action(req).await
}
pub async fn get_corporate_symbol_change(
&self,
mut req: CorporateActionRequest,
) -> Result<Vec<CorporateSymbolChange>, TigerError> {
req.action_type = "symbol_change".into();
let grouped: std::collections::BTreeMap<String, Vec<CorporateSymbolChange>> =
self.call_into("corporate_action", req).await?;
let mut out = Vec::new();
for (_, mut list) in grouped {
out.append(&mut list);
}
Ok(out)
}
pub async fn get_corporate_delisting(
&self,
mut req: CorporateActionRequest,
) -> Result<Vec<CorporateDelisting>, TigerError> {
req.action_type = "delisting".into();
let grouped: std::collections::BTreeMap<String, Vec<CorporateDelisting>> =
self.call_into("corporate_action", req).await?;
let mut out = Vec::new();
for (_, mut list) in grouped {
out.append(&mut list);
}
Ok(out)
}
pub async fn get_corporate_ipo(
&self,
mut req: CorporateActionRequest,
) -> Result<Vec<CorporateIPO>, TigerError> {
req.action_type = "ipo".into();
let grouped: std::collections::BTreeMap<String, Vec<CorporateIPO>> =
self.call_into("corporate_action", req).await?;
let mut out = Vec::new();
for (_, mut list) in grouped {
out.append(&mut list);
}
Ok(out)
}
pub async fn get_financial_daily(
&self,
req: FinancialDailyRequest,
) -> Result<Vec<FinancialDailyItem>, TigerError> {
self.call_into("financial_daily", req).await
}
pub async fn get_financial_report(
&self,
req: FinancialReportRequest,
) -> Result<Vec<FinancialReportItem>, TigerError> {
self.call_into("financial_report", req).await
}
pub async fn get_financial_currency(
&self,
req: FinancialCurrencyRequest,
) -> Result<Vec<FinancialCurrency>, TigerError> {
self.call_into("financial_currency", req).await
}
pub async fn get_financial_exchange_rate(
&self,
req: FinancialExchangeRateRequest,
) -> Result<Vec<ExchangeRate>, TigerError> {
self.call_into("financial_exchange_rate", req).await
}
pub async fn get_trading_calendar(
&self,
req: TradingCalendarRequest,
) -> Result<Vec<TradingCalendarItem>, TigerError> {
self.call_into("trading_calendar", req).await
}
pub async fn get_capital_flow(
&self,
symbol: &str,
market: &str,
period: &str,
) -> Result<Option<CapitalFlow>, TigerError> {
self.call_optional(
"capital_flow",
serde_json::json!({ "symbol": symbol, "market": market, "period": period }),
)
.await
}
pub async fn get_capital_distribution(
&self,
symbol: &str,
market: &str,
) -> Result<Option<CapitalDistribution>, TigerError> {
self.call_optional(
"capital_distribution",
serde_json::json!({ "symbol": symbol, "market": market }),
)
.await
}
pub async fn market_scanner(
&self,
req: MarketScannerRequest,
) -> Result<Option<ScannerResult>, TigerError> {
self.call_optional_versioned("market_scanner", req, Some(VERSION_V1))
.await
}
pub async fn grab_quote_permission(&self) -> Result<Vec<QuotePermission>, TigerError> {
self.call_into("grab_quote_permission", serde_json::json!({}))
.await
}
pub async fn get_market_scanner_tags(
&self,
req: MarketScannerTagsRequest,
) -> Result<Vec<MarketScannerTagGroup>, TigerError> {
self.call_into("market_scanner_tags", req).await
}
pub async fn get_quote_overnight(
&self,
req: QuoteOvernightRequest,
) -> Result<Vec<QuoteOvernight>, TigerError> {
self.call_into("quote_overnight", req).await
}
pub async fn call_optional<T, P>(
&self,
method: &str,
params: P,
) -> Result<Option<T>, TigerError>
where
T: serde::de::DeserializeOwned,
P: Serialize,
{
self.call_optional_versioned(method, params, None).await
}
pub async fn call_optional_versioned<T, P>(
&self,
method: &str,
params: P,
version: Option<&str>,
) -> Result<Option<T>, TigerError>
where
T: serde::de::DeserializeOwned,
P: Serialize,
{
let biz = serde_json::to_string(¶ms)
.map_err(|e| TigerError::Config(format!("serialize biz params failed: {}", e)))?;
let req = match version {
Some(v) => ApiRequest::with_version(method, biz, v),
None => ApiRequest::new(method, biz),
};
let resp = self.http_client.execute_request(&req).await?;
match resp.data {
None => Ok(None),
Some(v) if v.is_null() => Ok(None),
Some(v) => {
let parsed: T = decode_value(v)?;
Ok(Some(parsed))
}
}
}
}
fn unmarshal_data<T>(data: Option<Value>) -> Result<T, TigerError>
where
T: serde::de::DeserializeOwned + Default,
{
match data {
None => Ok(T::default()),
Some(v) if v.is_null() => Ok(T::default()),
Some(v) => decode_value(v),
}
}
#[cfg(test)]
mod tests;