use crate::client::Client;
use crate::error::Error;
use nordnet_model::ids::{InstrumentId, IssuerId};
use nordnet_model::models::instruments::{
Instrument, InstrumentEligibility, InstrumentPublicTrades, InstrumentType, LeverageFilter,
};
#[derive(Debug, Clone, Default)]
pub struct LeveragesQuery<'a> {
pub currency: Option<&'a str>,
pub expiration_date: Option<&'a str>,
pub instrument_group_type: Option<&'a str>,
pub instrument_type: Option<&'a str>,
pub issuer_id: Option<IssuerId>,
pub market_view: Option<&'a str>,
}
fn build_leverages_query(filters: &LeveragesQuery<'_>) -> String {
let mut url = match reqwest::Url::parse("http://_/") {
Ok(u) => u,
Err(_) => return String::new(),
};
{
let mut pairs = url.query_pairs_mut();
if let Some(v) = filters.currency {
pairs.append_pair("currency", v);
}
if let Some(v) = filters.expiration_date {
pairs.append_pair("expiration_date", v);
}
if let Some(v) = filters.instrument_group_type {
pairs.append_pair("instrument_group_type", v);
}
if let Some(v) = filters.instrument_type {
pairs.append_pair("instrument_type", v);
}
if let Some(v) = filters.issuer_id {
pairs.append_pair("issuer_id", &v.0.to_string());
}
if let Some(v) = filters.market_view {
pairs.append_pair("market_view", v);
}
}
url.query().unwrap_or("").to_owned()
}
impl Client {
#[doc(alias = "GET /instruments/lookup/{lookup_type}/{lookup}")]
pub async fn lookup(&self, lookup_type: &str, lookup: &str) -> Result<Vec<Instrument>, Error> {
let path = format!("/instruments/lookup/{lookup_type}/{lookup}");
match self.get::<Vec<Instrument>>(&path).await {
Ok(v) => Ok(v),
Err(Error::Decode { ref body, .. }) if body.trim().is_empty() => Ok(vec![]),
Err(e) => Err(e),
}
}
#[doc(alias = "GET /instruments/types")]
pub async fn list_types(&self) -> Result<Vec<InstrumentType>, Error> {
self.get::<Vec<InstrumentType>>("/instruments/types").await
}
#[doc(alias = "GET /instruments/types/{instrument_type}")]
pub async fn get_type(&self, instrument_type: &str) -> Result<Vec<InstrumentType>, Error> {
let path = format!("/instruments/types/{instrument_type}");
match self.get::<Vec<InstrumentType>>(&path).await {
Ok(v) => Ok(v),
Err(Error::Decode { ref body, .. }) if body.trim().is_empty() => Ok(vec![]),
Err(e) => Err(e),
}
}
#[doc(alias = "GET /instruments/underlyings/{derivative_type}/{currency}")]
pub async fn list_underlyings(
&self,
derivative_type: &str,
currency: &str,
) -> Result<Vec<Instrument>, Error> {
let path = format!("/instruments/underlyings/{derivative_type}/{currency}");
match self.get::<Vec<Instrument>>(&path).await {
Ok(v) => Ok(v),
Err(Error::Decode { ref body, .. }) if body.trim().is_empty() => Ok(vec![]),
Err(e) => Err(e),
}
}
#[doc(alias = "GET /instruments/validation/suitability/{instrument_id}")]
pub async fn get_instrument_suitability(
&self,
instrument_id: InstrumentId,
) -> Result<Vec<InstrumentEligibility>, Error> {
let path = format!("/instruments/validation/suitability/{instrument_id}");
match self.get::<Vec<InstrumentEligibility>>(&path).await {
Ok(v) => Ok(v),
Err(Error::Decode { ref body, .. }) if body.trim().is_empty() => Ok(vec![]),
Err(e) => Err(e),
}
}
#[doc(alias = "GET /instruments/{instrument_id}")]
pub async fn get_instrument(
&self,
instrument_id: InstrumentId,
) -> Result<Vec<Instrument>, Error> {
let path = format!("/instruments/{instrument_id}");
match self.get::<Vec<Instrument>>(&path).await {
Ok(v) => Ok(v),
Err(Error::Decode { ref body, .. }) if body.trim().is_empty() => Ok(vec![]),
Err(e) => Err(e),
}
}
#[doc(alias = "GET /instruments/{instrument_id}/leverages")]
pub async fn list_leverages(
&self,
instrument_id: InstrumentId,
filters: LeveragesQuery<'_>,
) -> Result<Vec<Instrument>, Error> {
let qs = build_leverages_query(&filters);
let path = if qs.is_empty() {
format!("/instruments/{instrument_id}/leverages")
} else {
format!("/instruments/{instrument_id}/leverages?{qs}")
};
match self.get::<Vec<Instrument>>(&path).await {
Ok(v) => Ok(v),
Err(Error::Decode { ref body, .. }) if body.trim().is_empty() => Ok(vec![]),
Err(e) => Err(e),
}
}
#[doc(alias = "GET /instruments/{instrument_id}/leverages/filters")]
pub async fn get_leverage_filters(
&self,
instrument_id: InstrumentId,
) -> Result<LeverageFilter, Error> {
let path = format!("/instruments/{instrument_id}/leverages/filters");
self.get::<LeverageFilter>(&path).await
}
#[doc(alias = "GET /instruments/{instrument_id}/trades")]
pub async fn list_instrument_trades(
&self,
instrument_id: InstrumentId,
) -> Result<Vec<InstrumentPublicTrades>, Error> {
let path = format!("/instruments/{instrument_id}/trades");
match self.get::<Vec<InstrumentPublicTrades>>(&path).await {
Ok(v) => Ok(v),
Err(Error::Decode { ref body, .. }) if body.trim().is_empty() => Ok(vec![]),
Err(e) => Err(e),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_query_empty_when_no_filters() {
let qs = build_leverages_query(&LeveragesQuery::default());
assert_eq!(qs, "");
}
#[test]
fn build_query_includes_all_filters_in_order() {
let qs = build_leverages_query(&LeveragesQuery {
currency: Some("SEK"),
expiration_date: Some("2025-12-19"),
instrument_group_type: Some("LEVERAGE"),
instrument_type: Some("WNT"),
issuer_id: Some(IssuerId(42)),
market_view: Some("U"),
});
assert_eq!(
qs,
"currency=SEK&expiration_date=2025-12-19&instrument_group_type=LEVERAGE&instrument_type=WNT&issuer_id=42&market_view=U"
);
}
#[test]
fn build_query_percent_encodes_special_chars() {
let qs = build_leverages_query(&LeveragesQuery {
currency: Some("a&b"),
..LeveragesQuery::default()
});
assert_eq!(qs, "currency=a%26b");
}
}