11//! Backup, restore, and export commands
22
33use crate :: backup:: { ExportSummary , ImportPreview , ImportSummary } ;
4+ use crate :: error:: AppError ;
45use crate :: AppState ;
56use tauri:: State ;
67use tauri_plugin_dialog:: DialogExt ;
@@ -57,13 +58,23 @@ impl ExportFormat {
5758 }
5859}
5960
61+ /// Map a `crate::backup` error to AppError, preserving the detail for logs.
62+ fn backup_err ( op : & str , e : impl std:: fmt:: Display ) -> AppError {
63+ AppError :: new (
64+ crate :: error:: ErrorCode :: INTERNAL_ERROR ,
65+ format ! ( "Backup {} failed" , op) ,
66+ crate :: error:: ErrorCategory :: Internal ,
67+ )
68+ . with_detail ( e. to_string ( ) )
69+ }
70+
6071/// Export a draft response to a file
6172#[ tauri:: command]
6273pub async fn export_draft (
6374 app : tauri:: AppHandle ,
6475 response_text : String ,
6576 format : ExportFormat ,
66- ) -> Result < bool , String > {
77+ ) -> Result < bool , AppError > {
6778 let content = format. format_content ( & response_text) ;
6879 let default_filename = format ! ( "response.{}" , format. extension( ) ) ;
6980
@@ -77,18 +88,17 @@ pub async fn export_draft(
7788
7889 match file_handle {
7990 Some ( path) => {
80- // Get the actual path
8191 let file_path = path
8292 . as_path ( )
83- . ok_or_else ( || "Invalid file path" . to_string ( ) ) ?;
93+ . ok_or_else ( || AppError :: invalid_path ( "Invalid file path" ) ) ?;
8494
85- std:: fs:: write ( file_path, content)
86- . map_err ( |e| format ! ( "Failed to write file: {}" , e) ) ?;
95+ std:: fs:: write ( file_path, content) . map_err ( AppError :: from) ?;
8796
8897 Ok ( true )
8998 }
9099 None => {
91- // User cancelled
100+ // User cancelled — return Ok(false) to preserve the existing
101+ // "cancelled is not an error" semantic for this command.
92102 Ok ( false )
93103 }
94104 }
@@ -104,9 +114,9 @@ pub async fn export_backup(
104114 app : tauri:: AppHandle ,
105115 state : State < ' _ , AppState > ,
106116 password : Option < String > ,
107- ) -> Result < ExportSummary , String > {
108- let db_lock = state. db . lock ( ) . map_err ( |e| e . to_string ( ) ) ?;
109- let db = db_lock. as_ref ( ) . ok_or ( "Database not initialized" ) ?;
117+ ) -> Result < ExportSummary , AppError > {
118+ let db_lock = state. db . lock ( ) . map_err ( |_| AppError :: db_lock_failed ( ) ) ?;
119+ let db = db_lock. as_ref ( ) . ok_or_else ( AppError :: db_not_initialized ) ?;
110120
111121 // Determine file extension based on encryption
112122 let ( filename, filter_name, extensions) = if password. is_some ( ) {
@@ -127,12 +137,12 @@ pub async fn export_backup(
127137 Some ( path) => {
128138 let file_path = path
129139 . as_path ( )
130- . ok_or_else ( || "Invalid file path" . to_string ( ) ) ?;
140+ . ok_or_else ( || AppError :: invalid_path ( "Invalid file path" ) ) ?;
131141
132142 crate :: backup:: export_backup ( db, file_path, password. as_deref ( ) )
133- . map_err ( |e| e . to_string ( ) )
143+ . map_err ( |e| backup_err ( "export" , e ) )
134144 }
135- None => Err ( "Export cancelled" . to_string ( ) ) ,
145+ None => Err ( AppError :: cancelled ( ) ) ,
136146 }
137147}
138148
@@ -141,7 +151,7 @@ pub async fn export_backup(
141151pub async fn preview_backup_import (
142152 app : tauri:: AppHandle ,
143153 password : Option < String > ,
144- ) -> Result < ImportPreview , String > {
154+ ) -> Result < ImportPreview , AppError > {
145155 // Show open file dialog (accept both ZIP and encrypted backups)
146156 let file_handle = app
147157 . dialog ( )
@@ -155,11 +165,12 @@ pub async fn preview_backup_import(
155165 Some ( path) => {
156166 let file_path = path
157167 . as_path ( )
158- . ok_or_else ( || "Invalid file path" . to_string ( ) ) ?;
168+ . ok_or_else ( || AppError :: invalid_path ( "Invalid file path" ) ) ?;
159169
160- crate :: backup:: preview_import ( file_path, password. as_deref ( ) ) . map_err ( |e| e. to_string ( ) )
170+ crate :: backup:: preview_import ( file_path, password. as_deref ( ) )
171+ . map_err ( |e| backup_err ( "preview" , e) )
161172 }
162- None => Err ( "Import cancelled" . to_string ( ) ) ,
173+ None => Err ( AppError :: cancelled ( ) ) ,
163174 }
164175}
165176
@@ -169,7 +180,7 @@ pub async fn import_backup(
169180 app : tauri:: AppHandle ,
170181 state : State < ' _ , AppState > ,
171182 password : Option < String > ,
172- ) -> Result < ImportSummary , String > {
183+ ) -> Result < ImportSummary , AppError > {
173184 // Show open file dialog (accept both ZIP and encrypted backups)
174185 let file_handle = app
175186 . dialog ( )
@@ -183,27 +194,35 @@ pub async fn import_backup(
183194 Some ( path) => {
184195 let file_path = path
185196 . as_path ( )
186- . ok_or_else ( || "Invalid file path" . to_string ( ) ) ?;
197+ . ok_or_else ( || AppError :: invalid_path ( "Invalid file path" ) ) ?;
187198
188199 {
189- let db_lock = state. db . lock ( ) . map_err ( |e| e . to_string ( ) ) ?;
200+ let db_lock = state. db . lock ( ) . map_err ( |_| AppError :: db_lock_failed ( ) ) ?;
190201 if let Some ( db) = db_lock. as_ref ( ) {
191202 return crate :: backup:: import_backup ( db, file_path, password. as_deref ( ) )
192- . map_err ( |e| e . to_string ( ) ) ;
203+ . map_err ( |e| backup_err ( "import" , e ) ) ;
193204 }
194205 }
195206
207+ // No live DB — fall back to the recovery-mode restore path.
196208 let restore_result = {
197- let recovery_lock = state. recovery . lock ( ) . map_err ( |e| e. to_string ( ) ) ?;
198- let recovery = recovery_lock. as_ref ( ) . ok_or ( "Database not initialized" ) ?;
199- let db_path = recovery
200- . db_path
201- . as_ref ( )
202- . ok_or ( "Backup restore is not available for this recovery issue" ) ?;
203- let master_key = recovery
204- . master_key
209+ let recovery_lock = state
210+ . recovery
211+ . lock ( )
212+ . map_err ( |_| AppError :: lock_failed ( "recovery context" ) ) ?;
213+ let recovery = recovery_lock
205214 . as_ref ( )
206- . ok_or ( "Backup restore is not available for this recovery issue" ) ?;
215+ . ok_or_else ( AppError :: db_not_initialized) ?;
216+ let db_path = recovery. db_path . as_ref ( ) . ok_or_else ( || {
217+ AppError :: invalid_format (
218+ "Backup restore is not available for this recovery issue" ,
219+ )
220+ } ) ?;
221+ let master_key = recovery. master_key . as_ref ( ) . ok_or_else ( || {
222+ AppError :: invalid_format (
223+ "Backup restore is not available for this recovery issue" ,
224+ )
225+ } ) ?;
207226
208227 crate :: backup:: restore_backup_into_fresh_database (
209228 db_path,
@@ -214,12 +233,15 @@ pub async fn import_backup(
214233 } ;
215234
216235 if restore_result. is_ok ( ) {
217- let mut recovery_lock = state. recovery . lock ( ) . map_err ( |e| e. to_string ( ) ) ?;
236+ let mut recovery_lock = state
237+ . recovery
238+ . lock ( )
239+ . map_err ( |_| AppError :: lock_failed ( "recovery context" ) ) ?;
218240 * recovery_lock = None ;
219241 }
220242
221- restore_result. map_err ( |e| e . to_string ( ) )
243+ restore_result. map_err ( |e| backup_err ( "restore" , e ) )
222244 }
223- None => Err ( "Import cancelled" . to_string ( ) ) ,
245+ None => Err ( AppError :: cancelled ( ) ) ,
224246 }
225247}
0 commit comments