| @@ -7,119 +7,7 @@ pub(crate) mod types; · 6 unmodified lines |
| 7 | pub use types::{Item, Mapping, Outcome, Source, SourceRef}; | 7 | pub use types::{Item, Mapping, Outcome, Source, SourceRef}; |
| 8 | | 8 | |
| 9 | /// | 9 | /// |
| 10 | pub mod validate { | 10 | pub mod validate; |
| 11 | use crate::match_group::{Outcome, Source}; | | |
| 12 | use bstr::BString; | | |
| 13 | use std::collections::BTreeMap; | | |
| 14 | | | |
| 15 | /// All possible issues found while validating matched mappings. | | |
| 16 | #[derive(Debug, PartialEq, Eq)] | | |
| 17 | pub enum Issue { | | |
| 18 | /// Multiple sources try to write the same destination. | | |
| 19 | /// | | |
| 20 | /// Note that this issue doesn't take into consideration that these sources might contain the same object behind a reference. | | |
| 21 | Conflict { | | |
| 22 | /// The unenforced full name of the reference to be written. | | |
| 23 | destination_full_ref_name: BString, | | |
| 24 | /// The list of sources that map to this destination. | | |
| 25 | sources: Vec<Source>, | | |
| 26 | /// The list of specs that caused the mapping conflict, each matching the respective one in `sources` to allow both | | |
| 27 | /// `sources` and `specs` to be zipped together. | | |
| 28 | specs: Vec<BString>, | | |
| 29 | }, | | |
| 30 | } | | |
| 31 | | | |
| 32 | impl std::fmt::Display for Issue { | | |
| 33 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | | |
| 34 | match self { | | |
| 35 | Issue::Conflict { | | |
| 36 | destination_full_ref_name, | | |
| 37 | sources, | | |
| 38 | specs, | | |
| 39 | } => { | | |
| 40 | write!( | | |
| 41 | f, | | |
| 42 | "Conflicting destination {destination_full_ref_name:?} would be written by {}", | | |
| 43 | sources | | |
| 44 | .iter() | | |
| 45 | .zip(specs.iter()) | | |
| 46 | .map(|(src, spec)| format!("{src} ({spec:?})")) | | |
| 47 | .collect::<Vec<_>>() | | |
| 48 | .join(", ") | | |
| 49 | ) | | |
| 50 | } | | |
| 51 | } | | |
| 52 | } | | |
| 53 | } | | |
| 54 | | | |
| 55 | /// All possible fixes corrected while validating matched mappings. | | |
| 56 | #[derive(Debug, PartialEq, Eq)] | | |
| 57 | pub enum Fix {} | | |
| 58 | | | |
| 59 | /// The error returned [outcome validation][Outcome::validated()]. | | |
| 60 | #[derive(Debug)] | | |
| 61 | pub struct Error { | | |
| 62 | /// All issues discovered during validation. | | |
| 63 | pub issues: Vec<Issue>, | | |
| 64 | } | | |
| 65 | | | |
| 66 | impl std::fmt::Display for Error { | | |
| 67 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | | |
| 68 | write!( | | |
| 69 | f, | | |
| 70 | "Found {} {} the refspec mapping to be used: \n\t{}", | | |
| 71 | self.issues.len(), | | |
| 72 | (self.issues.len() == 1) | | |
| 73 | .then(|| "issue that prevents") | | |
| 74 | .unwrap_or("issues that prevent"), | | |
| 75 | self.issues | | |
| 76 | .iter() | | |
| 77 | .map(|issue| issue.to_string()) | | |
| 78 | .collect::<Vec<_>>() | | |
| 79 | .join("\n\t") | | |
| 80 | ) | | |
| 81 | } | | |
| 82 | } | | |
| 83 | | | |
| 84 | impl std::error::Error for Error {} | | |
| 85 | | | |
| 86 | impl<'spec, 'item> Outcome<'spec, 'item> { | | |
| 87 | /// Validate all mappings or dissolve them into an error stating the discovered issues. | | |
| 88 | /// Return `(modified self, issues)` providing a fixed-up set of mappings in `self` with the fixed `issues` | | |
| 89 | /// provided as part of it. | | |
| 90 | /// Terminal issues are communicated using the [`Error`] type accordingly. | | |
| 91 | pub fn validated(self) -> Result<(Self, Vec<Fix>), Error> { | | |
| 92 | let fixed = Vec::new(); | | |
| 93 | let mut sources_by_destinations = BTreeMap::new(); | | |
| 94 | for (dst, (spec_index, src)) in self | | |
| 95 | .mappings | | |
| 96 | .iter() | | |
| 97 | .filter_map(|m| m.rhs.as_ref().map(|dst| (dst.as_ref(), (m.spec_index, &m.lhs)))) | | |
| 98 | { | | |
| 99 | let sources = sources_by_destinations.entry(dst).or_insert_with(Vec::new); | | |
| 100 | if !sources.iter().any(|(_, lhs)| lhs == &src) { | | |
| 101 | sources.push((spec_index, src)) | | |
| 102 | } | | |
| 103 | } | | |
| 104 | let mut issues = Vec::new(); | | |
| 105 | for (dst, conflicting_sources) in sources_by_destinations.into_iter().filter(|(_, v)| v.len() > 1) { | | |
| 106 | issues.push(Issue::Conflict { | | |
| 107 | destination_full_ref_name: dst.to_owned(), | | |
| 108 | specs: conflicting_sources | | |
| 109 | .iter() | | |
| 110 | .map(|(spec_idx, _)| self.group.specs[*spec_idx].to_bstring()) | | |
| 111 | .collect(), | | |
| 112 | sources: conflicting_sources.into_iter().map(|(_, src)| src.to_owned()).collect(), | | |
| 113 | }) | | |
| 114 | } | | |
| 115 | if !issues.is_empty() { | | |
| 116 | Err(Error { issues }) | | |
| 117 | } else { | | |
| 118 | Ok((self, fixed)) | | |
| 119 | } | | |
| 120 | } | | |
| 121 | } | | |
| 122 | } | | |
| 123 | | 11 | |
| 124 | /// Initialization | 12 | /// Initialization |
| 125 | impl<'a> MatchGroup<'a> { | 13 | impl<'a> MatchGroup<'a> { |