1use nom::bytes::streaming::tag;
7use nom::character::streaming::char as nomchar;
8use nom::combinator::{all_consuming, consumed, map_res};
9use nom::multi::{separated_list0, separated_list1};
10use nom::sequence::{delimited, preceded, separated_pair, terminated};
11use nom::Parser;
12use std::collections::{btree_map, BTreeMap, BTreeSet};
13use thiserror;
14
15use crate::derivation::parse_error::{into_nomerror, ErrorKind, NomError, NomResult};
16use crate::derivation::{write, CAHash, Derivation, Output};
17use crate::store_path::{self, StorePath};
18use crate::{aterm, nixhash, nixhash::NixHash};
19
20#[derive(Debug, thiserror::Error)]
21pub enum Error<I> {
22 #[error("parsing error: {0}")]
23 Parser(#[from] NomError<I>),
24 #[error("premature EOF")]
25 Incomplete,
26 #[error("validation error: {0}")]
27 Validation(super::DerivationError),
28}
29
30impl From<Error<&[u8]>> for Error<Vec<u8>> {
32 fn from(value: Error<&[u8]>) -> Self {
33 match value {
34 Error::Parser(nom_error) => Error::Parser(NomError {
35 input: nom_error.input.to_vec(),
36 code: nom_error.code,
37 }),
38 Error::Incomplete => Error::Incomplete,
39 Error::Validation(e) => Error::Validation(e),
40 }
41 }
42}
43
44pub(crate) fn parse(i: &[u8]) -> Result<Derivation, Error<&[u8]>> {
45 match all_consuming(parse_derivation).parse(i) {
46 Ok((rest, derivation)) => {
47 debug_assert!(rest.is_empty());
49
50 derivation.validate(true).map_err(Error::Validation)?;
52
53 Ok(derivation)
54 }
55 Err(nom::Err::Incomplete(_)) => Err(Error::Incomplete),
56 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(e.into()),
57 }
58}
59
60#[allow(dead_code)]
65pub fn parse_streaming(i: &[u8]) -> (Result<Derivation, Error<&[u8]>>, &[u8]) {
66 match consumed(parse_derivation).parse(i) {
67 Ok((_, (rest, derivation))) => {
68 if let Err(e) = derivation.validate(true).map_err(Error::Validation) {
70 return (Err(e), i);
71 }
72
73 (Ok(derivation), rest)
74 }
75 Err(nom::Err::Incomplete(_)) => (Err(Error::Incomplete), i),
76 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => (Err(e.into()), i),
77 }
78}
79
80fn from_algo_and_mode_and_digest<B: AsRef<[u8]>>(
84 algo_and_mode: &str,
85 digest: B,
86) -> Result<CAHash, nixhash::Error> {
87 Ok(match algo_and_mode.strip_prefix("r:") {
88 Some(algo) => nixhash::CAHash::Nar(NixHash::from_algo_and_digest(
89 algo.try_into()?,
90 digest.as_ref(),
91 )?),
92 None => nixhash::CAHash::Flat(NixHash::from_algo_and_digest(
93 algo_and_mode.try_into()?,
94 digest.as_ref(),
95 )?),
96 })
97}
98
99fn parse_output(i: &[u8]) -> NomResult<&[u8], (String, Output)> {
103 delimited(
104 nomchar('('),
105 map_res(
106 |i| {
107 (
108 terminated(aterm::parse_string_field, nomchar(',')),
109 terminated(aterm::parse_string_field, nomchar(',')),
110 terminated(aterm::parse_string_field, nomchar(',')),
111 aterm::parse_bytes_field,
112 )
113 .parse(i)
114 .map_err(into_nomerror)
115 },
116 |(output_name, output_path, algo_and_mode, encoded_digest)| {
117 let ca_hash_res = {
119 if algo_and_mode.is_empty() && encoded_digest.is_empty() {
120 None
121 } else {
122 match data_encoding::HEXLOWER.decode(&encoded_digest) {
123 Ok(digest) => {
124 Some(from_algo_and_mode_and_digest(&algo_and_mode, digest))
125 }
126 Err(e) => Some(Err(nixhash::Error::InvalidBase64Encoding(e))),
127 }
128 }
129 }
130 .transpose();
131
132 match ca_hash_res {
133 Ok(hash_with_mode) => Ok((
134 output_name,
135 Output {
136 path: if output_path.is_empty() {
139 None
140 } else {
141 Some(string_to_store_path(i, &output_path)?)
142 },
143 ca_hash: hash_with_mode,
144 },
145 )),
146 Err(e) => Err(nom::Err::Failure(NomError {
147 input: i,
148 code: ErrorKind::NixHashError(e),
149 })),
150 }
151 },
152 ),
153 nomchar(')'),
154 )
155 .parse(i)
156}
157
158fn parse_outputs(i: &[u8]) -> NomResult<&[u8], BTreeMap<String, Output>> {
164 let res = delimited(
165 nomchar('['),
166 separated_list1(tag(","), parse_output),
167 nomchar(']'),
168 )
169 .parse(i);
170
171 match res {
172 Ok((rst, outputs_lst)) => {
173 let mut outputs = BTreeMap::default();
174 for (output_name, output) in outputs_lst.into_iter() {
175 if outputs.contains_key(&output_name) {
176 return Err(nom::Err::Failure(NomError {
177 input: i,
178 code: ErrorKind::DuplicateMapKey(output_name.to_string()),
179 }));
180 }
181 outputs.insert(output_name, output);
182 }
183 Ok((rst, outputs))
184 }
185 Err(e) => Err(e),
187 }
188}
189
190fn parse_input_derivations(
191 i: &[u8],
192) -> NomResult<&[u8], BTreeMap<StorePath<String>, BTreeSet<String>>> {
193 let (i, input_derivations_list) = parse_kv(aterm::parse_string_list)(i)?;
194
195 let mut input_derivations: BTreeMap<StorePath<String>, BTreeSet<_>> = BTreeMap::new();
197
198 for (input_derivation, output_names) in input_derivations_list {
199 let mut new_output_names = BTreeSet::new();
200 for output_name in output_names.into_iter() {
201 if new_output_names.contains(&output_name) {
202 return Err(nom::Err::Failure(NomError {
203 input: i,
204 code: ErrorKind::DuplicateInputDerivationOutputName(
205 input_derivation.to_string(),
206 output_name.to_string(),
207 ),
208 }));
209 }
210 new_output_names.insert(output_name);
211 }
212
213 let input_derivation = string_to_store_path(i, input_derivation.as_str())?;
214
215 input_derivations.insert(input_derivation, new_output_names);
216 }
217
218 Ok((i, input_derivations))
219}
220
221fn parse_input_sources(i: &[u8]) -> NomResult<&[u8], BTreeSet<StorePath<String>>> {
222 let (i, input_sources_lst) = aterm::parse_string_list(i).map_err(into_nomerror)?;
223
224 let mut input_sources: BTreeSet<_> = BTreeSet::new();
225 for input_source in input_sources_lst.into_iter() {
226 let input_source = string_to_store_path(i, input_source.as_str())?;
227 if input_sources.contains(&input_source) {
228 return Err(nom::Err::Failure(NomError {
229 input: i,
230 code: ErrorKind::DuplicateInputSource(input_source.to_owned()),
231 }));
232 } else {
233 input_sources.insert(input_source);
234 }
235 }
236
237 Ok((i, input_sources))
238}
239
240fn string_to_store_path<'a, 'i, S>(
241 i: &'i [u8],
242 path_str: &'a str,
243) -> Result<StorePath<S>, nom::Err<NomError<&'i [u8]>>>
244where
245 S: std::clone::Clone + AsRef<str> + std::convert::From<&'a str>,
246{
247 let path =
248 StorePath::from_absolute_path(path_str.as_bytes()).map_err(|e: store_path::Error| {
249 nom::Err::Failure(NomError {
250 input: i,
251 code: e.into(),
252 })
253 })?;
254
255 #[cfg(debug_assertions)]
256 assert_eq!(path_str, path.to_absolute_path());
257
258 Ok(path)
259}
260
261pub fn parse_derivation(i: &[u8]) -> NomResult<&[u8], Derivation> {
262 use nom::Parser;
263 preceded(
264 tag(write::DERIVATION_PREFIX),
265 delimited(
266 nomchar('('),
268 (
271 terminated(parse_outputs, nomchar(',')),
273 terminated(parse_input_derivations, nomchar(',')),
275 terminated(parse_input_sources, nomchar(',')),
277 |i| {
279 terminated(aterm::parse_string_field, nomchar(','))
280 .parse(i)
281 .map_err(into_nomerror)
282 },
283 |i| {
285 terminated(aterm::parse_string_field, nomchar(','))
286 .parse(i)
287 .map_err(into_nomerror)
288 },
289 |i| {
291 terminated(aterm::parse_string_list, nomchar(','))
292 .parse(i)
293 .map_err(into_nomerror)
294 },
295 parse_kv(aterm::parse_bytes_field),
297 ),
298 nomchar(')'),
299 )
300 .map(
301 |(
302 outputs,
303 input_derivations,
304 input_sources,
305 system,
306 builder,
307 arguments,
308 environment,
309 )| {
310 Derivation {
311 arguments,
312 builder,
313 environment,
314 input_derivations,
315 input_sources,
316 outputs,
317 system,
318 }
319 },
320 ),
321 )
322 .parse(i)
323}
324
325pub(crate) fn parse_kv<'a, V, VF>(
331 vf: VF,
332) -> impl FnMut(&'a [u8]) -> NomResult<&'a [u8], BTreeMap<String, V>> + 'static
333where
334 VF: FnMut(&'a [u8]) -> nom::IResult<&'a [u8], V, nom::error::Error<&'a [u8]>> + Clone + 'static,
335{
336 move |i|
337 delimited(
339 nomchar('['),
340 |ii| {
341 let res = separated_list0(
342 nomchar(','),
343 delimited(
345 nomchar('('),
346 separated_pair(
347 aterm::parse_string_field,
348 nomchar(','),
349 vf.clone(),
350 ),
351 nomchar(')'),
352 ),
353 ).parse(ii).map_err(into_nomerror);
354
355 match res {
356 Ok((rest, pairs)) => {
357 let mut kvs: BTreeMap<String, V> = BTreeMap::new();
358 for (k, v) in pairs.into_iter() {
359 match kvs.entry(k) {
362 btree_map::Entry::Vacant(e) => { e.insert(v); },
363 btree_map::Entry::Occupied(e) => {
364 return Err(nom::Err::Failure(NomError {
365 input: i,
366 code: ErrorKind::DuplicateMapKey(e.key().clone()),
367 }));
368 }
369 }
370 }
371 Ok((rest, kvs))
372 }
373 Err(e) => Err(e),
374 }
375 },
376 nomchar(']'),
377 ).parse(i)
378}
379
380#[cfg(test)]
381mod tests {
382 use crate::store_path::StorePathRef;
383 use std::collections::{BTreeMap, BTreeSet};
384 use std::sync::LazyLock;
385
386 use crate::{
387 derivation::{
388 parse_error::ErrorKind, parser::from_algo_and_mode_and_digest, CAHash, NixHash, Output,
389 },
390 store_path::StorePath,
391 };
392 use bstr::{BString, ByteSlice};
393 use hex_literal::hex;
394 use rstest::rstest;
395
396 const DIGEST_SHA256: [u8; 32] =
397 hex!("a5ce9c155ed09397614646c9717fc7cd94b1023d7b76b618d409e4fefd6e9d39");
398
399 static NIXHASH_SHA256: NixHash = NixHash::Sha256(DIGEST_SHA256);
400 static EXP_MULTI_OUTPUTS: LazyLock<BTreeMap<String, Output>> = LazyLock::new(|| {
401 let mut b = BTreeMap::new();
402 b.insert(
403 "lib".to_string(),
404 Output {
405 path: Some(
406 StorePath::from_bytes(b"2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib")
407 .unwrap(),
408 ),
409 ca_hash: None,
410 },
411 );
412 b.insert(
413 "out".to_string(),
414 Output {
415 path: Some(
416 StorePath::from_bytes(
417 b"55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out".as_bytes(),
418 )
419 .unwrap(),
420 ),
421 ca_hash: None,
422 },
423 );
424 b
425 });
426
427 static EXP_AB_MAP: LazyLock<BTreeMap<String, BString>> = LazyLock::new(|| {
428 let mut b = BTreeMap::new();
429 b.insert("a".to_string(), b"1".into());
430 b.insert("b".to_string(), b"2".into());
431 b
432 });
433
434 static EXP_INPUT_DERIVATIONS_SIMPLE: LazyLock<BTreeMap<StorePath<String>, BTreeSet<String>>> =
435 LazyLock::new(|| {
436 let mut b = BTreeMap::new();
437 b.insert(
438 StorePath::from_bytes(b"8bjm87p310sb7r2r0sg4xrynlvg86j8k-hello-2.12.1.tar.gz.drv")
439 .unwrap(),
440 {
441 let mut output_names = BTreeSet::new();
442 output_names.insert("out".to_string());
443 output_names
444 },
445 );
446 b.insert(
447 StorePath::from_bytes(b"p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv")
448 .unwrap(),
449 {
450 let mut output_names = BTreeSet::new();
451 output_names.insert("out".to_string());
452 output_names.insert("lib".to_string());
453 output_names
454 },
455 );
456 b
457 });
458
459 static EXP_INPUT_DERIVATIONS_SIMPLE_ATERM: LazyLock<String> = LazyLock::new(|| {
460 format!(
461 "[(\"{0}\",[\"out\"]),(\"{1}\",[\"out\",\"lib\"])]",
462 "/nix/store/8bjm87p310sb7r2r0sg4xrynlvg86j8k-hello-2.12.1.tar.gz.drv",
463 "/nix/store/p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv"
464 )
465 });
466
467 static EXP_INPUT_SOURCES_SIMPLE: LazyLock<BTreeSet<String>> = LazyLock::new(|| {
468 let mut b = BTreeSet::new();
469 b.insert("/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out".to_string());
470 b.insert("/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib".to_string());
471 b
472 });
473
474 #[rstest]
476 #[case::empty(b"[]", &BTreeMap::new(), b"")]
477 #[case::simple(b"[(\"a\",\"1\"),(\"b\",\"2\")]", &EXP_AB_MAP, b"")]
478 fn parse_kv(
479 #[case] input: &'static [u8],
480 #[case] expected: &BTreeMap<String, BString>,
481 #[case] exp_rest: &[u8],
482 ) {
483 let (rest, parsed) =
484 super::parse_kv(crate::aterm::parse_bytes_field)(input).expect("must parse");
485 assert_eq!(exp_rest, rest, "expected remainder");
486 assert_eq!(*expected, parsed);
487 }
488
489 #[rstest]
490 #[case::incomplete_empty(b"[")]
491 #[case::incomplete_simple(b"[(\"a\",\"1\")")]
492 #[case::incomplete_complicated_escape(b"[(\"a")]
493 #[case::incomplete_complicated_sep(b"[(\"a\",")]
494 #[case::incomplete_complicated_multi_escape(b"[(\"a\",\"")]
495 #[case::incomplete_complicated_multi_outer_sep(b"[(\"a\",\"b\"),")]
496 fn parse_kv_incomplete(#[case] input: &'static [u8]) {
497 assert!(matches!(
498 super::parse_kv(crate::aterm::parse_bytes_field)(input),
499 Err(nom::Err::Incomplete(_))
500 ));
501 }
502
503 #[test]
505 fn parse_kv_fail_dup_keys() {
506 let input: &'static [u8] = b"[(\"a\",\"1\"),(\"a\",\"2\")]";
507 let e = super::parse_kv(crate::aterm::parse_bytes_field)(input).expect_err("must fail");
508
509 match e {
510 nom::Err::Failure(e) => {
511 assert_eq!(ErrorKind::DuplicateMapKey("a".to_string()), e.code);
512 }
513 _ => panic!("unexpected error"),
514 }
515 }
516
517 #[rstest]
519 #[case::empty(b"[]", &BTreeMap::new())]
520 #[case::simple(EXP_INPUT_DERIVATIONS_SIMPLE_ATERM.as_bytes(), &EXP_INPUT_DERIVATIONS_SIMPLE)]
521 fn parse_input_derivations(
522 #[case] input: &'static [u8],
523 #[case] expected: &BTreeMap<StorePath<String>, BTreeSet<String>>,
524 ) {
525 let (rest, parsed) = super::parse_input_derivations(input).expect("must parse");
526
527 assert_eq!(expected, &parsed, "parsed mismatch");
528 assert!(rest.is_empty(), "rest must be empty");
529 }
530
531 #[test]
533 fn parse_input_derivations_fail_dup_output_names() {
534 let input_str = format!(
535 "[(\"{0}\",[\"out\"]),(\"{1}\",[\"out\",\"out\"])]",
536 "/nix/store/8bjm87p310sb7r2r0sg4xrynlvg86j8k-hello-2.12.1.tar.gz.drv",
537 "/nix/store/p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv"
538 );
539 let e = super::parse_input_derivations(input_str.as_bytes()).expect_err("must fail");
540
541 match e {
542 nom::Err::Failure(e) => {
543 assert_eq!(
544 ErrorKind::DuplicateInputDerivationOutputName(
545 "/nix/store/p3jc8aw45dza6h52v81j7lk69khckmcj-bash-5.2-p15.drv".to_string(),
546 "out".to_string()
547 ),
548 e.code
549 );
550 }
551 _ => panic!("unexpected error"),
552 }
553 }
554
555 #[rstest]
557 #[case::empty(b"[]", &BTreeSet::new())]
558 #[case::simple(b"[\"/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out\",\"/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib\"]", &EXP_INPUT_SOURCES_SIMPLE)]
559 fn parse_input_sources(#[case] input: &'static [u8], #[case] expected: &BTreeSet<String>) {
560 let (rest, parsed) = super::parse_input_sources(input).expect("must parse");
561
562 assert_eq!(
563 expected,
564 &parsed
565 .iter()
566 .map(StorePath::to_absolute_path)
567 .collect::<BTreeSet<_>>(),
568 "parsed mismatch"
569 );
570 assert!(rest.is_empty(), "rest must be empty");
571 }
572
573 #[test]
575 fn parse_input_sources_fail_dup_keys() {
576 let input: &'static [u8] = b"[\"/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-foo\",\"/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-foo\"]";
577 let e = super::parse_input_sources(input).expect_err("must fail");
578
579 match e {
580 nom::Err::Failure(e) => {
581 assert_eq!(
582 ErrorKind::DuplicateInputSource(
583 StorePathRef::from_absolute_path(
584 "/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-foo".as_bytes()
585 )
586 .unwrap()
587 .to_owned()
588 ),
589 e.code
590 );
591 }
592 _ => panic!("unexpected error"),
593 }
594 }
595
596 #[rstest]
597 #[case::simple(
598 br#"("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo","","")"#,
599 ("out".to_string(), Output {
600 path: Some(
601 StorePathRef::from_absolute_path("/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo".as_bytes()).unwrap().to_owned()),
602 ca_hash: None
603 })
604 )]
605 #[case::fod(
606 br#"("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar","r:sha256","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")"#,
607 ("out".to_string(), Output {
608 path: Some(
609 StorePathRef::from_absolute_path(
610 "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar".as_bytes()).unwrap().to_owned()),
611 ca_hash: Some(from_algo_and_mode_and_digest("r:sha256",
612 data_encoding::HEXLOWER.decode(b"08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba").unwrap() ).unwrap()),
613 })
614 )]
615 fn parse_output(#[case] input: &[u8], #[case] expected: (String, Output)) {
616 let (rest, parsed) = super::parse_output(input).expect("must parse");
617 assert!(rest.is_empty());
618 assert_eq!(expected, parsed);
619 }
620
621 #[rstest]
622 #[case::multi_out(
623 br#"[("lib","/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib","",""),("out","/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out","","")]"#,
624 &EXP_MULTI_OUTPUTS
625 )]
626 fn parse_outputs(#[case] input: &[u8], #[case] expected: &BTreeMap<String, Output>) {
627 let (rest, parsed) = super::parse_outputs(input).expect("must parse");
628 assert!(rest.is_empty());
629 assert_eq!(*expected, parsed);
630 }
631
632 #[rstest]
633 #[case::sha256_flat("sha256", &DIGEST_SHA256, CAHash::Flat(NIXHASH_SHA256.clone()))]
634 #[case::sha256_recursive("r:sha256", &DIGEST_SHA256, CAHash::Nar(NIXHASH_SHA256.clone()))]
635 fn test_from_algo_and_mode_and_digest(
636 #[case] algo_and_mode: &str,
637 #[case] digest: &[u8],
638 #[case] expected: CAHash,
639 ) {
640 assert_eq!(
641 expected,
642 from_algo_and_mode_and_digest(algo_and_mode, digest).unwrap()
643 );
644 }
645
646 #[test]
647 fn from_algo_and_mode_and_digest_failure() {
648 assert!(from_algo_and_mode_and_digest("r:sha256", []).is_err());
649 assert!(from_algo_and_mode_and_digest("ha256", DIGEST_SHA256).is_err());
650 }
651}