
Pagination.pudu
Pudu42 lines1.5 KB
1/** @Domain.Protocol.Pagination.Module — opaque cursors over list results */2module PuduLangMcp.Domain.Protocol.Pagination34import Std.Json as Json5import Std.Math as Math6import Std.Text as Text7import PuduLangMcp.Errors.RpcError as RpcError8import PuduLangMcp.Utils.JsonAccess as Access91011const CURSOR_PREFIX: Str = "offset:"121314export fn cursorOf(params: &Json.Json) -> Result[Option[Str], RpcError.ProtocolError] {15 match Access.member(params, "cursor") {16 case None => Ok(None)17 case Some(Json.Text(cursor)) => Ok(Some(cursor))18 case Some(_) => Err(RpcError.InvalidParams("cursor must be a string"))19 }20}212223export fn page[T](items: &Array[T], cursor: &Option[Str], size: Int) -> Result[(Array[T], Option[Str]), RpcError.ProtocolError] {24 let start = match cursor {25 case None => 026 case Some(text) => match offsetOf(text, items.length()) {27 case Some(offset) => offset28 case None => { return Err(RpcError.InvalidParams("invalid cursor")) }29 }30 }31 let end = Math.min(start + size, items.length())32 let next = if end < items.length() { Some(CURSOR_PREFIX + show(end)) } else { None }33 Ok((items.slice(start, end), next))34}353637fn offsetOf(cursor: Str, length: Int) -> Option[Int] {38 if !cursor.startsWith(CURSOR_PREFIX) { return None }39 let offset = Text.countOf(cursor.drop(CURSOR_PREFIX.length())) ?40 if offset < length { Some(offset) } else { None }41}42