axum_sql_viewer/api/
rows.rs

1//! Row fetching endpoints with pagination
2
3use axum::{
4    extract::{Path, Query, State},
5    http::StatusCode,
6    response::{IntoResponse, Json, Response},
7};
8use std::sync::Arc;
9
10use crate::database::traits::DatabaseProvider;
11use crate::schema::RowQuery;
12
13/// Maximum allowed limit to prevent excessive memory usage
14const MAX_LIMIT: u64 = 500;
15
16/// Handler for GET /api/tables/:name/rows
17///
18/// Fetches rows from a table with pagination, sorting, and filtering.
19///
20/// Query parameters:
21/// - offset: Starting row offset (default: 0)
22/// - limit: Maximum rows to return (default: 100, max: 500)
23/// - sortBy: Column name to sort by (optional)
24/// - sortOrder: "ascending" or "descending" (optional, default: "ascending")
25/// - filter[column]: Filter value for specific column (supports % wildcards)
26///
27/// # Arguments
28///
29/// * `database` - Database provider from state
30/// * `table_name` - Name of the table to fetch rows from
31/// * `query` - Query parameters for pagination, sorting, and filtering
32///
33/// # Returns
34///
35/// JSON response containing rows, columns, and pagination metadata
36pub async fn get_rows_handler<DB: DatabaseProvider>(
37    State(database): State<Arc<DB>>,
38    Path(table_name): Path<String>,
39    Query(mut query): Query<RowQuery>,
40) -> Response {
41    // Enforce maximum limit
42    if query.limit > MAX_LIMIT {
43        query.limit = MAX_LIMIT;
44    }
45
46    match database.get_rows(&table_name, query).await {
47        Ok(response) => (StatusCode::OK, Json(response)).into_response(),
48        Err(error) => {
49            eprintln!(
50                "Failed to get rows from table '{}': {}",
51                table_name,
52                error
53            );
54
55            // Return appropriate status code based on error type
56            let status = if error.to_string().contains("not found") {
57                StatusCode::NOT_FOUND
58            } else if error.to_string().contains("Invalid column") {
59                StatusCode::BAD_REQUEST
60            } else if error.to_string().contains("timeout") {
61                StatusCode::REQUEST_TIMEOUT
62            } else {
63                StatusCode::INTERNAL_SERVER_ERROR
64            };
65
66            (
67                status,
68                Json(serde_json::json!({
69                    "error": error.to_string()
70                })),
71            )
72                .into_response()
73        }
74    }
75}
76
77/// Handler for GET /api/tables/:name/count
78///
79/// Returns the total row count for a table (with optional filters applied).
80///
81/// Query parameters:
82/// - filter[column]: Filter value for specific column (same as get_rows_handler)
83///
84/// # Arguments
85///
86/// * `database` - Database provider from state
87/// * `table_name` - Name of the table to count rows from
88/// * `query` - Query parameters (filters only, other fields ignored)
89///
90/// # Returns
91///
92/// JSON response containing the total row count
93pub async fn count_rows_handler<DB: DatabaseProvider>(
94    State(database): State<Arc<DB>>,
95    Path(table_name): Path<String>,
96    Query(query): Query<RowQuery>,
97) -> Response {
98    match database.count_rows(&table_name, &query).await {
99        Ok(response) => (StatusCode::OK, Json(response)).into_response(),
100        Err(error) => {
101            eprintln!(
102                "Failed to count rows from table '{}': {}",
103                table_name,
104                error
105            );
106
107            // Return appropriate status code based on error type
108            let status = if error.to_string().contains("not found") {
109                StatusCode::NOT_FOUND
110            } else if error.to_string().contains("Invalid column") {
111                StatusCode::BAD_REQUEST
112            } else {
113                StatusCode::INTERNAL_SERVER_ERROR
114            };
115
116            (
117                status,
118                Json(serde_json::json!({
119                    "error": error.to_string()
120                })),
121            )
122                .into_response()
123        }
124    }
125}