Skip to main content

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}