nix_compat/nix_daemon/
mod.rs

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