From 0ea9aa7870d73b5f665241a4183ffd899e628b9c Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Thu, 5 Mar 2026 01:36:52 +0100 Subject: [PATCH] refactor: remove session auth, add DbType enum, native async broker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove `auth`, `sessions-db` features and all tower-sessions/tower-sessions-sqlx-store deps - Delete `src/session.rs` (InfraSessionStore) - `ServerConfig`: remove `session_secret` field and `attach_session_layer()` helper - `db.rs`: add explicit `DbType` enum to `DatabaseConfig`; `connect()` now matches on `db_type` instead of sniffing URL prefixes; remove `connect_sqlite()` standalone fn - `broker/mod.rs`: replace `#[async_trait]` with native async fn (Rust 2024 AFIT) - `broker/nats.rs`: remove `#[async_trait]` to match trait definition - `ai/embeddings.rs`: remove sync `generate_embedding()` (wasteful thread spawn); keep only `generate_embedding_async()` as the public API - Default features: `["logging", "db-sqlx", "auth", "http"]` → `["logging"]` - Bump version to 0.1.11 Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 121 +------------------------------------------ Cargo.toml | 23 +++----- src/ai/embeddings.rs | 28 +--------- src/broker/mod.rs | 2 - src/broker/nats.rs | 3 -- src/db.rs | 76 +++++++++++++++------------ src/http/server.rs | 15 ------ src/lib.rs | 2 - src/session.rs | 79 ---------------------------- 9 files changed, 53 insertions(+), 296 deletions(-) delete mode 100644 src/session.rs diff --git a/Cargo.lock b/Cargo.lock index a891c8f..a3ceb5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -533,17 +533,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -1775,7 +1764,7 @@ dependencies = [ [[package]] name = "k-core" -version = "0.1.10" +version = "0.1.11" dependencies = [ "anyhow", "async-nats", @@ -1789,12 +1778,9 @@ dependencies = [ "serde", "sqlx", "thiserror 2.0.17", - "time", "tokio", "tower 0.5.2", "tower-http", - "tower-sessions", - "tower-sessions-sqlx-store", "tracing", "tracing-subscriber", "uuid", @@ -1878,7 +1864,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", - "serde", ] [[package]] @@ -2958,25 +2943,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rmp" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" -dependencies = [ - "num-traits", -] - -[[package]] -name = "rmp-serde" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" -dependencies = [ - "rmp", - "serde", -] - [[package]] name = "rsa" version = "0.9.9" @@ -3479,7 +3445,6 @@ dependencies = [ "sha2", "smallvec 1.15.1", "thiserror 2.0.17", - "time", "tokio", "tokio-stream", "tracing", @@ -3564,7 +3529,6 @@ dependencies = [ "sqlx-core", "stringprep", "thiserror 2.0.17", - "time", "tracing", "uuid", "whoami", @@ -3604,7 +3568,6 @@ dependencies = [ "sqlx-core", "stringprep", "thiserror 2.0.17", - "time", "tracing", "uuid", "whoami", @@ -3631,7 +3594,6 @@ dependencies = [ "serde_urlencoded", "sqlx-core", "thiserror 2.0.17", - "time", "tracing", "url", "uuid", @@ -4063,22 +4025,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tower-cookies" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151b5a3e3c45df17466454bb74e9ecedecc955269bdedbf4d150dfa393b55a36" -dependencies = [ - "axum-core 0.5.6", - "cookie", - "futures-util", - "http", - "parking_lot", - "pin-project-lite", - "tower-layer", - "tower-service", -] - [[package]] name = "tower-http" version = "0.6.8" @@ -4110,71 +4056,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" -[[package]] -name = "tower-sessions" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a05911f23e8fae446005fe9b7b97e66d95b6db589dc1c4d59f6a2d4d4927d3" -dependencies = [ - "async-trait", - "http", - "time", - "tokio", - "tower-cookies", - "tower-layer", - "tower-service", - "tower-sessions-core", - "tower-sessions-memory-store", - "tracing", -] - -[[package]] -name = "tower-sessions-core" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8cce604865576b7751b7a6bc3058f754569a60d689328bb74c52b1d87e355b" -dependencies = [ - "async-trait", - "axum-core 0.5.6", - "base64 0.22.1", - "futures", - "http", - "parking_lot", - "rand 0.8.5", - "serde", - "serde_json", - "thiserror 2.0.17", - "time", - "tokio", - "tracing", -] - -[[package]] -name = "tower-sessions-memory-store" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb05909f2e1420135a831dd5df9f5596d69196d0a64c3499ca474c4bd3d33242" -dependencies = [ - "async-trait", - "time", - "tokio", - "tower-sessions-core", -] - -[[package]] -name = "tower-sessions-sqlx-store" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e054622079f57fc1a7d6a6089c9334f963d62028fe21dc9eddd58af9a78480b3" -dependencies = [ - "async-trait", - "rmp-serde", - "sqlx", - "thiserror 1.0.69", - "time", - "tower-sessions-core", -] - [[package]] name = "tracing" version = "0.1.44" diff --git a/Cargo.toml b/Cargo.toml index a3c5871..4da2e48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,18 @@ [package] name = "k-core" -version = "0.1.10" +version = "0.1.11" edition = "2024" [features] -default = ["logging", "db-sqlx", "auth", "http"] +default = ["logging"] logging = ["dep:tracing", "dep:tracing-subscriber"] db-sqlx = ["dep:sqlx"] -postgres = ["db-sqlx", "sqlx/postgres", "tower-sessions-sqlx-store?/postgres"] -sqlite = ["db-sqlx", "sqlx/sqlite", "tower-sessions-sqlx-store?/sqlite"] -auth = ["dep:tower-sessions"] -sessions-db = ["auth", "dep:tower-sessions-sqlx-store"] +postgres = ["db-sqlx", "sqlx/postgres"] +sqlite = ["db-sqlx", "sqlx/sqlite"] ai = ["dep:fastembed", "dep:qdrant-client"] broker = [] broker-nats = ["broker", "dep:async-nats", "dep:futures-util", "dep:futures-core"] -http = ["dep:axum", "dep:tower", "dep:tower-http", "dep:time", "logging"] +http = ["dep:axum", "dep:tower", "dep:tower-http", "logging"] [dependencies] # Error handling @@ -22,7 +20,7 @@ thiserror = "2.0.17" anyhow = "1.0.100" # Async -tokio = { version = "1.48.0", features = ["full"]} +tokio = { version = "1.48.0", features = ["full"] } # Database sqlx = { version = "0.8.6", features = [ @@ -39,10 +37,6 @@ tracing-subscriber = { version = "0.3.22", features = [ "fmt", ], optional = true } -# Auth -tower-sessions = { version = "0.14.0", optional = true } -tower-sessions-sqlx-store = { version = "0.15", optional = true} - # Utils chrono = { version = "0.4.42", features = ["serde"] } uuid = { version = "1.19.0", features = ["v4", "serde"] } @@ -59,8 +53,7 @@ async-nats = { version = "0.45", optional = true } # HTTP axum = { version = "0.8.8", features = ["macros"], optional = true } tower = { version = "0.5.2", optional = true } -tower-http = { version = "0.6.2", features = ["cors", "trace"], optional = true} -time = { version = "0.3", optional = true } +tower-http = { version = "0.6.2", features = ["cors", "trace"], optional = true } futures-util = { version = "0.3", optional = true } -futures-core = {version = "0.3", optional = true } \ No newline at end of file +futures-core = { version = "0.3", optional = true } diff --git a/src/ai/embeddings.rs b/src/ai/embeddings.rs index 046631b..55d3361 100644 --- a/src/ai/embeddings.rs +++ b/src/ai/embeddings.rs @@ -20,33 +20,7 @@ impl FastEmbedAdapter { }) } - pub fn generate_embedding(&self, text: &str) -> anyhow::Result> { - let model = self.model.clone(); - let text = text.to_string(); - - // FastEmbed is blocking, so we run it in a blocking task if we are in an async context, - // but since this method signature doesn't force async, we wrap the internal logic. - // For strictly async usage in k-core: - let embeddings = std::thread::scope(|s| { - s.spawn(|| { - let mut model = model - .lock() - .map_err(|e| anyhow::anyhow!("Lock error: {}", e))?; - model - .embed(vec![text], None) - .map_err(|e| anyhow::anyhow!("Embed error: {}", e)) - }) - .join() - .map_err(|_| anyhow::anyhow!("Thread join error"))? - })?; - - embeddings - .into_iter() - .next() - .ok_or_else(|| anyhow::anyhow!("No embedding generated")) - } - - /// Async wrapper for use in async contexts (like Axum handlers) + /// Generate an embedding asynchronously using a blocking thread pool. pub async fn generate_embedding_async(&self, text: &str) -> anyhow::Result> { let model = self.model.clone(); let text = text.to_string(); diff --git a/src/broker/mod.rs b/src/broker/mod.rs index 610052a..d6c0734 100644 --- a/src/broker/mod.rs +++ b/src/broker/mod.rs @@ -1,11 +1,9 @@ -use async_trait::async_trait; use futures_core::Stream; use std::pin::Pin; #[cfg(feature = "broker-nats")] pub mod nats; -#[async_trait] pub trait MessageBroker: Send + Sync { async fn publish(&self, topic: &str, payload: Vec) -> anyhow::Result<()>; diff --git a/src/broker/nats.rs b/src/broker/nats.rs index 415345c..5755565 100644 --- a/src/broker/nats.rs +++ b/src/broker/nats.rs @@ -1,5 +1,4 @@ use super::MessageBroker; -use async_trait::async_trait; use futures_util::StreamExt; use std::pin::Pin; @@ -15,7 +14,6 @@ impl NatsBroker { } } -#[async_trait] impl MessageBroker for NatsBroker { async fn publish(&self, topic: &str, payload: Vec) -> anyhow::Result<()> { self.client @@ -35,7 +33,6 @@ impl MessageBroker for NatsBroker { .await .map_err(|e| anyhow::anyhow!("NATS subscribe error: {}", e))?; - // Map NATS Message to generic Vec let stream = subscriber.map(|msg| msg.payload.to_vec()); Ok(Box::pin(stream)) diff --git a/src/db.rs b/src/db.rs index f2a17d6..24234ea 100644 --- a/src/db.rs +++ b/src/db.rs @@ -8,9 +8,22 @@ use sqlx::Sqlite; #[cfg(feature = "postgres")] use sqlx::Postgres; +/// Explicitly declares which database engine to connect to. +/// +/// Using an explicit type avoids URL-prefix sniffing in `connect()`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum DbType { + #[default] + #[cfg_attr(not(feature = "sqlite"), allow(dead_code))] + Sqlite, + #[cfg_attr(not(feature = "postgres"), allow(dead_code))] + Postgres, +} + /// Universal Database Configuration #[derive(Debug, Clone)] pub struct DatabaseConfig { + pub db_type: DbType, pub url: String, pub max_connections: u32, pub min_connections: u32, @@ -22,6 +35,7 @@ impl Default for DatabaseConfig { #[cfg(feature = "sqlite")] { Self { + db_type: DbType::Sqlite, url: "sqlite::memory:".to_string(), max_connections: 5, min_connections: 1, @@ -32,6 +46,7 @@ impl Default for DatabaseConfig { #[cfg(all(not(feature = "sqlite"), feature = "postgres"))] { Self { + db_type: DbType::Postgres, url: "postgres://localhost:5432/mydb".to_string(), max_connections: 5, min_connections: 1, @@ -41,6 +56,7 @@ impl Default for DatabaseConfig { #[cfg(not(any(feature = "sqlite", feature = "postgres")))] Self { + db_type: DbType::Sqlite, url: "".to_string(), max_connections: 5, min_connections: 1, @@ -50,8 +66,9 @@ impl Default for DatabaseConfig { } impl DatabaseConfig { - pub fn new(url: impl Into) -> Self { + pub fn new(db_type: DbType, url: impl Into) -> Self { Self { + db_type, url: url.into(), ..Default::default() } @@ -60,8 +77,9 @@ impl DatabaseConfig { #[cfg(feature = "sqlite")] pub fn in_memory() -> Self { Self { + db_type: DbType::Sqlite, url: "sqlite::memory:".to_string(), - max_connections: 1, // SQLite in-memory is single-connection + max_connections: 1, min_connections: 1, ..Default::default() } @@ -69,7 +87,6 @@ impl DatabaseConfig { } /// A wrapper around various DB pools. -/// The Template uses this type so it doesn't care if it's Sqlite or Postgres. #[derive(Clone, Debug)] pub enum DatabasePool { #[cfg(feature = "sqlite")] @@ -78,36 +95,33 @@ pub enum DatabasePool { Postgres(Pool), } -/// The single entry point for connecting to any DB. +/// Connect to a database using the explicit `db_type` from `DatabaseConfig`. pub async fn connect(config: &DatabaseConfig) -> Result { - // 1. Try Postgres if the feature is enabled AND the URL looks like postgres - #[cfg(feature = "postgres")] - if config.url.starts_with("postgres://") || config.url.starts_with("postgresql://") { - let pool = sqlx::postgres::PgPoolOptions::new() - .max_connections(config.max_connections) - .acquire_timeout(config.acquire_timeout) - .connect(&config.url) - .await?; - return Ok(DatabasePool::Postgres(pool)); - } + match config.db_type { + #[cfg(feature = "postgres")] + DbType::Postgres => { + let pool = sqlx::postgres::PgPoolOptions::new() + .max_connections(config.max_connections) + .acquire_timeout(config.acquire_timeout) + .connect(&config.url) + .await?; + Ok(DatabasePool::Postgres(pool)) + } - // 2. Fallback to Sqlite if the feature is enabled - #[cfg(feature = "sqlite")] - { - let pool = sqlx::sqlite::SqlitePoolOptions::new() - .max_connections(config.max_connections) - .acquire_timeout(config.acquire_timeout) - .connect(&config.url) - .await?; + #[cfg(feature = "sqlite")] + DbType::Sqlite => { + let pool = sqlx::sqlite::SqlitePoolOptions::new() + .max_connections(config.max_connections) + .acquire_timeout(config.acquire_timeout) + .connect(&config.url) + .await?; + Ok(DatabasePool::Sqlite(pool)) + } - Ok(DatabasePool::Sqlite(pool)) - } - - #[cfg(not(feature = "sqlite"))] - { - Err(sqlx::Error::Configuration( + #[allow(unreachable_patterns)] + _ => Err(sqlx::Error::Configuration( "No supported database features enabled".into(), - )) + )), } } @@ -130,7 +144,3 @@ impl DatabasePool { } } } -#[cfg(feature = "sqlite")] -pub async fn connect_sqlite(url: &str) -> Result, sqlx::Error> { - sqlx::sqlite::SqlitePoolOptions::new().connect(url).await -} diff --git a/src/http/server.rs b/src/http/server.rs index 74e42a9..1d108ef 100644 --- a/src/http/server.rs +++ b/src/http/server.rs @@ -1,11 +1,9 @@ use axum::Router; use tower_http::cors::CorsLayer; use tower_http::trace::TraceLayer; -use tower_sessions::{Expiry, SessionManagerLayer, SessionStore}; pub struct ServerConfig { pub cors_origins: Vec, - pub session_secret: Option, } pub fn apply_standard_middleware(app: Router, config: &ServerConfig) -> Router { @@ -24,7 +22,6 @@ pub fn apply_standard_middleware(app: Router, config: &ServerConfig) -> Router { ]) .allow_credentials(true); - // Configure CORS origins let mut allowed_origins = Vec::new(); for origin in &config.cors_origins { if let Ok(value) = origin.parse::() { @@ -38,15 +35,3 @@ pub fn apply_standard_middleware(app: Router, config: &ServerConfig) -> Router { app.layer(cors).layer(TraceLayer::new_for_http()) } - -/// Helper to attach a session layer with standard K-Suite configuration -pub fn attach_session_layer(app: Router, store: S) -> Router -where - S: SessionStore + Clone + Send + Sync + 'static, -{ - let session_layer = SessionManagerLayer::new(store) - .with_secure(false) // Set to true if you handle HTTPS termination in the app - .with_expiry(Expiry::OnInactivity(time::Duration::days(7))); - - app.layer(session_layer) -} diff --git a/src/lib.rs b/src/lib.rs index 03d6fb5..5b0ce96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,8 +11,6 @@ pub mod ai; #[cfg(feature = "broker")] pub mod broker; -#[cfg(feature = "auth")] -pub mod session; #[cfg(feature = "http")] pub mod http; diff --git a/src/session.rs b/src/session.rs deleted file mode 100644 index 640f303..0000000 --- a/src/session.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Only compile this module if we have the store dependency AND sqlx enabled -#[cfg(all(feature = "sessions-db", feature = "db-sqlx"))] -pub mod store { - use async_trait::async_trait; - use tower_sessions::{ - SessionStore, - session::{Id, Record}, - session_store, - }; - - // We only import what is actually enabled by features - #[cfg(feature = "postgres")] - use tower_sessions_sqlx_store::PostgresStore; - #[cfg(feature = "sqlite")] - use tower_sessions_sqlx_store::SqliteStore; - - #[derive(Clone, Debug)] - pub enum InfraSessionStore { - #[cfg(feature = "sqlite")] - Sqlite(SqliteStore), - #[cfg(feature = "postgres")] - Postgres(PostgresStore), - // Fallback variant to allow compilation if sessions-db is enabled but no generic DB feature is selected - // (Though in practice you should ensure your config validates this) - #[allow(dead_code)] - Unused, - } - - #[async_trait] - impl SessionStore for InfraSessionStore { - async fn save(&self, session_record: &Record) -> session_store::Result<()> { - match self { - #[cfg(feature = "sqlite")] - Self::Sqlite(store) => store.save(session_record).await, - #[cfg(feature = "postgres")] - Self::Postgres(store) => store.save(session_record).await, - _ => Err(session_store::Error::Backend( - "No database feature enabled".to_string(), - )), - } - } - - async fn load(&self, session_id: &Id) -> session_store::Result> { - match self { - #[cfg(feature = "sqlite")] - Self::Sqlite(store) => store.load(session_id).await, - #[cfg(feature = "postgres")] - Self::Postgres(store) => store.load(session_id).await, - _ => Err(session_store::Error::Backend( - "No database feature enabled".to_string(), - )), - } - } - - async fn delete(&self, session_id: &Id) -> session_store::Result<()> { - match self { - #[cfg(feature = "sqlite")] - Self::Sqlite(store) => store.delete(session_id).await, - #[cfg(feature = "postgres")] - Self::Postgres(store) => store.delete(session_id).await, - _ => Err(session_store::Error::Backend( - "No database feature enabled".to_string(), - )), - } - } - } - - impl InfraSessionStore { - pub async fn migrate(&self) -> anyhow::Result<()> { - match self { - #[cfg(feature = "sqlite")] - Self::Sqlite(store) => store.migrate().await.map_err(|e| anyhow::anyhow!(e)), - #[cfg(feature = "postgres")] - Self::Postgres(store) => store.migrate().await.map_err(|e| anyhow::anyhow!(e)), - _ => Ok(()), // No migration needed for no-op - } - } - } -}