1use std::collections::{BTreeMap, BTreeSet};
3use std::fmt;
4
5use crate::{
6 derivation::{OutputHash, OutputName},
7 store_path::{self, StorePath},
8};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct Outputs(OutputsInner);
27
28impl Outputs {
29 pub fn fixed_output(output_hash: OutputHash, store_path: StorePath) -> Self {
33 Outputs(OutputsInner::Fixed {
34 output_hash,
35 store_path,
36 })
37 }
38
39 pub fn input_addressed_from_iter<I>(it: I) -> Result<Self, OutputsError>
46 where
47 I: IntoIterator<Item = (OutputName, StorePath)>,
48 {
49 Self::try_from_iter(
50 it.into_iter()
51 .map(|(output_name, store_path)| (output_name, store_path, None)),
52 )
53 }
54
55 pub fn try_from_iter<I>(it: I) -> Result<Self, OutputsError>
62 where
63 I: IntoIterator<Item = (OutputName, StorePath, Option<OutputHash>)>,
64 {
65 let mut builder = UnverifiedOutputsBuilder::new();
66 for (output_name, store_path, output_hash) in it {
67 builder.try_insert(output_name, store_path, output_hash)?;
68 }
69 builder.try_build()
70 }
71
72 pub fn as_fixed_output_hash(&self) -> Option<&OutputHash> {
74 if let OutputsInner::Fixed { output_hash, .. } = &self.0 {
75 Some(output_hash)
76 } else {
77 None
78 }
79 }
80
81 pub fn as_fixed_output(&self) -> Option<(&OutputHash, &StorePath)> {
83 if let OutputsInner::Fixed {
84 output_hash,
85 store_path,
86 } = &self.0
87 {
88 Some((output_hash, store_path))
89 } else {
90 None
91 }
92 }
93
94 pub fn into_builder(self) -> OutputsBuilder {
96 match self.0 {
97 OutputsInner::Fixed { output_hash, .. } => OutputsBuilder::Fixed(output_hash),
98 OutputsInner::InputAddressed(outputs) => {
99 let output_names = outputs.into_keys().collect();
100 OutputsBuilder::InputAddressed(output_names)
101 }
102 }
103 }
104
105 pub fn into_unverified_builder(self) -> UnverifiedOutputsBuilder {
107 UnverifiedOutputsBuilder(self.0)
108 }
109
110 #[must_use]
112 pub fn contains_key(&self, name: &OutputName) -> bool {
113 self.0.contains_key(name)
114 }
115
116 pub fn get(&self, name: &OutputName) -> Option<&StorePath> {
118 self.0.get(name)
119 }
120
121 pub fn iter(&self) -> Iter<'_> {
123 self.0.iter()
124 }
125
126 pub fn names(&self) -> OutputNames<'_> {
147 self.0.names()
148 }
149
150 pub fn into_names(self) -> IntoOutputNames {
152 self.0.into_names()
153 }
154
155 pub fn store_paths(&self) -> impl Iterator<Item = &StorePath> {
169 self.iter().map(|(_, path)| path)
170 }
171
172 #[expect(clippy::len_without_is_empty)]
191 pub fn len(&self) -> usize {
192 self.0.len()
193 }
194
195 #[must_use]
225 pub fn is_single(&self) -> bool {
226 self.0.is_single()
227 }
228
229 #[must_use]
258 pub fn is_fixed(&self) -> bool {
259 self.0.is_fixed()
260 }
261
262 #[must_use]
264 pub fn is_input_addressed(&self) -> bool {
265 self.0.is_input_addressed()
266 }
267}
268
269impl From<Outputs> for OutputsBuilder {
270 fn from(value: Outputs) -> Self {
271 value.into_builder()
272 }
273}
274
275impl<'a> IntoIterator for &'a Outputs {
276 type Item = (&'a OutputName, &'a StorePath);
277
278 type IntoIter = Iter<'a>;
279
280 fn into_iter(self) -> Self::IntoIter {
281 self.iter()
282 }
283}
284
285impl IntoIterator for Outputs {
286 type Item = (OutputName, StorePath);
287
288 type IntoIter = IntoIter;
289
290 fn into_iter(self) -> Self::IntoIter {
291 self.0.into_iter_internal()
292 }
293}
294
295#[cfg(feature = "serde")]
296impl<'de> serde::Deserialize<'de> for Outputs {
297 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
298 where
299 D: serde::Deserializer<'de>,
300 {
301 struct OutputsVisitor;
302 impl<'de> serde::de::Visitor<'de> for OutputsVisitor {
303 type Value = Outputs;
304
305 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
306 f.write_str("derivation outputs")
307 }
308
309 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
310 where
311 A: serde::de::MapAccess<'de>,
312 {
313 use data_encoding::HEXLOWER;
314 use serde::de::Error;
315 #[derive(serde::Deserialize)]
316 struct Output<'o> {
317 path: StorePath,
318 #[serde(rename = "hashAlgo")]
319 #[serde(default)]
320 hash_algo: &'o str,
321 #[serde(default)]
322 hash: &'o str,
323 }
324 fn extract<'o, E: serde::de::Error>(
325 output: Output<'o>,
326 ) -> Result<(StorePath, Option<OutputHash>), E> {
327 if output.hash.is_empty() && output.hash_algo.is_empty() {
328 Ok((output.path, None))
329 } else {
330 let digest = HEXLOWER.decode(output.hash.as_bytes()).map_err(E::custom)?;
331 let output_hash =
332 OutputHash::from_mode_algo_and_digest(output.hash_algo, digest)
333 .map_err(E::custom)?;
334 Ok((output.path, Some(output_hash)))
335 }
336 }
337
338 let Some((output_name, output)) = map.next_entry::<OutputName, Output>()? else {
339 return Err(A::Error::invalid_length(0, &"non-empty derivation outputs"));
340 };
341 let (store_path, output_hash) = extract(output)?;
342 let mut builder = UnverifiedOutputsBuilder::new();
343 builder
344 .try_insert(output_name, store_path, output_hash)
345 .map_err(A::Error::custom)?;
346
347 while let Some((output_name, output)) = map.next_entry::<OutputName, Output>()? {
348 let (store_path, output_hash) = extract(output)?;
349 builder
350 .try_insert(output_name, store_path, output_hash)
351 .map_err(A::Error::custom)?;
352 }
353
354 builder.try_build().map_err(A::Error::custom)
355 }
356 }
357
358 deserializer.deserialize_map(OutputsVisitor)
359 }
360}
361
362#[cfg(feature = "serde")]
363impl serde::Serialize for Outputs {
364 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
365 where
366 S: serde::Serializer,
367 {
368 use serde::ser::SerializeMap as _;
369 #[derive(serde::Serialize)]
370 struct OutputRef<'b> {
371 path: &'b StorePath,
372 #[serde(rename = "hashAlgo")]
373 #[serde(skip_serializing_if = "str::is_empty")]
374 hash_algo: &'b str,
375 #[serde(skip_serializing_if = "String::is_empty")]
376 hash: String,
377 }
378 let mut map = serializer.serialize_map(Some(self.len()))?;
379 if let Some(output_hash) = self.as_fixed_output_hash() {
380 use data_encoding::HEXLOWER;
381 let digest = HEXLOWER.encode(output_hash.hash.digest_as_bytes());
382 for (output_name, path) in self {
383 map.serialize_entry(
384 output_name,
385 &OutputRef {
386 path,
387 hash_algo: output_hash.as_mode_and_algo_str(),
388 hash: digest.clone(),
389 },
390 )?;
391 }
392 } else {
393 for (output_name, path) in self {
394 map.serialize_entry(
395 output_name,
396 &OutputRef {
397 path,
398 hash_algo: "",
399 hash: String::new(),
400 },
401 )?;
402 }
403 }
404 map.end()
405 }
406}
407
408#[derive(Clone, Debug, Default, PartialEq, Eq)]
428pub struct UnverifiedOutputsBuilder(OutputsInner);
429
430impl UnverifiedOutputsBuilder {
431 pub const fn new() -> Self {
433 Self(OutputsInner::new())
434 }
435
436 pub fn len(&self) -> usize {
452 self.0.len()
453 }
454
455 #[must_use]
471 pub fn is_empty(&self) -> bool {
472 self.0.len() == 0
473 }
474
475 #[must_use]
506 pub fn is_input_addressed(&self) -> bool {
507 self.0.is_input_addressed()
508 }
509
510 #[must_use]
539 pub fn is_single(&self) -> bool {
540 self.0.is_single()
541 }
542
543 #[must_use]
570 pub fn is_fixed(&self) -> bool {
571 self.0.is_fixed()
572 }
573
574 #[must_use]
576 pub fn contains_key(&self, name: &OutputName) -> bool {
577 self.0.contains_key(name)
578 }
579
580 pub fn get(&self, name: &OutputName) -> Option<&StorePath> {
582 self.0.get(name)
583 }
584
585 pub fn names(&self) -> OutputNames<'_> {
587 self.0.names()
588 }
589
590 pub fn into_names(self) -> IntoOutputNames {
592 self.0.into_names()
593 }
594
595 pub fn iter(&self) -> Iter<'_> {
597 self.0.iter()
598 }
599
600 pub fn store_paths(&self) -> impl Iterator<Item = &StorePath> {
602 self.iter().map(|(_, path)| path)
603 }
604
605 pub fn try_insert(
610 &mut self,
611 output_name: OutputName,
612 output_path: StorePath,
613 output_hash: Option<OutputHash>,
614 ) -> Result<(), OutputsError> {
615 if self.is_empty() {
616 if let Some(output_hash) = output_hash {
617 self.0 = OutputsInner::Fixed {
618 output_hash,
619 store_path: output_path,
620 };
621 } else {
622 self.0 =
623 OutputsInner::InputAddressed(BTreeMap::from_iter([(output_name, output_path)]));
624 }
625 return Ok(());
626 }
627
628 if self.is_fixed() || output_hash.is_some() {
629 return Err(OutputsError::MoreThanOneOutputButFixed());
630 }
631
632 match std::mem::take(&mut self.0) {
633 OutputsInner::Fixed { .. } => {
634 return Err(OutputsError::MoreThanOneOutputButFixed());
635 }
636 OutputsInner::InputAddressed(mut outputs) => {
637 if outputs.insert(output_name.clone(), output_path).is_some() {
638 return Err(OutputsError::DuplicateOutputName(output_name));
639 }
640 self.0 = OutputsInner::InputAddressed(outputs);
641 }
642 }
643 Ok(())
644 }
645
646 pub fn try_build(self) -> Result<Outputs, OutputsError> {
648 if self.is_empty() {
649 return Err(OutputsError::NoOutputs());
650 }
651
652 if self.len() == 1 && !self.is_single() {
653 return Err(OutputsError::InvalidOutputName(
654 self.names().next().unwrap().to_string(),
655 ));
656 }
657
658 Ok(Outputs(self.0))
659 }
660}
661
662impl From<Outputs> for UnverifiedOutputsBuilder {
663 fn from(value: Outputs) -> Self {
664 value.into_unverified_builder()
665 }
666}
667
668impl<'a> IntoIterator for &'a UnverifiedOutputsBuilder {
669 type Item = (&'a OutputName, &'a StorePath);
670
671 type IntoIter = Iter<'a>;
672
673 fn into_iter(self) -> Self::IntoIter {
674 self.iter()
675 }
676}
677
678impl IntoIterator for UnverifiedOutputsBuilder {
679 type Item = (OutputName, StorePath);
680
681 type IntoIter = IntoIter;
682
683 fn into_iter(self) -> Self::IntoIter {
684 self.0.into_iter_internal()
685 }
686}
687
688#[derive(Clone, Debug, PartialEq, Eq)]
689enum OutputsInner {
690 Fixed {
691 output_hash: OutputHash,
692 store_path: StorePath,
693 },
694 InputAddressed(BTreeMap<OutputName, StorePath>),
695}
696
697impl OutputsInner {
698 pub const fn new() -> Self {
699 OutputsInner::InputAddressed(BTreeMap::new())
700 }
701
702 pub fn len(&self) -> usize {
703 match self {
704 OutputsInner::Fixed { .. } => 1,
705 OutputsInner::InputAddressed(outputs) => outputs.len(),
706 }
707 }
708
709 pub fn is_fixed(&self) -> bool {
710 matches!(self, OutputsInner::Fixed { .. })
711 }
712
713 pub fn is_input_addressed(&self) -> bool {
714 matches!(self, OutputsInner::InputAddressed(outputs) if !outputs.is_empty())
715 }
716
717 pub fn is_single(&self) -> bool {
718 self.len() == 1
719 }
720
721 pub fn contains_key(&self, name: &OutputName) -> bool {
722 match self {
723 OutputsInner::Fixed { .. } => *name == OutputName::out(),
724 OutputsInner::InputAddressed(outputs) => outputs.contains_key(name),
725 }
726 }
727
728 pub fn get(&self, name: &OutputName) -> Option<&StorePath> {
729 match self {
730 OutputsInner::Fixed { store_path, .. } if *name == OutputName::out() => {
731 Some(store_path)
732 }
733 OutputsInner::InputAddressed(outputs) => outputs.get(name),
734 _ => None,
735 }
736 }
737
738 pub fn names(&self) -> OutputNames<'_> {
739 match self {
740 OutputsInner::Fixed { .. } => {
741 const OUT: &OutputName = &OutputName::out();
742 OutputNames(OutputNameInner::Single(std::iter::once(OUT)))
743 }
744 OutputsInner::InputAddressed(outputs) => {
745 OutputNames(OutputNameInner::BTreeMap(outputs.keys()))
746 }
747 }
748 }
749
750 pub fn into_names(self) -> IntoOutputNames {
751 match self {
752 OutputsInner::Fixed { .. } => IntoOutputNames(IntoOutputNameInner::Single(
753 std::iter::once(OutputName::out()),
754 )),
755 OutputsInner::InputAddressed(outputs) => {
756 IntoOutputNames(IntoOutputNameInner::BTreeMap(outputs.into_keys()))
757 }
758 }
759 }
760
761 pub fn iter(&self) -> Iter<'_> {
763 match self {
764 OutputsInner::Fixed { store_path, .. } => {
765 const OUT: &OutputName = &OutputName::out();
766 Iter(IterI::Fixed(std::iter::once((OUT, store_path))))
767 }
768 OutputsInner::InputAddressed(outputs) => Iter(IterI::InputAddressed(outputs.iter())),
769 }
770 }
771
772 fn into_iter_internal(self) -> IntoIter {
773 match self {
774 OutputsInner::Fixed { store_path, .. } => IntoIter(IntoIterI::Fixed(std::iter::once(
775 (OutputName::out(), store_path),
776 ))),
777 OutputsInner::InputAddressed(outputs) => {
778 IntoIter(IntoIterI::InputAddressed(outputs.into_iter()))
779 }
780 }
781 }
782}
783
784impl Default for OutputsInner {
785 fn default() -> Self {
786 Self::new()
787 }
788}
789
790#[derive(Clone, Debug, PartialEq, Eq)]
792#[cfg_attr(feature = "serde", derive(serde::Deserialize), serde(from = "Outputs"))]
793pub enum OutputsBuilder {
794 Fixed(OutputHash),
796 InputAddressed(BTreeSet<OutputName>),
798}
799
800impl OutputsBuilder {
801 pub fn names(&self) -> OutputNames<'_> {
803 const OUT: &OutputName = &OutputName::out();
804 match self {
805 OutputsBuilder::Fixed(_) => OutputNames(OutputNameInner::Single(std::iter::once(OUT))),
806 OutputsBuilder::InputAddressed(outputs) => {
807 OutputNames(OutputNameInner::BTreeSet(outputs.iter()))
808 }
809 }
810 }
811
812 pub fn into_names(self) -> IntoOutputNames {
814 match self {
815 OutputsBuilder::Fixed(_) => IntoOutputNames(IntoOutputNameInner::Single(
816 std::iter::once(OutputName::out()),
817 )),
818 OutputsBuilder::InputAddressed(outputs) => {
819 IntoOutputNames(IntoOutputNameInner::BTreeSet(outputs.into_iter()))
820 }
821 }
822 }
823
824 pub fn len(&self) -> usize {
826 match self {
827 OutputsBuilder::Fixed(_) => 1,
828 OutputsBuilder::InputAddressed(outputs) => outputs.len(),
829 }
830 }
831
832 pub fn is_empty(&self) -> bool {
834 self.len() == 0
835 }
836
837 pub fn is_single(&self) -> bool {
839 self.len() == 1
840 }
841
842 pub fn is_fixed(&self) -> bool {
844 matches!(self, Self::Fixed(_))
845 }
846
847 pub fn contains(&self, name: &OutputName) -> bool {
849 match self {
850 OutputsBuilder::Fixed(_) => *name == OutputName::out(),
851 OutputsBuilder::InputAddressed(output_names) => output_names.contains(name),
852 }
853 }
854}
855
856impl Default for OutputsBuilder {
857 fn default() -> Self {
858 Self::InputAddressed(BTreeSet::from_iter([OutputName::out()]))
859 }
860}
861
862impl PartialEq<Outputs> for OutputsBuilder {
863 fn eq(&self, other: &Outputs) -> bool {
864 self.is_fixed() == other.is_fixed() && self.names().eq(other.names())
865 }
866}
867
868impl PartialEq<OutputsBuilder> for Outputs {
869 fn eq(&self, other: &OutputsBuilder) -> bool {
870 self.is_fixed() == other.is_fixed() && self.names().eq(other.names())
871 }
872}
873
874impl<'a> IntoIterator for &'a OutputsBuilder {
875 type Item = &'a OutputName;
876
877 type IntoIter = OutputNames<'a>;
878
879 fn into_iter(self) -> Self::IntoIter {
880 self.names()
881 }
882}
883
884impl IntoIterator for OutputsBuilder {
885 type Item = OutputName;
886
887 type IntoIter = IntoOutputNames;
888
889 fn into_iter(self) -> Self::IntoIter {
890 self.into_names()
891 }
892}
893
894impl FromIterator<OutputName> for OutputsBuilder {
895 fn from_iter<T: IntoIterator<Item = OutputName>>(iter: T) -> Self {
896 OutputsBuilder::InputAddressed(iter.into_iter().collect())
897 }
898}
899
900#[derive(Debug, PartialEq, thiserror::Error)]
902#[allow(missing_docs)]
903pub enum OutputsError {
904 #[error("no outputs defined")]
905 NoOutputs(),
906 #[error("outputs failed verification")]
907 VerificationError(),
908 #[error("invalid output name: {0}")]
909 InvalidOutputName(String),
910 #[error("duplicate output name: {0}")]
911 DuplicateOutputName(OutputName),
912 #[error("encountered fixed-output derivation, but more than 1 output in total")]
913 MoreThanOneOutputButFixed(),
914 #[error("invalid output name for fixed-output derivation: {0}")]
915 InvalidOutputNameForFixed(String),
916 #[error("invalid calculated output derivation path name: {0}")]
917 InvalidOutputDerivationPath(String, #[source] store_path::ParseStorePathError),
918}
919
920pub struct Iter<'a>(IterI<'a>);
927impl fmt::Debug for Iter<'_> {
928 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
929 f.write_str("Iter")
930 }
931}
932
933enum IterI<'a> {
934 Fixed(std::iter::Once<(&'a OutputName, &'a StorePath)>),
935 InputAddressed(std::collections::btree_map::Iter<'a, OutputName, StorePath>),
936}
937
938impl<'a> Iterator for Iter<'a> {
939 type Item = (&'a OutputName, &'a StorePath);
940
941 fn next(&mut self) -> Option<Self::Item> {
942 match &mut self.0 {
943 IterI::Fixed(it) => it.next(),
944 IterI::InputAddressed(it) => it.next(),
945 }
946 }
947 fn size_hint(&self) -> (usize, Option<usize>) {
948 match &self.0 {
949 IterI::Fixed(it) => it.size_hint(),
950 IterI::InputAddressed(it) => it.size_hint(),
951 }
952 }
953}
954
955impl<'a> ExactSizeIterator for Iter<'a> {}
956
957pub struct IntoIter(IntoIterI);
964
965impl fmt::Debug for IntoIter {
966 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
967 f.write_str("IntoIter")
968 }
969}
970
971enum IntoIterI {
972 Fixed(std::iter::Once<(OutputName, StorePath)>),
973 InputAddressed(std::collections::btree_map::IntoIter<OutputName, StorePath>),
974}
975
976impl Iterator for IntoIter {
977 type Item = (OutputName, StorePath);
978
979 fn next(&mut self) -> Option<Self::Item> {
980 match &mut self.0 {
981 IntoIterI::Fixed(it) => it.next(),
982 IntoIterI::InputAddressed(it) => it.next(),
983 }
984 }
985 fn size_hint(&self) -> (usize, Option<usize>) {
986 match &self.0 {
987 IntoIterI::Fixed(it) => it.size_hint(),
988 IntoIterI::InputAddressed(it) => it.size_hint(),
989 }
990 }
991}
992
993impl ExactSizeIterator for IntoIter {}
994
995pub struct OutputNames<'a>(OutputNameInner<'a>);
1000
1001impl fmt::Debug for OutputNames<'_> {
1002 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1003 f.write_str("OutputNames")
1004 }
1005}
1006
1007impl<'a> Iterator for OutputNames<'a> {
1008 type Item = &'a OutputName;
1009
1010 fn next(&mut self) -> Option<Self::Item> {
1011 match &mut self.0 {
1012 OutputNameInner::Single(it) => it.next(),
1013 OutputNameInner::BTreeSet(it) => it.next(),
1014 OutputNameInner::BTreeMap(it) => it.next(),
1015 }
1016 }
1017 fn size_hint(&self) -> (usize, Option<usize>) {
1018 match &self.0 {
1019 OutputNameInner::Single(it) => it.size_hint(),
1020 OutputNameInner::BTreeSet(it) => it.size_hint(),
1021 OutputNameInner::BTreeMap(it) => it.size_hint(),
1022 }
1023 }
1024}
1025impl<'a> ExactSizeIterator for OutputNames<'a> {}
1026
1027enum OutputNameInner<'a> {
1028 Single(std::iter::Once<&'a OutputName>),
1029 BTreeSet(std::collections::btree_set::Iter<'a, OutputName>),
1030 BTreeMap(std::collections::btree_map::Keys<'a, OutputName, StorePath>),
1031}
1032
1033pub struct IntoOutputNames(IntoOutputNameInner);
1038impl fmt::Debug for IntoOutputNames {
1039 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1040 f.write_str("IntoOutputNames")
1041 }
1042}
1043
1044impl Iterator for IntoOutputNames {
1045 type Item = OutputName;
1046
1047 fn next(&mut self) -> Option<Self::Item> {
1048 match &mut self.0 {
1049 IntoOutputNameInner::Single(it) => it.next(),
1050 IntoOutputNameInner::BTreeSet(it) => it.next(),
1051 IntoOutputNameInner::BTreeMap(it) => it.next(),
1052 }
1053 }
1054 fn size_hint(&self) -> (usize, Option<usize>) {
1055 match &self.0 {
1056 IntoOutputNameInner::Single(it) => it.size_hint(),
1057 IntoOutputNameInner::BTreeSet(it) => it.size_hint(),
1058 IntoOutputNameInner::BTreeMap(it) => it.size_hint(),
1059 }
1060 }
1061}
1062impl ExactSizeIterator for IntoOutputNames {}
1063
1064enum IntoOutputNameInner {
1065 Single(std::iter::Once<OutputName>),
1066 BTreeSet(std::collections::btree_set::IntoIter<OutputName>),
1067 BTreeMap(std::collections::btree_map::IntoKeys<OutputName, StorePath>),
1068}
1069
1070#[cfg(test)]
1071mod tests {
1072 use std::sync::LazyLock;
1073
1074 use rstest::rstest;
1075
1076 use crate::{
1077 derivation::{OutputHash, OutputHashMode, OutputName, Outputs, OutputsBuilder},
1078 nixhash::NixHash,
1079 store_path::StorePath,
1080 };
1081
1082 const DIGEST_SHA256: [u8; 32] =
1083 hex_literal::hex!("a5ce9c155ed09397614646c9717fc7cd94b1023d7b76b618d409e4fefd6e9d39");
1084 const OUTPUT_HASH: OutputHash = OutputHash {
1085 mode: OutputHashMode::Flat,
1086 hash: NixHash::Sha256(DIGEST_SHA256),
1087 };
1088 static STORE_PATH: LazyLock<StorePath> = LazyLock::new(|| {
1089 StorePath::from_bytes(b"2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib").unwrap()
1090 });
1091
1092 const FOD_OUTPUTS_BUILDER: OutputsBuilder = OutputsBuilder::Fixed(OutputHash {
1093 mode: OutputHashMode::Flat,
1094 hash: NixHash::Sha256(DIGEST_SHA256),
1095 });
1096 static SINGLE_OUTPUTS_BUILDER: LazyLock<OutputsBuilder> =
1097 LazyLock::new(|| OutputsBuilder::from_iter([OutputName::out()]));
1098 static SINGLE_NON_OUT_OUTPUTS_BUILDER: LazyLock<OutputsBuilder> =
1099 LazyLock::new(|| OutputsBuilder::from_iter([OutputName::from_static("bin").unwrap()]));
1100 static MULTIPLE_OUTPUTS_BUILDER: LazyLock<OutputsBuilder> = LazyLock::new(|| {
1101 OutputsBuilder::from_iter([
1102 OutputName::from_static("dev").unwrap(),
1103 OutputName::from_static("bin").unwrap(),
1104 ])
1105 });
1106
1107 static FOD_OUTPUTS: LazyLock<Outputs> =
1108 LazyLock::new(|| Outputs::fixed_output(OUTPUT_HASH.clone(), STORE_PATH.clone()));
1109 static TRY_SINGLE_OUTPUTS: LazyLock<Outputs> = LazyLock::new(|| {
1110 Outputs::input_addressed_from_iter([(OutputName::out(), STORE_PATH.clone())])
1111 .expect("single output")
1112 });
1113 static TRY_SINGLE_NON_OUT_OUTPUTS: LazyLock<Outputs> = LazyLock::new(|| {
1114 Outputs::input_addressed_from_iter([(
1115 OutputName::from_static("bin").unwrap(),
1116 STORE_PATH.clone(),
1117 )])
1118 .expect("single output")
1119 });
1120 static TRY_FOD_OUTPUTS: LazyLock<Outputs> = LazyLock::new(|| -> Outputs {
1121 Outputs::try_from_iter([(
1122 OutputName::out(),
1123 STORE_PATH.clone(),
1124 Some(OUTPUT_HASH.clone()),
1125 )])
1126 .expect("single fod")
1127 });
1128 static TRY_MULTIPLE_OUTPUTS: LazyLock<Outputs> = LazyLock::new(|| -> Outputs {
1129 Outputs::input_addressed_from_iter([
1130 (OutputName::from_static("dev").unwrap(), STORE_PATH.clone()),
1131 (OutputName::from_static("bin").unwrap(), STORE_PATH.clone()),
1132 ])
1133 .expect("multiple")
1134 });
1135
1136 mod outputs_builder {
1137 use super::*;
1138 use rstest::rstest;
1139
1140 #[rstest]
1141 #[case::single(&SINGLE_OUTPUTS_BUILDER)]
1142 #[case::single_non_out(&SINGLE_NON_OUT_OUTPUTS_BUILDER)]
1143 #[case::fod(&FOD_OUTPUTS_BUILDER)]
1144 #[case::default(&Default::default())]
1145 fn is_single(#[case] value: &OutputsBuilder) {
1146 assert!(value.is_single())
1147 }
1148
1149 #[rstest]
1150 #[case::multiple(&MULTIPLE_OUTPUTS_BUILDER)]
1151 fn is_not_single(#[case] value: &OutputsBuilder) {
1152 assert!(!value.is_single())
1153 }
1154
1155 #[rstest]
1156 #[case::fod(&FOD_OUTPUTS_BUILDER)]
1157 fn is_fixed(#[case] value: &OutputsBuilder) {
1158 assert!(value.is_fixed())
1159 }
1160
1161 #[rstest]
1162 #[case::single(&SINGLE_OUTPUTS_BUILDER)]
1163 #[case::single_non_out(&SINGLE_NON_OUT_OUTPUTS_BUILDER)]
1164 #[case::multiple(&MULTIPLE_OUTPUTS_BUILDER)]
1165 #[case::default(&Default::default())]
1166 fn is_not_fixed(#[case] value: &OutputsBuilder) {
1167 assert!(!value.is_fixed())
1168 }
1169
1170 #[rstest]
1171 #[case::single(&SINGLE_OUTPUTS_BUILDER, 1)]
1172 #[case::single_non_out(&SINGLE_NON_OUT_OUTPUTS_BUILDER, 1)]
1173 #[case::fod(&FOD_OUTPUTS_BUILDER, 1)]
1174 #[case::multiple(&MULTIPLE_OUTPUTS_BUILDER, 2)]
1175 #[case::default(&Default::default(), 1)]
1176 fn len(#[case] value: &OutputsBuilder, #[case] expected: usize) {
1177 assert_eq!(value.len(), expected)
1178 }
1179
1180 #[rstest]
1181 #[case::single(&SINGLE_OUTPUTS_BUILDER, OutputName::out())]
1182 #[case::single_non_out(&SINGLE_NON_OUT_OUTPUTS_BUILDER, "bin")]
1183 #[case::fod(&FOD_OUTPUTS_BUILDER, OutputName::out())]
1184 #[case::multiple_bin(&MULTIPLE_OUTPUTS_BUILDER, "bin")]
1185 #[case::default(&Default::default(), OutputName::out())]
1186 fn contains(#[case] value: &OutputsBuilder, #[case] name: OutputName) {
1187 assert!(value.contains(&name))
1188 }
1189
1190 #[rstest]
1191 #[case::single(&SINGLE_OUTPUTS_BUILDER, "bin")]
1192 #[case::single_non_out(&SINGLE_NON_OUT_OUTPUTS_BUILDER, "out")]
1193 #[case::fod(&FOD_OUTPUTS_BUILDER, "bin")]
1194 #[case::multiple_out(&MULTIPLE_OUTPUTS_BUILDER, OutputName::out())]
1195 fn does_not_contain(#[case] value: &OutputsBuilder, #[case] name: OutputName) {
1196 assert!(!value.contains(&name))
1197 }
1198
1199 #[rstest]
1200 #[case::single(&SINGLE_OUTPUTS_BUILDER, vec![OutputName::out()])]
1201 #[case::single_non_out(&SINGLE_NON_OUT_OUTPUTS_BUILDER, vec![OutputName::from_static("bin").unwrap()])]
1202 #[case::fod(&FOD_OUTPUTS_BUILDER, vec![OutputName::out()])]
1203 #[case::multiple(&MULTIPLE_OUTPUTS_BUILDER, vec![
1204 OutputName::from_static("bin").unwrap(),
1205 OutputName::from_static("dev").unwrap(),
1206 ])]
1207 #[case::default(&Default::default(), vec![OutputName::out()])]
1208 fn names(#[case] value: &OutputsBuilder, #[case] expected: Vec<OutputName>) {
1209 let actual: Vec<_> = value.names().cloned().collect();
1210 assert_eq!(actual, expected);
1211 }
1212
1213 #[rstest]
1214 #[case::single(&SINGLE_OUTPUTS_BUILDER, vec![OutputName::out()])]
1215 #[case::single_non_out(&SINGLE_NON_OUT_OUTPUTS_BUILDER, vec![OutputName::from_static("bin").unwrap()])]
1216 #[case::fod(&FOD_OUTPUTS_BUILDER, vec![OutputName::out()])]
1217 #[case::multiple(&MULTIPLE_OUTPUTS_BUILDER, vec![
1218 OutputName::from_static("bin").unwrap(),
1219 OutputName::from_static("dev").unwrap(),
1220 ])]
1221 #[case::default(&Default::default(), vec![OutputName::out()])]
1222 fn into_names(#[case] value: &OutputsBuilder, #[case] expected: Vec<OutputName>) {
1223 let actual: Vec<_> = value.clone().into_names().collect();
1224 assert_eq!(actual, expected);
1225 }
1226 }
1227
1228 #[rstest]
1229 #[should_panic(expected = "no outputs defined")]
1230 #[case::empty(&[])]
1231 #[should_panic(expected = "duplicate output name: bin")]
1232 #[case::duplicate(&[
1233 (OutputName::from_static("bin").unwrap(), STORE_PATH.clone(), None),
1234 (OutputName::from_static("bin").unwrap(), STORE_PATH.clone(), None),
1235 ])]
1236 #[should_panic(
1237 expected = "encountered fixed-output derivation, but more than 1 output in total"
1238 )]
1239 #[case::mixed(&[
1240 (OutputName::from_static("bin").unwrap(), STORE_PATH.clone(), Some(OUTPUT_HASH.clone())),
1241 (OutputName::from_static("dev").unwrap(), STORE_PATH.clone(), None),
1242 ])]
1243 fn try_from_iter_failure(#[case] it: &[(OutputName, StorePath, Option<OutputHash>)]) {
1244 panic!(
1245 "{}",
1246 Outputs::try_from_iter(it.iter().cloned()).expect_err("try_from_iter succeeded")
1247 );
1248 }
1249
1250 #[rstest]
1251 #[should_panic(expected = "no outputs defined")]
1252 #[case::empty(&[])]
1253 #[should_panic(expected = "duplicate output name: bin")]
1254 #[case::duplicate(&[
1255 (OutputName::from_static("bin").unwrap(), STORE_PATH.clone()),
1256 (OutputName::from_static("bin").unwrap(), STORE_PATH.clone()),
1257 ])]
1258 fn input_addressed_from_iter_failure(#[case] it: &[(OutputName, StorePath)]) {
1259 panic!(
1260 "{}",
1261 Outputs::input_addressed_from_iter(it.iter().cloned())
1262 .expect_err("try_from_iter succeeded")
1263 );
1264 }
1265
1266 #[rstest]
1267 #[case::fod(&FOD_OUTPUTS)]
1268 #[case::try_single(&TRY_SINGLE_OUTPUTS)]
1269 #[case::try_single_non_out(&TRY_SINGLE_NON_OUT_OUTPUTS)]
1270 #[case::try_fod(&TRY_FOD_OUTPUTS)]
1271 fn is_single(#[case] value: &Outputs) {
1272 assert!(value.is_single())
1273 }
1274
1275 #[rstest]
1276 #[case::multiple(&TRY_MULTIPLE_OUTPUTS)]
1277 fn is_not_single(#[case] value: &Outputs) {
1278 assert!(!value.is_single())
1279 }
1280
1281 #[rstest]
1282 #[case::fod(&FOD_OUTPUTS)]
1283 #[case::try_fod(&TRY_FOD_OUTPUTS)]
1284 fn is_fixed(#[case] value: &Outputs) {
1285 assert!(value.is_fixed())
1286 }
1287
1288 #[rstest]
1289 #[case::try_single(&TRY_SINGLE_OUTPUTS)]
1290 #[case::try_single_non_out(&TRY_SINGLE_NON_OUT_OUTPUTS)]
1291 #[case::multiple(&TRY_MULTIPLE_OUTPUTS)]
1292 fn is_not_fixed(#[case] value: &Outputs) {
1293 assert!(!value.is_fixed())
1294 }
1295
1296 #[rstest]
1297 #[case::fod(&FOD_OUTPUTS, 1)]
1298 #[case::try_fod(&TRY_FOD_OUTPUTS, 1)]
1299 #[case::try_single(&TRY_SINGLE_OUTPUTS, 1)]
1300 #[case::try_single_non_out(&TRY_SINGLE_NON_OUT_OUTPUTS, 1)]
1301 #[case::multiple(&TRY_MULTIPLE_OUTPUTS, 2)]
1302 fn len(#[case] value: &Outputs, #[case] expected: usize) {
1303 assert_eq!(value.len(), expected)
1304 }
1305
1306 #[rstest]
1307 #[case::fod(&FOD_OUTPUTS, OutputName::out(), Some(&*STORE_PATH))]
1308 #[case::try_fod(&TRY_FOD_OUTPUTS, OutputName::out(), Some(&*STORE_PATH))]
1309 #[case::try_single(&TRY_SINGLE_OUTPUTS, OutputName::out(), Some(&*STORE_PATH))]
1310 #[case::try_single_non_out(&TRY_SINGLE_NON_OUT_OUTPUTS, OutputName::from_static("bin").unwrap(), Some(&*STORE_PATH))]
1311 #[case::multiple_out(&TRY_MULTIPLE_OUTPUTS, OutputName::out(), None)]
1312 #[case::multiple_bin(&TRY_MULTIPLE_OUTPUTS, OutputName::from_static("bin").unwrap(), Some(&*STORE_PATH))]
1313 fn get(
1314 #[case] value: &Outputs,
1315 #[case] name: OutputName,
1316 #[case] expected: Option<&StorePath>,
1317 ) {
1318 assert_eq!(value.get(&name), expected)
1319 }
1320
1321 #[rstest]
1322 #[case::fod(&FOD_OUTPUTS, OutputName::out())]
1323 #[case::try_fod(&TRY_FOD_OUTPUTS, OutputName::out())]
1324 #[case::try_single(&TRY_SINGLE_OUTPUTS, OutputName::out())]
1325 #[case::try_single_non_out(&TRY_SINGLE_NON_OUT_OUTPUTS, OutputName::from_static("bin").unwrap())]
1326 #[case::multiple_bin(&TRY_MULTIPLE_OUTPUTS, OutputName::from_static("bin").unwrap())]
1327 fn contains_key(#[case] value: &Outputs, #[case] name: OutputName) {
1328 assert!(value.contains_key(&name))
1329 }
1330
1331 #[rstest]
1332 #[case::multiple_out(&TRY_MULTIPLE_OUTPUTS, OutputName::out())]
1333 fn does_not_contain_key(#[case] value: &Outputs, #[case] name: OutputName) {
1334 assert!(!value.contains_key(&name))
1335 }
1336
1337 #[rstest]
1338 #[case::fod(&FOD_OUTPUTS, vec![(OutputName::out(), STORE_PATH.clone())])]
1339 #[case::try_fod(&TRY_FOD_OUTPUTS, vec![(OutputName::out(), STORE_PATH.clone())])]
1340 #[case::try_single(&TRY_SINGLE_OUTPUTS, vec![(OutputName::out(), STORE_PATH.clone())])]
1341 #[case::try_single_non_out(&TRY_SINGLE_NON_OUT_OUTPUTS, vec![(OutputName::from_static("bin").unwrap(), STORE_PATH.clone())])]
1342 #[case::multiple(&TRY_MULTIPLE_OUTPUTS, vec![
1343 (OutputName::from_static("bin").unwrap(), STORE_PATH.clone()),
1344 (OutputName::from_static("dev").unwrap(), STORE_PATH.clone()),
1345 ])]
1346 fn iter(#[case] value: &Outputs, #[case] expected: Vec<(OutputName, StorePath)>) {
1347 let actual: Vec<_> = value
1348 .iter()
1349 .map(|(name, output)| (name.clone(), output.clone()))
1350 .collect();
1351 assert_eq!(actual, expected);
1352 }
1353
1354 #[rstest]
1355 #[case::fod(&FOD_OUTPUTS, vec![OutputName::out()])]
1356 #[case::try_fod(&TRY_FOD_OUTPUTS, vec![OutputName::out()])]
1357 #[case::try_single(&TRY_SINGLE_OUTPUTS, vec![OutputName::out()])]
1358 #[case::try_single_non_out(&TRY_SINGLE_NON_OUT_OUTPUTS, vec![OutputName::from_static("bin").unwrap()])]
1359 #[case::multiple(&TRY_MULTIPLE_OUTPUTS, vec![
1360 OutputName::from_static("bin").unwrap(),
1361 OutputName::from_static("dev").unwrap(),
1362 ])]
1363 fn names(#[case] value: &Outputs, #[case] expected: Vec<OutputName>) {
1364 let actual: Vec<_> = value.names().cloned().collect();
1365 assert_eq!(actual, expected);
1366 }
1367
1368 #[rstest]
1369 #[case::fod(&FOD_OUTPUTS, vec![OutputName::out()])]
1370 #[case::try_fod(&TRY_FOD_OUTPUTS, vec![OutputName::out()])]
1371 #[case::try_single(&TRY_SINGLE_OUTPUTS, vec![OutputName::out()])]
1372 #[case::try_single_non_out(&TRY_SINGLE_NON_OUT_OUTPUTS, vec![OutputName::from_static("bin").unwrap()])]
1373 #[case::multiple(&TRY_MULTIPLE_OUTPUTS, vec![
1374 OutputName::from_static("bin").unwrap(),
1375 OutputName::from_static("dev").unwrap(),
1376 ])]
1377 fn into_names(#[case] value: &Outputs, #[case] expected: Vec<OutputName>) {
1378 let actual: Vec<_> = value.clone().into_names().collect();
1379 assert_eq!(actual, expected);
1380 }
1381
1382 #[rstest]
1383 #[case::fod(&FOD_OUTPUTS, vec![STORE_PATH.clone()])]
1384 #[case::try_fod(&TRY_FOD_OUTPUTS, vec![STORE_PATH.clone()])]
1385 #[case::try_single(&TRY_SINGLE_OUTPUTS, vec![STORE_PATH.clone()])]
1386 #[case::try_single_non_out(&TRY_SINGLE_NON_OUT_OUTPUTS, vec![STORE_PATH.clone()])]
1387 #[case::multiple(&TRY_MULTIPLE_OUTPUTS, vec![STORE_PATH.clone(), STORE_PATH.clone()])]
1388 fn values(#[case] value: &Outputs, #[case] expected: Vec<StorePath>) {
1389 let actual: Vec<_> = value.store_paths().cloned().collect();
1390 assert_eq!(actual, expected);
1391 }
1392
1393 #[cfg(feature = "serde")]
1394 #[rstest]
1395 #[case::fod(&FOD_OUTPUTS)]
1396 #[case::try_fod(&TRY_FOD_OUTPUTS)]
1397 #[case::try_single(&TRY_SINGLE_OUTPUTS)]
1398 #[case::try_single_non_out(&TRY_SINGLE_NON_OUT_OUTPUTS)]
1399 #[case::multiple(&TRY_MULTIPLE_OUTPUTS)]
1400 fn serde(#[case] value: &Outputs) {
1401 let serialize = serde_json::to_string_pretty(&value).unwrap();
1402 let actual: Outputs = serde_json::from_str(&serialize).unwrap();
1403 assert_eq!(&actual, value);
1404 }
1405
1406 #[cfg(feature = "serde")]
1409 #[test]
1410 fn deserialize_valid_input_addressed_output() {
1411 let json_bytes = r#"
1412 {
1413 "out": {
1414 "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432"
1415 }
1416 }"#;
1417 let output: Outputs = serde_json::from_str(json_bytes).expect("must parse");
1418
1419 assert!(!output.is_fixed());
1420 }
1421
1422 #[cfg(feature = "serde")]
1425 #[test]
1426 fn deserialize_valid_fixed_output() {
1427 let json_bytes = r#"
1428 {
1429 "out": {
1430 "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
1431 "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
1432 "hashAlgo": "r:sha256"
1433 }
1434 }"#;
1435 let output: Outputs = serde_json::from_str(json_bytes).expect("must parse");
1436
1437 assert!(output.is_fixed());
1438 }
1439
1440 #[cfg(feature = "serde")]
1443 #[test]
1444 fn deserialize_with_error_invalid_hash_encoding_fixed_output() {
1445 let json_bytes = r#"
1446 {
1447 "out": {
1448 "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
1449 "hash": "IAMNOTVALIDNIXBASE32",
1450 "hashAlgo": "r:sha256"
1451 }
1452 }"#;
1453 let output: Result<Outputs, _> = serde_json::from_str(json_bytes);
1454
1455 assert!(output.is_err());
1456 }
1457
1458 #[cfg(feature = "serde")]
1461 #[test]
1462 fn deserialize_with_error_invalid_hash_algo_fixed_output() {
1463 let json_bytes = r#"
1464 {
1465 "out": {
1466 "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
1467 "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
1468 "hashAlgo": "r:sha1024"
1469 }
1470 }"#;
1471 let output: Result<Outputs, _> = serde_json::from_str(json_bytes);
1472
1473 assert!(output.is_err());
1474 }
1475
1476 #[cfg(feature = "serde")]
1479 #[test]
1480 fn deserialize_with_error_missing_hash_algo_fixed_output() {
1481 let json_bytes = r#"
1482 {
1483 "out": {
1484 "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
1485 "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
1486 }
1487 }"#;
1488 let output: Result<Outputs, _> = serde_json::from_str(json_bytes);
1489
1490 assert!(output.is_err());
1491 }
1492
1493 #[cfg(feature = "serde")]
1496 #[test]
1497 fn deserialize_with_error_missing_hash_fixed_output() {
1498 let json_bytes = r#"
1499 {
1500 "out": {
1501 "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
1502 "hashAlgo": "r:sha1024"
1503 }
1504 }"#;
1505 let output: Result<Outputs, _> = serde_json::from_str(json_bytes);
1506
1507 assert!(output.is_err());
1508 }
1509
1510 #[cfg(feature = "serde")]
1511 #[test]
1512 fn serialize_deserialize() {
1513 let json_bytes = r#"
1514 {
1515 "out": {
1516 "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432"
1517 }
1518 }"#;
1519 let output: Outputs = serde_json::from_str(json_bytes).expect("must parse");
1520
1521 let s = serde_json::to_string(&output).expect("Serialize");
1522 let output2: Outputs = serde_json::from_str(&s).expect("must parse again");
1523
1524 assert_eq!(output, output2);
1525 }
1526
1527 #[cfg(feature = "serde")]
1528 #[test]
1529 fn serialize_deserialize_fixed() {
1530 let json_bytes = r#"
1531 {
1532 "out": {
1533 "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
1534 "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
1535 "hashAlgo": "r:sha256"
1536 }
1537 }"#;
1538 let output: Outputs = serde_json::from_str(json_bytes).expect("must parse");
1539
1540 let s = serde_json::to_string_pretty(&output).expect("Serialize");
1541 let output2: Outputs = serde_json::from_str(&s).expect("must parse again");
1542
1543 assert_eq!(output, output2);
1544 }
1545}