@@ -17,11 +17,11 @@ use chat2db_contract::{
1717 AgentRunSnapshot , AgentSession , AgentSessionList , AgentStreamMessage ,
1818 AgentSubscriptionAccepted , ApiError , CancelAgentRunResponse , CancelOperationResponse ,
1919 CreateAgentSessionRequest , CreateDatasourceRequest , CreateProviderProfileRequest , Datasource ,
20- DatasourceList , DecideAgentPermissionRequest , HealthResponse , OperationEventEnvelope ,
21- OperationSnapshot , OperationStreamMessage , OperationSubscriptionAccepted , ProviderProfile ,
22- ProviderProfileList , QueryAccepted , ResultPage , ResultPageRequest , StartAgentRunRequest ,
23- StartQueryRequest , UpdateAgentSessionRequest , UpdateDatasourceRequest ,
24- UpdateProviderProfileRequest ,
20+ DatasourceList , DecideAgentPermissionRequest , HealthResponse , JdbcDriverList ,
21+ OperationEventEnvelope , OperationSnapshot , OperationStreamMessage ,
22+ OperationSubscriptionAccepted , ProviderProfile , ProviderProfileList , QueryAccepted , ResultPage ,
23+ ResultPageRequest , StartAgentRunRequest , StartQueryRequest , UpdateAgentSessionRequest ,
24+ UpdateDatasourceRequest , UpdateProviderProfileRequest ,
2525} ;
2626use chat2db_core:: { AppError , Application , RuntimeConfig , RuntimeHost } ;
2727use chat2db_java_bridge:: { EngineCommand , EngineConfig } ;
@@ -30,6 +30,7 @@ use tauri::{State, ipc::Channel};
3030use tokio:: sync:: { Mutex , oneshot} ;
3131
3232const DATA_DIR_ENV : & str = "CHAT2DB_DATA_DIR" ;
33+ const DRIVER_PACK_DIR_ENV : & str = "CHAT2DB_DRIVER_PACK_DIR" ;
3334const JAVA_BIN_ENV : & str = "CHAT2DB_JAVA_BIN" ;
3435const JAVA_ENGINE_JAR_ENV : & str = "CHAT2DB_JAVA_ENGINE_JAR" ;
3536const VAULT_MASTER_KEY_ENV : & str = "CHAT2DB_VAULT_MASTER_KEY" ;
@@ -154,6 +155,7 @@ impl DesktopState {
154155#[ derive( Debug ) ]
155156pub enum DesktopError {
156157 MissingJavaEngineJar ,
158+ EmptyEnvironmentVariable ( & ' static str ) ,
157159 InvalidJavaEngineJar ( PathBuf ) ,
158160 JavaEngineJarMetadata {
159161 path : PathBuf ,
@@ -186,6 +188,9 @@ impl std::fmt::Display for DesktopError {
186188 formatter,
187189 "{JAVA_ENGINE_JAR_ENV} is required and must point to the compatibility-engine JAR"
188190 ) ,
191+ Self :: EmptyEnvironmentVariable ( name) => {
192+ write ! ( formatter, "{name} must not be empty when configured" )
193+ }
189194 Self :: InvalidJavaEngineJar ( path) => write ! (
190195 formatter,
191196 "{JAVA_ENGINE_JAR_ENV} does not point to a regular file: {}" ,
@@ -215,6 +220,7 @@ impl std::error::Error for DesktopError {
215220 Self :: Runtime ( error) => Some ( error. as_ref ( ) ) ,
216221 Self :: Tauri ( error) => Some ( error. as_ref ( ) ) ,
217222 Self :: MissingJavaEngineJar
223+ | Self :: EmptyEnvironmentVariable ( _)
218224 | Self :: InvalidJavaEngineJar ( _)
219225 | Self :: InvalidVaultMasterKeyEncoding => None ,
220226 }
@@ -236,6 +242,7 @@ pub fn run() -> Result<i32, DesktopError> {
236242 . manage ( managed_state)
237243 . invoke_handler ( tauri:: generate_handler![
238244 health,
245+ list_drivers,
239246 list_datasources,
240247 create_datasource,
241248 get_datasource,
@@ -281,13 +288,16 @@ pub fn run() -> Result<i32, DesktopError> {
281288
282289fn runtime_config_from_environment ( ) -> Result < RuntimeConfig , DesktopError > {
283290 let engine_jar = required_java_engine_jar ( ) ?;
284- let java = env :: var_os ( JAVA_BIN_ENV ) . unwrap_or_else ( || OsString :: from ( "java" ) ) ;
291+ let java = optional_nonempty_os_env ( JAVA_BIN_ENV ) ? . unwrap_or_else ( || OsString :: from ( "java" ) ) ;
285292 let engine = EngineConfig :: new ( EngineCommand :: java_jar ( java, engine_jar) ) ;
286293 let mut config = RuntimeConfig :: new ( engine) ;
287294
288- if let Some ( data_dir) = env :: var_os ( DATA_DIR_ENV ) . filter ( |value| !value . is_empty ( ) ) {
295+ if let Some ( data_dir) = optional_nonempty_os_env ( DATA_DIR_ENV ) ? {
289296 config = config. with_data_dir ( PathBuf :: from ( data_dir) ) ;
290297 }
298+ if let Some ( driver_pack_dir) = optional_nonempty_os_env ( DRIVER_PACK_DIR_ENV ) ? {
299+ config = config. with_driver_pack_dir ( PathBuf :: from ( driver_pack_dir) ) ;
300+ }
291301 match env:: var ( VAULT_MASTER_KEY_ENV ) {
292302 Ok ( master_key) => config = config. with_vault_master_key_base64 ( master_key) ,
293303 Err ( env:: VarError :: NotPresent ) => { }
@@ -299,14 +309,27 @@ fn runtime_config_from_environment() -> Result<RuntimeConfig, DesktopError> {
299309}
300310
301311fn required_java_engine_jar ( ) -> Result < PathBuf , DesktopError > {
302- let path = env:: var_os ( JAVA_ENGINE_JAR_ENV )
303- . filter ( |value| !value. is_empty ( ) )
312+ let path = optional_nonempty_os_env ( JAVA_ENGINE_JAR_ENV ) ?
304313 . map ( PathBuf :: from)
305314 . ok_or ( DesktopError :: MissingJavaEngineJar ) ?;
306315 validate_java_engine_jar ( & path) ?;
307316 Ok ( path)
308317}
309318
319+ fn optional_nonempty_os_env ( name : & ' static str ) -> Result < Option < OsString > , DesktopError > {
320+ validate_optional_os_env ( name, env:: var_os ( name) )
321+ }
322+
323+ fn validate_optional_os_env (
324+ name : & ' static str ,
325+ value : Option < OsString > ,
326+ ) -> Result < Option < OsString > , DesktopError > {
327+ match value {
328+ Some ( value) if value. is_empty ( ) => Err ( DesktopError :: EmptyEnvironmentVariable ( name) ) ,
329+ value => Ok ( value) ,
330+ }
331+ }
332+
310333fn validate_java_engine_jar ( path : & Path ) -> Result < ( ) , DesktopError > {
311334 match fs:: metadata ( path) {
312335 Ok ( metadata) if metadata. is_file ( ) => Ok ( ( ) ) ,
@@ -344,6 +367,12 @@ fn health(state: State<'_, Arc<DesktopState>>) -> HealthResponse {
344367 state. application . health ( )
345368}
346369
370+ #[ tauri:: command]
371+ #[ allow( clippy:: needless_pass_by_value) ]
372+ fn list_drivers ( state : State < ' _ , Arc < DesktopState > > ) -> JdbcDriverList {
373+ state. application . list_drivers ( )
374+ }
375+
347376#[ tauri:: command]
348377async fn list_datasources ( state : State < ' _ , Arc < DesktopState > > ) -> Result < DatasourceList , ApiError > {
349378 state
@@ -823,7 +852,7 @@ async fn result_page(
823852
824853#[ cfg( test) ]
825854mod tests {
826- use std:: { fs:: File , sync:: Arc } ;
855+ use std:: { ffi :: OsString , fs:: File , sync:: Arc } ;
827856
828857 use chat2db_contract:: {
829858 AgentEvent , AgentEventEnvelope , AgentStreamMessage , OperationEvent , OperationEventEnvelope ,
@@ -834,7 +863,7 @@ mod tests {
834863
835864 use super :: {
836865 DesktopError , SubscriptionRegistry , agent_stream_message, operation_stream_message,
837- parse_after_sequence, validate_java_engine_jar,
866+ parse_after_sequence, validate_java_engine_jar, validate_optional_os_env ,
838867 } ;
839868
840869 #[ test]
@@ -867,6 +896,21 @@ mod tests {
867896 ) ) ;
868897 }
869898
899+ #[ test]
900+ fn optional_path_environment_rejects_explicit_empty_values ( ) {
901+ assert ! ( matches!(
902+ validate_optional_os_env( "CHAT2DB_DRIVER_PACK_DIR" , Some ( OsString :: new( ) ) ) ,
903+ Err ( DesktopError :: EmptyEnvironmentVariable (
904+ "CHAT2DB_DRIVER_PACK_DIR"
905+ ) )
906+ ) ) ;
907+ assert_eq ! (
908+ validate_optional_os_env( "CHAT2DB_DRIVER_PACK_DIR" , None )
909+ . expect( "missing optional variable must be accepted" ) ,
910+ None
911+ ) ;
912+ }
913+
870914 #[ test]
871915 fn stream_result_maps_events_errors_and_clean_end ( ) {
872916 let event = OperationEventEnvelope {
0 commit comments