1+ use crate :: error:: { AppError , ErrorCategory , ErrorCode } ;
12use crate :: validation:: { validate_output_file_within_home, ValidationError } ;
23use crate :: AppState ;
34use std:: path:: Path ;
@@ -26,17 +27,34 @@ fn pilot_logging_enabled() -> bool {
2627 parse_bool_env ( PILOT_LOGGING_POLICY_ENV , false )
2728}
2829
29- fn require_pilot_logging_enabled ( ) -> Result < ( ) , String > {
30+ fn require_pilot_logging_enabled ( ) -> Result < ( ) , AppError > {
3031 if pilot_logging_enabled ( ) {
3132 Ok ( ( ) )
3233 } else {
33- Err ( format ! (
34- "Pilot logging is disabled by policy. Set {}=1 and restart AssistSupport to enable." ,
35- PILOT_LOGGING_POLICY_ENV
34+ Err ( AppError :: new (
35+ ErrorCode :: SECURITY_PERMISSION_DENIED ,
36+ format ! (
37+ "Pilot logging is disabled by policy. Set {}=1 and restart AssistSupport to enable." ,
38+ PILOT_LOGGING_POLICY_ENV
39+ ) ,
40+ ErrorCategory :: Security ,
3641 ) )
3742 }
3843}
3944
45+ /// Map a `crate::feedback` string error to a properly-categorized AppError.
46+ ///
47+ /// The feedback module returns stringly-typed errors today; until it migrates
48+ /// we categorize as Database (its real source) with the message as detail.
49+ fn feedback_err ( op : & str , e : impl std:: fmt:: Display ) -> AppError {
50+ AppError :: new (
51+ ErrorCode :: DB_QUERY_FAILED ,
52+ format ! ( "Pilot {} failed" , op) ,
53+ ErrorCategory :: Database ,
54+ )
55+ . with_detail ( e. to_string ( ) )
56+ }
57+
4058#[ derive( serde:: Serialize , Clone ) ]
4159pub struct PilotLoggingPolicy {
4260 pub enabled : bool ,
@@ -60,11 +78,12 @@ pub fn log_pilot_query(
6078 query : String ,
6179 response : String ,
6280 operator_id : String ,
63- ) -> Result < String , String > {
81+ ) -> Result < String , AppError > {
6482 require_pilot_logging_enabled ( ) ?;
65- let db_lock = state. db . lock ( ) . map_err ( |e| e . to_string ( ) ) ?;
66- let db = db_lock. as_ref ( ) . ok_or ( "Database not initialized" ) ?;
83+ let db_lock = state. db . lock ( ) . map_err ( |_| AppError :: db_lock_failed ( ) ) ?;
84+ let db = db_lock. as_ref ( ) . ok_or_else ( AppError :: db_not_initialized ) ?;
6785 crate :: feedback:: log_query ( db, & query, & response, & operator_id)
86+ . map_err ( |e| feedback_err ( "query log" , e) )
6887}
6988
7089/// Submit user feedback on a pilot query response
@@ -77,10 +96,10 @@ pub fn submit_pilot_feedback(
7796 clarity : i32 ,
7897 helpfulness : i32 ,
7998 comment : Option < String > ,
80- ) -> Result < String , String > {
99+ ) -> Result < String , AppError > {
81100 require_pilot_logging_enabled ( ) ?;
82- let db_lock = state. db . lock ( ) . map_err ( |e| e . to_string ( ) ) ?;
83- let db = db_lock. as_ref ( ) . ok_or ( "Database not initialized" ) ?;
101+ let db_lock = state. db . lock ( ) . map_err ( |_| AppError :: db_lock_failed ( ) ) ?;
102+ let db = db_lock. as_ref ( ) . ok_or_else ( AppError :: db_not_initialized ) ?;
84103 crate :: feedback:: submit_feedback (
85104 db,
86105 & query_log_id,
@@ -90,31 +109,34 @@ pub fn submit_pilot_feedback(
90109 helpfulness,
91110 comment. as_deref ( ) ,
92111 )
112+ . map_err ( |e| feedback_err ( "feedback submission" , e) )
93113}
94114
95115/// Get pilot dashboard summary stats
96116#[ tauri:: command]
97- pub fn get_pilot_stats ( state : State < ' _ , AppState > ) -> Result < crate :: feedback:: PilotStats , String > {
117+ pub fn get_pilot_stats (
118+ state : State < ' _ , AppState > ,
119+ ) -> Result < crate :: feedback:: PilotStats , AppError > {
98120 require_pilot_logging_enabled ( ) ?;
99- let db_lock = state. db . lock ( ) . map_err ( |e| e . to_string ( ) ) ?;
100- let db = db_lock. as_ref ( ) . ok_or ( "Database not initialized" ) ?;
101- crate :: feedback:: get_pilot_stats ( db)
121+ let db_lock = state. db . lock ( ) . map_err ( |_| AppError :: db_lock_failed ( ) ) ?;
122+ let db = db_lock. as_ref ( ) . ok_or_else ( AppError :: db_not_initialized ) ?;
123+ crate :: feedback:: get_pilot_stats ( db) . map_err ( |e| feedback_err ( "stats read" , e ) )
102124}
103125
104126/// Get all pilot query logs
105127#[ tauri:: command]
106128pub fn get_pilot_query_logs (
107129 state : State < ' _ , AppState > ,
108- ) -> Result < Vec < crate :: feedback:: QueryLog > , String > {
130+ ) -> Result < Vec < crate :: feedback:: QueryLog > , AppError > {
109131 require_pilot_logging_enabled ( ) ?;
110- let db_lock = state. db . lock ( ) . map_err ( |e| e . to_string ( ) ) ?;
111- let db = db_lock. as_ref ( ) . ok_or ( "Database not initialized" ) ?;
112- crate :: feedback:: get_query_logs ( db)
132+ let db_lock = state. db . lock ( ) . map_err ( |_| AppError :: db_lock_failed ( ) ) ?;
133+ let db = db_lock. as_ref ( ) . ok_or_else ( AppError :: db_not_initialized ) ?;
134+ crate :: feedback:: get_query_logs ( db) . map_err ( |e| feedback_err ( "query logs read" , e ) )
113135}
114136
115137/// Export pilot data to CSV
116138#[ tauri:: command]
117- pub fn export_pilot_data ( state : State < ' _ , AppState > , path : String ) -> Result < usize , String > {
139+ pub fn export_pilot_data ( state : State < ' _ , AppState > , path : String ) -> Result < usize , AppError > {
118140 require_pilot_logging_enabled ( ) ?;
119141
120142 let candidate = Path :: new ( & path) ;
@@ -124,19 +146,26 @@ pub fn export_pilot_data(state: State<'_, AppState>, path: String) -> Result<usi
124146 . unwrap_or ( "" )
125147 . to_ascii_lowercase ( ) ;
126148 if ext != "csv" {
127- return Err ( "Export path must be a .csv file" . into ( ) ) ;
149+ return Err ( AppError :: invalid_format ( "Export path must be a .csv file" ) ) ;
128150 }
129151
130152 let validated_path = validate_output_file_within_home ( candidate) . map_err ( |e| match e {
131- ValidationError :: PathTraversal => "Export path must be within your home directory" . into ( ) ,
132- ValidationError :: PathNotFound ( _) => "Export parent directory does not exist" . into ( ) ,
153+ ValidationError :: PathTraversal => AppError :: new (
154+ ErrorCode :: VALIDATION_PATH_TRAVERSAL ,
155+ "Export path must be within your home directory" ,
156+ ErrorCategory :: Validation ,
157+ ) ,
158+ ValidationError :: PathNotFound ( _) => {
159+ AppError :: invalid_path ( "Export parent directory does not exist" )
160+ }
133161 ValidationError :: InvalidFormat ( msg) if msg. contains ( "sensitive" ) => {
134- "This export path is blocked because it contains sensitive data" . into ( )
162+ AppError :: sensitive_path ( )
135163 }
136- _ => format ! ( "Invalid export path: {}" , e ) ,
164+ other => AppError :: invalid_path ( format ! ( "Invalid export path: {}" , other ) ) ,
137165 } ) ?;
138166
139- let db_lock = state. db . lock ( ) . map_err ( |e| e . to_string ( ) ) ?;
140- let db = db_lock. as_ref ( ) . ok_or ( "Database not initialized" ) ?;
167+ let db_lock = state. db . lock ( ) . map_err ( |_| AppError :: db_lock_failed ( ) ) ?;
168+ let db = db_lock. as_ref ( ) . ok_or_else ( AppError :: db_not_initialized ) ?;
141169 crate :: feedback:: export:: export_to_csv ( db, validated_path. as_path ( ) )
170+ . map_err ( |e| feedback_err ( "data export" , e) )
142171}
0 commit comments