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