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    fn path_str(&self) -> String {
216        if cfg!(windows) {
217            self.temppath.path().to_str().unwrap().replace("\\", "/")
218        } else {
219            self.temppath.path().to_str().unwrap().to_owned()
220        }
221    }
222
223    /// Get the path as a standard library path
224    ///
225    /// If you convert this to a string, it will NOT be safe to use as a URI on Windows.
226    /// Use [`TempFile::path_str`] instead.
227    ///
228    /// It is safe to use this as a standard path on Windows.
229    pub fn std_path(&self) -> &StdPath {
230        self.temppath.path()
231    }
232
233    /// Get the path as an object store path
234    ///
235    /// This path will be safe to use as a URI on Windows
236    pub fn obj_path(&self) -> ObjPath {
237        ObjPath::parse(self.path_str()).unwrap()
238    }
239}
240
241impl Default for TempFile {
242    fn default() -> Self {
243        Self::new()
244    }
245}
246
247/// A temporary file that is exposed as a standard library path
248///
249/// This is a wrapper around [`TempFile`] that exposes the path as a standard library path.
250/// It is useful when you need to create a temporary file that is only used as a standard library path.
251#[derive(Default)]
252pub struct TempStdFile {
253    tempfile: TempFile,
254}
255
256impl AsRef<StdPath> for TempStdFile {
257    fn as_ref(&self) -> &StdPath {
258        self.tempfile.std_path()
259    }
260}
261
262impl Deref for TempStdFile {
263    type Target = StdPath;
264
265    fn deref(&self) -> &Self::Target {
266        self.tempfile.std_path()
267    }
268}
269
270/// A temporary file that is exposed as an object store path
271///
272/// This is a wrapper around [`TempFile`] that exposes the path as an object store path.
273/// It is useful when you need to create a temporary file that is only used as an object store path.
274pub struct TempObjFile {
275    _tempfile: TempFile,
276    path: ObjPath,
277}
278
279impl AsRef<ObjPath> for TempObjFile {
280    fn as_ref(&self) -> &ObjPath {
281        &self.path
282    }
283}
284
285impl std::ops::Deref for TempObjFile {
286    type Target = ObjPath;
287
288    fn deref(&self) -> &Self::Target {
289        &self.path
290    }
291}
292
293impl Default for TempObjFile {
294    fn default() -> Self {
295        let tempfile = TempFile::default();
296        let path = tempfile.obj_path();
297        Self {
298            _tempfile: tempfile,
299            path,
300        }
301    }
302}
303
304/// Get a unique path to a temporary file
305///
306/// Unlike [`TempFile`], this function will not create an empty file.  We create
307/// a temporary directory and then create a path inside of it.  Since the temporary
308/// directory is created first, we can be confident that the path is unique.
309///
310/// This path will be safe to use as a URI on Windows
311pub struct TempStdPath {
312    _tempdir: TempDir,
313    path: PathBuf,
314}
315
316impl Default for TempStdPath {
317    fn default() -> Self {
318        let tempdir = TempDir::default();
319        let path = format!("{}/some_file", tempdir.path_str());
320        let path = PathBuf::from(path);
321        Self {
322            _tempdir: tempdir,
323            path,
324        }
325    }
326}
327
328impl Deref for TempStdPath {
329    type Target = PathBuf;
330
331    fn deref(&self) -> &Self::Target {
332        &self.path
333    }
334}
335
336impl AsRef<StdPath> for TempStdPath {
337    fn as_ref(&self) -> &StdPath {
338        self.path.as_path()
339    }
340}