Skip to main content

nix_compat/derived_path/
legacy.rs

1use std::{fmt, str::FromStr};
2
3use crate::{derived_path::OutputSpec, store_path::StorePath};
4
5use super::DerivedPath;
6
7/// Format a [`DerivedPath`] in the "legacy" format.
8///
9/// Normally a [`DerivedPath::Built`] it formatted like
10/// `/nix/store/00000000000000000000000000000000-test.drv^out`. But in some
11/// places (most notably in the [Nix daemon protocol]) a format like
12/// `/nix/store/00000000000000000000000000000000-test.drv!out` is used.
13///
14/// This formatter implements [`FromStr`] and [`fmt::Display`] that use this format.
15///
16/// [Nix daemon protocol]: http://snix.dev/docs/reference/nix-daemon-protocol/intro/
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18#[repr(transparent)]
19pub struct LegacyDerivedPath(DerivedPath);
20impl LegacyDerivedPath {
21    pub fn from_path(path: DerivedPath) -> Self {
22        Self(path)
23    }
24
25    pub fn as_path(&self) -> &DerivedPath {
26        &self.0
27    }
28
29    pub fn into_path(self) -> DerivedPath {
30        self.0
31    }
32}
33
34impl fmt::Display for LegacyDerivedPath {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match &self.0 {
37            DerivedPath::Opaque(store_path) => write!(f, "{}", store_path.to_absolute_path()),
38            DerivedPath::Built { drv_path, outputs } => {
39                write!(f, "{}!{}", drv_path.to_absolute_path(), outputs)
40            }
41        }
42    }
43}
44
45impl FromStr for LegacyDerivedPath {
46    type Err = super::ParseDerivedPathError;
47
48    fn from_str(s: &str) -> Result<Self, Self::Err> {
49        if let Some((prefix, outputs_s)) = s.rsplit_once('!') {
50            let drv_path = StorePath::from_absolute_path(prefix.as_bytes())?;
51            let outputs = outputs_s.parse::<OutputSpec>()?;
52            Ok(LegacyDerivedPath(DerivedPath::Built { drv_path, outputs }))
53        } else {
54            Ok(LegacyDerivedPath(DerivedPath::Opaque(
55                StorePath::from_absolute_path(s.as_bytes())?,
56            )))
57        }
58    }
59}
60
61impl From<DerivedPath> for LegacyDerivedPath {
62    fn from(value: DerivedPath) -> Self {
63        value.into_legacy_format()
64    }
65}
66
67impl<'a> From<&'a DerivedPath> for &'a LegacyDerivedPath {
68    fn from(value: &'a DerivedPath) -> Self {
69        value.as_legacy_format()
70    }
71}
72
73impl From<LegacyDerivedPath> for DerivedPath {
74    fn from(value: LegacyDerivedPath) -> Self {
75        value.into_path()
76    }
77}
78
79impl<'a> From<&'a LegacyDerivedPath> for &'a DerivedPath {
80    fn from(value: &'a LegacyDerivedPath) -> Self {
81        value.as_path()
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use rstest::rstest;
88
89    use super::*;
90    #[rstest]
91    #[case("/nix/store/00000000000000000000000000000000-test.drv", DerivedPath::Opaque("00000000000000000000000000000000-test.drv".parse().unwrap()))]
92    #[case("/nix/store/00000000000000000000000000000000-test.drv!out", DerivedPath::Built {
93        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
94        outputs: "out".parse().unwrap(),
95    })]
96    #[case("/nix/store/00000000000000000000000000000000-test.drv!*", DerivedPath::Built {
97        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
98        outputs: "*".parse().unwrap(),
99    })]
100    #[case("/nix/store/00000000000000000000000000000000-test.drv!bin,lib", DerivedPath::Built {
101        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
102        outputs: "bin,lib".parse().unwrap(),
103    })]
104    fn parse(#[case] input: &str, #[case] expected: DerivedPath) {
105        let actual = input.parse::<LegacyDerivedPath>().unwrap().into_path();
106        assert_eq!(actual, expected);
107    }
108
109    #[rstest]
110    // tries to parse `test.drv!out` as store path name
111    #[case("/nix/store/00000000000000000000000000000000-test.drv!out!bin,lib")]
112    // tries to parse `test.drv!out!bin` as store path name
113    #[case("/nix/store/00000000000000000000000000000000-test.drv!out!bin!lib")]
114    // treats it as opaque
115    #[case("/nix/store/00000000000000000000000000000000-test.drv^out")]
116    // tries to parse `test.drv^out` as store path name
117    #[case("/nix/store/00000000000000000000000000000000-test.drv^out!bin")]
118    // tries to parse `test.drv!out!bin^out` as store path name
119    #[case("/nix/store/00000000000000000000000000000000-test.drv!out!bin^out!lib")]
120    #[should_panic(expected = "StorePath(Name)")]
121    fn parse_fail(#[case] input: &str) {
122        input.parse::<LegacyDerivedPath>().unwrap();
123    }
124
125    #[rstest]
126    #[case(DerivedPath::Opaque("00000000000000000000000000000000-test.drv".parse().unwrap()), "/nix/store/00000000000000000000000000000000-test.drv")]
127    #[case(DerivedPath::Built {
128        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
129        outputs: "out".parse().unwrap(),
130    }, "/nix/store/00000000000000000000000000000000-test.drv!out")]
131    #[case(DerivedPath::Built {
132        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
133        outputs: "*".parse().unwrap(),
134    }, "/nix/store/00000000000000000000000000000000-test.drv!*")]
135    #[case(DerivedPath::Built {
136        drv_path: "00000000000000000000000000000000-test.drv".parse().unwrap(),
137        outputs: "bin,lib".parse().unwrap(),
138    }, "/nix/store/00000000000000000000000000000000-test.drv!bin,lib")]
139    fn display(#[case] value: DerivedPath, #[case] expected: &str) {
140        assert_eq!(value.as_legacy_format().to_string(), expected);
141    }
142}