pray_core/
resolve_git_refresh.rs1use 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}