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}