|
1 | 1 | //! Type definitions and functions for working with `wkg.toml` files. |
2 | 2 |
|
3 | 3 | use std::{ |
4 | | - collections::{BTreeSet, HashMap, HashSet}, |
| 4 | + collections::{BTreeSet, HashMap}, |
5 | 5 | path::{Path, PathBuf}, |
6 | 6 | }; |
7 | 7 |
|
@@ -43,7 +43,8 @@ pub struct Manifest { |
43 | 43 | } |
44 | 44 |
|
45 | 45 | impl Manifest { |
46 | | - fn from_toml(contents: &str) -> Result<Manifest> { |
| 46 | + /// Parses and validates a manifest from its TOML contents. |
| 47 | + pub fn from_toml(contents: &str) -> Result<Manifest> { |
47 | 48 | let manifest: Manifest = toml::from_str(contents)?; |
48 | 49 | manifest.validate()?; |
49 | 50 | Ok(manifest) |
@@ -87,44 +88,52 @@ impl Manifest { |
87 | 88 |
|
88 | 89 | /// Checks that override keys parse and that no package is covered by both a bare and a |
89 | 90 | /// versioned key. |
90 | | -
|
91 | 91 | pub(crate) fn validate_override_keys(&self) -> Result<()> { |
92 | 92 | let Some(overrides) = self.overrides.as_ref() else { |
93 | 93 | return Ok(()); |
94 | 94 | }; |
95 | 95 | // `overrides` is a map, so walk it in a stable order |
96 | 96 | let sorted_keys: BTreeSet<&str> = overrides.keys().map(String::as_str).collect(); |
97 | 97 |
|
98 | | - let mut bare: HashSet<PackageRef> = HashSet::new(); |
99 | | - let mut versioned: HashMap<PackageRef, Vec<&str>> = HashMap::new(); |
| 98 | + // A bare key (`ns:pkg`) covers every version of its package, a versioned key |
| 99 | + // (`ns:pkg@1.2.3`) exactly one, so a package may be named by one kind or the other but |
| 100 | + // not both |
| 101 | + let mut bare: BTreeSet<PackageRef> = BTreeSet::new(); |
| 102 | + let mut versioned: Vec<(&str, PackageRef)> = Vec::new(); |
100 | 103 | for key in sorted_keys { |
101 | 104 | let spec: PackageSpec = key |
102 | 105 | .parse() |
103 | 106 | .with_context(|| format!("invalid override key `{key}`"))?; |
104 | 107 | match spec.version { |
105 | | - Some(_) => versioned.entry(spec.package).or_default().push(key), |
| 108 | + Some(_) => versioned.push((key, spec.package)), |
106 | 109 | None => { |
107 | 110 | bare.insert(spec.package); |
108 | 111 | } |
109 | 112 | } |
110 | 113 | } |
111 | | - |
112 | | - let mut conflicts: Vec<String> = versioned |
| 114 | + let conflicts: Vec<String> = bare |
113 | 115 | .iter() |
114 | | - .filter(|(package, _)| bare.contains(*package)) |
115 | | - .map(|(package, keys)| { |
116 | | - format!( |
117 | | - "override `{package}` applies to every version of the package, so it \ |
118 | | - conflicts with the versioned override(s) `{}`", |
119 | | - keys.join("`, `") |
120 | | - ) |
| 116 | + .filter_map(|package| { |
| 117 | + let keys: Vec<&str> = versioned |
| 118 | + .iter() |
| 119 | + .filter(|(_, p)| p == package) |
| 120 | + .map(|(key, _)| *key) |
| 121 | + .collect(); |
| 122 | + (!keys.is_empty()).then(|| { |
| 123 | + format!( |
| 124 | + "`{package}` covers every version and overlaps `{}`", |
| 125 | + keys.join("`, `") |
| 126 | + ) |
| 127 | + }) |
121 | 128 | }) |
122 | 129 | .collect(); |
123 | 130 | if conflicts.is_empty() { |
124 | 131 | return Ok(()); |
125 | 132 | } |
126 | | - conflicts.sort_unstable(); |
127 | | - anyhow::bail!("{} - remove one or the other", conflicts.join("; ")); |
| 133 | + anyhow::bail!( |
| 134 | + "overrides must not overlap: {} - remove one or the other", |
| 135 | + conflicts.join("; ") |
| 136 | + ); |
128 | 137 | } |
129 | 138 |
|
130 | 139 | // no overrides or top-level metadata when workspace is present |
|
0 commit comments