Skip to main content

fret_platform_native/
lib.rs

1//! Native (non-wasm) platform implementations for `fret-platform` contracts.
2//!
3//! This crate is intentionally native-only:
4//! - uses native clipboard/file-dialog/open-url backends (`arboard`, `rfd`, `webbrowser`)
5//! - uses real filesystem paths for external drops and file dialog selections
6//!
7//! For module ownership and “where should this go?” guidance, see
8//! `crates/fret-platform-native/README.md`.
9
10pub mod clipboard;
11pub mod external_drop;
12pub mod file_dialog;
13pub mod open_url;
14
15// -----------------------------------------------------------------------------
16// Stable re-exports (native platform surface)
17// -----------------------------------------------------------------------------
18pub use clipboard::{DesktopClipboard, NativeClipboard};
19pub use external_drop::{DesktopExternalDrop, NativeExternalDrop};
20pub use file_dialog::{DesktopFileDialog, NativeFileDialog};
21pub use open_url::{DesktopOpenUrl, NativeOpenUrl};
22
23#[cfg(all(test, not(target_arch = "wasm32")))]
24mod tests {
25    use super::*;
26    use fret_core::{ExternalDropToken, FileDialogToken};
27    use fret_platform::external_drop::ExternalDropReadLimits;
28    use std::path::{Path, PathBuf};
29    use std::time::{SystemTime, UNIX_EPOCH};
30
31    struct TempDirGuard {
32        path: PathBuf,
33    }
34
35    impl TempDirGuard {
36        fn new(prefix: &str) -> Self {
37            let pid = std::process::id();
38            let now = SystemTime::now()
39                .duration_since(UNIX_EPOCH)
40                .expect("system time after unix epoch")
41                .as_nanos();
42            let path = std::env::temp_dir().join(format!("{prefix}-{pid}-{now}"));
43            std::fs::create_dir_all(&path).expect("create temp dir");
44            Self { path }
45        }
46
47        fn path(&self) -> &Path {
48            &self.path
49        }
50    }
51
52    impl Drop for TempDirGuard {
53        fn drop(&mut self) {
54            let _ = std::fs::remove_dir_all(&self.path);
55        }
56    }
57
58    fn write_file(dir: &Path, name: &str, bytes: &[u8]) -> PathBuf {
59        let path = dir.join(name);
60        std::fs::write(&path, bytes).expect("write temp file");
61        path
62    }
63
64    #[test]
65    fn external_drop_read_paths_enforces_max_total_bytes() {
66        let dir = TempDirGuard::new("fret-platform-native-external-drop");
67        let f1 = write_file(dir.path(), "one.txt", b"abcd");
68        let f2 = write_file(dir.path(), "two.txt", b"efgh");
69
70        let token = ExternalDropToken(1);
71        let event = NativeExternalDrop::read_paths(
72            token,
73            vec![f1.clone(), f2.clone()],
74            ExternalDropReadLimits {
75                max_total_bytes: 5,
76                max_file_bytes: 1024,
77                max_files: 10,
78            },
79        );
80
81        assert_eq!(event.token, token);
82        assert_eq!(event.files.len(), 1);
83        assert_eq!(event.files[0].name, "one.txt");
84        assert_eq!(event.files[0].bytes, b"abcd");
85
86        assert_eq!(event.errors.len(), 1);
87        assert_eq!(event.errors[0].name, "two.txt");
88        assert!(event.errors[0].message.contains("next_total"));
89        assert!(event.errors[0].message.contains("max_total_bytes"));
90    }
91
92    #[test]
93    fn file_dialog_read_paths_enforces_max_total_bytes() {
94        let dir = TempDirGuard::new("fret-platform-native-file-dialog");
95        let f1 = write_file(dir.path(), "one.txt", b"abcd");
96        let f2 = write_file(dir.path(), "two.txt", b"efgh");
97
98        let token = FileDialogToken(1);
99        let event = NativeFileDialog::read_paths(
100            token,
101            vec![f1, f2],
102            ExternalDropReadLimits {
103                max_total_bytes: 5,
104                max_file_bytes: 1024,
105                max_files: 10,
106            },
107        );
108
109        assert_eq!(event.token, token);
110        assert_eq!(event.files.len(), 1);
111        assert_eq!(event.files[0].name, "one.txt");
112        assert_eq!(event.files[0].bytes, b"abcd");
113
114        assert_eq!(event.errors.len(), 1);
115        assert_eq!(event.errors[0].name, "two.txt");
116        assert!(event.errors[0].message.contains("selection too large"));
117        assert!(event.errors[0].message.contains("max_total_bytes"));
118    }
119
120    #[test]
121    fn external_drop_read_paths_skips_oversize_files_and_continues() {
122        let dir = TempDirGuard::new("fret-platform-native-external-drop-oversize");
123        let big = write_file(dir.path(), "big.bin", &[0u8; 12]);
124        let ok = write_file(dir.path(), "ok.bin", &[1u8; 4]);
125
126        let token = ExternalDropToken(2);
127        let event = NativeExternalDrop::read_paths(
128            token,
129            vec![big, ok],
130            ExternalDropReadLimits {
131                max_total_bytes: 1024,
132                max_file_bytes: 10,
133                max_files: 10,
134            },
135        );
136
137        assert_eq!(event.token, token);
138        assert_eq!(event.files.len(), 1);
139        assert_eq!(event.files[0].name, "ok.bin");
140        assert_eq!(event.files[0].bytes, vec![1u8; 4]);
141
142        assert_eq!(event.errors.len(), 1);
143        assert_eq!(event.errors[0].name, "big.bin");
144        assert!(event.errors[0].message.contains("file too large"));
145        assert!(event.errors[0].message.contains("max_file_bytes"));
146    }
147}