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}