1use std::{collections::BTreeMap, fmt::Write as _};
3
4use crate::{
5 derivation::{Output, OutputHash, OutputHashMode, OutputName, output::ParseOutputError},
6 store_path,
7};
8
9#[derive(Debug, PartialEq, thiserror::Error)]
11pub enum OutputsError {
12 #[error("no outputs defined")]
13 NoOutputs(),
14 #[error("invalid output name: {0}")]
15 InvalidOutputName(String),
16 #[error("duplicate output name: {0}")]
17 DuplicateOutputName(OutputName),
18 #[error("encountered fixed-output derivation, but more than 1 output in total")]
19 MoreThanOneOutputButFixed(),
20 #[error("invalid output name for fixed-output derivation: {0}")]
21 InvalidOutputNameForFixed(String),
22 #[error("unable to validate output {0}: {1}")]
23 InvalidOutput(String, #[source] ParseOutputError),
24 #[error("invalid calculated output derivation path name: {0}")]
25 InvalidOutputDerivationPath(String, #[source] store_path::ParseStorePathError),
26}
27
28pub struct Iter<'a>(IterI<'a>);
35
36enum IterI<'a> {
37 Single(std::iter::Once<(&'a OutputName, &'a Output)>),
38 Multiple(std::collections::btree_map::Iter<'a, OutputName, Output>),
39}
40
41impl<'a> Iterator for Iter<'a> {
42 type Item = (&'a OutputName, &'a Output);
43
44 fn next(&mut self) -> Option<Self::Item> {
45 match &mut self.0 {
46 IterI::Single(it) => it.next(),
47 IterI::Multiple(it) => it.next(),
48 }
49 }
50 fn size_hint(&self) -> (usize, Option<usize>) {
51 match &self.0 {
52 IterI::Single(it) => it.size_hint(),
53 IterI::Multiple(it) => it.size_hint(),
54 }
55 }
56}
57
58impl<'a> ExactSizeIterator for Iter<'a> {}
59
60#[derive(Clone, Debug, PartialEq, Eq)]
61enum OutputsInner {
62 Single(Output),
63 Multiple(BTreeMap<OutputName, Output>),
64}
65
66#[derive(Clone, Debug, PartialEq, Eq)]
90pub struct Outputs(OutputsInner);
91
92impl Outputs {
93 pub const fn from_fod_hash(output_hash: OutputHash) -> Self {
111 Outputs(OutputsInner::Single(Output {
112 path: None,
113 output_hash: Some(output_hash),
114 }))
115 }
116
117 pub const fn with_single_output() -> Self {
130 Outputs(OutputsInner::Single(Output {
131 path: None,
132 output_hash: None,
133 }))
134 }
135
136 pub fn try_from_iter<I>(it: I) -> Result<Self, OutputsError>
143 where
144 I: IntoIterator<Item = (OutputName, Output)>,
145 {
146 let mut it = it.into_iter();
147 let Some((output_name, output)) = it.next() else {
148 return Err(OutputsError::NoOutputs());
149 };
150 if let Some((second_name, second_output)) = it.next() {
151 if output.is_fixed() || second_output.is_fixed() {
152 return Err(OutputsError::MoreThanOneOutputButFixed());
153 }
154 let mut outputs = BTreeMap::new();
155 outputs.insert(output_name, output);
156 if outputs.insert(second_name.clone(), second_output).is_some() {
157 return Err(OutputsError::DuplicateOutputName(second_name));
158 }
159 for (output_name, output) in it {
160 if output.is_fixed() {
161 return Err(OutputsError::MoreThanOneOutputButFixed());
162 }
163 if outputs.insert(output_name.clone(), output).is_some() {
164 return Err(OutputsError::DuplicateOutputName(output_name));
165 }
166 }
167 Ok(Outputs(OutputsInner::Multiple(outputs)))
168 } else if output_name == OutputName::out() {
169 Ok(Outputs(OutputsInner::Single(output)))
170 } else {
171 Err(OutputsError::InvalidOutputName(output_name.to_string()))
172 }
173 }
174
175 #[must_use]
177 pub fn contains_key(&self, name: &OutputName) -> bool {
178 match &self.0 {
179 OutputsInner::Single(_) => *name == OutputName::out(),
180 OutputsInner::Multiple(outputs) => outputs.contains_key(name),
181 }
182 }
183
184 pub fn get(&self, name: &OutputName) -> Option<&Output> {
186 match &self.0 {
187 OutputsInner::Single(output) if *name == OutputName::out() => Some(output),
188 OutputsInner::Multiple(outputs) => outputs.get(name),
189 _ => None,
190 }
191 }
192
193 pub fn iter(&self) -> Iter<'_> {
195 match &self.0 {
196 OutputsInner::Single(output) => {
197 const OUT: &OutputName = &OutputName::out();
198 Iter(IterI::Single(std::iter::once((OUT, output))))
199 }
200 OutputsInner::Multiple(outputs) => Iter(IterI::Multiple(outputs.iter())),
201 }
202 }
203
204 pub fn keys(&self) -> impl Iterator<Item = &OutputName> {
222 self.iter().map(|(name, _)| name)
223 }
224
225 pub fn values(&self) -> impl Iterator<Item = &Output> {
237 self.iter().map(|(_, output)| output)
238 }
239
240 #[expect(clippy::len_without_is_empty)]
256 pub fn len(&self) -> usize {
257 match &self.0 {
258 OutputsInner::Single(_) => 1,
259 OutputsInner::Multiple(outputs) => outputs.len(),
260 }
261 }
262
263 #[must_use]
289 pub fn is_single(&self) -> bool {
290 self.len() == 1
291 }
292
293 #[must_use]
295 pub fn is_fixed(&self) -> bool {
296 matches!(&self.0, OutputsInner::Single(out) if out.is_fixed())
297 }
298
299 pub fn calculate_output_paths(
319 &mut self,
320 drv_name: &str,
321 hash_derivation_modulo: &[u8; 32],
322 ) -> Result<(), OutputsError> {
323 match &mut self.0 {
324 OutputsInner::Single(output) => {
325 assert!(output.path.is_none());
329
330 let name = drv_name.to_owned();
333
334 let store_path = if let Some(output_hash) = &output.output_hash {
337 store_path::build_ca_path(
338 &name,
339 output_hash.mode == OutputHashMode::Recursive,
340 &output_hash.hash,
341 [],
342 false,
343 )
344 } else {
345 store_path::build_output_path(&name, hash_derivation_modulo, &OutputName::out())
346 }
347 .map_err(|e| OutputsError::InvalidOutputDerivationPath(name.to_string(), e))?;
348
349 output.path = Some(store_path.to_owned());
350 }
351 OutputsInner::Multiple(outputs) => {
352 for (output_name, output) in outputs.iter_mut() {
353 assert!(!output.is_fixed(), "Snix bug: multiple FOD");
354
355 let name = {
358 let mut name = drv_name.to_owned();
359 if output_name != &OutputName::out() {
360 name.write_fmt(format_args!("-{output_name}")).unwrap();
361 }
362 name
363 };
364
365 let store_path =
367 store_path::build_output_path(&name, hash_derivation_modulo, output_name)
368 .map_err(|e| {
369 OutputsError::InvalidOutputDerivationPath(name.to_string(), e)
370 })?;
371
372 output.path = Some(store_path.to_owned());
373 }
374 }
375 }
376 Ok(())
377 }
378}
379
380impl<'a> IntoIterator for &'a Outputs {
381 type Item = (&'a OutputName, &'a Output);
382
383 type IntoIter = Iter<'a>;
384
385 fn into_iter(self) -> Self::IntoIter {
386 self.iter()
387 }
388}
389
390impl Default for Outputs {
391 fn default() -> Self {
392 Self(OutputsInner::Single(Default::default()))
393 }
394}
395
396impl TryFrom<BTreeMap<OutputName, Output>> for Outputs {
397 type Error = OutputsError;
398
399 fn try_from(outputs: BTreeMap<OutputName, Output>) -> Result<Self, Self::Error> {
400 Self::try_from_iter(outputs)
401 }
402}
403
404#[cfg(test)]
405impl Outputs {
406 pub fn trim_store_paths(&mut self) {
408 match &mut self.0 {
409 OutputsInner::Single(output) => {
410 output.path = None;
411 }
412 OutputsInner::Multiple(outputs) => {
413 for output in outputs.values_mut() {
414 output.path = None;
415 }
416 }
417 }
418 }
419}
420
421#[cfg(feature = "serde")]
422impl<'de> serde::Deserialize<'de> for Outputs {
423 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
424 where
425 D: serde::Deserializer<'de>,
426 {
427 struct OutputsVisitor;
428 impl<'de> serde::de::Visitor<'de> for OutputsVisitor {
429 type Value = Outputs;
430
431 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
432 f.write_str("derivation outputs")
433 }
434
435 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
436 where
437 A: serde::de::MapAccess<'de>,
438 {
439 use serde::de::{Error, Unexpected};
440 let Some((output_name, output)) = map.next_entry::<OutputName, Output>()? else {
441 return Err(A::Error::invalid_length(0, &"non-empty derivation outputs"));
442 };
443 if let Some((second_name, second_output)) =
444 map.next_entry::<OutputName, Output>()?
445 {
446 if output.is_fixed() || second_output.is_fixed() {
447 return Err(A::Error::invalid_value(
448 Unexpected::Other("FOD output"),
449 &"non-FOD outputs",
450 ));
451 }
452 let mut outputs = BTreeMap::new();
453 outputs.insert(output_name, output);
454 if outputs.insert(second_name, second_output).is_some() {
455 return Err(A::Error::custom(format_args!(
456 "duplicate derivation output"
457 )));
458 }
459 while let Some((output_name, output)) =
460 map.next_entry::<OutputName, Output>()?
461 {
462 if output.is_fixed() {
463 return Err(A::Error::invalid_value(
464 Unexpected::Other("FOD output"),
465 &"non-FOD outputs",
466 ));
467 }
468 if outputs.insert(output_name, output).is_some() {
469 return Err(A::Error::custom(format_args!(
470 "duplicate derivation output"
471 )));
472 }
473 }
474 Ok(Outputs(OutputsInner::Multiple(outputs)))
475 } else if output_name == OutputName::out() {
476 Ok(Outputs(OutputsInner::Single(output)))
477 } else {
478 Err(A::Error::invalid_value(
479 Unexpected::Other(output_name.as_str()),
480 &"single derivation output named 'out'",
481 ))
482 }
483 }
484 }
485
486 deserializer.deserialize_map(OutputsVisitor)
487 }
488}
489
490#[cfg(feature = "serde")]
491impl serde::Serialize for Outputs {
492 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
493 where
494 S: serde::Serializer,
495 {
496 use serde::ser::SerializeMap as _;
497 let mut map = serializer.serialize_map(Some(self.len()))?;
498 for (k, v) in self {
499 map.serialize_entry(k, v)?;
500 }
501 map.end()
502 }
503}
504
505#[cfg(test)]
506mod tests {
507 use std::{collections::BTreeMap, sync::LazyLock};
508
509 use rstest::rstest;
510
511 use crate::{
512 derivation::{Output, OutputHash, OutputHashMode, OutputName, Outputs},
513 nixhash::NixHash,
514 };
515
516 const DIGEST_SHA256: [u8; 32] =
517 hex_literal::hex!("a5ce9c155ed09397614646c9717fc7cd94b1023d7b76b618d409e4fefd6e9d39");
518 const OUTPUT_HASH: OutputHash = OutputHash {
519 mode: OutputHashMode::Flat,
520 hash: NixHash::Sha256(DIGEST_SHA256),
521 };
522 const FOD_OUTPUT: Output = Output {
523 output_hash: Some(OutputHash {
524 mode: OutputHashMode::Flat,
525 hash: NixHash::Sha256(DIGEST_SHA256),
526 }),
527 path: None,
528 };
529
530 const SINGLE_OUTPUTS: Outputs = Outputs::with_single_output();
531 const FOD_OUTPUTS: Outputs = Outputs::from_fod_hash(OutputHash {
532 mode: OutputHashMode::Flat,
533 hash: NixHash::Sha256(DIGEST_SHA256),
534 });
535 static TRY_SINGLE: LazyLock<Outputs> = LazyLock::new(|| {
536 Outputs::try_from_iter([(OutputName::out(), Default::default())]).expect("single output")
537 });
538 static TRY_FOD: LazyLock<Outputs> = LazyLock::new(|| -> Outputs {
539 Outputs::try_from_iter([(
540 OutputName::out(),
541 Output {
542 path: None,
543 output_hash: Some(OUTPUT_HASH.clone()),
544 },
545 )])
546 .expect("single fod")
547 });
548 static TRY_MULTIPLE: LazyLock<Outputs> = LazyLock::new(|| -> Outputs {
549 Outputs::try_from_iter([
550 (OutputName::from_static("dev").unwrap(), Default::default()),
551 (OutputName::from_static("bin").unwrap(), Default::default()),
552 ])
553 .expect("multiple")
554 });
555
556 #[rstest]
557 #[should_panic(expected = "no outputs defined")]
558 #[case::empty(&[])]
559 #[should_panic(expected = "invalid output name: bin")]
560 #[case::single(&[(OutputName::from_static("bin").unwrap(), Default::default())])]
561 #[should_panic(expected = "duplicate output name: bin")]
562 #[case::duplicate(&[
563 (OutputName::from_static("bin").unwrap(), Default::default()),
564 (OutputName::from_static("bin").unwrap(), Default::default()),
565 ])]
566 #[should_panic(
567 expected = "encountered fixed-output derivation, but more than 1 output in total"
568 )]
569 #[case::mixed(&[
570 (OutputName::from_static("bin").unwrap(), FOD_OUTPUT.clone()),
571 (OutputName::from_static("dev").unwrap(), Default::default()),
572 ])]
573 fn try_from_iter_failure(#[case] it: &[(OutputName, Output)]) {
574 panic!(
575 "{}",
576 Outputs::try_from_iter(it.iter().cloned()).expect_err("try_from_iter succeeded")
577 );
578 }
579
580 #[rstest]
581 #[should_panic(expected = "no outputs defined")]
582 #[case::empty(&[])]
583 #[should_panic(expected = "invalid output name: bin")]
584 #[case::single(&[(OutputName::from_static("bin").unwrap(), Default::default())])]
585 #[should_panic(
586 expected = "encountered fixed-output derivation, but more than 1 output in total"
587 )]
588 #[case::mixed(&[
589 (OutputName::from_static("bin").unwrap(), FOD_OUTPUT.clone()),
590 (OutputName::from_static("dev").unwrap(), Default::default()),
591 ])]
592 fn try_from_failure(#[case] it: &[(OutputName, Output)]) {
593 let b = BTreeMap::from_iter(it.iter().cloned());
594 panic!("{}", Outputs::try_from(b).expect_err("try_from succeeded"));
595 }
596
597 #[rstest]
598 #[case::single(&SINGLE_OUTPUTS)]
599 #[case::fod(&FOD_OUTPUTS)]
600 #[case::try_single(&TRY_SINGLE)]
601 #[case::try_fod(&TRY_FOD)]
602 #[case::default(&Default::default())]
603 fn is_single(#[case] value: &Outputs) {
604 assert!(value.is_single())
605 }
606
607 #[rstest]
608 #[case::multiple(&TRY_MULTIPLE)]
609 fn is_not_single(#[case] value: &Outputs) {
610 assert!(!value.is_single())
611 }
612
613 #[rstest]
614 #[case::fod(&FOD_OUTPUTS)]
615 #[case::try_fod(&TRY_FOD)]
616 fn is_fixed(#[case] value: &Outputs) {
617 assert!(value.is_fixed())
618 }
619
620 #[rstest]
621 #[case::single(&SINGLE_OUTPUTS)]
622 #[case::try_single(&TRY_SINGLE)]
623 #[case::multiple(&TRY_MULTIPLE)]
624 #[case::default(&Default::default())]
625 fn is_not_fixed(#[case] value: &Outputs) {
626 assert!(!value.is_fixed())
627 }
628
629 #[rstest]
630 #[case::single(&SINGLE_OUTPUTS, 1)]
631 #[case::fod(&FOD_OUTPUTS, 1)]
632 #[case::try_fod(&TRY_FOD, 1)]
633 #[case::try_single(&TRY_SINGLE, 1)]
634 #[case::multiple(&TRY_MULTIPLE, 2)]
635 #[case::default(&Default::default(), 1)]
636 fn len(#[case] value: &Outputs, #[case] expected: usize) {
637 assert_eq!(value.len(), expected)
638 }
639
640 #[rstest]
641 #[case::single(&SINGLE_OUTPUTS, OutputName::out(), Some(&Default::default()))]
642 #[case::fod(&FOD_OUTPUTS, OutputName::out(), Some(&FOD_OUTPUT))]
643 #[case::try_fod(&TRY_FOD, OutputName::out(), Some(&FOD_OUTPUT))]
644 #[case::try_single(&TRY_SINGLE, OutputName::out(), Some(&Default::default()))]
645 #[case::multiple_out(&TRY_MULTIPLE, OutputName::out(), None)]
646 #[case::multiple_bin(&TRY_MULTIPLE, OutputName::from_static("bin").unwrap(), Some(&Default::default()))]
647 #[case::default(&Default::default(), OutputName::out(), Some(&Default::default()))]
648 fn get(#[case] value: &Outputs, #[case] name: OutputName, #[case] expected: Option<&Output>) {
649 assert_eq!(value.get(&name), expected)
650 }
651
652 #[rstest]
653 #[case::single(&SINGLE_OUTPUTS, OutputName::out())]
654 #[case::fod(&FOD_OUTPUTS, OutputName::out())]
655 #[case::try_fod(&TRY_FOD, OutputName::out())]
656 #[case::try_single(&TRY_SINGLE, OutputName::out())]
657 #[case::multiple_bin(&TRY_MULTIPLE, OutputName::from_static("bin").unwrap())]
658 #[case::default(&Default::default(), OutputName::out())]
659 fn contains_key(#[case] value: &Outputs, #[case] name: OutputName) {
660 assert!(value.contains_key(&name))
661 }
662
663 #[rstest]
664 #[case::multiple_out(&TRY_MULTIPLE, OutputName::out())]
665 fn does_not_contain_key(#[case] value: &Outputs, #[case] name: OutputName) {
666 assert!(!value.contains_key(&name))
667 }
668
669 #[rstest]
670 #[case::single(&SINGLE_OUTPUTS, vec![(OutputName::out(), Default::default())])]
671 #[case::fod(&FOD_OUTPUTS, vec![(OutputName::out(), FOD_OUTPUT.clone())])]
672 #[case::try_fod(&TRY_FOD, vec![(OutputName::out(), FOD_OUTPUT.clone())])]
673 #[case::try_single(&TRY_SINGLE, vec![(OutputName::out(), Default::default())])]
674 #[case::multiple(&TRY_MULTIPLE, vec![
675 (OutputName::from_static("bin").unwrap(), Default::default()),
676 (OutputName::from_static("dev").unwrap(), Default::default()),
677 ])]
678 #[case::default(&Default::default(), vec![(OutputName::out(), Default::default())])]
679 fn iter(#[case] value: &Outputs, #[case] expected: Vec<(OutputName, Output)>) {
680 let actual: Vec<_> = value
681 .iter()
682 .map(|(name, output)| (name.clone(), output.clone()))
683 .collect();
684 assert_eq!(actual, expected);
685 }
686
687 #[rstest]
688 #[case::single(&SINGLE_OUTPUTS, vec![OutputName::out()])]
689 #[case::fod(&FOD_OUTPUTS, vec![OutputName::out()])]
690 #[case::try_fod(&TRY_FOD, vec![OutputName::out()])]
691 #[case::try_single(&TRY_SINGLE, vec![OutputName::out()])]
692 #[case::multiple(&TRY_MULTIPLE, vec![
693 OutputName::from_static("bin").unwrap(),
694 OutputName::from_static("dev").unwrap(),
695 ])]
696 #[case::default(&Default::default(), vec![OutputName::out()])]
697 fn keys(#[case] value: &Outputs, #[case] expected: Vec<OutputName>) {
698 let actual: Vec<_> = value.keys().cloned().collect();
699 assert_eq!(actual, expected);
700 }
701
702 #[rstest]
703 #[case::single(&SINGLE_OUTPUTS, vec![Default::default()])]
704 #[case::fod(&FOD_OUTPUTS, vec![FOD_OUTPUT.clone()])]
705 #[case::try_fod(&TRY_FOD, vec![FOD_OUTPUT.clone()])]
706 #[case::try_single(&TRY_SINGLE, vec![Default::default()])]
707 #[case::multiple(&TRY_MULTIPLE, vec![Default::default(), Default::default()])]
708 #[case::default(&Default::default(), vec![Default::default()])]
709 fn values(#[case] value: &Outputs, #[case] expected: Vec<Output>) {
710 let actual: Vec<_> = value.values().cloned().collect();
711 assert_eq!(actual, expected);
712 }
713}