Skip to content

Commit 5781c35

Browse files
Handle the case where there is no parent, and make some items private
1 parent 271d2d1 commit 5781c35

File tree

1 file changed

+68
-13
lines changed

1 file changed

+68
-13
lines changed

src/resolution.rs

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use pep508_rs::Requirement;
44
use thiserror::Error;
55

66
/// Resolves a single dependency group or extra.
7-
pub fn resolve_group<'a, T: DependencyEntry>(
7+
fn resolve_group<'a, T: DependencyEntry>(
88
groups: &'a IndexMap<String, Vec<T>>,
99
group: &'a str,
1010
resolved: &mut IndexMap<String, Vec<Requirement>>,
@@ -15,14 +15,15 @@ pub fn resolve_group<'a, T: DependencyEntry>(
1515
if resolved.get(group).is_some() {
1616
return Ok(());
1717
}
18+
// If the group does not exist in groups, return an error.
1819
let Some(items) = groups.get(group) else {
19-
// If the group included in another group does not exist, return an error
20-
let parent = parents.iter().last().expect("should have a parent");
21-
return Err(RecursionResolutionError::GroupNotFound(
22-
T::group_name(),
23-
group.to_string(),
24-
parent.to_string(),
25-
));
20+
let parent = parents.iter().last().map(|s| s.to_string());
21+
return Err(GroupNotFound {
22+
group_name: T::group_name(),
23+
group: group.to_string(),
24+
parent,
25+
}
26+
.into());
2627
};
2728
// If there is a cycle in dependency groups, return an error
2829
if parents.contains(&group) {
@@ -55,8 +56,8 @@ pub fn resolve_group<'a, T: DependencyEntry>(
5556

5657
#[derive(Debug, Error)]
5758
pub enum RecursionResolutionError {
58-
#[error("Failed to find {0} `{1}` included by `{2}`")]
59-
GroupNotFound(String, String, String),
59+
#[error(transparent)]
60+
GroupNotFound(#[from] GroupNotFound),
6061
#[error("Detected a cycle in `{0}`: {1}")]
6162
DependencyGroupCycle(String, Cycle),
6263
#[error(
@@ -65,6 +66,28 @@ pub enum RecursionResolutionError {
6566
NameCollision(String),
6667
}
6768

69+
#[derive(Debug, Error)]
70+
pub struct GroupNotFound {
71+
group_name: String,
72+
group: String,
73+
parent: Option<String>,
74+
}
75+
76+
impl std::fmt::Display for GroupNotFound {
77+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78+
let Self {
79+
group_name,
80+
group,
81+
parent,
82+
} = self;
83+
write!(f, "Failed to find {group_name} `{group}`")?;
84+
if let Some(parent) = parent {
85+
write!(f, " included by `{parent}`")?;
86+
}
87+
Ok(())
88+
}
89+
}
90+
6891
/// A cycle in the recursion.
6992
#[derive(Debug)]
7093
pub struct Cycle(Vec<String>);
@@ -85,7 +108,7 @@ impl std::fmt::Display for Cycle {
85108
}
86109

87110
/// A trait that defines how to parse a recursion item.
88-
pub trait DependencyEntry {
111+
trait DependencyEntry {
89112
/// Parse the item into a requirement or a reference to other groups.
90113
fn parse<'a>(&'a self, name: Option<&str>) -> Item<'a>;
91114
/// The name of the group in the TOML file.
@@ -94,7 +117,7 @@ pub trait DependencyEntry {
94117
fn table_name() -> String;
95118
}
96119

97-
pub enum Item<'a> {
120+
enum Item<'a> {
98121
Requirement(Requirement),
99122
Groups(Vec<&'a str>),
100123
}
@@ -216,10 +239,11 @@ impl PyProjectToml {
216239

217240
#[cfg(test)]
218241
mod tests {
242+
use indexmap::IndexMap;
219243
use pep508_rs::Requirement;
220244
use std::str::FromStr;
221245

222-
use crate::PyProjectToml;
246+
use crate::{resolution::resolve_group, PyProjectToml};
223247

224248
#[test]
225249
fn test_parse_pyproject_toml_optional_dependencies_resolve() {
@@ -279,6 +303,37 @@ mod tests {
279303
)
280304
}
281305

306+
#[test]
307+
fn test_parse_pyproject_toml_optional_dependencies_missing_top_level() {
308+
let source = r#"
309+
[project]
310+
name = "spam"
311+
312+
[project.optional-dependencies]
313+
alpha = ["beta"]
314+
"#;
315+
let project_toml = PyProjectToml::new(source).unwrap();
316+
let mut resolved = IndexMap::new();
317+
let err = resolve_group(
318+
project_toml
319+
.project
320+
.as_ref()
321+
.unwrap()
322+
.optional_dependencies
323+
.as_ref()
324+
.unwrap(),
325+
"foo",
326+
&mut resolved,
327+
&mut Vec::new(),
328+
Some("spam"),
329+
)
330+
.unwrap_err();
331+
assert_eq!(
332+
err.to_string(),
333+
"Failed to find optional dependency group `foo`"
334+
);
335+
}
336+
282337
#[test]
283338
fn test_parse_pyproject_toml_dependency_groups_resolve() {
284339
let source = r#"

0 commit comments

Comments
 (0)