use bon::Builder;
#[derive(Debug, Clone, Default, Builder, PartialEq, Eq)]
#[non_exhaustive]
pub struct ListOptions {
#[builder(into)]
pub page: Option<u32>,
#[builder(into)]
pub limit: Option<u32>,
#[builder(into)]
pub cursor: Option<String>,
#[builder(into)]
pub shape: Option<String>,
#[builder(default)]
pub flat: bool,
#[builder(default)]
pub flat_lists: bool,
}
impl ListOptions {
pub(crate) fn apply(&self, q: &mut Vec<(String, String)>) {
apply_pagination(
q,
self.page,
self.limit,
self.cursor.as_deref(),
self.shape.as_deref(),
self.flat,
self.flat_lists,
);
}
}
pub(crate) fn apply_pagination(
q: &mut Vec<(String, String)>,
page: Option<u32>,
limit: Option<u32>,
cursor: Option<&str>,
shape: Option<&str>,
flat: bool,
flat_lists: bool,
) {
if let Some(c) = cursor.filter(|s| !s.is_empty()) {
q.push(("cursor".into(), c.into()));
} else if let Some(p) = page.filter(|p| *p > 0) {
q.push(("page".into(), p.to_string()));
}
if let Some(l) = limit.filter(|l| *l > 0) {
q.push(("limit".into(), l.to_string()));
}
if let Some(s) = shape.filter(|s| !s.is_empty()) {
q.push(("shape".into(), s.into()));
}
if flat {
q.push(("flat".into(), "true".into()));
}
if flat_lists {
q.push(("flat_lists".into(), "true".into()));
}
}
pub(crate) fn push_opt(q: &mut Vec<(String, String)>, key: &str, value: Option<&str>) {
if let Some(v) = value.filter(|v| !v.is_empty()) {
q.push((key.into(), v.into()));
}
}
pub(crate) fn push_opt_bool(q: &mut Vec<(String, String)>, key: &str, value: Option<bool>) {
if let Some(v) = value {
q.push((key.into(), v.to_string()));
}
}
pub(crate) fn push_opt_u32(q: &mut Vec<(String, String)>, key: &str, value: Option<u32>) {
if let Some(v) = value.filter(|n| *n > 0) {
q.push((key.into(), v.to_string()));
}
}
pub(crate) fn first_non_empty<'a>(values: &[Option<&'a str>]) -> Option<&'a str> {
values.iter().copied().flatten().find(|s| !s.is_empty())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn list_options_cursor_wins_over_page() {
let opts = ListOptions::builder()
.page(2u32)
.cursor("abc".to_string())
.build();
let mut q = Vec::new();
opts.apply(&mut q);
assert!(q.contains(&("cursor".into(), "abc".into())));
assert!(!q.iter().any(|(k, _)| k == "page"));
}
#[test]
fn list_options_only_emits_set_fields() {
let opts = ListOptions::builder().limit(25u32).build();
let mut q = Vec::new();
opts.apply(&mut q);
assert_eq!(q, vec![("limit".to_string(), "25".to_string())]);
}
#[test]
fn list_options_flat_emits_string() {
let opts = ListOptions::builder().flat(true).flat_lists(true).build();
let mut q = Vec::new();
opts.apply(&mut q);
assert!(q.contains(&("flat".into(), "true".into())));
assert!(q.contains(&("flat_lists".into(), "true".into())));
}
#[test]
fn first_non_empty_skips_empties() {
assert_eq!(
first_non_empty(&[Some(""), Some("a"), Some("b")]),
Some("a")
);
assert_eq!(first_non_empty(&[None, None]), None);
assert_eq!(first_non_empty(&[Some("")]), None);
}
}