Skip to main content

nix_compat/derived_path/
output_spec.rs

1use std::collections::BTreeSet;
2use std::fmt;
3use std::str::FromStr;
4
5use crate::store_path::{ValidateNameError, validate_name};
6
7/// A derivation output name.
8///
9/// This is a derivation output name, so the 'out' or 'bin' bit that has
10/// been verified to not contain invalid characters.
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[cfg_attr(
13    feature = "serde",
14    derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
15)]
16pub struct OutputName(pub(crate) String);
17
18impl OutputName {
19    /// Returns `true` if this output name is the default of `out`.
20    pub fn is_default(&self) -> bool {
21        self.0 == "out"
22    }
23}
24
25impl fmt::Display for OutputName {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        f.write_str(&self.0)
28    }
29}
30
31impl AsRef<str> for OutputName {
32    fn as_ref(&self) -> &str {
33        &self.0
34    }
35}
36
37impl Default for OutputName {
38    fn default() -> Self {
39        OutputName("out".into())
40    }
41}
42
43impl FromStr for OutputName {
44    type Err = ValidateNameError;
45
46    fn from_str(s: &str) -> Result<Self, Self::Err> {
47        let name = validate_name(&s)?.to_string();
48        Ok(OutputName(name))
49    }
50}
51
52// FUTUREWORK: reduce the amount of heap allocation needed for this small set of small strings.
53/// An output selection spec.
54///
55/// This is either all outputs (formatted as '*' when displaying or parsing) or
56/// a set of [`OutputName`] with the outputs that is to be selected.
57///
58/// This is used in [`super::DerivedPath`] to perform selection of the outputs to make
59/// sure, while building, are valid or substituted.
60#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61#[cfg_attr(
62    feature = "serde",
63    derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
64)]
65pub enum OutputSpec {
66    All,
67    Named(BTreeSet<OutputName>),
68}
69
70impl OutputSpec {
71    pub fn single(output_name: OutputName) -> Self {
72        let mut set = BTreeSet::new();
73        set.insert(output_name);
74        Self::Named(set)
75    }
76}
77
78impl fmt::Display for OutputSpec {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        match self {
81            OutputSpec::All => f.write_str("*")?,
82            OutputSpec::Named(outputs) => {
83                let mut it = outputs.iter();
84                if let Some(output) = it.next() {
85                    write!(f, "{output}")?;
86                    for output in it {
87                        write!(f, ",{output}")?;
88                    }
89                }
90            }
91        }
92        Ok(())
93    }
94}
95
96impl FromStr for OutputSpec {
97    type Err = ValidateNameError;
98
99    fn from_str(s: &str) -> Result<Self, Self::Err> {
100        if s == "*" {
101            Ok(OutputSpec::All)
102        } else {
103            let mut outputs = BTreeSet::new();
104            for name in s.split(",") {
105                let output = name.parse()?;
106                outputs.insert(output);
107            }
108            Ok(OutputSpec::Named(outputs))
109        }
110    }
111}
112
113impl From<OutputName> for OutputSpec {
114    fn from(output_name: OutputName) -> Self {
115        Self::single(output_name)
116    }
117}
118
119#[cfg(test)]
120mod unittests {
121    use rstest::rstest;
122
123    use crate::derived_path::OutputSpec;
124
125    #[macro_export]
126    macro_rules! set {
127        () => { BTreeSet::new() };
128        ($($x:expr),+ $(,)?) => {{
129            let mut ret = std::collections::BTreeSet::new();
130            $(
131                ret.insert($x.parse().unwrap());
132            )+
133            ret
134        }};
135    }
136
137    #[rstest]
138    #[case("*", OutputSpec::All)]
139    #[case("out", OutputSpec::Named(set!("out")))]
140    #[case("bin,dev,out", OutputSpec::Named(set!("bin", "dev", "out")))]
141    fn parse(#[case] value: &str, #[case] expected: OutputSpec) {
142        let actual = value.parse::<OutputSpec>().unwrap();
143        assert_eq!(actual, expected);
144    }
145
146    #[rstest]
147    #[should_panic(expected = "Invalid name")]
148    #[case("bin{n")]
149    #[should_panic(expected = "Invalid name")]
150    #[case("out,bin{n")]
151    #[should_panic(expected = "Invalid name")]
152    #[case(" bin{n")]
153    #[should_panic(expected = "Invalid length")]
154    #[case("out,")]
155    #[should_panic(expected = "Invalid length")]
156    #[case("")]
157    #[should_panic(expected = "Invalid length")]
158    #[case(",out")]
159    #[should_panic(expected = "Invalid length")]
160    #[case::too_long(
161        "test-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
162    )]
163    fn parse_failure(#[case] value: &str) {
164        let actual = value.parse::<OutputSpec>().unwrap_err();
165        panic!("{actual}");
166    }
167
168    #[rstest]
169    #[case(OutputSpec::All, "*")]
170    #[case(OutputSpec::Named(set!("out")), "out")]
171    #[case(OutputSpec::Named(set!("bin", "dev", "out")), "bin,dev,out")]
172    fn display(#[case] value: OutputSpec, #[case] expected: &str) {
173        assert_eq!(value.to_string(), expected);
174    }
175}