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