rust 56 lines · 8 steps

Opaque pagination cursors in Rust

Encode a page's position into a base64 token and decode it back for keyset pagination.

Explained by highlit
1use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
2use serde::{Deserialize, Serialize};
3use time::OffsetDateTime;
4use uuid::Uuid;
5 
6#[derive(Debug, Serialize, Deserialize)]
7struct Cursor {
8 created_at: OffsetDateTime,
9 id: Uuid,
10}
11 
12#[derive(Debug, thiserror::Error)]
13pub enum CursorError {
14 #[error("malformed cursor encoding")]
15 Decode(#[from] base64::DecodeError),
16 #[error("malformed cursor payload")]
17 Deserialize(#[from] serde_json::Error),
18}
19 
20impl Cursor {
21 fn encode(&self) -> String {
22 let payload = serde_json::to_vec(self).expect("cursor is serializable");
23 URL_SAFE_NO_PAD.encode(payload)
24 }
25 
26 fn decode(token: &str) -> Result<Self, CursorError> {
27 let bytes = URL_SAFE_NO_PAD.decode(token)?;
28 Ok(serde_json::from_slice(&bytes)?)
29 }
30}
31 
32pub struct Page<T> {
33 pub items: Vec<T>,
34 pub next_cursor: Option<String>,
35}
36 
37impl Page<Article> {
38 pub fn from_rows(mut rows: Vec<Article>, limit: usize) -> Self {
39 let has_more = rows.len() > limit;
40 rows.truncate(limit);
41 
42 let next_cursor = has_more.then(|| {
43 let last = rows.last().expect("non-empty page has a last row");
44 Cursor { created_at: last.created_at, id: last.id }.encode()
45 });
46 
47 Page { items: rows, next_cursor }
48 }
49}
50 
51pub fn parse_cursor(token: Option<&str>) -> Result<Option<(OffsetDateTime, Uuid)>, CursorError> {
52 token
53 .map(Cursor::decode)
54 .transpose()
55 .map(|opt| opt.map(|c| (c.created_at, c.id)))
56}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A cursor is just a serialized position turned into an opaque, URL-safe token.
  2. 2Fetching one row past the limit is a cheap way to detect whether more pages exist.
  3. 3Combining map, transpose, and map lets you thread an Option through a fallible step cleanly.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Opaque pagination cursors in Rust — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code