nix_compat/nix_daemon/
mod.rs

1pub mod worker_protocol;
2
3use std::io::Result;
4
5use futures::future::try_join_all;
6use tokio::io::AsyncRead;
7use tracing::warn;
8use types::{AddToStoreNarRequest, QueryValidPaths, UnkeyedValidPathInfo};
9
10use crate::store_path::StorePath;
11
12pub mod framing;
13pub mod handler;
14pub mod types;
15
16#[cfg(test)]
17use mockall::automock;
18
19/// Represents all possible operations over the nix-daemon protocol.
20#[cfg_attr(test, automock)]
21pub trait NixDaemonIO: Sync {
22    fn is_valid_path(
23        &self,
24        path: &StorePath<String>,
25    ) -> impl std::future::Future<Output = Result<bool>> + Send {
26        async move { Ok(self.query_path_info(path).await?.is_some()) }
27    }
28
29    fn query_path_info(
30        &self,
31        path: &StorePath<String>,
32    ) -> impl std::future::Future<Output = Result<Option<UnkeyedValidPathInfo>>> + Send;
33
34    fn query_path_from_hash_part(
35        &self,
36        hash: &[u8],
37    ) -> impl std::future::Future<Output = Result<Option<UnkeyedValidPathInfo>>> + Send;
38
39    fn query_valid_paths(
40        &self,
41        request: &QueryValidPaths,
42    ) -> impl std::future::Future<Output = Result<Vec<UnkeyedValidPathInfo>>> + Send {
43        async move {
44            if request.substitute {
45                warn!("tvix does not yet support substitution, ignoring the 'substitute' flag...");
46            }
47            // Using try_join_all here to avoid returning partial results to the client.
48            // The only reason query_path_info can fail is due to transient IO errors,
49            // so we return such errors to the client as opposed to only returning paths
50            // that succeeded.
51            let results =
52                try_join_all(request.paths.iter().map(|path| self.query_path_info(path))).await?;
53
54            Ok(results.into_iter().flatten().collect())
55        }
56    }
57
58    fn query_valid_derivers(
59        &self,
60        path: &StorePath<String>,
61    ) -> impl std::future::Future<Output = Result<Vec<StorePath<String>>>> + Send {
62        async move {
63            let result = self.query_path_info(path).await?;
64            let result: Vec<_> = result.into_iter().filter_map(|info| info.deriver).collect();
65            Ok(result)
66        }
67    }
68
69    #[cfg_attr(test, mockall::concretize)]
70    fn add_to_store_nar<R>(
71        &self,
72        request: AddToStoreNarRequest,
73        reader: &mut R,
74    ) -> impl std::future::Future<Output = Result<()>> + Send
75    where
76        R: AsyncRead + Send + Unpin;
77}
78
79#[cfg(test)]
80mod tests {
81
82    use crate::{nix_daemon::types::QueryValidPaths, store_path::StorePath};
83
84    use super::{types::UnkeyedValidPathInfo, NixDaemonIO};
85
86    // Very simple mock
87    // Unable to use mockall as it does not support unboxed async traits.
88    pub struct MockNixDaemonIO {
89        query_path_info_result: Option<UnkeyedValidPathInfo>,
90    }
91
92    impl NixDaemonIO for MockNixDaemonIO {
93        async fn query_path_info(
94            &self,
95            _path: &StorePath<String>,
96        ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
97            Ok(self.query_path_info_result.clone())
98        }
99
100        async fn query_path_from_hash_part(
101            &self,
102            _hash: &[u8],
103        ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
104            Ok(None)
105        }
106
107        async fn add_to_store_nar<R>(
108            &self,
109            _request: super::types::AddToStoreNarRequest,
110            _reader: &mut R,
111        ) -> std::io::Result<()>
112        where
113            R: tokio::io::AsyncRead + Send + Unpin,
114        {
115            Ok(())
116        }
117    }
118
119    #[tokio::test]
120    async fn test_is_valid_path_returns_true() {
121        let path =
122            StorePath::<String>::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes())
123                .unwrap();
124        let io = MockNixDaemonIO {
125            query_path_info_result: Some(UnkeyedValidPathInfo::default()),
126        };
127
128        let result = io
129            .is_valid_path(&path)
130            .await
131            .expect("expected to get a non-empty response");
132        assert!(result, "expected to get true");
133    }
134
135    #[tokio::test]
136    async fn test_is_valid_path_returns_false() {
137        let path =
138            StorePath::<String>::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes())
139                .unwrap();
140        let io = MockNixDaemonIO {
141            query_path_info_result: None,
142        };
143
144        let result = io
145            .is_valid_path(&path)
146            .await
147            .expect("expected to get a non-empty response");
148        assert!(!result, "expected to get false");
149    }
150
151    #[tokio::test]
152    async fn test_query_valid_paths_returns_empty_response() {
153        let path =
154            StorePath::<String>::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes())
155                .unwrap();
156        let io = MockNixDaemonIO {
157            query_path_info_result: None,
158        };
159
160        let result = io
161            .query_valid_paths(&QueryValidPaths {
162                paths: vec![path],
163                substitute: false,
164            })
165            .await
166            .expect("expected to get a non-empty response");
167        assert_eq!(result, vec![], "expected to get empty response");
168    }
169
170    #[tokio::test]
171    async fn test_query_valid_paths_returns_non_empty_response() {
172        let path =
173            StorePath::<String>::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes())
174                .unwrap();
175        let io = MockNixDaemonIO {
176            query_path_info_result: Some(UnkeyedValidPathInfo::default()),
177        };
178
179        let result = io
180            .query_valid_paths(&QueryValidPaths {
181                paths: vec![path],
182                substitute: false,
183            })
184            .await
185            .expect("expected to get a non-empty response");
186        assert_eq!(
187            result,
188            vec![UnkeyedValidPathInfo::default()],
189            "expected to get non empty response"
190        );
191    }
192
193    #[tokio::test]
194    async fn test_query_valid_derivers_returns_empty_response() {
195        let path =
196            StorePath::<String>::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes())
197                .unwrap();
198        let io = MockNixDaemonIO {
199            query_path_info_result: None,
200        };
201
202        let result = io
203            .query_valid_derivers(&path)
204            .await
205            .expect("expected to get a non-empty response");
206        assert_eq!(result, vec![], "expected to get empty response");
207    }
208
209    #[tokio::test]
210    async fn test_query_valid_derivers_returns_non_empty_response() {
211        let path =
212            StorePath::<String>::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes())
213                .unwrap();
214        let deriver = StorePath::<String>::from_bytes(
215            "z6r3bn5l51679pwkvh9nalp6c317z34m-hello.drv".as_bytes(),
216        )
217        .unwrap();
218        let io = MockNixDaemonIO {
219            query_path_info_result: Some(UnkeyedValidPathInfo {
220                deriver: Some(deriver.clone()),
221                nar_hash: "".to_owned(),
222                references: vec![],
223                registration_time: 0,
224                nar_size: 1,
225                ultimate: true,
226                signatures: vec![],
227                ca: None,
228            }),
229        };
230
231        let result = io
232            .query_valid_derivers(&path)
233            .await
234            .expect("expected to get a non-empty response");
235        assert_eq!(result, vec![deriver], "expected to get non empty response");
236    }
237}