Skip to main content

nix_compat/
realisation.rs

1use std::collections::{BTreeMap, HashSet};
2use std::fmt;
3use std::str::FromStr;
4
5use thiserror::Error;
6
7use crate::derivation::{OutputName, ParseOutputNameError};
8use crate::narinfo::Signature;
9use crate::nixhash::{self, NixHash};
10use crate::store_path::StorePath;
11
12#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
13#[cfg_attr(
14    feature = "serde",
15    derive(serde_with::SerializeDisplay, serde_with::DeserializeFromStr)
16)]
17pub struct DrvOutput {
18    pub drv_hash: NixHash,
19    pub output_name: OutputName,
20}
21
22impl fmt::Display for DrvOutput {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        write!(
25            f,
26            "{}!{}",
27            self.drv_hash.as_nix_lowerhex_string_fmt(),
28            self.output_name
29        )
30    }
31}
32
33#[derive(Debug, PartialEq, Error)]
34pub enum ParseDrvOutputError {
35    #[error("invalid hash in DrvOutput")]
36    Hash(
37        #[from]
38        #[source]
39        nixhash::Error,
40    ),
41    #[error("invalid output name in DrvOutput")]
42    OutputName(
43        #[from]
44        #[source]
45        ParseOutputNameError,
46    ),
47    #[error("missing '!' in DrvOutput '{0}'")]
48    InvalidDerivationOutputId(String),
49}
50
51impl FromStr for DrvOutput {
52    type Err = ParseDrvOutputError;
53
54    fn from_str(s: &str) -> Result<Self, Self::Err> {
55        if let Some((drv_hash_s, output_name_s)) = s.split_once('!') {
56            let drv_hash = NixHash::from_str(drv_hash_s, None)?;
57            let output_name = output_name_s.parse()?;
58            Ok(DrvOutput {
59                drv_hash,
60                output_name,
61            })
62        } else {
63            Err(ParseDrvOutputError::InvalidDerivationOutputId(s.into()))
64        }
65    }
66}
67
68#[cfg_attr(
69    feature = "serde",
70    cfg_eval::cfg_eval,
71    serde_with::serde_as,
72    derive(serde::Deserialize, serde::Serialize),
73    serde(rename_all = "camelCase")
74)]
75#[derive(Debug, PartialEq, Eq, Clone)]
76pub struct Realisation {
77    pub id: DrvOutput,
78    #[cfg_attr(feature = "serde", serde_as(as = "serde_with::DisplayFromStr"))]
79    pub out_path: StorePath,
80    pub signatures: HashSet<Signature<String>>,
81    #[cfg_attr(
82        feature = "serde",
83        serde_as(as = "BTreeMap<_, serde_with::DisplayFromStr>")
84    )]
85    pub dependent_realisations: BTreeMap<DrvOutput, StorePath>,
86}
87
88pub type DrvOutputs = BTreeMap<DrvOutput, Realisation>;
89
90#[cfg(test)]
91mod unittests {
92    use rstest::rstest;
93
94    use crate::btree_map;
95    use crate::derivation::OutputName;
96    use crate::hash_set;
97    use crate::nixhash::NixHash;
98
99    use super::{DrvOutput, Realisation};
100
101    #[rstest]
102    #[case("sha256:248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1!out", DrvOutput {
103        drv_hash: NixHash::from_str("sha256:248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", None).unwrap(),
104        output_name: OutputName::default(),
105    })]
106    #[case("sha256:1h86vccx9vgcyrkj3zv4b7j3r8rrc0z0r4r6q3jvhf06s9hnm394!out_put", DrvOutput {
107        drv_hash: NixHash::from_str("sha256:1h86vccx9vgcyrkj3zv4b7j3r8rrc0z0r4r6q3jvhf06s9hnm394", None).unwrap(),
108        output_name: "out_put".parse().unwrap(),
109    })]
110    fn parse_drv_output(#[case] value: &str, #[case] expected: DrvOutput) {
111        let actual: DrvOutput = value.parse().unwrap();
112        assert_eq!(actual, expected);
113    }
114
115    #[rstest]
116    #[should_panic = "missing '!' in DrvOutput 'sha256:1h86vccx9vgcyrkj3zv4b7j3r8rrc0z0r4r6q3jvhf06s9hnm394'"]
117    #[case("sha256:1h86vccx9vgcyrkj3zv4b7j3r8rrc0z0r4r6q3jvhf06s9hnm394")]
118    #[should_panic = "invalid hash in DrvOutput"]
119    #[case("sha256:1h86vccx9vgcyrkj3zv4b7j3r8rrc0z0r4r6q3jvhf06s9hnm39!out")]
120    #[should_panic = "invalid output name in DrvOutput"]
121    #[case("sha256:1h86vccx9vgcyrkj3zv4b7j3r8rrc0z0r4r6q3jvhf06s9hnm394!out{put")]
122    fn parse_drv_output_failure(#[case] value: &str) {
123        let actual = value.parse::<DrvOutput>().unwrap_err();
124        panic!("{actual}");
125    }
126
127    #[rstest]
128    #[case(DrvOutput {
129        drv_hash: NixHash::from_str("sha256:248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", None).unwrap(),
130        output_name: OutputName::default(),
131    }, "sha256:248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1!out")]
132    #[case(DrvOutput {
133        drv_hash: NixHash::from_str("sha256:1h86vccx9vgcyrkj3zv4b7j3r8rrc0z0r4r6q3jvhf06s9hnm394", None).unwrap(),
134        output_name: OutputName::default(),
135    }, "sha256:248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1!out")]
136    #[case(DrvOutput {
137        drv_hash: NixHash::from_str("sha1:y5q4drg5558zk8aamsx6xliv3i23x644", None).unwrap(),
138        output_name: "out_put".parse().unwrap(),
139    }, "sha1:84983e441c3bd26ebaae4aa1f95129e5e54670f1!out_put")]
140    fn display_drv_output(#[case] value: DrvOutput, #[case] expected: &str) {
141        assert_eq!(value.to_string(), expected);
142    }
143
144    #[cfg(feature = "serde")]
145    #[rstest]
146    #[case(
147        "{\"dependentRealisations\":{\"sha256:ba7816bf8f01cfea414140de5dae2223b00361a496177a9cf410ff61f20015ad!dev\":\"7h7qgvs4kgzsn8a6rb273saxyqh4jxlz-konsole-18.12.3-dev\",\"sha256:ba7816bf8f01cfea414140de5dae2223b00361a696177a9cf410ff61f20015ad!bin\":\"7h7qgvs4kgzsn8a6rb273saxyqh4jxlz-konsole-18.12.3-bin\"},\"id\":\"sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad!out\",\"outPath\":\"7h7qgvs4kgzsn8a6rb273saxyqh4jxlz-konsole-18.12.3\",\"signatures\":[\"cache.nixos.org-1:0CpHca+06TwFp9VkMyz5OaphT3E8mnS+1SWymYlvFaghKSYPCMQ66TS1XPAr1+y9rfQZPLaHrBjjnIRktE/nAA==\"]}",
148        Realisation {
149            id: "sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad!out".parse().unwrap(),
150            out_path: "7h7qgvs4kgzsn8a6rb273saxyqh4jxlz-konsole-18.12.3".parse().unwrap(),
151            signatures: hash_set!["cache.nixos.org-1:0CpHca+06TwFp9VkMyz5OaphT3E8mnS+1SWymYlvFaghKSYPCMQ66TS1XPAr1+y9rfQZPLaHrBjjnIRktE/nAA=="],
152            dependent_realisations: btree_map![
153                "sha256:ba7816bf8f01cfea414140de5dae2223b00361a496177a9cf410ff61f20015ad!dev" => "7h7qgvs4kgzsn8a6rb273saxyqh4jxlz-konsole-18.12.3-dev",
154                "sha256:ba7816bf8f01cfea414140de5dae2223b00361a696177a9cf410ff61f20015ad!bin" => "7h7qgvs4kgzsn8a6rb273saxyqh4jxlz-konsole-18.12.3-bin",
155
156            ],
157        }
158    )]
159    fn parse_realisation(#[case] value: &str, #[case] expected: Realisation) {
160        let actual: Realisation = serde_json::from_str(value).unwrap();
161        pretty_assertions::assert_eq!(actual, expected);
162    }
163
164    #[cfg(feature = "serde")]
165    #[rstest]
166    #[case(
167        Realisation {
168            id: "sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad!out".parse().unwrap(),
169            out_path: "7h7qgvs4kgzsn8a6rb273saxyqh4jxlz-konsole-18.12.3".parse().unwrap(),
170            signatures: hash_set!["cache.nixos.org-1:0CpHca+06TwFp9VkMyz5OaphT3E8mnS+1SWymYlvFaghKSYPCMQ66TS1XPAr1+y9rfQZPLaHrBjjnIRktE/nAA=="],
171            dependent_realisations: btree_map![
172                "sha256:ba7816bf8f01cfea414140de5dae2223b00361a496177a9cf410ff61f20015ad!dev" => "7h7qgvs4kgzsn8a6rb273saxyqh4jxlz-konsole-18.12.3-dev",
173                "sha256:ba7816bf8f01cfea414140de5dae2223b00361a696177a9cf410ff61f20015ad!bin" => "7h7qgvs4kgzsn8a6rb273saxyqh4jxlz-konsole-18.12.3-bin",
174
175            ],
176        },
177        "{\"id\":\"sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad!out\",\"outPath\":\"7h7qgvs4kgzsn8a6rb273saxyqh4jxlz-konsole-18.12.3\",\"signatures\":[\"cache.nixos.org-1:0CpHca+06TwFp9VkMyz5OaphT3E8mnS+1SWymYlvFaghKSYPCMQ66TS1XPAr1+y9rfQZPLaHrBjjnIRktE/nAA==\"],\"dependentRealisations\":{\"sha256:ba7816bf8f01cfea414140de5dae2223b00361a496177a9cf410ff61f20015ad!dev\":\"7h7qgvs4kgzsn8a6rb273saxyqh4jxlz-konsole-18.12.3-dev\",\"sha256:ba7816bf8f01cfea414140de5dae2223b00361a696177a9cf410ff61f20015ad!bin\":\"7h7qgvs4kgzsn8a6rb273saxyqh4jxlz-konsole-18.12.3-bin\"}}",
178    )]
179    fn write_realisation(#[case] value: Realisation, #[case] expected: &str) {
180        let actual = serde_json::to_string(&value).unwrap();
181        pretty_assertions::assert_eq!(actual, expected);
182    }
183}