1pub mod bitbucket_ci;
6pub mod deriver;
7pub mod env;
8pub mod fs;
9pub mod git;
10pub mod github_ci;
11pub mod gitlab_ci;
12pub mod jenkins_ci;
13pub mod selector;
14pub mod travis_ci;
15
16use std::path::Path;
17use std::sync::LazyLock;
18
19use cli_utils::{BoxError, BoxResult};
20use thiserror::Error;
21
22use crate::environment::Environment;
23use crate::var::{Confidence, Key, C_HIGH};
24use crate::{cleanup, std_error, tools, validator, value_conversions};
25
26#[derive(PartialEq, Eq, PartialOrd, Ord)]
27pub enum Hierarchy {
28 Low,
29 Middle,
30 High,
31 Higher,
32 EvenHigher,
33 Top,
34}
35
36static NO_PROPS: LazyLock<Vec<String>> = LazyLock::new(Vec::<String>::new);
37
38#[derive(Error, Debug)]
40pub enum Error {
41 #[error("The value '{low_level_value}' - fetched from the underlying source - was bad: {msg}")]
42 BadLowLevelValue {
43 msg: String,
44 low_level_value: String,
45 },
46
47 #[error(transparent)]
49 ConversionError(#[from] value_conversions::Error),
50
51 #[error(transparent)]
53 IO(#[from] std::io::Error),
54
55 #[error(transparent)]
57 Git(#[from] tools::git::Error),
58
59 #[error(transparent)]
61 Std(#[from] std_error::Error),
62
63 #[error(transparent)]
65 Other(#[from] BoxError),
66}
67
68pub type ConfVal = (Confidence, String);
69pub type RetrieveRes = BoxResult<Option<ConfVal>>;
70
71pub trait VarSource {
72 fn is_usable(&self, environment: &mut Environment) -> bool;
76
77 fn hierarchy(&self) -> Hierarchy;
81
82 fn type_name(&self) -> &'static str;
85
86 fn properties(&self) -> &Vec<String>;
90
91 fn display(&self) -> String {
94 format!("{}{:?}", self.type_name(), self.properties())
95 }
96
97 fn retrieve(&self, environment: &mut Environment, key: Key) -> RetrieveRes;
106
107 fn version_from_build_tag(&self, environment: &mut Environment, key: Key) -> RetrieveRes {
114 assert!(matches!(key, Key::Version));
115 Ok(self
116 .retrieve(environment, Key::BuildTag)?
117 .map(|conf_val| cleanup::conf_version(environment, conf_val))
118 .filter(|conf_val| {
119 if let Ok(validity) = validator::get(key)(environment, &conf_val.1) {
120 validity.is_good()
121 } else {
122 false
123 }
124 }))
125 }
126}
127
128#[must_use]
129pub fn var(
130 environment: &Environment,
131 key: &str,
132 confidence: Confidence,
133) -> Option<(Confidence, String)> {
134 environment
135 .vars
136 .get(key)
137 .map(|val| (confidence, val.clone()))
138}
139
140fn ref_ok_or_err<'t>(refr: &str, part: Option<&'t str>) -> Result<&'t str, Error> {
141 part.ok_or_else(|| Error::BadLowLevelValue {
142 msg: "Invalid git reference, should be 'refs/<TYPE>/<NAME>'".to_owned(),
143 low_level_value: refr.to_owned(),
144 })
145}
146
147fn ref_extract_name_if_type_matches(refr: &str, required_ref_type: &str) -> RetrieveRes {
148 let mut parts = refr.split('/');
149 let extracted_ref_type = ref_ok_or_err(refr, parts.nth(1))?;
150 Ok(if extracted_ref_type == required_ref_type {
151 let branch_name = ref_ok_or_err(refr, parts.next())?;
153 Some((C_HIGH, branch_name.to_owned()))
154 } else {
155 None
156 })
157}
158
159pub fn ref_extract_branch(refr: &str) -> RetrieveRes {
171 ref_extract_name_if_type_matches(refr, "heads")
172}
173
174pub fn ref_extract_tag(refr: &str) -> RetrieveRes {
186 ref_extract_name_if_type_matches(refr, "tags")
187}
188
189fn is_git_repo_root(repo_path: Option<&Path>) -> bool {
190 tools::git::Repo::try_from(repo_path).is_ok()
191}
192
193#[must_use]
194pub fn default_list(repo_path: &Path) -> Vec<Box<dyn VarSource>> {
195 let mut sources: Vec<Box<dyn VarSource>> = vec![];
196 if is_git_repo_root(Some(repo_path)) {
197 sources.push(Box::new(git::VarSource {}));
198 }
199 sources.push(Box::new(fs::VarSource {}));
200 sources.push(Box::new(bitbucket_ci::VarSource {}));
201 sources.push(Box::new(github_ci::VarSource {}));
202 sources.push(Box::new(gitlab_ci::VarSource {}));
203 sources.push(Box::new(jenkins_ci::VarSource {}));
204 sources.push(Box::new(travis_ci::VarSource {}));
205 sources.push(Box::new(env::VarSource {}));
206 sources.push(Box::new(selector::VarSource {}));
207 sources.push(Box::new(deriver::VarSource {}));
208 sources.push(Box::new(deriver::VarSource {}));
211 if log::log_enabled!(log::Level::Trace) {
212 for source in &sources {
213 log::trace!("Registered source {}.", source.display());
214 }
215 }
216 sources
217}