Skip to main content

nix_compat/derived_path/
path.rs

1use std::{fmt, str::FromStr};
2
3use crate::store_path;
4
5use super::OutputSpec;
6
7/// A deriving path.
8///
9/// Deriving paths are a way to refer to store objects that may or may not yet
10/// be realised. There are two forms:
11///     - opaque: just a store path.
12///     - built: a pair of a store path to a store derivation and an output name.
13///
14/// See: <https://nix.dev/manual/nix/latest/store/derivation/#deriving-path>
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub enum DerivedPath {
17    Opaque(store_path::StorePath<String>),
18    Built {
19        drv_path: store_path::StorePath<String>,
20        outputs: OutputSpec,
21    },
22}
23
24impl DerivedPath {
25    pub fn into_legacy_format(self) -> LegacyDerivedPath {
26        LegacyDerivedPath(self)
27    }
28
29    pub fn as_legacy_format(&self) -> &LegacyDerivedPath {
30        // SAFETY: `DerivedPath` and `LegacyDerivedPath` have the same ABI because of #[repr(transparent)]
31        unsafe { &*(self as *const Self as *const LegacyDerivedPath) }
32    }
33}
34
35impl fmt::Display for DerivedPath {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        match self {
38            DerivedPath::Opaque(store_path) => write!(f, "{}", store_path.to_absolute_path()),
39            DerivedPath::Built { drv_path, outputs } => {
40                write!(f, "{}^{}", drv_path.to_absolute_path(), outputs)
41            }
42        }
43    }
44}
45
46impl FromStr for DerivedPath {
47    type Err = store_path::Error;
48
49    fn from_str(s: &str) -> Result<Self, Self::Err> {
50        if let Some((prefix, outputs_s)) = s.rsplit_once('^') {
51            let drv_path = store_path::StorePath::from_absolute_path(prefix.as_bytes())?;
52            let outputs = outputs_s.parse::<OutputSpec>()?;
53            Ok(DerivedPath::Built { drv_path, outputs })
54        } else {
55            Ok(DerivedPath::Opaque(
56                store_path::StorePath::from_absolute_path(s.as_bytes())?,
57            ))
58        }
59    }
60}
61
62/// Format a [`DerivedPath`] in the "legacy" format.
63///
64/// Normally a [`DerivedPath::Built`] it formatted like
65/// `/nix/store/00000000000000000000000000000000-test.drv^out`. But in some
66/// places (most notably in the [Nix daemon protocol]) a format like
67/// `/nix/store/00000000000000000000000000000000-test.drv!out` is used.
68///
69/// This formatter implements [`FromStr`] and [`fmt::Display`] that use this format.
70///
71/// [Nix daemon protocol]: http://snix.dev/docs/reference/nix-daemon-protocol/intro/
72#[derive(Debug, Clone, PartialEq, Eq, Hash)]
73#[repr(transparent)]
74pub struct LegacyDerivedPath(DerivedPath);
75impl LegacyDerivedPath {
76    pub fn from_path(path: DerivedPath) -> Self {
77        path.into_legacy_format()
78    }
79
80    pub fn as_path(&self) -> &DerivedPath {
81        &self.0
82    }
83
84    pub fn into_path(self) -> DerivedPath {
85        self.0
86    }
87}
88
89impl fmt::Display for LegacyDerivedPath {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match &self.0 {
92            DerivedPath::Opaque(store_path) => write!(f, "{}", store_path.to_absolute_path()),
93            DerivedPath::Built { drv_path, outputs } => {
94                write!(f, "{}!{}", drv_path.to_absolute_path(), outputs)
95            }
96        }
97    }
98}
99
100impl FromStr for LegacyDerivedPath {
101    type Err = store_path::Error;
102
103    fn from_str(s: &str) -> Result<Self, Self::Err> {
104        if let Some((prefix, outputs_s)) = s.rsplit_once('!') {
105            let drv_path = store_path::StorePath::from_absolute_path(prefix.as_bytes())?;
106            let outputs = outputs_s.parse::<OutputSpec>()?;
107            Ok(LegacyDerivedPath(DerivedPath::Built { drv_path, outputs }))
108        } else {
109            Ok(LegacyDerivedPath(DerivedPath::Opaque(
110                store_path::StorePath::from_absolute_path(s.as_bytes())?,
111            )))
112        }
113    }
114}
115
116impl From<DerivedPath> for LegacyDerivedPath {
117    fn from(value: DerivedPath) -> Self {
118        value.into_legacy_format()
119    }
120}
121
122impl<'a> From<&'a DerivedPath> for &'a LegacyDerivedPath {
123    fn from(value: &'a DerivedPath) -> Self {
124        value.as_legacy_format()
125    }
126}
127
128impl From<LegacyDerivedPath> for DerivedPath {
129    fn from(value: LegacyDerivedPath) -> Self {
130        value.into_path()
131    }
132}
133
134impl<'a> From<&'a LegacyDerivedPath> for &'a DerivedPath {
135    fn from(value: &'a LegacyDerivedPath) -> Self {
136        value.as_path()
137    }
138}
139
140#[cfg(test)]
141mod unittests {
142    use rstest::rstest;
143
144    use super::*;
145
146    #[rstest]
147    #[case("/nix/store/00000000000000000000000000000000-test.drv", DerivedPath::Opaque("00000000000000000000000000000000-test.drv".parse().unwrap()))]
148    #[case("/nix/store/00000000000000000000000000000000-test.drv^out", DerivedPath::Built {
149        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
150        outputs: "out".parse().unwrap(),
151    })]
152    #[case("/nix/store/00000000000000000000000000000000-test.drv^*", DerivedPath::Built {
153        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
154        outputs: "*".parse().unwrap(),
155    })]
156    #[case("/nix/store/00000000000000000000000000000000-test.drv^bin,lib", DerivedPath::Built {
157        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
158        outputs: "bin,lib".parse().unwrap(),
159    })]
160    fn parse_path(#[case] input: &str, #[case] expected: DerivedPath) {
161        let actual = input.parse::<DerivedPath>().unwrap();
162        assert_eq!(actual, expected);
163    }
164
165    #[rstest]
166    #[should_panic(expected = "Invalid name")]
167    #[case("/nix/store/00000000000000000000000000000000-test.drv^out^bin,lib")]
168    #[should_panic(expected = "Invalid name")]
169    #[case("/nix/store/00000000000000000000000000000000-test.drv^out^bin^lib")]
170    #[should_panic(expected = "Invalid name")]
171    #[case("/nix/store/00000000000000000000000000000000-test.drv!out")]
172    #[should_panic(expected = "Invalid name")]
173    #[case("/nix/store/00000000000000000000000000000000-test.drv!out^bin")]
174    #[should_panic(expected = "Invalid name")]
175    #[case("/nix/store/00000000000000000000000000000000-test.drv^out^bin!out^lib")]
176    fn parse_path_failure(#[case] input: &str) {
177        let actual = input.parse::<DerivedPath>().unwrap_err();
178        panic!("{actual}");
179    }
180
181    #[rstest]
182    #[case("/nix/store/00000000000000000000000000000000-test.drv", DerivedPath::Opaque("00000000000000000000000000000000-test.drv".parse().unwrap()))]
183    #[case("/nix/store/00000000000000000000000000000000-test.drv!out", DerivedPath::Built {
184        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
185        outputs: "out".parse().unwrap(),
186    })]
187    #[case("/nix/store/00000000000000000000000000000000-test.drv!*", DerivedPath::Built {
188        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
189        outputs: "*".parse().unwrap(),
190    })]
191    #[case("/nix/store/00000000000000000000000000000000-test.drv!bin,lib", DerivedPath::Built {
192        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
193        outputs: "bin,lib".parse().unwrap(),
194    })]
195    fn parse_legacy_path(#[case] input: &str, #[case] expected: DerivedPath) {
196        let actual = input.parse::<LegacyDerivedPath>().unwrap().into_path();
197        assert_eq!(actual, expected);
198    }
199
200    #[rstest]
201    #[should_panic(expected = "Invalid name")]
202    #[case("/nix/store/00000000000000000000000000000000-test.drv!out!bin,lib")]
203    #[should_panic(expected = "Invalid name")]
204    #[case("/nix/store/00000000000000000000000000000000-test.drv!out!bin!lib")]
205    #[should_panic(expected = "Invalid name")]
206    #[case("/nix/store/00000000000000000000000000000000-test.drv^out")]
207    #[should_panic(expected = "Invalid name")]
208    #[case("/nix/store/00000000000000000000000000000000-test.drv^out!bin")]
209    #[should_panic(expected = "Invalid name")]
210    #[case("/nix/store/00000000000000000000000000000000-test.drv!out!bin^out!lib")]
211    fn parse_legacy_path_failure(#[case] input: &str) {
212        let actual = input.parse::<LegacyDerivedPath>().unwrap_err();
213        panic!("{actual}");
214    }
215
216    #[rstest]
217    #[case(DerivedPath::Opaque("00000000000000000000000000000000-test.drv".parse().unwrap()), "/nix/store/00000000000000000000000000000000-test.drv")]
218    #[case(DerivedPath::Built {
219        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
220        outputs: "out".parse().unwrap(),
221    }, "/nix/store/00000000000000000000000000000000-test.drv^out")]
222    #[case(DerivedPath::Built {
223        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
224        outputs: "*".parse().unwrap(),
225    }, "/nix/store/00000000000000000000000000000000-test.drv^*")]
226    #[case(DerivedPath::Built {
227        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
228        outputs: "bin,lib".parse().unwrap(),
229    }, "/nix/store/00000000000000000000000000000000-test.drv^bin,lib")]
230    fn display_path(#[case] value: DerivedPath, #[case] expected: &str) {
231        assert_eq!(value.to_string(), expected);
232    }
233
234    #[rstest]
235    #[case(DerivedPath::Opaque("00000000000000000000000000000000-test.drv".parse().unwrap()), "/nix/store/00000000000000000000000000000000-test.drv")]
236    #[case(DerivedPath::Built {
237        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
238        outputs: "out".parse().unwrap(),
239    }, "/nix/store/00000000000000000000000000000000-test.drv!out")]
240    #[case(DerivedPath::Built {
241        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
242        outputs: "*".parse().unwrap(),
243    }, "/nix/store/00000000000000000000000000000000-test.drv!*")]
244    #[case(DerivedPath::Built {
245        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
246        outputs: "bin,lib".parse().unwrap(),
247    }, "/nix/store/00000000000000000000000000000000-test.drv!bin,lib")]
248    fn display_legacy_path(#[case] value: DerivedPath, #[case] expected: &str) {
249        assert_eq!(value.as_legacy_format().to_string(), expected);
250    }
251}