use serde::de::DeserializeOwned;
use serde::Deserialize;
use crate::client::Client;
use crate::error::OpenAIError;
use crate::request::RequestOptions;
#[derive(Debug, Clone, Deserialize)]
#[non_exhaustive]
pub struct List<T> {
pub data: Vec<T>,
#[serde(default)]
pub object: Option<String>,
#[serde(default)]
pub has_more: Option<bool>,
#[serde(default)]
pub first_id: Option<String>,
#[serde(default)]
pub last_id: Option<String>,
}
impl<T> List<T> {
pub fn has_next_page(&self) -> bool {
!self.data.is_empty() && self.has_more != Some(false)
}
}
pub trait HasId {
fn id(&self) -> Option<&str>;
}
pub(crate) fn cursor_query(
after: Option<&str>,
before: Option<&str>,
limit: Option<u32>,
order: Option<&str>,
) -> Vec<(String, String)> {
let mut query = Vec::new();
if let Some(after) = after {
query.push(("after".to_string(), after.to_string()));
}
if let Some(before) = before {
query.push(("before".to_string(), before.to_string()));
}
if let Some(limit) = limit {
query.push(("limit".to_string(), limit.to_string()));
}
if let Some(order) = order {
query.push(("order".to_string(), order.to_string()));
}
query
}
impl Client {
pub(crate) async fn paginate_all<T: HasId + DeserializeOwned>(
&self,
path: &str,
query: Vec<(String, String)>,
) -> Result<Vec<T>, OpenAIError> {
self.paginate_all_with_headers(path, query, Vec::new())
.await
}
pub(crate) async fn paginate_all_with_headers<T: HasId + DeserializeOwned>(
&self,
path: &str,
mut query: Vec<(String, String)>,
extra_headers: Vec<(String, String)>,
) -> Result<Vec<T>, OpenAIError> {
let mut items: Vec<T> = Vec::new();
let mut previous_cursor: Option<String> = None;
loop {
let options = RequestOptions {
query: query.clone(),
extra_headers: extra_headers.clone(),
..RequestOptions::default()
};
let page: List<T> = self.execute(reqwest::Method::GET, path, options).await?;
let has_next = page.has_next_page();
items.extend(page.data);
if !has_next {
return Ok(items);
}
let Some(cursor) = items.last().and_then(HasId::id).map(str::to_owned) else {
return Ok(items);
};
if previous_cursor.as_deref() == Some(cursor.as_str()) {
return Ok(items);
}
previous_cursor = Some(cursor.clone());
query.retain(|(k, _)| k != "after");
query.push(("after".into(), cursor));
}
}
}