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