nordnet_api/pagination.rs
1//! Offset/limit pagination helpers.
2//!
3//! The Nordnet API uses query-string offset/limit for paged endpoints
4//! (e.g. `?offset=0&limit=100`). The actual response shape varies by
5//! endpoint — some return a bare JSON array, others wrap it in a typed
6//! object. This module provides a generic [`Page`] holder that group
7//! implementers can either compose into their own response types or use
8//! directly when the wire shape matches.
9
10use serde::{Deserialize, Serialize};
11
12/// One page of results plus the offset/limit window it represents. The
13/// API does not always return a total count, so it is omitted here.
14#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
15pub struct Page<T> {
16 pub items: Vec<T>,
17 pub offset: u64,
18 pub limit: u64,
19}
20
21impl<T> Page<T> {
22 /// Build a page from a vector. `offset` and `limit` correspond to the
23 /// query parameters that produced this page.
24 pub fn new(items: Vec<T>, offset: u64, limit: u64) -> Self {
25 Self {
26 items,
27 offset,
28 limit,
29 }
30 }
31
32 /// Number of items returned in this page (0..=limit).
33 pub fn len(&self) -> usize {
34 self.items.len()
35 }
36
37 /// True if this page is empty.
38 pub fn is_empty(&self) -> bool {
39 self.items.is_empty()
40 }
41
42 /// True if this page is "probably last" — i.e. fewer items than the
43 /// page size were returned. Heuristic; some endpoints return exactly
44 /// `limit` on the last page.
45 pub fn is_probably_last(&self) -> bool {
46 (self.items.len() as u64) < self.limit
47 }
48
49 /// The offset to request for the next page.
50 pub fn next_offset(&self) -> u64 {
51 self.offset + self.items.len() as u64
52 }
53}
54
55/// Iterator helper: converts a `Page<T>` into an owned iterator over its
56/// items, discarding the window metadata.
57impl<T> IntoIterator for Page<T> {
58 type Item = T;
59 type IntoIter = std::vec::IntoIter<T>;
60
61 fn into_iter(self) -> Self::IntoIter {
62 self.items.into_iter()
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn page_basics() {
72 let p = Page::new(vec![1, 2, 3], 0, 10);
73 assert_eq!(p.len(), 3);
74 assert!(!p.is_empty());
75 assert!(p.is_probably_last());
76 assert_eq!(p.next_offset(), 3);
77 }
78
79 #[test]
80 fn full_page_not_probably_last() {
81 let p = Page::new(vec![1, 2, 3], 0, 3);
82 assert!(!p.is_probably_last());
83 assert_eq!(p.next_offset(), 3);
84 }
85
86 #[test]
87 fn iter() {
88 let p = Page::new(vec!["a", "b"], 5, 10);
89 let v: Vec<_> = p.into_iter().collect();
90 assert_eq!(v, vec!["a", "b"]);
91 }
92}