use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct LogicalProjection {
pub fields: Vec<String>,
#[serde(default)]
pub include_metadata: bool,
}
impl LogicalProjection {
pub fn fields(fields: impl IntoIterator<Item = String>) -> Self {
Self {
fields: fields.into_iter().collect(),
include_metadata: false,
}
}
pub fn is_select_all(&self) -> bool {
self.fields.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LogicalSort {
pub field: String,
pub direction: SortDirection,
#[serde(default)]
pub nulls: NullOrder,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SortDirection {
Asc,
Desc,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NullOrder {
First,
Last,
#[default]
Default,
}
impl SortDirection {
pub fn token(self) -> &'static str {
match self {
Self::Asc => "asc",
Self::Desc => "desc",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct LogicalPagination {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub offset: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
impl LogicalPagination {
pub fn limit(n: u32) -> Self {
Self {
limit: Some(n),
..Self::default()
}
}
pub fn page(offset: u64, limit: u32) -> Self {
Self {
limit: Some(limit),
offset: Some(offset),
cursor: None,
}
}
pub fn uses_cursor(&self) -> bool {
self.cursor.as_ref().is_some_and(|c| !c.is_empty())
}
pub fn uses_offset(&self) -> bool {
self.offset.is_some_and(|o| o > 0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn projection_default_is_select_all() {
let p = LogicalProjection::default();
assert!(p.is_select_all());
assert!(p.fields.is_empty());
assert!(!p.include_metadata);
}
#[test]
fn pagination_mode_helpers() {
let p = LogicalPagination::page(20, 10);
assert!(p.uses_offset());
assert!(!p.uses_cursor());
let c = LogicalPagination {
cursor: Some("abc".into()),
limit: Some(10),
..Default::default()
};
assert!(c.uses_cursor());
assert!(!c.uses_offset());
let empty = LogicalPagination::default();
assert!(!empty.uses_cursor());
assert!(!empty.uses_offset());
}
#[test]
fn sort_direction_token() {
assert_eq!(SortDirection::Asc.token(), "asc");
assert_eq!(SortDirection::Desc.token(), "desc");
}
}