use crate::client::Client;
use crate::error::Result;
use crate::types::{Card, CardSearchResponse, HistoryResponse};
pub const SEARCH_IDS_CHUNK_SIZE: usize = 20;
#[derive(Debug, Clone, Default)]
pub struct CardSearchParams {
pub q: Option<String>,
pub ids: Option<Vec<String>>,
pub game: Option<String>,
pub set: Option<String>,
pub limit: Option<u32>,
pub offset: Option<u32>,
}
#[derive(Debug, Clone, Default)]
pub struct HistoryParams {
pub period: Option<String>,
}
pub struct CardsResource<'a> {
client: &'a Client,
}
impl<'a> CardsResource<'a> {
pub(crate) fn new(client: &'a Client) -> Self {
Self { client }
}
pub async fn search(&self, params: CardSearchParams) -> Result<CardSearchResponse> {
let ids = params.ids.clone().unwrap_or_default();
if ids.is_empty() {
return self.search_once(¶ms, None).await;
}
if ids.len() <= SEARCH_IDS_CHUNK_SIZE {
return self.search_once(¶ms, Some(ids.join(","))).await;
}
self.search_chunked(¶ms, ids).await
}
pub async fn get(&self, id: &str) -> Result<Card> {
let path = format!("/cards/{}", urlencode(id));
self.client.get(&path, &[]).await
}
pub async fn history(&self, id: &str, params: HistoryParams) -> Result<HistoryResponse> {
let path = format!("/cards/{}/history", urlencode(id));
let mut q: Vec<(&str, String)> = Vec::new();
if let Some(p) = params.period {
q.push(("period", p));
}
self.client.get(&path, &q).await
}
async fn search_once(
&self,
params: &CardSearchParams,
ids: Option<String>,
) -> Result<CardSearchResponse> {
let mut q: Vec<(&str, String)> = Vec::new();
if let Some(v) = ¶ms.q {
q.push(("q", v.clone()));
}
if let Some(v) = ids {
q.push(("ids", v));
}
if let Some(v) = ¶ms.game {
q.push(("game", v.clone()));
}
if let Some(v) = ¶ms.set {
q.push(("set", v.clone()));
}
if let Some(v) = params.limit {
q.push(("limit", v.to_string()));
}
if let Some(v) = params.offset {
q.push(("offset", v.to_string()));
}
self.client.get("/cards/search", &q).await
}
async fn search_chunked(
&self,
params: &CardSearchParams,
ids: Vec<String>,
) -> Result<CardSearchResponse> {
let mut merged: Vec<Card> = Vec::new();
for chunk in ids.chunks(SEARCH_IDS_CHUNK_SIZE) {
let page = self.search_once(params, Some(chunk.join(","))).await?;
merged.extend(page.data);
}
let total = merged.len();
Ok(CardSearchResponse {
data: merged,
total,
limit: params.limit.map(|v| v as usize).unwrap_or(total),
offset: params.offset.map(|v| v as usize).unwrap_or(0),
})
}
}
fn urlencode(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for b in s.bytes() {
if b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.' | b'~') {
out.push(b as char);
} else {
out.push_str(&format!("%{:02X}", b));
}
}
out
}