use crate::error::{Result, YfError};
use crate::http::YfSession;
use crate::mic::is_isin;
pub async fn resolve_isin(session: &YfSession, isin: &str) -> Result<String> {
if !is_isin(isin) {
return Err(YfError::msg(format!("Invalid ISIN number: {isin}")));
}
if let Some(t) = session.cache().get_isin(isin) {
return Ok(t);
}
let res = session.search(isin, 1, 0).await?;
let ticker = res
.quotes
.first()
.and_then(|q| q.symbol.clone())
.filter(|s| !s.is_empty())
.ok_or_else(|| YfError::msg(format!("Invalid ISIN number: {isin}")))?;
session.cache().set_isin(isin, &ticker);
Ok(ticker)
}
pub async fn isin_for_ticker(session: &YfSession, ticker: &str) -> Result<String> {
if ticker.contains('-') || ticker.contains('^') {
return Ok("-".to_string());
}
let q = match session.info(ticker).await.ok().and_then(|i| i.short_name) {
Some(name) if !name.is_empty() => name,
_ => ticker.to_string(),
};
let url = "https://markets.businessinsider.com/ajax/SearchController_Suggest";
let params = vec![
("max_results", "25".to_string()),
("query", encode_query(&q)),
];
let data = session.get_text(url, ¶ms).await?;
let mut anchor = format!("\"{ticker}|");
if !data.contains(&anchor) {
if data.to_lowercase().contains(&q.to_lowercase()) {
anchor = "\"|".to_string();
if !data.contains(&anchor) {
return Ok("-".to_string());
}
} else {
return Ok("-".to_string());
}
}
let after = data.split_once(&anchor).map(|(_, rest)| rest).unwrap_or("");
let isin = after
.split('"')
.next()
.unwrap_or("")
.split('|')
.next()
.unwrap_or("")
.to_string();
if isin.is_empty() {
Ok("-".to_string())
} else {
Ok(isin)
}
}
fn encode_query(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.' | '~' => out.push(c),
' ' => out.push_str("%20"),
_ => {
for b in c.to_string().bytes() {
out.push_str(&format!("%{b:02X}"));
}
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encodes_spaces_and_symbols() {
assert_eq!(encode_query("Apple Inc."), "Apple%20Inc.");
assert_eq!(encode_query("AAPL"), "AAPL");
assert_eq!(encode_query("BRK.B"), "BRK.B");
}
#[test]
fn reverse_isin_parsing() {
let html = r#"foo "AAPL|US0378331005|Apple Inc." bar"#;
let out = html.split_once("\"AAPL|").map(|(_, r)| r).unwrap_or("");
let isin = out
.split('"')
.next()
.unwrap_or("")
.split('|')
.next()
.unwrap_or("");
assert_eq!(isin, "US0378331005");
}
}