backhopper_core/model/
release_roots.rs1use std::str::FromStr;
9
10use serde::Serialize;
11use thiserror::Error;
12
13use crate::erlang_mk::MakeVariables;
14use crate::model::app_manifest::ManifestUnreadable;
15use crate::model::names::{ApplicationName, MakeVariableName, RelativePath};
16use crate::model::non_empty::NonEmpty;
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
19pub struct MakefileVariable {
20 pub file_path: RelativePath,
21 pub variable: MakeVariableName,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
27pub enum ReleaseRoots {
28 Apps(NonEmpty<ApplicationName>),
29 Variable(MakefileVariable),
30 Both {
31 apps: NonEmpty<ApplicationName>,
32 variable: MakefileVariable,
33 },
34}
35
36#[derive(Debug, Error, PartialEq, Eq)]
37pub enum ReleaseRootsError {
38 #[error("{file_path} is not present at the tag")]
39 FileMissing { file_path: RelativePath },
40
41 #[error("{variable} in {file_path} cannot be read: {reason:?}")]
42 VariableUnreadable {
43 file_path: RelativePath,
44 variable: MakeVariableName,
45 reason: ManifestUnreadable,
46 },
47
48 #[error("{variable} in {file_path} lists no apps and no literal roots are configured")]
49 NoRoots {
50 file_path: RelativePath,
51 variable: MakeVariableName,
52 },
53
54 #[error("{word:?} in {variable} is not an app name")]
55 NotAnApp {
56 variable: MakeVariableName,
57 word: String,
58 },
59}
60
61impl ReleaseRoots {
62 pub fn read(
63 &self,
64 read_file: impl Fn(&RelativePath) -> Option<String>,
65 ) -> Result<NonEmpty<ApplicationName>, ReleaseRootsError> {
66 match self {
67 Self::Apps(apps) => Ok(apps.clone()),
68 Self::Variable(variable) => {
69 let words = variable_roots(variable, &read_file)?;
70 NonEmpty::try_from(words).map_err(|_| ReleaseRootsError::NoRoots {
71 file_path: variable.file_path.clone(),
72 variable: variable.variable.clone(),
73 })
74 }
75 Self::Both { apps, variable } => {
76 let more = variable_roots(variable, &read_file)?;
77 Ok(NonEmpty::new(
78 apps.first().clone(),
79 apps.iter().skip(1).cloned().chain(more),
80 ))
81 }
82 }
83 }
84}
85
86fn variable_roots(
87 variable: &MakefileVariable,
88 read_file: &impl Fn(&RelativePath) -> Option<String>,
89) -> Result<Vec<ApplicationName>, ReleaseRootsError> {
90 let text = read_file(&variable.file_path).ok_or_else(|| ReleaseRootsError::FileMissing {
91 file_path: variable.file_path.clone(),
92 })?;
93 let variables = MakeVariables::parse(&text);
94 let words = variables.words(&variable.variable).map_err(|reason| {
95 ReleaseRootsError::VariableUnreadable {
96 file_path: variable.file_path.clone(),
97 variable: variable.variable.clone(),
98 reason,
99 }
100 })?;
101 words
102 .iter()
103 .map(|word| {
104 ApplicationName::from_str(word).map_err(|_| ReleaseRootsError::NotAnApp {
105 variable: variable.variable.clone(),
106 word: word.clone(),
107 })
108 })
109 .collect()
110}