kegani 0.1.0

A developer-friendly, ergonomic, production-ready Rust web framework
Documentation
//! Query builder utilities for Kegani
//!
//! Provides helper functions for building SQL queries.

use sqlx::PgPool;
use crate::error::Result;

/// Pagination parameters
#[derive(Debug, Clone)]
pub struct Pagination {
    pub page: u32,
    pub size: u32,
}

impl Pagination {
    /// Create new pagination
    pub fn new(page: u32, size: u32) -> Self {
        Self { page, size }
    }

    /// Calculate offset
    pub fn offset(&self) -> i64 {
        (self.page.saturating_sub(1)) as i64 * self.size as i64
    }

    /// Get limit
    pub fn limit(&self) -> i64 {
        self.size as i64
    }
}

impl Default for Pagination {
    fn default() -> Self {
        Self { page: 1, size: 20 }
    }
}

/// Page result wrapper
#[derive(Debug)]
pub struct Page<T> {
    pub items: Vec<T>,
    pub page: u32,
    pub size: u32,
    pub total: i64,
}

impl<T> Page<T> {
    /// Create a new page
    pub fn new(items: Vec<T>, page: u32, size: u32, total: i64) -> Self {
        Self {
            items,
            page,
            size,
            total,
        }
    }

    /// Check if has next page
    pub fn has_next(&self) -> bool {
        (self.page as i64) * (self.size as i64) < self.total
    }

    /// Check if has previous page
    pub fn has_prev(&self) -> bool {
        self.page > 1
    }

    /// Total pages
    pub fn total_pages(&self) -> u32 {
        ((self.total as f64) / (self.size as f64)).ceil() as u32
    }
}

/// Sort direction
#[derive(Debug, Clone)]
pub enum SortDirection {
    Asc,
    Desc,
}

/// Sort parameters
#[derive(Debug, Clone)]
pub struct Sort {
    pub field: String,
    pub direction: SortDirection,
}

impl Sort {
    /// Create ascending sort
    pub fn asc(field: &str) -> Self {
        Self {
            field: field.to_string(),
            direction: SortDirection::Asc,
        }
    }

    /// Create descending sort
    pub fn desc(field: &str) -> Self {
        Self {
            field: field.to_string(),
            direction: SortDirection::Desc,
        }
    }

    /// Convert to SQL
    pub fn to_sql(&self) -> String {
        let dir = match self.direction {
            SortDirection::Asc => "ASC",
            SortDirection::Desc => "DESC",
        };
        format!("{} {}", self.field, dir)
    }
}

/// Query builder helper
pub struct QueryBuilder {
    table: String,
    conditions: Vec<String>,
    order_by: Option<Sort>,
    limit_val: Option<i64>,
    offset_val: Option<i64>,
}

impl QueryBuilder {
    /// Create a new query builder
    pub fn new(table: &str) -> Self {
        Self {
            table: table.to_string(),
            conditions: Vec::new(),
            order_by: None,
            limit_val: None,
            offset_val: None,
        }
    }

    /// Add a condition
    pub fn where_clause(mut self, condition: &str) -> Self {
        self.conditions.push(condition.to_string());
        self
    }

    /// Set order by
    pub fn order_by(mut self, sort: Sort) -> Self {
        self.order_by = Some(sort);
        self
    }

    /// Set limit
    pub fn limit(mut self, limit: i64) -> Self {
        self.limit_val = Some(limit);
        self
    }

    /// Set offset
    pub fn offset(mut self, offset: i64) -> Self {
        self.offset_val = Some(offset);
        self
    }

    /// Build SELECT query
    pub fn build_select(&self) -> String {
        let mut sql = format!("SELECT * FROM {}", self.table);

        if !self.conditions.is_empty() {
            sql.push_str(" WHERE ");
            sql.push_str(&self.conditions.join(" AND "));
        }

        if let Some(ref sort) = self.order_by {
            sql.push_str(" ORDER BY ");
            sql.push_str(&sort.to_sql());
        }

        if let Some(limit) = self.limit_val {
            sql.push_str(&format!(" LIMIT {}", limit));
        }

        if let Some(offset) = self.offset_val {
            sql.push_str(&format!(" OFFSET {}", offset));
        }

        sql
    }

    /// Build COUNT query
    pub fn build_count(&self) -> String {
        let mut sql = format!("SELECT COUNT(*) FROM {}", self.table);

        if !self.conditions.is_empty() {
            sql.push_str(" WHERE ");
            sql.push_str(&self.conditions.join(" AND "));
        }

        sql
    }
}