268 lines
8.4 KiB
Rust
268 lines
8.4 KiB
Rust
// file: crates/ksp-store-api/src/model/raw_pagination.rs
|
|
// version: 1
|
|
|
|
/// Maximum opaque cursor length accepted by Store API queries.
|
|
///
|
|
/// This bounds untrusted token material only. It is not a policy limit on the
|
|
/// number of rows/results a backend may return.
|
|
pub const MAX_RAW_PAGE_CURSOR_BYTES: usize = 4 * 1024;
|
|
|
|
/// Opaque backend-owned cursor returned by one deterministic Store query.
|
|
#[derive(Clone, Eq, PartialEq)]
|
|
pub struct RawPageCursor(std::boxed::Box<[u8]>);
|
|
|
|
impl RawPageCursor {
|
|
/// Creates one non-empty bounded opaque cursor.
|
|
pub fn try_new(bytes: std::boxed::Box<[u8]>) -> crate::Result<Self> {
|
|
if bytes.is_empty() || bytes.len() > crate::MAX_RAW_PAGE_CURSOR_BYTES {
|
|
return std::result::Result::Err(raw_query_error("cursor"));
|
|
}
|
|
return std::result::Result::Ok(Self(bytes));
|
|
}
|
|
|
|
/// Returns the opaque cursor bytes unchanged.
|
|
#[must_use]
|
|
pub fn as_bytes(&self) -> &[u8] {
|
|
return self.0.as_ref();
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for RawPageCursor {
|
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
return formatter.debug_struct("RawPageCursor").field("len", &self.0.len()).finish();
|
|
}
|
|
}
|
|
|
|
/// Caller-requested page size without an arbitrary KSP policy ceiling.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub struct RawPageLimit(u64);
|
|
|
|
impl RawPageLimit {
|
|
/// Creates one strictly positive requested page size.
|
|
///
|
|
/// `ksp-store-api` deliberately imposes no smaller functional maximum.
|
|
/// Concrete backends may expose or return their real physical limitations.
|
|
pub fn new(value: u64) -> crate::Result<Self> {
|
|
if value == 0 {
|
|
return std::result::Result::Err(raw_query_error("limit"));
|
|
}
|
|
return std::result::Result::Ok(Self(value));
|
|
}
|
|
|
|
/// Returns the exact caller-requested item count.
|
|
#[must_use]
|
|
pub const fn get(&self) -> u64 {
|
|
return self.0;
|
|
}
|
|
}
|
|
|
|
/// Opaque-cursor page request used by backend-independent list operations.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct RawPageRequest {
|
|
cursor: std::option::Option<crate::RawPageCursor>,
|
|
limit: crate::RawPageLimit,
|
|
}
|
|
|
|
impl RawPageRequest {
|
|
/// Creates one first-page request.
|
|
#[must_use]
|
|
pub const fn first(limit: crate::RawPageLimit) -> Self {
|
|
return Self { cursor: std::option::Option::None, limit };
|
|
}
|
|
|
|
/// Creates one continuation-page request with an opaque backend cursor.
|
|
#[must_use]
|
|
pub const fn after(limit: crate::RawPageLimit, cursor: crate::RawPageCursor) -> Self {
|
|
return Self { cursor: std::option::Option::Some(cursor), limit };
|
|
}
|
|
|
|
/// Returns the opaque continuation cursor when present.
|
|
#[must_use]
|
|
pub fn cursor(&self) -> std::option::Option<&crate::RawPageCursor> {
|
|
return self.cursor.as_ref();
|
|
}
|
|
|
|
/// Returns the exact caller-requested page size.
|
|
#[must_use]
|
|
pub const fn limit(&self) -> crate::RawPageLimit {
|
|
return self.limit;
|
|
}
|
|
}
|
|
|
|
/// One deterministic page of backend-independent Store results.
|
|
#[derive(Debug)]
|
|
pub struct RawPage<T> {
|
|
items: std::vec::Vec<T>,
|
|
next_cursor: std::option::Option<crate::RawPageCursor>,
|
|
}
|
|
|
|
impl<T> RawPage<T> {
|
|
/// Creates one result page from backend-provided items and optional continuation cursor.
|
|
#[must_use]
|
|
pub fn new(items: std::vec::Vec<T>, next_cursor: std::option::Option<crate::RawPageCursor>) -> Self {
|
|
return Self { items, next_cursor };
|
|
}
|
|
|
|
/// Returns the current page items.
|
|
#[must_use]
|
|
pub fn items(&self) -> &[T] {
|
|
return self.items.as_slice();
|
|
}
|
|
|
|
/// Consumes the page and returns its items.
|
|
#[must_use]
|
|
pub fn into_items(self) -> std::vec::Vec<T> {
|
|
return self.items;
|
|
}
|
|
|
|
/// Returns the next opaque cursor when more results are available.
|
|
#[must_use]
|
|
pub fn next_cursor(&self) -> std::option::Option<&crate::RawPageCursor> {
|
|
return self.next_cursor.as_ref();
|
|
}
|
|
}
|
|
|
|
/// Deterministic traversal direction for Store list queries.
|
|
#[non_exhaustive]
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub enum RawSortDirection {
|
|
/// Oldest/lower ordered keys first.
|
|
Ascending,
|
|
/// Newest/higher ordered keys first.
|
|
Descending,
|
|
}
|
|
|
|
/// Optional inclusive Solana slot bounds for one Store query.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub struct RawSlotRange {
|
|
end_inclusive: std::option::Option<u64>,
|
|
start_inclusive: std::option::Option<u64>,
|
|
}
|
|
|
|
impl RawSlotRange {
|
|
/// Creates one optional inclusive slot range and rejects reversed bounds.
|
|
pub fn new(start_inclusive: std::option::Option<u64>, end_inclusive: std::option::Option<u64>) -> crate::Result<Self> {
|
|
if let (std::option::Option::Some(start), std::option::Option::Some(end)) = (start_inclusive, end_inclusive)
|
|
&& start > end
|
|
{
|
|
return std::result::Result::Err(raw_query_error("slot_range"));
|
|
}
|
|
return std::result::Result::Ok(Self { end_inclusive, start_inclusive });
|
|
}
|
|
|
|
/// Returns the optional inclusive ending slot.
|
|
#[must_use]
|
|
pub const fn end_inclusive(&self) -> std::option::Option<u64> {
|
|
return self.end_inclusive;
|
|
}
|
|
|
|
/// Returns the optional inclusive starting slot.
|
|
#[must_use]
|
|
pub const fn start_inclusive(&self) -> std::option::Option<u64> {
|
|
return self.start_inclusive;
|
|
}
|
|
}
|
|
|
|
/// Backend-independent list query for canonical RAW transactions.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct RawTransactionQuery {
|
|
direction: crate::RawSortDirection,
|
|
network: crate::RawNetworkId,
|
|
page: crate::RawPageRequest,
|
|
slots: crate::RawSlotRange,
|
|
}
|
|
|
|
impl RawTransactionQuery {
|
|
/// Creates one deterministic transaction-reference query.
|
|
#[must_use]
|
|
pub fn new(network: crate::RawNetworkId, slots: crate::RawSlotRange, direction: crate::RawSortDirection, page: crate::RawPageRequest) -> Self {
|
|
return Self { direction, network, page, slots };
|
|
}
|
|
|
|
/// Returns the requested deterministic traversal direction.
|
|
#[must_use]
|
|
pub const fn direction(&self) -> crate::RawSortDirection {
|
|
return self.direction;
|
|
}
|
|
|
|
/// Returns the required logical network scope.
|
|
#[must_use]
|
|
pub fn network(&self) -> &crate::RawNetworkId {
|
|
return &self.network;
|
|
}
|
|
|
|
/// Returns pagination inputs without exposing backend cursor contents.
|
|
#[must_use]
|
|
pub fn page(&self) -> &crate::RawPageRequest {
|
|
return &self.page;
|
|
}
|
|
|
|
/// Returns optional inclusive slot bounds.
|
|
#[must_use]
|
|
pub const fn slots(&self) -> crate::RawSlotRange {
|
|
return self.slots;
|
|
}
|
|
}
|
|
|
|
/// Backend-independent list query for complete canonical RAW account states.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct RawAccountStateQuery {
|
|
direction: crate::RawSortDirection,
|
|
network: crate::RawNetworkId,
|
|
page: crate::RawPageRequest,
|
|
pubkey: std::option::Option<crate::Pubkey>,
|
|
slots: crate::RawSlotRange,
|
|
}
|
|
|
|
impl RawAccountStateQuery {
|
|
/// Creates one deterministic account-state-reference query.
|
|
#[must_use]
|
|
pub fn new(
|
|
network: crate::RawNetworkId,
|
|
pubkey: std::option::Option<crate::Pubkey>,
|
|
slots: crate::RawSlotRange,
|
|
direction: crate::RawSortDirection,
|
|
page: crate::RawPageRequest,
|
|
) -> Self {
|
|
return Self { direction, network, page, pubkey, slots };
|
|
}
|
|
|
|
/// Returns the requested deterministic traversal direction.
|
|
#[must_use]
|
|
pub const fn direction(&self) -> crate::RawSortDirection {
|
|
return self.direction;
|
|
}
|
|
|
|
/// Returns the required logical network scope.
|
|
#[must_use]
|
|
pub fn network(&self) -> &crate::RawNetworkId {
|
|
return &self.network;
|
|
}
|
|
|
|
/// Returns pagination inputs without exposing backend cursor contents.
|
|
#[must_use]
|
|
pub fn page(&self) -> &crate::RawPageRequest {
|
|
return &self.page;
|
|
}
|
|
|
|
/// Returns an optional account-address restriction.
|
|
#[must_use]
|
|
pub fn pubkey(&self) -> std::option::Option<&crate::Pubkey> {
|
|
return self.pubkey.as_ref();
|
|
}
|
|
|
|
/// Returns optional inclusive slot bounds.
|
|
#[must_use]
|
|
pub const fn slots(&self) -> crate::RawSlotRange {
|
|
return self.slots;
|
|
}
|
|
}
|
|
|
|
fn raw_query_error(field: &'static str) -> crate::Error {
|
|
return crate::Error::new(crate::ERROR_CODE_RAW_QUERY_INVALID, "invalid backend-agnostic RAW Store query").with_context("field", field);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "../../unit_tests/model/raw_pagination.rs"]
|
|
mod tests;
|