Skip to main content

pray_core/
resolve_git_refresh.rs

1use crate::lockfile::read_lockfile;
2use crate::PrayError;
3use std::path::Path;
4
5pub fn resolution_may_benefit_from_git_source_refresh(message: &str) -> bool {
6    message.contains("no registry version")
7        || message.contains("not found in distribution")
8        || message.contains("not found in git source")
9        || message.contains("v1/packages/")
10}
11
12pub fn annotate_missing_git_catalog(
13    error: PrayError,
14    package_name: &str,
15    source_name: &str,
16    revision: &str,
17) -> PrayError {
18    match error {
19        PrayError::Resolution(message)
20            if !revision.is_empty()
21                && resolution_may_benefit_from_git_source_refresh(&message)
22                && !message.contains("not found in git source") =>
23        {
24            PrayError::Resolution(format!(
25                "package {package_name} was not found in git source {source_name} at revision {revision}. \
26                 Run `pray update` to advance the source pin."
27            ))
28        }
29        other => other,
30    }
31}
32
33pub fn annotate_failed_git_refresh(lockfile_path: &Path, error: PrayError) -> PrayError {
34    match error {
35        PrayError::Resolution(message)
36            if resolution_may_benefit_from_git_source_refresh(&message) =>
37        {
38            match locked_git_revision_guidance(lockfile_path) {
39                Some(pins) => {
40                    let package =
41                        package_name_from_catalog_miss(&message).unwrap_or("the declared package");
42                    PrayError::Resolution(format!(
43                        "package {package} was not found in locked git source ({pins}). \
44                         Run `pray update` to advance the source pin."
45                    ))
46                }
47                None => PrayError::Resolution(message),
48            }
49        }
50        other => other,
51    }
52}
53
54fn locked_git_revision_guidance(lockfile_path: &Path) -> Option<String> {
55    let lockfile = read_lockfile(lockfile_path).ok()?;
56    let pins: Vec<String> = lockfile
57        .source
58        .iter()
59        .filter(|source| source.kind == "git")
60        .filter_map(|source| {
61            source
62                .revision
63                .as_ref()
64                .map(|revision| format!("{} at {revision}", source.name))
65        })
66        .collect();
67    if pins.is_empty() {
68        None
69    } else {
70        Some(pins.join(", "))
71    }
72}
73
74fn package_name_from_catalog_miss(message: &str) -> Option<&str> {
75    let rest = message.strip_prefix("package ")?;
76    let end = rest
77        .find(" was not found")
78        .or_else(|| rest.find(" not found"))?;
79    let name = rest.get(..end)?.trim();
80    if name.is_empty() {
81        None
82    } else {
83        Some(name)
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn matches_catalog_miss_messages() {
93        assert!(resolution_may_benefit_from_git_source_refresh(
94            "no registry version for sample/base satisfies ~> 2.0"
95        ));
96        assert!(resolution_may_benefit_from_git_source_refresh(
97            "package sample/extra not found in distribution /tmp/prayers. Missing /tmp/prayers/v1/packages/sample/extra.json. Check the package name."
98        ));
99        assert!(resolution_may_benefit_from_git_source_refresh(
100            "package sample/extra was not found in git source dist at revision abc. Run `pray update` to advance the source pin."
101        ));
102        assert!(!resolution_may_benefit_from_git_source_refresh(
103            "failed to read package metadata"
104        ));
105    }
106
107    #[test]
108    fn annotates_git_catalog_miss_with_update_guidance() {
109        let error = PrayError::Resolution(
110            "package sample/extra not found in distribution /cache. \
111             Missing /cache/v1/packages/sample/extra.json. Check the package name, version constraint `~> 1.0`, and that the source publishes registry metadata."
112                .to_string(),
113        );
114        let annotated = annotate_missing_git_catalog(error, "sample/extra", "dist", "abc123");
115        let text = annotated.to_string();
116        assert!(text.contains("sample/extra"));
117        assert!(text.contains("dist"));
118        assert!(text.contains("abc123"));
119        assert!(text.contains("pray update"));
120        assert!(!text.contains("check the package name"));
121    }
122}