use serde::{Deserialize, Serialize};
use crate::adapters::common::encode_path_segment;
use crate::error::Result;
use super::build_client;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ScreenerResult {
pub symbol: Option<String>,
#[serde(rename = "companyName")]
pub company_name: Option<String>,
#[serde(rename = "marketCap")]
pub market_cap: Option<f64>,
pub sector: Option<String>,
pub industry: Option<String>,
pub beta: Option<f64>,
pub price: Option<f64>,
#[serde(rename = "lastAnnualDividend")]
pub last_annual_dividend: Option<f64>,
pub volume: Option<f64>,
pub exchange: Option<String>,
#[serde(rename = "exchangeShortName")]
pub exchange_short_name: Option<String>,
pub country: Option<String>,
#[serde(rename = "isEtf")]
pub is_etf: Option<bool>,
#[serde(rename = "isFund")]
pub is_fund: Option<bool>,
#[serde(rename = "isActivelyTrading")]
pub is_actively_trading: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct SearchResult {
pub symbol: Option<String>,
pub name: Option<String>,
pub currency: Option<String>,
#[serde(rename = "stockExchange")]
pub stock_exchange: Option<String>,
#[serde(rename = "exchangeShortName")]
pub exchange_short_name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CikResult {
pub symbol: Option<String>,
#[serde(rename = "companyCik")]
pub company_cik: Option<String>,
}
pub async fn stock_screener(params: &[(&str, &str)]) -> Result<Vec<ScreenerResult>> {
let client = build_client()?;
client.get("/api/v3/stock-screener", params).await
}
pub async fn symbol_search(
query: &str,
limit: Option<u32>,
exchange: Option<&str>,
) -> Result<Vec<SearchResult>> {
let client = build_client()?;
let limit_str = limit.map(|l| l.to_string());
let mut params: Vec<(&str, &str)> = vec![("query", query)];
if let Some(ref l) = limit_str {
params.push(("limit", l));
}
if let Some(e) = exchange {
params.push(("exchange", e));
}
client.get("/api/v3/search", ¶ms).await
}
pub async fn cik_search(cik: &str) -> Result<Vec<CikResult>> {
let client = build_client()?;
let path = format!("/api/v3/cik/{}", encode_path_segment(cik));
client.get(&path, &[]).await
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_symbol_search_mock() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/api/v3/search")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
mockito::Matcher::UrlEncoded("query".into(), "apple".into()),
mockito::Matcher::UrlEncoded("limit".into(), "5".into()),
]))
.with_status(200)
.with_body(
serde_json::json!([
{
"symbol": "AAPL",
"name": "Apple Inc.",
"currency": "USD",
"stockExchange": "NASDAQ",
"exchangeShortName": "NASDAQ"
}
])
.to_string(),
)
.create_async()
.await;
let client = super::super::build_test_client(&server.url()).unwrap();
let result: Vec<SearchResult> = client
.get("/api/v3/search", &[("query", "apple"), ("limit", "5")])
.await
.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].symbol.as_deref(), Some("AAPL"));
assert_eq!(result[0].name.as_deref(), Some("Apple Inc."));
}
}