1use super::utils::select_string;
4use crate::{
5 fetchers::{Fetch, url_basename},
6 snix_store_io::SnixStoreIO,
7};
8use nix_compat::nixhash::{HashAlgo, NixHash};
9use snix_eval::builtin_macros::builtins;
10use snix_eval::generators::Gen;
11use snix_eval::generators::GenCo;
12use snix_eval::{CatchableErrorKind, ErrorKind, Value, try_cek};
13use std::{rc::Rc, sync::Arc};
14use url::Url;
15
16struct NixFetchArgs {
19 url: Url,
20 name: Option<String>,
21 sha256: Option<[u8; 32]>,
22}
23
24async fn extract_fetch_args(
27 co: &GenCo,
28 args: Value,
29) -> Result<Result<NixFetchArgs, CatchableErrorKind>, ErrorKind> {
30 if let Ok(url_str) = args.to_str() {
31 let url_str =
33 String::from_utf8(url_str.as_bytes().to_vec()).map_err(|_| ErrorKind::Utf8)?;
34
35 let url = Url::parse(&url_str).map_err(|e| ErrorKind::SnixError(Arc::from(e)))?;
37
38 return Ok(Ok(NixFetchArgs {
39 url,
40 name: None,
41 sha256: None,
42 }));
43 }
44
45 let attrs = args.to_attrs().map_err(|_| ErrorKind::TypeError {
46 expected: "attribute set or contextless string",
47 actual: args.type_of(),
48 })?;
49
50 const VALID_KEYS: [&[u8]; 3] = [b"url", b"name", b"sha256"];
53 if let Some(first_invalid_key) = attrs.keys().find(|k| !&VALID_KEYS.contains(&k.as_bytes())) {
54 return Err(ErrorKind::UnexpectedArgumentBuiltin(
55 first_invalid_key.clone(),
56 ));
57 }
58
59 let url_str = try_cek!(select_string(co, &attrs, "url").await?)
60 .ok_or_else(|| ErrorKind::AttributeNotFound { name: "url".into() })?;
61 let name = try_cek!(select_string(co, &attrs, "name").await?);
62 let sha256_str = try_cek!(select_string(co, &attrs, "sha256").await?);
63
64 Ok(Ok(NixFetchArgs {
65 url: Url::parse(&url_str).map_err(|e| ErrorKind::SnixError(Arc::from(e)))?,
66 name,
67 sha256: sha256_str
69 .map(
70 |sha256_str| match NixHash::from_str(&sha256_str, Some(HashAlgo::Sha256)) {
71 Ok(NixHash::Sha256(digest)) => Ok(digest),
72 _ => Err(ErrorKind::InvalidHash(sha256_str)),
73 },
74 )
75 .transpose()?,
76 }))
77}
78
79#[allow(unused_variables)] #[builtins(state = "Rc<SnixStoreIO>")]
81pub(crate) mod fetcher_builtins {
82 use bstr::ByteSlice;
83 use nix_compat::{flakeref, nixhash::NixHash};
84 use snix_eval::{NixContext, NixString, try_cek_to_value};
85 use std::collections::BTreeMap;
86
87 use super::*;
88
89 fn fetch_lazy(state: Rc<SnixStoreIO>, name: String, fetch: Fetch) -> Result<Value, ErrorKind> {
97 let store_path = match fetch
98 .store_path(&name)
99 .map_err(|e| ErrorKind::SnixError(Arc::from(e)))?
100 {
101 Some(store_path) => {
102 let sp = state
104 .known_paths
105 .borrow_mut()
106 .add_fetch(fetch, &name)
107 .expect("Snix bug: should only fail if the store path cannot be calculated");
108
109 debug_assert_eq!(
110 sp, store_path,
111 "calculated store path by KnownPaths should match"
112 );
113 sp
114 }
115 None => {
116 let (store_path, _path_info) = state
118 .tokio_handle
119 .block_on(async { state.fetcher.ingest_and_persist(&name, fetch).await })
120 .map_err(|e| ErrorKind::SnixError(Arc::from(e)))?;
121
122 store_path
123 }
124 };
125
126 let s = store_path.to_absolute_path();
127
128 let context = NixContext::new().append(snix_eval::NixContextElement::Plain(s.clone()));
130 Ok(Value::String(NixString::new_context_from(context, s)))
131 }
132
133 #[builtin("fetchurl")]
134 async fn builtin_fetchurl(
135 state: Rc<SnixStoreIO>,
136 co: GenCo,
137 args: Value,
138 ) -> Result<Value, ErrorKind> {
139 let args = try_cek_to_value!(extract_fetch_args(&co, args).await?);
140
141 let name = args
143 .name
144 .unwrap_or_else(|| url_basename(&args.url).to_owned());
145
146 fetch_lazy(
147 state,
148 name,
149 Fetch::URL {
150 url: args.url,
151 exp_hash: args.sha256.map(NixHash::Sha256),
152 },
153 )
154 }
155
156 #[builtin("fetchTarball")]
157 async fn builtin_fetch_tarball(
158 state: Rc<SnixStoreIO>,
159 co: GenCo,
160 args: Value,
161 ) -> Result<Value, ErrorKind> {
162 let args = try_cek_to_value!(extract_fetch_args(&co, args).await?);
163
164 const DEFAULT_NAME_FETCH_TARBALL: &str = "source";
166 let name = args
167 .name
168 .unwrap_or_else(|| DEFAULT_NAME_FETCH_TARBALL.to_owned());
169
170 fetch_lazy(
171 state,
172 name,
173 Fetch::Tarball {
174 url: args.url,
175 exp_nar_sha256: args.sha256,
176 },
177 )
178 }
179
180 #[builtin("fetchGit")]
181 async fn builtin_fetch_git(
182 state: Rc<SnixStoreIO>,
183 co: GenCo,
184 args: Value,
185 ) -> Result<Value, ErrorKind> {
186 Err(ErrorKind::NotImplemented("fetchGit"))
187 }
188
189 #[builtin("parseFlakeRef")]
191 async fn builtin_parse_flake_ref(
192 state: Rc<SnixStoreIO>,
193 co: GenCo,
194 value: Value,
195 ) -> Result<Value, ErrorKind> {
196 let flake_ref = value.to_str()?;
197 let flake_ref_str = flake_ref.to_str()?;
198
199 let fetch_args = flake_ref_str
200 .parse()
201 .map_err(|err| ErrorKind::SnixError(Arc::new(err)))?;
202
203 let mut attrs = BTreeMap::new();
205
206 match fetch_args {
208 flakeref::FlakeRef::Git { url, .. } => {
209 attrs.insert("type".into(), Value::from("git"));
210 attrs.insert("url".into(), Value::from(url.to_string()));
211 }
212 flakeref::FlakeRef::GitHub {
213 owner, repo, r#ref, ..
214 } => {
215 attrs.insert("type".into(), Value::from("github"));
216 attrs.insert("owner".into(), Value::from(owner));
217 attrs.insert("repo".into(), Value::from(repo));
218 if let Some(ref_name) = r#ref {
219 attrs.insert("ref".into(), Value::from(ref_name));
220 }
221 }
222 flakeref::FlakeRef::GitLab { owner, repo, .. } => {
223 attrs.insert("type".into(), Value::from("gitlab"));
224 attrs.insert("owner".into(), Value::from(owner));
225 attrs.insert("repo".into(), Value::from(repo));
226 }
227 flakeref::FlakeRef::File { url, .. } => {
228 attrs.insert("type".into(), Value::from("file"));
229 attrs.insert("url".into(), Value::from(url.to_string()));
230 }
231 flakeref::FlakeRef::Tarball { url, .. } => {
232 attrs.insert("type".into(), Value::from("tarball"));
233 attrs.insert("url".into(), Value::from(url.to_string()));
234 }
235 flakeref::FlakeRef::Path { path, .. } => {
236 attrs.insert("type".into(), Value::from("path"));
237 attrs.insert(
238 "path".into(),
239 Value::from(path.to_string_lossy().into_owned()),
240 );
241 }
242 _ => {
243 attrs.insert("type".into(), Value::from("indirect"));
245 attrs.insert("url".into(), Value::from(flake_ref_str));
246 }
247 }
248
249 Ok(Value::Attrs(attrs.into()))
250 }
251}