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 inherited_refs: Vec::new(),
79 });
80 }
81 if let Some(env_ref) = env_override {
82 validate(&env_ref, AuditBaseOrigin::Environment)?;
83 return Ok(ResolvedAuditBase {
84 description: Some(format!("FALLOW_AUDIT_BASE={env_ref}")),
85 git_ref: env_ref,
86 inherited_refs: Vec::new(),
87 });
88 }
89 let detected =
90 repo_refs::auto_detect_audit_base_ref(root).ok_or(AuditBaseError::NotDetected)?;
91 validate(&detected.git_ref, AuditBaseOrigin::Detected)?;
92 Ok(detected)
93}
94
95fn validate(value: &str, origin: AuditBaseOrigin) -> Result<(), AuditBaseError> {
96 fallow_engine::validate::validate_git_ref(value)
97 .map(|_| ())
98 .map_err(|reason| AuditBaseError::InvalidRef {
99 origin,
100 value: value.to_string(),
101 reason,
102 })
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn the_override_is_trimmed_and_an_empty_one_is_unset() {
111 assert_eq!(parse_audit_base_override(None), None);
112 assert_eq!(parse_audit_base_override(Some(String::new())), None);
113 assert_eq!(parse_audit_base_override(Some(" ".to_string())), None);
114 assert_eq!(
115 parse_audit_base_override(Some(" origin/main ".to_string())),
116 Some("origin/main".to_string())
117 );
118 }
119
120 #[test]
121 fn an_explicit_ref_wins_over_the_override() {
122 let resolved = resolve_audit_base_with_override(
123 Path::new("."),
124 Some("main"),
125 Some("upstream/main".to_string()),
126 )
127 .expect("explicit ref resolves");
128 assert_eq!(resolved.git_ref, "main");
129 assert_eq!(resolved.description, None);
130 }
131
132 #[test]
133 fn the_override_names_its_source() {
134 let resolved = resolve_audit_base_with_override(
135 Path::new("."),
136 None,
137 Some("upstream/main".to_string()),
138 )
139 .expect("override resolves");
140 assert_eq!(resolved.git_ref, "upstream/main");
141 assert_eq!(
142 resolved.description.as_deref(),
143 Some("FALLOW_AUDIT_BASE=upstream/main")
144 );
145 }
146
147 #[test]
148 fn an_invalid_override_names_its_origin() {
149 let error = resolve_audit_base_with_override(
150 Path::new("."),
151 None,
152 Some("--upload-pack=evil".to_string()),
153 )
154 .expect_err("an option-like ref is refused");
155 assert!(
156 matches!(
157 error,
158 AuditBaseError::InvalidRef {
159 origin: AuditBaseOrigin::Environment,
160 ..
161 }
162 ),
163 "{error:?}"
164 );
165 }
166}