Skip to main content

sqry_mcp/
pagination.rs

1use anyhow::{Context, Result, anyhow, bail};
2use base64::{Engine as _, engine::general_purpose};
3
4const CURSOR_PREFIX: &str = "offset:";
5
6#[must_use]
7pub fn encode_cursor(offset: usize) -> String {
8    let payload = format!("{CURSOR_PREFIX}{offset}");
9    general_purpose::URL_SAFE_NO_PAD.encode(payload.as_bytes())
10}
11
12/// Decode a cursor string into an offset.
13///
14/// # Errors
15///
16/// Returns an error when the cursor is malformed, not UTF-8, or cannot be parsed.
17pub fn decode_cursor(cursor: &str) -> Result<usize> {
18    if cursor.trim().is_empty() {
19        return Ok(0);
20    }
21
22    if let Ok(value) = cursor.parse::<usize>() {
23        return Ok(value);
24    }
25
26    if let Some(rest) = cursor.strip_prefix(CURSOR_PREFIX) {
27        return rest
28            .parse::<usize>()
29            .map_err(|_| anyhow!("Invalid cursor payload"));
30    }
31
32    let decoded = general_purpose::URL_SAFE_NO_PAD
33        .decode(cursor)
34        .with_context(|| format!("Failed to decode cursor '{cursor}'"))?;
35    let decoded_str = String::from_utf8(decoded)
36        .with_context(|| format!("Cursor '{cursor}' is not valid UTF-8"))?;
37
38    let Some(rest) = decoded_str.strip_prefix(CURSOR_PREFIX) else {
39        bail!("Invalid cursor payload");
40    };
41
42    rest.parse::<usize>()
43        .map_err(|_| anyhow!("Invalid cursor payload"))
44}