nix_compat/derivation/
output.rs

1use crate::nixhash::CAHash;
2use crate::{derivation::OutputError, store_path::StorePath};
3#[cfg(feature = "serde")]
4use serde::de::Unexpected;
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7#[cfg(feature = "serde")]
8use serde_json::Map;
9use std::borrow::Cow;
10
11/// References the derivation output.
12#[derive(Clone, Debug, Default, Eq, PartialEq)]
13#[cfg_attr(feature = "serde", derive(Serialize))]
14pub struct Output {
15    /// Store path of build result.
16    pub path: Option<StorePath<String>>,
17
18    #[cfg_attr(feature = "serde", serde(flatten))]
19    pub ca_hash: Option<CAHash>, // we can only represent a subset here.
20}
21
22#[cfg(feature = "serde")]
23impl<'de> Deserialize<'de> for Output {
24    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
25    where
26        D: serde::Deserializer<'de>,
27    {
28        let fields = Map::deserialize(deserializer)?;
29        let path: &str = fields
30            .get("path")
31            .ok_or(serde::de::Error::missing_field(
32                "`path` is missing but required for outputs",
33            ))?
34            .as_str()
35            .ok_or(serde::de::Error::invalid_type(
36                serde::de::Unexpected::Other("certainly not a string"),
37                &"a string",
38            ))?;
39
40        let path = StorePath::from_absolute_path(path.as_bytes())
41            .map_err(|_| serde::de::Error::invalid_value(Unexpected::Str(path), &"StorePath"))?;
42        Ok(Self {
43            path: Some(path),
44            ca_hash: CAHash::from_map::<D>(&fields)?,
45        })
46    }
47}
48
49impl Output {
50    pub fn is_fixed(&self) -> bool {
51        self.ca_hash.is_some()
52    }
53
54    /// The output path as a string -- use `""` to indicate an unset output path.
55    pub fn path_str(&self) -> Cow<str> {
56        match &self.path {
57            None => Cow::Borrowed(""),
58            Some(path) => Cow::Owned(path.to_absolute_path()),
59        }
60    }
61
62    pub fn validate(&self, validate_output_paths: bool) -> Result<(), OutputError> {
63        if let Some(fixed_output_hash) = &self.ca_hash {
64            match fixed_output_hash {
65                CAHash::Flat(_) | CAHash::Nar(_) => {
66                    // all hashes allowed for Flat, and Nar.
67                }
68                _ => return Err(OutputError::InvalidCAHash(fixed_output_hash.clone())),
69            }
70        }
71
72        if validate_output_paths && self.path.is_none() {
73            return Err(OutputError::MissingOutputPath);
74        }
75        Ok(())
76    }
77}
78
79/// This ensures that a potentially valid input addressed
80/// output is deserialized as a non-fixed output.
81#[cfg(feature = "serde")]
82#[test]
83fn deserialize_valid_input_addressed_output() {
84    let json_bytes = r#"
85    {
86      "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432"
87    }"#;
88    let output: Output = serde_json::from_str(json_bytes).expect("must parse");
89
90    assert!(!output.is_fixed());
91}
92
93/// This ensures that a potentially valid fixed output
94/// output deserializes fine as a fixed output.
95#[cfg(feature = "serde")]
96#[test]
97fn deserialize_valid_fixed_output() {
98    let json_bytes = r#"
99    {
100        "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
101        "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
102        "hashAlgo": "r:sha256"
103    }"#;
104    let output: Output = serde_json::from_str(json_bytes).expect("must parse");
105
106    assert!(output.is_fixed());
107}
108
109/// This ensures that parsing an input with the invalid hash encoding
110/// will result in a parsing failure.
111#[cfg(feature = "serde")]
112#[test]
113fn deserialize_with_error_invalid_hash_encoding_fixed_output() {
114    let json_bytes = r#"
115    {
116        "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
117        "hash": "IAMNOTVALIDNIXBASE32",
118        "hashAlgo": "r:sha256"
119    }"#;
120    let output: Result<Output, _> = serde_json::from_str(json_bytes);
121
122    assert!(output.is_err());
123}
124
125/// This ensures that parsing an input with the wrong hash algo
126/// will result in a parsing failure.
127#[cfg(feature = "serde")]
128#[test]
129fn deserialize_with_error_invalid_hash_algo_fixed_output() {
130    let json_bytes = r#"
131    {
132        "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
133        "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
134        "hashAlgo": "r:sha1024"
135    }"#;
136    let output: Result<Output, _> = serde_json::from_str(json_bytes);
137
138    assert!(output.is_err());
139}
140
141/// This ensures that parsing an input with the missing hash algo but present hash will result in a
142/// parsing failure.
143#[cfg(feature = "serde")]
144#[test]
145fn deserialize_with_error_missing_hash_algo_fixed_output() {
146    let json_bytes = r#"
147    {
148        "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
149        "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
150    }"#;
151    let output: Result<Output, _> = serde_json::from_str(json_bytes);
152
153    assert!(output.is_err());
154}
155
156/// This ensures that parsing an input with the missing hash but present hash algo will result in a
157/// parsing failure.
158#[cfg(feature = "serde")]
159#[test]
160fn deserialize_with_error_missing_hash_fixed_output() {
161    let json_bytes = r#"
162    {
163        "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
164        "hashAlgo": "r:sha1024"
165    }"#;
166    let output: Result<Output, _> = serde_json::from_str(json_bytes);
167
168    assert!(output.is_err());
169}
170
171#[cfg(feature = "serde")]
172#[test]
173fn serialize_deserialize() {
174    let json_bytes = r#"
175    {
176      "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432"
177    }"#;
178    let output: Output = serde_json::from_str(json_bytes).expect("must parse");
179
180    let s = serde_json::to_string(&output).expect("Serialize");
181    let output2: Output = serde_json::from_str(&s).expect("must parse again");
182
183    assert_eq!(output, output2);
184}
185
186#[cfg(feature = "serde")]
187#[test]
188fn serialize_deserialize_fixed() {
189    let json_bytes = r#"
190    {
191        "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
192        "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
193        "hashAlgo": "r:sha256"
194    }"#;
195    let output: Output = serde_json::from_str(json_bytes).expect("must parse");
196
197    let s = serde_json::to_string_pretty(&output).expect("Serialize");
198    let output2: Output = serde_json::from_str(&s).expect("must parse again");
199
200    assert_eq!(output, output2);
201}