openai_interface/pagination.rs
1//! Cursor pagination helpers shared by list endpoints.
2//!
3//! > ![warn] This module is untested!
4//! > If you encounter any issues, please report them on the repository.
5//!
6//! OpenAI-compatible list endpoints return a page object with a `data`
7//! array and pagination cursor fields. The [`Page`] type models that
8//! shape generically, and [`PaginationQuery`] collects the standard query
9//! parameters so individual list requests can embed it via
10//! `Deref`-style composition or flattening.
11
12/// A single page of a paginated list response.
13///
14/// Mirrors the official list shape:
15/// `{"object": "list", "data": [...], "first_id": ..., "last_id": ...,
16/// "has_more": ...}`. Fields other than `data` are optional because some
17/// OpenAI-compatible providers omit them.
18#[derive(Debug, Clone, serde::Deserialize)]
19pub struct Page<T> {
20 /// The items on this page.
21 #[serde(default)]
22 pub data: Vec<T>,
23 /// Whether more items exist after this page.
24 #[serde(default)]
25 pub has_more: Option<bool>,
26 /// The ID of the first item on the page, for cursor pagination.
27 #[serde(default)]
28 pub first_id: Option<String>,
29 /// The ID of the last item on the page, for cursor pagination.
30 #[serde(default)]
31 pub last_id: Option<String>,
32 /// The object type (`list`), if the provider sends it.
33 #[serde(default)]
34 pub object: Option<String>,
35}
36
37/// The standard pagination query parameters accepted by most list
38/// endpoints.
39///
40/// List request structs embed this and pass its values into
41/// `query_pairs_mut` when building the URL.
42#[derive(Debug, Clone, Copy, Default)]
43pub struct PaginationQuery<'a> {
44 /// A cursor for pagination: the object ID that defines the place in
45 /// the list *before* which items are returned.
46 pub before: Option<&'a str>,
47 /// A cursor for pagination: the object ID that defines the place in
48 /// the list *after* which items are returned.
49 pub after: Option<&'a str>,
50 /// A limit on the number of objects to be returned (1-100, default
51 /// 20 for most endpoints).
52 pub limit: Option<u32>,
53 /// The sort order of the returned objects.
54 pub order: Option<PaginationOrder>,
55}
56
57/// The sort order of objects in a list response.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
59pub enum PaginationOrder {
60 /// Ascending order (oldest first).
61 Asc,
62 /// Descending order (newest first, the default).
63 #[default]
64 Desc,
65}
66
67impl PaginationOrder {
68 /// The wire value of the order.
69 #[must_use]
70 pub fn as_str(self) -> &'static str {
71 match self {
72 PaginationOrder::Asc => "asc",
73 PaginationOrder::Desc => "desc",
74 }
75 }
76}
77
78impl<'a> PaginationQuery<'a> {
79 /// Appends the set parameters to `pairs`.
80 pub fn append_to<T: url::form_urlencoded::Target>(
81 &self,
82 pairs: &mut url::form_urlencoded::Serializer<'_, T>,
83 ) {
84 if let Some(before) = self.before {
85 pairs.append_pair("before", before);
86 }
87 if let Some(after) = self.after {
88 pairs.append_pair("after", after);
89 }
90 if let Some(limit) = self.limit {
91 pairs.append_pair("limit", &limit.to_string());
92 }
93 if let Some(order) = self.order {
94 pairs.append_pair("order", order.as_str());
95 }
96 }
97
98 /// Whether any parameter is set.
99 #[must_use]
100 pub fn any_set(&self) -> bool {
101 self.before.is_some()
102 || self.after.is_some()
103 || self.limit.is_some()
104 || self.order.is_some()
105 }
106}