axum_sql_viewer/layer.rs
1//! SqlViewerLayer - Main Axum integration layer
2//!
3//! This module provides the main entry point for integrating axum-sql-viewer
4//! into an Axum application.
5
6use crate::database::traits::DatabaseProvider;
7use axum::{routing::get, routing::post, Router};
8use std::sync::Arc;
9use tower_http::cors::CorsLayer;
10
11#[cfg(feature = "sqlite")]
12use crate::database::sqlite::SqliteProvider;
13
14#[cfg(feature = "postgres")]
15use crate::database::postgres::PostgresProvider;
16
17use crate::api::{
18 count_rows_handler, execute_query_handler, get_rows_handler, get_table_schema_handler,
19 list_tables_handler,
20};
21use crate::frontend::create_frontend_router;
22
23/// Main layer for integrating SQL viewer into an Axum application
24///
25/// # Example
26///
27/// ```rust,no_run
28/// use axum::Router;
29/// use axum_sql_viewer::SqlViewerLayer;
30/// use sqlx::SqlitePool;
31///
32/// # async fn example() {
33/// let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
34/// let viewer = SqlViewerLayer::sqlite("/sql-viewer", pool);
35/// let app = Router::new().merge(viewer.into_router());
36/// # }
37/// ```
38pub struct SqlViewerLayer<DB: DatabaseProvider> {
39 base_path: String,
40 database: Arc<DB>,
41}
42
43impl<DB: DatabaseProvider> SqlViewerLayer<DB> {
44 /// Create a new SQL viewer at the given base path
45 ///
46 /// # Arguments
47 ///
48 /// * `base_path` - The URL path where the viewer will be mounted (e.g., "/sql-viewer")
49 /// * `database` - The database provider implementation
50 pub fn new(base_path: impl Into<String>, database: DB) -> Self {
51 Self {
52 base_path: base_path.into(),
53 database: Arc::new(database),
54 }
55 }
56
57 /// Convert into an Axum Router that can be merged
58 ///
59 /// This method consumes the layer and returns a Router that can be merged
60 /// into your main application router.
61 ///
62 /// The returned router includes:
63 /// - Frontend serving at `{base_path}/`
64 /// - API endpoints at `{base_path}/api/*`
65 /// - Permissive CORS middleware for development
66 pub fn into_router(self) -> Router {
67 let database = self.database.clone();
68 let base_path = self.base_path.clone();
69
70 // Create API router with all endpoints
71 // Note: Axum 0.8 uses {param} syntax instead of :param
72 let api_router = Router::new()
73 .route("/tables", get(list_tables_handler::<DB>))
74 .route("/tables/{name}", get(get_table_schema_handler::<DB>))
75 .route("/tables/{name}/rows", get(get_rows_handler::<DB>))
76 .route("/tables/{name}/count", get(count_rows_handler::<DB>))
77 .route("/query", post(execute_query_handler::<DB>))
78 .with_state(database);
79
80 // Create frontend router
81 let frontend_router = create_frontend_router(base_path.clone());
82
83 // Nest API router under /api and frontend at root
84 // Apply permissive CORS for development
85 Router::new()
86 .nest(&format!("{}/api", base_path), api_router)
87 .nest(&base_path, frontend_router)
88 .layer(
89 CorsLayer::permissive(), // Permissive CORS for development
90 )
91 }
92}
93
94#[cfg(feature = "sqlite")]
95impl SqlViewerLayer<SqliteProvider> {
96 /// Create a new SQL viewer for SQLite
97 ///
98 /// # Arguments
99 ///
100 /// * `base_path` - The URL path where the viewer will be mounted
101 /// * `pool` - The SQLite connection pool
102 pub fn sqlite(base_path: impl Into<String>, pool: sqlx::SqlitePool) -> Self {
103 Self::new(base_path, SqliteProvider::new(pool))
104 }
105}
106
107#[cfg(feature = "postgres")]
108impl SqlViewerLayer<PostgresProvider> {
109 /// Create a new SQL viewer for PostgreSQL
110 ///
111 /// # Arguments
112 ///
113 /// * `base_path` - The URL path where the viewer will be mounted
114 /// * `pool` - The PostgreSQL connection pool
115 pub fn postgres(base_path: impl Into<String>, pool: sqlx::PgPool) -> Self {
116 Self::new(base_path, PostgresProvider::new(pool))
117 }
118}