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};
13use std::rc::Rc;
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(Rc::new(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 = match select_string(co, &attrs, "url").await? {
60 Ok(s) => s.ok_or_else(|| ErrorKind::AttributeNotFound { name: "url".into() })?,
61 Err(cek) => return Ok(Err(cek)),
62 };
63 let name = match select_string(co, &attrs, "name").await? {
64 Ok(s) => s,
65 Err(cek) => return Ok(Err(cek)),
66 };
67 let sha256_str = match select_string(co, &attrs, "sha256").await? {
68 Ok(s) => s,
69 Err(cek) => return Ok(Err(cek)),
70 };
71
72 Ok(Ok(NixFetchArgs {
73 url: Url::parse(&url_str).map_err(|e| ErrorKind::SnixError(Rc::new(e)))?,
74 name,
75 sha256: sha256_str
77 .map(
78 |sha256_str| match NixHash::from_str(&sha256_str, Some(HashAlgo::Sha256)) {
79 Ok(NixHash::Sha256(digest)) => Ok(digest),
80 _ => Err(ErrorKind::InvalidHash(sha256_str)),
81 },
82 )
83 .transpose()?,
84 }))
85}
86
87#[allow(unused_variables)] #[builtins(state = "Rc<SnixStoreIO>")]
89pub(crate) mod fetcher_builtins {
90 use bstr::ByteSlice;
91 use nix_compat::{flakeref, nixhash::NixHash};
92 use std::collections::BTreeMap;
93
94 use super::*;
95
96 fn fetch_lazy(state: Rc<SnixStoreIO>, name: String, fetch: Fetch) -> Result<Value, ErrorKind> {
102 match fetch
103 .store_path(&name)
104 .map_err(|e| ErrorKind::SnixError(Rc::new(e)))?
105 {
106 Some(store_path) => {
107 let sp = state
109 .known_paths
110 .borrow_mut()
111 .add_fetch(fetch, &name)
112 .expect("Snix bug: should only fail if the store path cannot be calculated");
113
114 debug_assert_eq!(
115 sp, store_path,
116 "calculated store path by KnownPaths should match"
117 );
118
119 Ok(Value::Path(Box::new(store_path.to_absolute_path().into())))
121 }
122 None => {
123 let (store_path, _path_info) = state
125 .tokio_handle
126 .block_on(async { state.fetcher.ingest_and_persist(&name, fetch).await })
127 .map_err(|e| ErrorKind::SnixError(Rc::new(e)))?;
128
129 Ok(Value::Path(Box::new(store_path.to_absolute_path().into())))
130 }
131 }
132 }
133
134 #[builtin("fetchurl")]
135 async fn builtin_fetchurl(
136 state: Rc<SnixStoreIO>,
137 co: GenCo,
138 args: Value,
139 ) -> Result<Value, ErrorKind> {
140 let args = match extract_fetch_args(&co, args).await? {
141 Ok(args) => args,
142 Err(cek) => return Ok(Value::from(cek)),
143 };
144
145 let name = args
147 .name
148 .unwrap_or_else(|| url_basename(&args.url).to_owned());
149
150 fetch_lazy(
151 state,
152 name,
153 Fetch::URL {
154 url: args.url,
155 exp_hash: args.sha256.map(NixHash::Sha256),
156 },
157 )
158 }
159
160 #[builtin("fetchTarball")]
161 async fn builtin_fetch_tarball(
162 state: Rc<SnixStoreIO>,
163 co: GenCo,
164 args: Value,
165 ) -> Result<Value, ErrorKind> {
166 let args = match extract_fetch_args(&co, args).await? {
167 Ok(args) => args,
168 Err(cek) => return Ok(Value::from(cek)),
169 };
170
171 const DEFAULT_NAME_FETCH_TARBALL: &str = "source";
173 let name = args
174 .name
175 .unwrap_or_else(|| DEFAULT_NAME_FETCH_TARBALL.to_owned());
176
177 fetch_lazy(
178 state,
179 name,
180 Fetch::Tarball {
181 url: args.url,
182 exp_nar_sha256: args.sha256,
183 },
184 )
185 }
186
187 #[builtin("fetchGit")]
188 async fn builtin_fetch_git(
189 state: Rc<SnixStoreIO>,
190 co: GenCo,
191 args: Value,
192 ) -> Result<Value, ErrorKind> {
193 Err(ErrorKind::NotImplemented("fetchGit"))
194 }
195
196 #[builtin("parseFlakeRef")]
198 async fn builtin_parse_flake_ref(
199 state: Rc<SnixStoreIO>,
200 co: GenCo,
201 value: Value,
202 ) -> Result<Value, ErrorKind> {
203 let flake_ref = value.to_str()?;
204 let flake_ref_str = flake_ref.to_str()?;
205
206 let fetch_args = flake_ref_str
207 .parse()
208 .map_err(|err| ErrorKind::SnixError(Rc::new(err)))?;
209
210 let mut attrs = BTreeMap::new();
212
213 match fetch_args {
215 flakeref::FlakeRef::Git { url, .. } => {
216 attrs.insert("type".into(), Value::from("git"));
217 attrs.insert("url".into(), Value::from(url.to_string()));
218 }
219 flakeref::FlakeRef::GitHub {
220 owner, repo, r#ref, ..
221 } => {
222 attrs.insert("type".into(), Value::from("github"));
223 attrs.insert("owner".into(), Value::from(owner));
224 attrs.insert("repo".into(), Value::from(repo));
225 if let Some(ref_name) = r#ref {
226 attrs.insert("ref".into(), Value::from(ref_name));
227 }
228 }
229 flakeref::FlakeRef::GitLab { owner, repo, .. } => {
230 attrs.insert("type".into(), Value::from("gitlab"));
231 attrs.insert("owner".into(), Value::from(owner));
232 attrs.insert("repo".into(), Value::from(repo));
233 }
234 flakeref::FlakeRef::File { url, .. } => {
235 attrs.insert("type".into(), Value::from("file"));
236 attrs.insert("url".into(), Value::from(url.to_string()));
237 }
238 flakeref::FlakeRef::Tarball { url, .. } => {
239 attrs.insert("type".into(), Value::from("tarball"));
240 attrs.insert("url".into(), Value::from(url.to_string()));
241 }
242 flakeref::FlakeRef::Path { path, .. } => {
243 attrs.insert("type".into(), Value::from("path"));
244 attrs.insert(
245 "path".into(),
246 Value::from(path.to_string_lossy().into_owned()),
247 );
248 }
249 _ => {
250 attrs.insert("type".into(), Value::from("indirect"));
252 attrs.insert("url".into(), Value::from(flake_ref_str));
253 }
254 }
255
256 Ok(Value::Attrs(Box::new(attrs.into())))
257 }
258}