1use std::{fmt, str::FromStr};
2
3use crate::store_path;
4
5use super::OutputSpec;
6
7#[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 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#[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}