diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index 2a36d2ed5..56a467e7e 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -2233,6 +2233,9 @@ const requiredRustHostSnippets = [ 'PermissionState::Prompt | PermissionState::PromptWithRationale', '"notification permission denied"', '"copied_to_clipboard"', + 'file_export_cancelled_response', + 'file_import_cancelled_response', + 'desktop_file_cancelled_responses_are_stable', '"file export cancelled"', '"file import cancelled"', 'BASE64_STANDARD.decode', diff --git a/apps/desktop-shell/src-tauri/src/host_bridge/files.rs b/apps/desktop-shell/src-tauri/src/host_bridge/files.rs index 9e927ad00..02c2b5785 100644 --- a/apps/desktop-shell/src-tauri/src/host_bridge/files.rs +++ b/apps/desktop-shell/src-tauri/src/host_bridge/files.rs @@ -7,6 +7,14 @@ use crate::host_bridge::protocol::{failed, ok, HostBridgeRequest, HostBridgeResp use serde_json::json; use tauri_plugin_dialog::DialogExt; +fn file_export_cancelled_response(request: &HostBridgeRequest) -> HostBridgeResponse { + failed(request.id.clone(), "cancelled", "file export cancelled") +} + +fn file_import_cancelled_response(request: &HostBridgeRequest) -> HostBridgeResponse { + failed(request.id.clone(), "cancelled", "file import cancelled") +} + pub(crate) async fn export_desktop_host_bridge_text_file( app: &tauri::AppHandle, request: &HostBridgeRequest, @@ -22,7 +30,7 @@ pub(crate) async fn export_desktop_host_bridge_text_file( .set_file_name(file_name.clone()) .blocking_save_file(); let Some(file_path) = file_path else { - return failed(request.id.clone(), "cancelled", "file export cancelled"); + return file_export_cancelled_response(request); }; let path = match file_path.into_path() { Ok(path) => path, @@ -56,7 +64,7 @@ pub(crate) async fn import_desktop_host_bridge_text_file( .add_filter("Text", &["txt", "md", "markdown", "csv", "json"]) .blocking_pick_file(); let Some(file_path) = file_path else { - return failed(request.id.clone(), "cancelled", "file import cancelled"); + return file_import_cancelled_response(request); }; let path = match file_path.into_path() { Ok(path) => path, @@ -84,7 +92,7 @@ pub(crate) async fn import_desktop_host_bridge_document_file( ) .blocking_pick_file(); let Some(file_path) = file_path else { - return failed(request.id.clone(), "cancelled", "file import cancelled"); + return file_import_cancelled_response(request); }; let path = match file_path.into_path() { Ok(path) => path, @@ -114,7 +122,7 @@ pub(crate) async fn export_desktop_host_bridge_image_file( .set_file_name(file_name.clone()) .blocking_save_file(); let Some(file_path) = file_path else { - return failed(request.id.clone(), "cancelled", "file export cancelled"); + return file_export_cancelled_response(request); }; let path = match file_path.into_path() { Ok(path) => path, @@ -147,7 +155,7 @@ pub(crate) async fn import_desktop_host_bridge_image_file( .add_filter("Image", &["png", "jpg", "jpeg", "webp"]) .blocking_pick_file(); let Some(file_path) = file_path else { - return failed(request.id.clone(), "cancelled", "file import cancelled"); + return file_import_cancelled_response(request); }; let path = match file_path.into_path() { Ok(path) => path, @@ -174,7 +182,7 @@ pub(crate) async fn import_desktop_host_bridge_audio_file( .add_filter("Audio", &["mp3", "m4a", "mp4", "wav", "ogg", "webm"]) .blocking_pick_file(); let Some(file_path) = file_path else { - return failed(request.id.clone(), "cancelled", "file import cancelled"); + return file_import_cancelled_response(request); }; let path = match file_path.into_path() { Ok(path) => path, @@ -204,7 +212,7 @@ pub(crate) async fn export_desktop_host_bridge_audio_file( .set_file_name(file_name.clone()) .blocking_save_file(); let Some(file_path) = file_path else { - return failed(request.id.clone(), "cancelled", "file export cancelled"); + return file_export_cancelled_response(request); }; let path = match file_path.into_path() { Ok(path) => path, @@ -226,3 +234,26 @@ pub(crate) async fn export_desktop_host_bridge_audio_file( }), ) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::host_bridge::protocol::request; + + #[test] + fn desktop_file_cancelled_responses_are_stable() { + let request = request("file.importText"); + + let export_error = file_export_cancelled_response(&request) + .error + .expect("export error"); + assert_eq!(export_error.code, "cancelled"); + assert_eq!(export_error.message, "file export cancelled"); + + let import_error = file_import_cancelled_response(&request) + .error + .expect("import error"); + assert_eq!(import_error.code, "cancelled"); + assert_eq!(import_error.message, "file import cancelled"); + } +}