Skip to main content

zelkova_compiler/
manifest.rs

1//! `zelkova.toml`, the package manifest.
2//!
3//! [`docs/spec/packages.md`](../../docs/spec/packages.md#the-manifest) is the normative
4//! shape: six fields, five of them required. This module reads the file, deserializes it
5//! with `serde`/`toml`, and validates each field on its own terms — a legal package
6//! [`name`](Manifest::name), a three-integer [`version`](Manifest::version), and each
7//! `dependencies`/`test-dependencies` entry naming exactly one source. `private_modules` is
8//! checked against the modules the package actually holds by [`check_package`](super::check_package),
9//! once source loading has produced that list — this module only knows the manifest, never
10//! the package's files, so [`load`] returns the names as written.
11//!
12//! Reading a manifest is where a build starts and not what it is:
13//! [`resolve`](super::resolve) is what follows both dependency maps to the other packages
14//! and decides what each module is called in each of them. `main` is validated here as a
15//! module name, and what it names is checked once the package's `src/` has checked — see
16//! [`program`](super::program).
17
18use std::collections::HashMap;
19use std::path::{Path, PathBuf};
20
21use serde::Deserialize;
22
23use super::name::Name;
24use super::{PackageName, PhaseError};
25
26/// The file every package directory must hold, beside `src/`.
27pub const MANIFEST_FILE_NAME: &str = "zelkova.toml";
28
29/// A validated `zelkova.toml`.
30///
31/// Built only by [`load`], which is the one place a `RawManifest` — the shape `serde`
32/// deserializes directly, still full of unchecked strings — is turned into this one field by
33/// field. `private_modules` holds the names exactly as written; whether each one names a
34/// module the package actually holds is checked by the caller once it has loaded the
35/// package's sources (see the module documentation).
36#[derive(Debug, Clone)]
37pub struct Manifest {
38    pub name: PackageName,
39    pub version: Version,
40    pub main: Option<Name>,
41    pub private_modules: Vec<Name>,
42    pub dependencies: HashMap<PackageName, Dependency>,
43    pub test_dependencies: HashMap<PackageName, Dependency>,
44}
45
46/// Three non-negative integers, with no pre-release suffix and no build metadata —
47/// `docs/spec/packages.md`'s whole rule for `version`.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct Version {
50    pub major: u64,
51    pub minor: u64,
52    pub patch: u64,
53}
54
55/// The three integers, dot-separated, exactly as a manifest writes them. A diagnostic
56/// naming a package names the version it was resolved to beside it.
57impl std::fmt::Display for Version {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
60    }
61}
62
63impl Version {
64    fn parse(raw: &str) -> Option<Version> {
65        let mut parts = raw.split('.');
66        let major = parts.next()?.parse().ok()?;
67        let minor = parts.next()?.parse().ok()?;
68        let patch = parts.next()?.parse().ok()?;
69        if parts.next().is_some() {
70            return None;
71        }
72        Some(Version {
73            major,
74            minor,
75            patch,
76        })
77    }
78}
79
80/// One entry of `dependencies` or `test-dependencies`: a source, and whether this package
81/// sees it wrapped (the default) or unwrapped.
82#[derive(Debug, Clone)]
83pub struct Dependency {
84    pub source: Source,
85    pub wrapped: bool,
86}
87
88/// Where a dependency comes from — exactly one of the two, per
89/// `docs/spec/packages.md#where-a-dependency-comes-from`.
90#[derive(Debug, Clone)]
91pub enum Source {
92    Git { url: String, pin: GitPin },
93    Path { path: String },
94}
95
96/// A `git` source asks for a version, by way of the tag it was published under, or pins one
97/// commit in full — never both, and never neither.
98#[derive(Debug, Clone)]
99pub enum GitPin {
100    Version(String),
101    Rev(String),
102}
103
104/// The shape `serde` deserializes `zelkova.toml` into directly, before any field has been
105/// checked. `main` is the one field genuinely optional at this layer — every other field is
106/// required by the spec, so its absence is itself a [`ManifestError`], which is what leaving
107/// off `#[serde(default)]` on the others gets for free: a missing `dependencies` table fails
108/// to deserialize with "missing field" rather than silently becoming empty.
109///
110/// `deny_unknown_fields` closes the same door from the other side. The spec's six fields are
111/// the whole of a manifest, so a key that is not one of them is a misspelling (`mian = "App"`
112/// would otherwise read as a library with no entry point) or a field a newer spec added and
113/// this compiler cannot honour.
114#[derive(Debug, Deserialize)]
115#[serde(deny_unknown_fields)]
116struct RawManifest {
117    name: String,
118    version: String,
119    #[serde(default)]
120    main: Option<String>,
121    #[serde(rename = "private-modules")]
122    private_modules: Vec<String>,
123    dependencies: HashMap<String, RawDependency>,
124    #[serde(rename = "test-dependencies")]
125    test_dependencies: HashMap<String, RawDependency>,
126}
127
128#[derive(Debug, Deserialize)]
129struct RawDependency {
130    #[serde(default)]
131    version: Option<String>,
132    #[serde(default)]
133    git: Option<String>,
134    #[serde(default)]
135    path: Option<String>,
136    #[serde(default)]
137    rev: Option<String>,
138    #[serde(default = "wrapped_by_default")]
139    wrapped: bool,
140}
141
142fn wrapped_by_default() -> bool {
143    true
144}
145
146/// Every way reading a manifest can fail.
147///
148/// Like [`SourceFileError`](super::source::files::SourceFileError), a manifest error has no
149/// [`Span`](zelkova_syntax::position::Span) to render: the location it would want is a byte range in
150/// `zelkova.toml`, which is not a file the [`SourceFiles`](super::source::SourceFiles) database
151/// holds. Every variant therefore carries the `manifest_path` it was found in, so that its
152/// [`message`](PhaseError::message) can name the file on its own — which is the whole of the
153/// location a reader gets, and is why every variant but
154/// [`PrivateModuleNotFound`](ManifestError::PrivateModuleNotFound) and
155/// [`MainModuleNotFound`](ManifestError::MainModuleNotFound) goes back to the caller
156/// unrendered, the same way loading errors already do.
157#[derive(Debug)]
158pub enum ManifestError {
159    /// No `zelkova.toml` at all beside `src/`.
160    Missing { manifest_path: PathBuf },
161    /// `zelkova.toml` exists but could not be read (permissions, not a regular file, …).
162    Unreadable {
163        manifest_path: PathBuf,
164        message: String,
165    },
166    /// `zelkova.toml` is not well-formed TOML, or does not have the shape `RawManifest`
167    /// expects — a missing required field among them.
168    Malformed {
169        manifest_path: PathBuf,
170        message: String,
171    },
172    /// `name` is not ASCII lowercase letters, digits and hyphens, starting with a letter,
173    /// with every hyphen followed by a letter.
174    InvalidName {
175        manifest_path: PathBuf,
176        name: String,
177    },
178    /// `version` is not exactly three non-negative integers separated by dots.
179    InvalidVersion {
180        manifest_path: PathBuf,
181        version: String,
182    },
183    /// A key of `dependencies` or `test-dependencies` is not a legal package name.
184    InvalidDependencyName {
185        manifest_path: PathBuf,
186        table: &'static str,
187        name: String,
188    },
189    /// An entry of `dependencies` or `test-dependencies` does not name exactly one source,
190    /// or pairs that source with a field it does not take.
191    InvalidDependencyEntry {
192        manifest_path: PathBuf,
193        table: &'static str,
194        package: String,
195        reason: String,
196    },
197    /// One package name in both `dependencies` and `test-dependencies` — the spec allows a
198    /// name in at most one of the two.
199    DependencyListedTwice {
200        manifest_path: PathBuf,
201        name: String,
202    },
203    /// `private-modules` names a module this package does not hold.
204    ///
205    /// Unlike every other variant here, this one cannot be raised by [`load`]: knowing what
206    /// the package holds needs its sources loaded, which happens only after the manifest
207    /// has already been accepted. `compile_package` builds this once it has parsed every
208    /// module, and folds it into the normal per-package error accumulation rather than the
209    /// unrendered path the rest of this enum takes.
210    PrivateModuleNotFound { manifest_path: PathBuf, name: Name },
211    /// `main` names a module this package does not hold under `src/` — a module under
212    /// `tests/` does not count.
213    ///
214    /// Raised after the package's `src/` has checked, for the same reason as
215    /// [`PrivateModuleNotFound`](ManifestError::PrivateModuleNotFound), and by the check
216    /// [`program`](super::program) describes, whose other two failures are about the
217    /// module the name finds.
218    MainModuleNotFound { manifest_path: PathBuf, name: Name },
219}
220
221impl PhaseError for ManifestError {
222    fn message(&self) -> String {
223        match self {
224            ManifestError::Missing { manifest_path } => {
225                format!("`{}` has no manifest", manifest_path.display())
226            }
227            ManifestError::Unreadable {
228                manifest_path,
229                message,
230            } => format!(
231                "`{}` could not be read: {}",
232                manifest_path.display(),
233                message
234            ),
235            ManifestError::Malformed {
236                manifest_path,
237                message,
238            } => format!(
239                "`{}` is not a valid manifest: {}",
240                manifest_path.display(),
241                message
242            ),
243            ManifestError::InvalidName {
244                manifest_path,
245                name,
246            } => format!(
247                "`{}` declares the name `{}`, which is not a legal package name: a package name \
248                 is ASCII lowercase letters, digits and hyphens, starting with a letter, with \
249                 every hyphen followed by a letter",
250                manifest_path.display(),
251                name
252            ),
253            ManifestError::InvalidVersion {
254                manifest_path,
255                version,
256            } => format!(
257                "`{}` declares the version `{}`, which is not a legal version: a version is \
258                 exactly three non-negative integers separated by dots",
259                manifest_path.display(),
260                version
261            ),
262            ManifestError::InvalidDependencyName {
263                manifest_path,
264                table,
265                name,
266            } => format!(
267                "`{}` in `[{}]` of `{}` is not a legal package name",
268                name,
269                table,
270                manifest_path.display()
271            ),
272            ManifestError::InvalidDependencyEntry {
273                manifest_path,
274                table,
275                package,
276                reason,
277            } => format!(
278                "`{}` in `[{}]` of `{}` {}",
279                package,
280                table,
281                manifest_path.display(),
282                reason
283            ),
284            ManifestError::DependencyListedTwice {
285                manifest_path,
286                name,
287            } => format!(
288                "`{}` lists `{}` in both `dependencies` and `test-dependencies`",
289                manifest_path.display(),
290                name
291            ),
292            ManifestError::PrivateModuleNotFound {
293                manifest_path,
294                name,
295            } => format!(
296                "`private-modules` in `{}` names `{}`, which this package does not hold",
297                manifest_path.display(),
298                name
299            ),
300            ManifestError::MainModuleNotFound {
301                manifest_path,
302                name,
303            } => format!(
304                "`main` in `{}` names `{}`, which is not a module under this package's `src/`",
305                manifest_path.display(),
306                name
307            ),
308        }
309    }
310
311    fn notes(&self) -> Vec<String> {
312        match self {
313            ManifestError::MainModuleNotFound { .. } => vec![
314                "`main` is set in the manifest, and the manifest is not a source file a \
315                 diagnostic can point into, so this error has no location"
316                    .to_string(),
317                "`main` names a module by its name within the package, and that module must \
318                 be under `src/`"
319                    .to_string(),
320            ],
321            _ => Vec::new(),
322        }
323    }
324}
325
326/// Read and validate the manifest of the package rooted at `package_dir`.
327///
328/// Every failure this can produce is raised before `compile_package` builds the file
329/// database `load_package_sources` needs, which is why this returns a plain `Vec` rather
330/// than going through a rendered `Diagnostic` — there is nothing yet to render one against.
331pub fn load(package_dir: &Path) -> Result<Manifest, Vec<ManifestError>> {
332    let manifest_path = package_dir.join(MANIFEST_FILE_NAME);
333
334    let text = match std::fs::read_to_string(&manifest_path) {
335        Ok(text) => text,
336        Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
337            return Err(vec![ManifestError::Missing { manifest_path }]);
338        }
339        Err(err) => {
340            return Err(vec![ManifestError::Unreadable {
341                manifest_path,
342                message: err.to_string(),
343            }]);
344        }
345    };
346
347    let raw: RawManifest = match toml::from_str(&text) {
348        Ok(raw) => raw,
349        Err(err) => {
350            return Err(vec![ManifestError::Malformed {
351                manifest_path,
352                message: err.to_string(),
353            }]);
354        }
355    };
356
357    let mut errors = Vec::new();
358
359    let name = PackageName::new(raw.name.clone());
360    if name.is_err() {
361        errors.push(ManifestError::InvalidName {
362            manifest_path: manifest_path.clone(),
363            name: raw.name,
364        });
365    }
366
367    let version = Version::parse(&raw.version);
368    if version.is_none() {
369        errors.push(ManifestError::InvalidVersion {
370            manifest_path: manifest_path.clone(),
371            version: raw.version,
372        });
373    }
374
375    let private_modules: Vec<Name> = raw.private_modules.iter().map(Name::new).collect();
376    let main = raw.main.as_deref().map(Name::new);
377
378    let mut dependencies = HashMap::new();
379    for (package, raw_dep) in raw.dependencies {
380        match validate_dependency(&manifest_path, "dependencies", package, raw_dep) {
381            Ok((name, dep)) => {
382                dependencies.insert(name, dep);
383            }
384            Err(err) => errors.push(err),
385        }
386    }
387
388    let mut test_dependencies = HashMap::new();
389    for (package, raw_dep) in raw.test_dependencies {
390        match validate_dependency(&manifest_path, "test-dependencies", package, raw_dep) {
391            Ok((name, dep)) => {
392                test_dependencies.insert(name, dep);
393            }
394            Err(err) => errors.push(err),
395        }
396    }
397
398    for shared in dependencies.keys() {
399        if test_dependencies.contains_key(shared) {
400            errors.push(ManifestError::DependencyListedTwice {
401                manifest_path: manifest_path.clone(),
402                name: shared.as_str().to_string(),
403            });
404        }
405    }
406
407    match (name, version) {
408        (Ok(name), Some(version)) if errors.is_empty() => Ok(Manifest {
409            name,
410            version,
411            main,
412            private_modules,
413            dependencies,
414            test_dependencies,
415        }),
416        _ => Err(errors),
417    }
418}
419
420/// Turn one raw `dependencies`/`test-dependencies` entry into a [`Dependency`], or the one
421/// [`ManifestError`] naming why it is not one. `table` names which of the two maps `package`
422/// came from, purely so the message can say where to look.
423fn validate_dependency(
424    manifest_path: &Path,
425    table: &'static str,
426    package: String,
427    raw: RawDependency,
428) -> Result<(PackageName, Dependency), ManifestError> {
429    let name =
430        PackageName::new(package.clone()).map_err(|_| ManifestError::InvalidDependencyName {
431            manifest_path: manifest_path.to_path_buf(),
432            table,
433            name: package.clone(),
434        })?;
435
436    let entry_error = |reason: &str| ManifestError::InvalidDependencyEntry {
437        manifest_path: manifest_path.to_path_buf(),
438        table,
439        package: package.clone(),
440        reason: reason.to_string(),
441    };
442
443    let dependency = match (raw.git, raw.path) {
444        (Some(_), Some(_)) => {
445            return Err(entry_error(
446                "names both `git` and `path`; an entry must name exactly one source",
447            ))
448        }
449        (None, None) => {
450            return Err(entry_error(
451                "names neither `git` nor `path`; a bare version constraint names no place a \
452                 package could be found",
453            ))
454        }
455        (Some(url), None) => match (raw.version, raw.rev) {
456            (Some(version), None) => Dependency {
457                source: Source::Git {
458                    url,
459                    pin: GitPin::Version(version),
460                },
461                wrapped: raw.wrapped,
462            },
463            (None, Some(rev)) => Dependency {
464                source: Source::Git {
465                    url,
466                    pin: GitPin::Rev(rev),
467                },
468                wrapped: raw.wrapped,
469            },
470            (None, None) => {
471                return Err(entry_error(
472                    "names a `git` source with neither a `version` nor a `rev`",
473                ))
474            }
475            (Some(_), Some(_)) => {
476                return Err(entry_error(
477                    "names a `git` source with both a `version` and a `rev`; a `git` entry \
478                     asks for one or the other",
479                ))
480            }
481        },
482        (None, Some(path)) => {
483            if raw.version.is_some() || raw.rev.is_some() {
484                return Err(entry_error(
485                    "names a `path` source alongside a `version` or a `rev`; a `path` source \
486                     carries neither",
487                ));
488            }
489            Dependency {
490                source: Source::Path { path },
491                wrapped: raw.wrapped,
492            }
493        }
494    };
495
496    Ok((name, dependency))
497}
498
499#[cfg(test)]
500mod tests {
501    use super::*;
502
503    fn write_manifest(dir: &Path, contents: &str) {
504        std::fs::write(dir.join(MANIFEST_FILE_NAME), contents).unwrap();
505    }
506
507    /// A missing manifest names the file it looked for, not just the directory.
508    #[test]
509    fn missing_manifest_names_the_file() {
510        let dir = tempdir();
511
512        let errors = load(dir.path()).expect_err("no zelkova.toml was written");
513
514        assert_eq!(errors.len(), 1, "got {:?}", errors);
515        match &errors[0] {
516            ManifestError::Missing { manifest_path } => {
517                assert_eq!(manifest_path, &dir.path().join("zelkova.toml"));
518            }
519            other => panic!("expected Missing, got {:?}", other),
520        }
521    }
522
523    /// An illegal `name` is reported as `InvalidName`, naming the offending string, even
524    /// when every other field is well-formed.
525    #[test]
526    fn illegal_name_is_rejected() {
527        let dir = tempdir();
528        write_manifest(
529            dir.path(),
530            r#"
531                name = "Not_Legal"
532                version = "0.1.0"
533                private-modules = []
534
535                [dependencies]
536
537                [test-dependencies]
538            "#,
539        );
540
541        let errors = load(dir.path()).expect_err("`Not_Legal` is not a legal package name");
542
543        assert_eq!(errors.len(), 1, "got {:?}", errors);
544        match &errors[0] {
545            ManifestError::InvalidName {
546                manifest_path,
547                name,
548            } => {
549                assert_eq!(name, "Not_Legal");
550                assert_eq!(manifest_path, &dir.path().join(MANIFEST_FILE_NAME));
551            }
552            other => panic!("expected InvalidName, got {:?}", other),
553        }
554
555        // The sentence the user is shown has to carry the file as well as the field, since
556        // it is the only location a manifest error ever gets.
557        let message = errors[0].message();
558        assert!(
559            message.contains("Not_Legal") && message.contains(MANIFEST_FILE_NAME),
560            "expected the name and the manifest path in the message, got {:?}",
561            message
562        );
563    }
564
565    /// A key that is not one of the manifest's six fields is reported rather than ignored:
566    /// `mian = "App"` is a misspelt `main`, and a manifest reader that shrugs at it reads the
567    /// package as a library.
568    #[test]
569    fn an_unknown_key_is_rejected() {
570        let dir = tempdir();
571        write_manifest(
572            dir.path(),
573            r#"
574                name = "todo"
575                version = "0.1.0"
576                mian = "App"
577                private-modules = []
578
579                [dependencies]
580
581                [test-dependencies]
582            "#,
583        );
584
585        let errors = load(dir.path()).expect_err("`mian` is not a manifest field");
586
587        assert_eq!(errors.len(), 1, "got {:?}", errors);
588        match &errors[0] {
589            ManifestError::Malformed { message, .. } => assert!(
590                message.contains("mian"),
591                "expected the unknown key to be named, got {:?}",
592                message
593            ),
594            other => panic!("expected Malformed, got {:?}", other),
595        }
596    }
597
598    /// A well-formed manifest is read into every field, dependencies included.
599    #[test]
600    fn a_well_formed_manifest_loads() {
601        let dir = tempdir();
602        write_manifest(
603            dir.path(),
604            r#"
605                name = "todo"
606                version = "0.4.1"
607                main = "App"
608                private-modules = ["Model.Internal"]
609
610                [dependencies.acme-widgets]
611                version = "^1.2.0"
612                git = "https://github.com/acme/widgets"
613
614                [dependencies.acme-parser]
615                path = "../acme-parser"
616                wrapped = false
617
618                [test-dependencies.acme-expect]
619                version = "^2.0.0"
620                git = "https://github.com/acme/expect"
621            "#,
622        );
623
624        let manifest = load(dir.path()).expect("manifest is well-formed");
625
626        assert_eq!(manifest.name.as_str(), "todo");
627        assert_eq!(
628            manifest.version,
629            Version {
630                major: 0,
631                minor: 4,
632                patch: 1
633            }
634        );
635        assert_eq!(manifest.main, Some(Name::new("App")));
636        assert_eq!(manifest.private_modules, vec![Name::new("Model.Internal")]);
637        assert_eq!(manifest.dependencies.len(), 2);
638        assert_eq!(manifest.test_dependencies.len(), 1);
639
640        let widgets = &manifest.dependencies[&PackageName::new("acme-widgets").unwrap()];
641        assert!(widgets.wrapped);
642        match &widgets.source {
643            Source::Git {
644                url,
645                pin: GitPin::Version(v),
646            } => {
647                assert_eq!(url, "https://github.com/acme/widgets");
648                assert_eq!(v, "^1.2.0");
649            }
650            other => panic!("expected a Git/Version source, got {:?}", other),
651        }
652
653        let parser = &manifest.dependencies[&PackageName::new("acme-parser").unwrap()];
654        assert!(!parser.wrapped);
655        match &parser.source {
656            Source::Path { path } => assert_eq!(path, "../acme-parser"),
657            other => panic!("expected a Path source, got {:?}", other),
658        }
659    }
660
661    /// A dependency entry naming neither `git` nor `path` is rejected — the ticket's
662    /// "a bare version string is invalid" case, spelled as an entry with no source at all
663    /// (a bare `acme-widgets = "^1.2.0"` does not even deserialize as a table and is caught
664    /// as `Malformed` instead; this is the same rule applied to a table that still forgot
665    /// the source).
666    #[test]
667    fn a_dependency_with_no_source_is_rejected() {
668        let dir = tempdir();
669        write_manifest(
670            dir.path(),
671            r#"
672                name = "todo"
673                version = "0.1.0"
674                private-modules = []
675
676                [dependencies.acme-widgets]
677                version = "^1.2.0"
678
679                [test-dependencies]
680            "#,
681        );
682
683        let errors = load(dir.path()).expect_err("a dependency with no source must be rejected");
684
685        assert_eq!(errors.len(), 1, "got {:?}", errors);
686        match &errors[0] {
687            ManifestError::InvalidDependencyEntry { table, package, .. } => {
688                assert_eq!(*table, "dependencies");
689                assert_eq!(package, "acme-widgets");
690            }
691            other => panic!("expected InvalidDependencyEntry, got {:?}", other),
692        }
693    }
694
695    /// A bare version string — `acme-widgets = "^1.2.0"` rather than a table — is not a
696    /// legal entry at all, and is caught as a malformed manifest.
697    #[test]
698    fn a_bare_version_string_is_malformed() {
699        let dir = tempdir();
700        write_manifest(
701            dir.path(),
702            r#"
703                name = "todo"
704                version = "0.1.0"
705                private-modules = []
706
707                [dependencies]
708                acme-widgets = "^1.2.0"
709
710                [test-dependencies]
711            "#,
712        );
713
714        let errors = load(dir.path()).expect_err("a bare version string is not a legal entry");
715
716        assert_eq!(errors.len(), 1, "got {:?}", errors);
717        assert!(
718            matches!(errors[0], ManifestError::Malformed { .. }),
719            "expected Malformed, got {:?}",
720            errors[0]
721        );
722    }
723
724    /// A directory under `std::env::temp_dir()`, unique per call and cleaned up on drop —
725    /// this module's own tests are the only thing in the tree that needs an on-disk
726    /// manifest, so a tiny local helper is enough and avoids a new dev-dependency.
727    struct TempDir(PathBuf);
728
729    impl TempDir {
730        fn path(&self) -> &Path {
731            &self.0
732        }
733    }
734
735    impl Drop for TempDir {
736        fn drop(&mut self) {
737            let _ = std::fs::remove_dir_all(&self.0);
738        }
739    }
740
741    fn tempdir() -> TempDir {
742        let mut path = std::env::temp_dir();
743        let unique = format!(
744            "zelkova-manifest-test-{}-{}",
745            std::process::id(),
746            std::time::SystemTime::now()
747                .duration_since(std::time::UNIX_EPOCH)
748                .unwrap()
749                .as_nanos()
750        );
751        path.push(unique);
752        std::fs::create_dir_all(&path).unwrap();
753        TempDir(path)
754    }
755}