lance_core/utils/tempfile.rs
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4//! Utility functions for creating temporary files and directories.
5//!
6//! Most of these types wrap around the `tempfile` crate. We add two
7//! additional features:
8//!
9//! * There are wrappers around temporary directories and files that expose
10//! the dir/file as an object store path, std path, or string, which can save
11//! some boilerplate.
12//! * We work around a current bug in the `url` crate which fails to parse
13//! Windows paths like `C:\` correctly. We do so by replacing all `\` with
14//! `/` in the path. This is not safe in general (e.g. paths may use `\` as
15//! an escape) but it should be safe for temporary paths.
16
17use object_store::path::Path as ObjPath;
18use std::{
19 ops::Deref,
20 path::{Path as StdPath, PathBuf},
21};
22use tempfile::NamedTempFile;
23
24use crate::Result;
25
26/// A temporary directory
27///
28/// This create a temporary directory using [`tempfile::tempdir`]. It will
29/// generally be cleaned up when the object is dropped.
30///
31/// This type is primarily useful when you need multiple representations (string,
32/// path, object store path) of the same temporary directory. If you only need
33/// a single representation you can use the [`TempStdDir`], [`TempStrDir`], or
34/// [`TempObjDir`] types.
35#[derive(Debug)]
36pub struct TempDir {
37 tempdir: tempfile::TempDir,
38}
39
40impl TempDir {
41 fn new() -> Self {
42 let tempdir = tempfile::tempdir().unwrap();
43 Self { tempdir }
44 }
45
46 /// Create a temporary directory, exposing any potential errors.
47 ///
48 /// For most test cases you should use the [`Default`] implementation instead.
49 /// However, when we use this type in production code, we may want to return
50 /// errors gracefully instead of panicking.
51 pub fn try_new() -> Result<Self> {
52 let tempdir = tempfile::tempdir()?;
53 Ok(Self { tempdir })
54 }
55
56 /// Get the path as a string
57 ///
58 /// This path will be safe to use as a URI on Windows
59 pub fn path_str(&self) -> String {
60 if cfg!(windows) {
61 self.tempdir.path().to_str().unwrap().replace("\\", "/")
62 } else {
63 self.tempdir.path().to_str().unwrap().to_owned()
64 }
65 }
66
67 /// Get the path as a standard library path
68 ///
69 /// If you convert this to a string, it will NOT be safe to use as a URI on Windows.
70 /// Use [`TempDir::path_str`] instead.
71 ///
72 /// It is safe to use this as a standard path on Windows.
73 pub fn std_path(&self) -> &StdPath {
74 self.tempdir.path()
75 }
76
77 /// Get the path as an object store path
78 ///
79 /// This path will be safe to use as a URI on Windows
80 pub fn obj_path(&self) -> ObjPath {
81 ObjPath::parse(self.path_str()).unwrap()
82 }
83}
84
85impl Default for TempDir {
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91/// A temporary directory that is exposed as an object store path
92///
93/// This is a wrapper around [`TempDir`] that exposes the path as an object store path.
94/// It is useful when you need to create a temporary directory that is only
95/// used as an object store path.
96pub struct TempObjDir {
97 _tempdir: TempDir,
98 path: ObjPath,
99}
100
101impl Deref for TempObjDir {
102 type Target = ObjPath;
103
104 fn deref(&self) -> &Self::Target {
105 &self.path
106 }
107}
108
109impl AsRef<ObjPath> for TempObjDir {
110 fn as_ref(&self) -> &ObjPath {
111 &self.path
112 }
113}
114
115impl Default for TempObjDir {
116 fn default() -> Self {
117 let tempdir = TempDir::default();
118 let path = tempdir.obj_path();
119 Self {
120 _tempdir: tempdir,
121 path,
122 }
123 }
124}
125
126/// A temporary directory that is exposed as a string
127///
128/// This is a wrapper around [`TempDir`] that exposes the path as a string.
129/// It is useful when you need to create a temporary directory that is only
130/// used as a string.
131pub struct TempStrDir {
132 _tempdir: TempDir,
133 string: String,
134}
135
136impl std::fmt::Display for TempStrDir {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 self.string.fmt(f)
139 }
140}
141
142impl TempStrDir {
143 /// Create a cloned copy of the string that can be used if `Into<String>` is needed
144 pub fn as_into_string(&self) -> impl Into<String> {
145 self.string.clone()
146 }
147}
148
149impl Default for TempStrDir {
150 fn default() -> Self {
151 let tempdir = TempDir::default();
152 let string = tempdir.path_str();
153 Self {
154 _tempdir: tempdir,
155 string,
156 }
157 }
158}
159
160impl Deref for TempStrDir {
161 type Target = String;
162
163 fn deref(&self) -> &Self::Target {
164 &self.string
165 }
166}
167
168impl AsRef<str> for TempStrDir {
169 fn as_ref(&self) -> &str {
170 self.string.as_ref()
171 }
172}
173
174/// A temporary directory that is exposed as a standard library path
175///
176/// This is a wrapper around [`TempDir`] that exposes the path as a standard library path.
177/// It is useful when you need to create a temporary directory that is only
178/// used as a standard library path.
179#[derive(Default)]
180pub struct TempStdDir {
181 tempdir: TempDir,
182}
183
184impl AsRef<StdPath> for TempStdDir {
185 fn as_ref(&self) -> &StdPath {
186 self.tempdir.std_path()
187 }
188}
189
190impl Deref for TempStdDir {
191 type Target = StdPath;
192
193 fn deref(&self) -> &Self::Target {
194 self.tempdir.std_path()
195 }
196}
197
198/// A temporary file
199///
200/// This is a wrapper around [`tempfile::NamedTempFile`]. The file will normally be cleaned
201/// up when the object is dropped.
202///
203/// Note: this function may create an empty file when the object is created. If you are checking
204/// that the path does not exist, you should use [`TempStdPath`] instead.
205pub struct TempFile {
206 temppath: NamedTempFile,
207}
208
209impl TempFile {
210 fn new() -> Self {
211 let temppath = tempfile::NamedTempFile::new().unwrap();
212 Self { temppath }
213 }
214
215 /// Get the path as a string safe to use as a URI on Windows.
216 pub fn path_str(&self) -> String {
217 if cfg!(windows) {
218 self.temppath.path().to_str().unwrap().replace("\\", "/")
219 } else {
220 self.temppath.path().to_str().unwrap().to_owned()
221 }
222 }
223
224 /// Get the path as a standard library path
225 ///
226 /// If you convert this to a string, it will NOT be safe to use as a URI on Windows.
227 /// Use [`TempFile::path_str`] instead.
228 ///
229 /// It is safe to use this as a standard path on Windows.
230 pub fn std_path(&self) -> &StdPath {
231 self.temppath.path()
232 }
233
234 /// Get the path as an object store path
235 ///
236 /// This path will be safe to use as a URI on Windows
237 pub fn obj_path(&self) -> ObjPath {
238 ObjPath::parse(self.path_str()).unwrap()
239 }
240}
241
242impl Default for TempFile {
243 fn default() -> Self {
244 Self::new()
245 }
246}
247
248/// A temporary file that is exposed as a standard library path
249///
250/// This is a wrapper around [`TempFile`] that exposes the path as a standard library path.
251/// It is useful when you need to create a temporary file that is only used as a standard library path.
252#[derive(Default)]
253pub struct TempStdFile {
254 tempfile: TempFile,
255}
256
257impl AsRef<StdPath> for TempStdFile {
258 fn as_ref(&self) -> &StdPath {
259 self.tempfile.std_path()
260 }
261}
262
263impl Deref for TempStdFile {
264 type Target = StdPath;
265
266 fn deref(&self) -> &Self::Target {
267 self.tempfile.std_path()
268 }
269}
270
271/// A temporary file that is exposed as an object store path
272///
273/// This is a wrapper around [`TempFile`] that exposes the path as an object store path.
274/// It is useful when you need to create a temporary file that is only used as an object store path.
275pub struct TempObjFile {
276 _tempfile: TempFile,
277 path: ObjPath,
278}
279
280impl AsRef<ObjPath> for TempObjFile {
281 fn as_ref(&self) -> &ObjPath {
282 &self.path
283 }
284}
285
286impl std::ops::Deref for TempObjFile {
287 type Target = ObjPath;
288
289 fn deref(&self) -> &Self::Target {
290 &self.path
291 }
292}
293
294impl Default for TempObjFile {
295 fn default() -> Self {
296 let tempfile = TempFile::default();
297 let path = tempfile.obj_path();
298 Self {
299 _tempfile: tempfile,
300 path,
301 }
302 }
303}
304
305/// Get a unique path to a temporary file
306///
307/// Unlike [`TempFile`], this function will not create an empty file. We create
308/// a temporary directory and then create a path inside of it. Since the temporary
309/// directory is created first, we can be confident that the path is unique.
310///
311/// This path will be safe to use as a URI on Windows
312pub struct TempStdPath {
313 _tempdir: TempDir,
314 path: PathBuf,
315}
316
317impl Default for TempStdPath {
318 fn default() -> Self {
319 let tempdir = TempDir::default();
320 let path = format!("{}/some_file", tempdir.path_str());
321 let path = PathBuf::from(path);
322 Self {
323 _tempdir: tempdir,
324 path,
325 }
326 }
327}
328
329impl Deref for TempStdPath {
330 type Target = PathBuf;
331
332 fn deref(&self) -> &Self::Target {
333 &self.path
334 }
335}
336
337impl AsRef<StdPath> for TempStdPath {
338 fn as_ref(&self) -> &StdPath {
339 self.path.as_path()
340 }
341}