Skip to main content

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}