fallow_api/audit_run/
base_ref.rs1use std::path::Path;
4
5use fallow_engine::repo_refs::{self, ResolvedAuditBase};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum AuditBaseOrigin {
10 Explicit,
12 Environment,
14 Detected,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum AuditBaseError {
21 InvalidRef {
23 origin: AuditBaseOrigin,
25 value: String,
27 reason: String,
29 },
30 NotDetected,
33}
34
35#[must_use]
38pub fn parse_audit_base_override(raw: Option<String>) -> Option<String> {
39 let trimmed = raw?.trim().to_string();
40 if trimmed.is_empty() {
41 None
42 } else {
43 Some(trimmed)
44 }
45}
46
47pub fn resolve_audit_base(
58 root: &Path,
59 explicit: Option<&str>,
60) -> Result<ResolvedAuditBase, AuditBaseError> {
61 resolve_audit_base_with_override(
62 root,
63 explicit,
64 parse_audit_base_override(std::env::var("FALLOW_AUDIT_BASE").ok()),
65 )
66}
67
68fn resolve_audit_base_with_override(
69 root: &Path,
70 explicit: Option<&str>,
71 env_override: Option<String>,
72) -> Result<ResolvedAuditBase, AuditBaseError> {
73 if let Some(explicit) = explicit {
74 validate(explicit, AuditBaseOrigin::Explicit)?;
75 return Ok(ResolvedAuditBase {
76 git_ref: explicit.to_string(),
77 description: None,
78 });
79 }
80 if let Some(env_ref) = env_override {
81 validate(&env_ref, AuditBaseOrigin::Environment)?;
82 return Ok(ResolvedAuditBase {
83 description: Some(format!("FALLOW_AUDIT_BASE={env_ref}")),
84 git_ref: env_ref,
85 });
86 }
87 let detected =
88 repo_refs::auto_detect_audit_base_ref(root).ok_or(AuditBaseError::NotDetected)?;
89 validate(&detected.git_ref, AuditBaseOrigin::Detected)?;
90 Ok(detected)
91}
92
93fn validate(value: &str, origin: AuditBaseOrigin) -> Result<(), AuditBaseError> {
94 fallow_engine::validate::validate_git_ref(value)
95 .map(|_| ())
96 .map_err(|reason| AuditBaseError::InvalidRef {
97 origin,
98 value: value.to_string(),
99 reason,
100 })
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn the_override_is_trimmed_and_an_empty_one_is_unset() {
109 assert_eq!(parse_audit_base_override(None), None);
110 assert_eq!(parse_audit_base_override(Some(String::new())), None);
111 assert_eq!(parse_audit_base_override(Some(" ".to_string())), None);
112 assert_eq!(
113 parse_audit_base_override(Some(" origin/main ".to_string())),
114 Some("origin/main".to_string())
115 );
116 }
117
118 #[test]
119 fn an_explicit_ref_wins_over_the_override() {
120 let resolved = resolve_audit_base_with_override(
121 Path::new("."),
122 Some("main"),
123 Some("upstream/main".to_string()),
124 )
125 .expect("explicit ref resolves");
126 assert_eq!(resolved.git_ref, "main");
127 assert_eq!(resolved.description, None);
128 }
129
130 #[test]
131 fn the_override_names_its_source() {
132 let resolved = resolve_audit_base_with_override(
133 Path::new("."),
134 None,
135 Some("upstream/main".to_string()),
136 )
137 .expect("override resolves");
138 assert_eq!(resolved.git_ref, "upstream/main");
139 assert_eq!(
140 resolved.description.as_deref(),
141 Some("FALLOW_AUDIT_BASE=upstream/main")
142 );
143 }
144
145 #[test]
146 fn an_invalid_override_names_its_origin() {
147 let error = resolve_audit_base_with_override(
148 Path::new("."),
149 None,
150 Some("--upload-pack=evil".to_string()),
151 )
152 .expect_err("an option-like ref is refused");
153 assert!(
154 matches!(
155 error,
156 AuditBaseError::InvalidRef {
157 origin: AuditBaseOrigin::Environment,
158 ..
159 }
160 ),
161 "{error:?}"
162 );
163 }
164}