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