1pub mod worker_protocol;
2
3use std::{collections::BTreeMap, io::Result};
4
5use tokio::io::{AsyncBufRead, AsyncRead};
6use tracing::warn;
7use types::{QueryMissingResult, QueryValidPaths, UnkeyedValidPathInfo, ValidPathInfo};
8
9use crate::{
10 derivation::OutputName,
11 derived_path::DerivedPath,
12 nix_daemon::types::{BuildMode, KeyedBuildResult},
13 store_path::StorePath,
14};
15
16pub mod framing;
17pub mod handler;
18pub mod types;
19
20#[cfg_attr(test, mockall::automock)]
22pub trait NixDaemonIO: Sync {
23 fn is_valid_path(
24 &self,
25 path: &StorePath,
26 ) -> impl std::future::Future<Output = Result<bool>> + Send {
27 async move { Ok(self.query_path_info(path).await?.is_some()) }
28 }
29
30 fn ensure_path(
31 &self,
32 path: &StorePath,
33 ) -> impl std::future::Future<Output = Result<()>> + Send {
34 async move {
35 if self.is_valid_path(path).await? {
36 Ok(())
37 } else {
38 Err(std::io::Error::other(format!("unknown path {}", path)))
39 }
40 }
41 }
42
43 fn query_path_info(
44 &self,
45 path: &StorePath,
46 ) -> impl std::future::Future<Output = Result<Option<UnkeyedValidPathInfo>>> + Send;
47
48 fn query_path_from_hash_part(
49 &self,
50 hash: &[u8],
51 ) -> impl std::future::Future<Output = Result<Option<UnkeyedValidPathInfo>>> + Send;
52
53 fn query_valid_paths(
54 &self,
55 request: &QueryValidPaths,
56 ) -> impl std::future::Future<Output = Result<Vec<StorePath>>> + Send {
57 async move {
58 if request.substitute {
59 warn!("snix does not yet support substitution, ignoring the 'substitute' flag...");
60 }
61
62 let mut results: Vec<StorePath> = Vec::with_capacity(request.paths.len());
63
64 for path in request.paths.iter() {
65 if self.is_valid_path(path).await? {
66 results.push(path.clone());
67 }
68 }
69
70 Ok(results)
71 }
72 }
73
74 fn query_valid_derivers(
75 &self,
76 path: &StorePath,
77 ) -> impl std::future::Future<Output = Result<Vec<StorePath>>> + Send {
78 async move {
79 let result = self.query_path_info(path).await?;
80 let result: Vec<_> = result.into_iter().filter_map(|info| info.deriver).collect();
81 Ok(result)
82 }
83 }
84
85 fn query_missing(
86 &self,
87 derived_paths: Vec<DerivedPath>,
88 ) -> impl std::future::Future<Output = Result<QueryMissingResult>> + Send;
89
90 fn query_derivation_output_map(
91 &self,
92 drv_path: &StorePath,
93 ) -> impl std::future::Future<Output = Result<BTreeMap<OutputName, Option<StorePath>>>> + Send;
94
95 #[cfg_attr(test, mockall::concretize)]
96 fn add_to_store_nar<R>(
97 &self,
98 info: ValidPathInfo,
99 reader: &mut R,
100 repair: bool,
101 dont_check_sigs: bool,
102 ) -> impl std::future::Future<Output = Result<()>> + Send
103 where
104 R: AsyncRead + Send + Unpin;
105
106 fn nar_from_path(
107 &self,
108 path: &StorePath,
109 ) -> impl std::future::Future<Output = Result<Box<dyn AsyncBufRead + Unpin + Send>>> + Send;
110
111 fn build_paths(
112 &self,
113 derived_paths: Vec<DerivedPath>,
114 mode: BuildMode,
115 ) -> impl std::future::Future<Output = Result<()>> + Send;
116
117 fn build_paths_with_results(
118 &self,
119 derived_paths: Vec<DerivedPath>,
120 mode: BuildMode,
121 ) -> impl std::future::Future<Output = Result<Vec<KeyedBuildResult>>> + Send;
122}
123
124#[cfg(test)]
125mod tests {
126
127 use std::collections::BTreeMap;
128
129 use crate::{
130 derivation::OutputName,
131 derived_path::DerivedPath,
132 nix_daemon::types::{NarHash, QueryValidPaths},
133 store_path::StorePath,
134 };
135
136 use super::{NixDaemonIO, types::UnkeyedValidPathInfo};
137
138 pub struct MockNixDaemonIO {
141 query_path_info_result: Option<UnkeyedValidPathInfo>,
142 }
143
144 impl NixDaemonIO for MockNixDaemonIO {
145 async fn query_path_info(
146 &self,
147 _path: &StorePath,
148 ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
149 Ok(self.query_path_info_result.clone())
150 }
151
152 async fn query_path_from_hash_part(
153 &self,
154 _hash: &[u8],
155 ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
156 Ok(None)
157 }
158
159 async fn add_to_store_nar<R>(
160 &self,
161 _info: super::types::ValidPathInfo,
162 _reader: &mut R,
163 _repair: bool,
164 _dont_check_sigs: bool,
165 ) -> std::io::Result<()>
166 where
167 R: tokio::io::AsyncRead + Send + Unpin,
168 {
169 Ok(())
170 }
171
172 async fn nar_from_path(
173 &self,
174 _path: &StorePath,
175 ) -> std::io::Result<Box<dyn tokio::io::AsyncBufRead + Unpin + Send>> {
176 Err(std::io::Error::other(
177 "Operation NarFromPath is not implemented",
178 ))
179 }
180
181 async fn build_paths(
182 &self,
183 _derived_paths: Vec<DerivedPath>,
184 _mode: super::types::BuildMode,
185 ) -> std::io::Result<()> {
186 Ok(())
187 }
188
189 async fn build_paths_with_results(
190 &self,
191 _derived_paths: Vec<DerivedPath>,
192 _mode: super::types::BuildMode,
193 ) -> std::io::Result<Vec<super::types::KeyedBuildResult>> {
194 Err(std::io::Error::other(
195 "Operation BuildPathsWithResults is not implemented",
196 ))
197 }
198
199 async fn query_missing(
200 &self,
201 _derived_paths: Vec<DerivedPath>,
202 ) -> std::io::Result<super::types::QueryMissingResult> {
203 Err(std::io::Error::other(
204 "Operation QueryMissing is not implemented",
205 ))
206 }
207
208 async fn query_derivation_output_map(
209 &self,
210 _drv_path: &StorePath,
211 ) -> std::io::Result<BTreeMap<OutputName, Option<StorePath>>> {
212 Err(std::io::Error::other(
213 "Operation QueryDerivationOutputMap is not implemented",
214 ))
215 }
216 }
217
218 #[tokio::test]
219 async fn test_is_valid_path_returns_true() {
220 let path =
221 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
222 let io = MockNixDaemonIO {
223 query_path_info_result: Some(UnkeyedValidPathInfo {
224 deriver: Some("00000000000000000000000000000000-_.drv".parse().unwrap()),
225 nar_hash: NarHash::from_digest([0u8; 32]),
226 references: Vec::new(),
227 registration_time: 0,
228 nar_size: 0,
229 ultimate: true,
230 signatures: Vec::new(),
231 ca: None,
232 }),
233 };
234
235 let result = io
236 .is_valid_path(&path)
237 .await
238 .expect("expected to get a non-empty response");
239 assert!(result, "expected to get true");
240 }
241
242 #[tokio::test]
243 async fn test_is_valid_path_returns_false() {
244 let path =
245 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
246 let io = MockNixDaemonIO {
247 query_path_info_result: None,
248 };
249
250 let result = io
251 .is_valid_path(&path)
252 .await
253 .expect("expected to get a non-empty response");
254 assert!(!result, "expected to get false");
255 }
256
257 #[tokio::test]
258 async fn test_query_valid_paths_returns_empty_response() {
259 let path =
260 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
261 let io = MockNixDaemonIO {
262 query_path_info_result: None,
263 };
264
265 let result = io
266 .query_valid_paths(&QueryValidPaths {
267 paths: vec![path],
268 substitute: false,
269 })
270 .await
271 .expect("expected to get a non-empty response");
272 assert_eq!(result, vec![], "expected to get empty response");
273 }
274
275 #[tokio::test]
276 async fn test_query_valid_paths_returns_non_empty_response() {
277 let path =
278 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
279 let io = MockNixDaemonIO {
280 query_path_info_result: Some(UnkeyedValidPathInfo {
281 deriver: Some("00000000000000000000000000000000-_.drv".parse().unwrap()),
282 nar_hash: NarHash::from_digest([0u8; 32]),
283 references: Vec::new(),
284 registration_time: 0,
285 nar_size: 0,
286 ultimate: true,
287 signatures: Vec::new(),
288 ca: None,
289 }),
290 };
291
292 let result = io
293 .query_valid_paths(&QueryValidPaths {
294 paths: vec![path.clone()],
295 substitute: false,
296 })
297 .await
298 .expect("expected to get a non-empty response");
299 assert_eq!(result, vec![path], "expected to get non empty response");
300 }
301
302 #[tokio::test]
303 async fn test_query_valid_derivers_returns_empty_response() {
304 let path =
305 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
306 let io = MockNixDaemonIO {
307 query_path_info_result: None,
308 };
309
310 let result = io
311 .query_valid_derivers(&path)
312 .await
313 .expect("expected to get a non-empty response");
314 assert_eq!(result, vec![], "expected to get empty response");
315 }
316
317 #[tokio::test]
318 async fn test_query_valid_derivers_returns_non_empty_response() {
319 let path =
320 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
321 let deriver =
322 StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello.drv".as_bytes()).unwrap();
323 let io = MockNixDaemonIO {
324 query_path_info_result: Some(UnkeyedValidPathInfo {
325 deriver: Some(deriver.clone()),
326 nar_hash: NarHash::from_digest([0u8; 32]),
327 references: vec![],
328 registration_time: 0,
329 nar_size: 1,
330 ultimate: true,
331 signatures: vec![],
332 ca: None,
333 }),
334 };
335
336 let result = io
337 .query_valid_derivers(&path)
338 .await
339 .expect("expected to get a non-empty response");
340 assert_eq!(result, vec![deriver], "expected to get non empty response");
341 }
342}