Skip to main content

nix_compat/derived_path/
mod.rs

1use std::{fmt, str::FromStr};
2
3mod legacy;
4mod output_spec;
5
6pub use legacy::LegacyDerivedPath;
7pub use output_spec::{OutputSpec, ParseOutputSpecError};
8
9use crate::store_path;
10
11/// A deriving path.
12///
13/// Deriving paths are a way to refer to store objects that may or may not yet
14/// be realised. There are two forms:
15///     - opaque: just a store path.
16///     - built: a pair of a store path to a store derivation and an output name.
17///
18/// See: <https://nix.dev/manual/nix/latest/store/derivation/#deriving-path>
19#[derive(Debug, Clone, PartialEq, Eq, Hash)]
20pub enum DerivedPath {
21    Opaque(store_path::StorePath),
22    Built {
23        drv_path: store_path::StorePath,
24        outputs: OutputSpec,
25    },
26}
27
28impl DerivedPath {
29    pub fn into_legacy_format(self) -> LegacyDerivedPath {
30        LegacyDerivedPath::from_path(self)
31    }
32
33    pub fn as_legacy_format(&self) -> &LegacyDerivedPath {
34        // SAFETY: `DerivedPath` and `LegacyDerivedPath` have the same ABI because of #[repr(transparent)]
35        unsafe { &*(self as *const Self as *const LegacyDerivedPath) }
36    }
37}
38
39impl fmt::Display for DerivedPath {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match self {
42            DerivedPath::Opaque(store_path) => write!(f, "{}", store_path.to_absolute_path()),
43            DerivedPath::Built { drv_path, outputs } => {
44                write!(f, "{}^{}", drv_path.to_absolute_path(), outputs)
45            }
46        }
47    }
48}
49
50impl FromStr for DerivedPath {
51    type Err = ParseDerivedPathError;
52
53    fn from_str(s: &str) -> Result<Self, Self::Err> {
54        if let Some((prefix, outputs_s)) = s.rsplit_once('^') {
55            let drv_path = store_path::StorePath::from_absolute_path(prefix.as_bytes())?;
56            let outputs = outputs_s.parse::<OutputSpec>()?;
57            Ok(DerivedPath::Built { drv_path, outputs })
58        } else {
59            Ok(DerivedPath::Opaque(
60                store_path::StorePath::from_absolute_path(s.as_bytes())?,
61            ))
62        }
63    }
64}
65
66#[derive(thiserror::Error, Debug)]
67pub enum ParseDerivedPathError {
68    #[error("failed to parse store path")]
69    StorePath(#[from] store_path::ParseStorePathError),
70    #[error("store path does not point to a derivation")]
71    MissingDrvSuffix,
72    #[error("failed to parse output spec")]
73    OutputSpec(#[from] ParseOutputSpecError),
74}
75
76#[cfg(test)]
77mod tests {
78    use rstest::rstest;
79
80    use super::*;
81
82    #[rstest]
83    #[case("/nix/store/00000000000000000000000000000000-test.drv", DerivedPath::Opaque("00000000000000000000000000000000-test.drv".parse().unwrap()))]
84    #[case("/nix/store/00000000000000000000000000000000-test.drv^out", DerivedPath::Built {
85        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
86        outputs: "out".parse().unwrap(),
87    })]
88    #[case("/nix/store/00000000000000000000000000000000-test.drv^*", DerivedPath::Built {
89        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
90        outputs: "*".parse().unwrap(),
91    })]
92    #[case("/nix/store/00000000000000000000000000000000-test.drv^bin,lib", DerivedPath::Built {
93        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
94        outputs: "bin,lib".parse().unwrap(),
95    })]
96    fn parse(#[case] input: &str, #[case] expected: DerivedPath) {
97        let actual = input.parse::<DerivedPath>().unwrap();
98        assert_eq!(actual, expected);
99    }
100
101    #[rstest]
102    // tries to parse `test.drv^out` as store path name
103    #[case("/nix/store/00000000000000000000000000000000-test.drv^out^bin,lib")]
104    // tries to parse `test.drv^out^bin` as store path name
105    #[case("/nix/store/00000000000000000000000000000000-test.drv^out^bin^lib")]
106    // treats it as opaque
107    #[case("/nix/store/00000000000000000000000000000000-test.drv!out")]
108    // tries to parse `test.drv!out` as store path name
109    #[case("/nix/store/00000000000000000000000000000000-test.drv!out^bin")]
110    // tries to parse `test.drv^out^bin!out` as store path name
111    #[case("/nix/store/00000000000000000000000000000000-test.drv^out^bin!out^lib")]
112    #[should_panic(expected = "StorePath(Name)")]
113    fn parse_fail(#[case] input: &str) {
114        input.parse::<DerivedPath>().unwrap();
115    }
116
117    #[rstest]
118    #[case(DerivedPath::Opaque("00000000000000000000000000000000-test.drv".parse().unwrap()), "/nix/store/00000000000000000000000000000000-test.drv")]
119    #[case(DerivedPath::Built {
120        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
121        outputs: "out".parse().unwrap(),
122    }, "/nix/store/00000000000000000000000000000000-test.drv^out")]
123    #[case(DerivedPath::Built {
124        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
125        outputs: "*".parse().unwrap(),
126    }, "/nix/store/00000000000000000000000000000000-test.drv^*")]
127    #[case(DerivedPath::Built {
128        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
129        outputs: "bin,lib".parse().unwrap(),
130    }, "/nix/store/00000000000000000000000000000000-test.drv^bin,lib")]
131    fn display(#[case] value: DerivedPath, #[case] expected: &str) {
132        assert_eq!(value.to_string(), expected);
133    }
134}