openai_compat/
pagination.rs1use serde::de::DeserializeOwned;
6use serde::Deserialize;
7
8use crate::client::Client;
9use crate::error::OpenAIError;
10use crate::request::RequestOptions;
11
12#[derive(Debug, Clone, Deserialize)]
14#[non_exhaustive]
15pub struct List<T> {
16 pub data: Vec<T>,
17 #[serde(default)]
18 pub object: Option<String>,
19 #[serde(default)]
20 pub has_more: Option<bool>,
21 #[serde(default)]
22 pub first_id: Option<String>,
23 #[serde(default)]
24 pub last_id: Option<String>,
25}
26
27impl<T> List<T> {
28 pub fn has_next_page(&self) -> bool {
31 !self.data.is_empty() && self.has_more != Some(false)
32 }
33}
34
35pub trait HasId {
37 fn id(&self) -> Option<&str>;
38}
39
40pub(crate) fn cursor_query(
43 after: Option<&str>,
44 before: Option<&str>,
45 limit: Option<u32>,
46 order: Option<&str>,
47) -> Vec<(String, String)> {
48 let mut query = Vec::new();
49 if let Some(after) = after {
50 query.push(("after".to_string(), after.to_string()));
51 }
52 if let Some(before) = before {
53 query.push(("before".to_string(), before.to_string()));
54 }
55 if let Some(limit) = limit {
56 query.push(("limit".to_string(), limit.to_string()));
57 }
58 if let Some(order) = order {
59 query.push(("order".to_string(), order.to_string()));
60 }
61 query
62}
63
64impl Client {
65 pub(crate) async fn paginate_all<T: HasId + DeserializeOwned>(
68 &self,
69 path: &str,
70 query: Vec<(String, String)>,
71 ) -> Result<Vec<T>, OpenAIError> {
72 self.paginate_all_with_headers(path, query, Vec::new())
73 .await
74 }
75
76 pub(crate) async fn paginate_all_with_headers<T: HasId + DeserializeOwned>(
79 &self,
80 path: &str,
81 mut query: Vec<(String, String)>,
82 extra_headers: Vec<(String, String)>,
83 ) -> Result<Vec<T>, OpenAIError> {
84 let mut items: Vec<T> = Vec::new();
85 let mut previous_cursor: Option<String> = None;
86 loop {
87 let options = RequestOptions {
88 query: query.clone(),
89 extra_headers: extra_headers.clone(),
90 ..RequestOptions::default()
91 };
92 let page: List<T> = self.execute(reqwest::Method::GET, path, options).await?;
93 let has_next = page.has_next_page();
94 items.extend(page.data);
95
96 if !has_next {
97 return Ok(items);
98 }
99 let Some(cursor) = items.last().and_then(HasId::id).map(str::to_owned) else {
100 return Ok(items);
101 };
102 if previous_cursor.as_deref() == Some(cursor.as_str()) {
105 return Ok(items);
106 }
107 previous_cursor = Some(cursor.clone());
108 query.retain(|(k, _)| k != "after");
109 query.push(("after".into(), cursor));
110 }
111 }
112}