ephemeris_forge_github/plan/
variables.rs1use std::collections::BTreeMap;
2
3use ephemeris_forge::{Collection, FieldDiff, Limitation, Mode, Report};
4
5use crate::api::model::Variable;
6use crate::api::request::VariableScope;
7use crate::live::VariablesView;
8use crate::operation::variables::Operation;
9use crate::{Environments, Variables};
10
11pub(super) fn plan(
12 config: Option<&Variables>,
13 environments: Option<&Environments>,
14 live: &VariablesView<'_>,
15 report: &mut Report,
16) -> Vec<Operation> {
17 let Some(config) = config else {
18 return Vec::new();
19 };
20 let mut operations = scope(
21 &VariableScope::Repository,
22 &config.repository,
23 Some(live.repository),
24 config.mode,
25 report,
26 );
27 for (environment, declared) in &config.environments {
28 let planned = environments
29 .is_some_and(|environments| environments.environments.contains_key(environment));
30 operations.extend(scope(
31 &VariableScope::Environment(environment.to_string()),
32 declared,
33 live.environments
34 .get(environment.as_str())
35 .map(Vec::as_slice)
36 .or_else(|| planned.then_some(&[])),
37 config.mode,
38 report,
39 ));
40 }
41 operations
42}
43
44fn scope(
45 scope: &VariableScope,
46 declared: &BTreeMap<String, String>,
47 live: Option<&[Variable]>,
48 mode: Mode,
49 report: &mut Report,
50) -> Vec<Operation> {
51 let prefix = match scope {
52 VariableScope::Repository => String::new(),
53 VariableScope::Environment(environment) => format!("environment {environment} "),
54 };
55 let Some(live) = live else {
56 if !declared.is_empty() {
57 report
58 .limitations
59 .push(Limitation::VariablesWithoutEnvironment {
60 environment: match scope {
61 VariableScope::Repository => None,
62 VariableScope::Environment(environment) => Some(environment.clone()),
63 },
64 });
65 }
66 return Vec::new();
67 };
68 let wanted: BTreeMap<String, &String> = declared
69 .iter()
70 .map(|(name, value)| (name.to_ascii_uppercase(), value))
71 .collect();
72 Collection {
73 noun: &format!("{prefix}variable"),
74 mode,
75 }
76 .plan(
77 &wanted,
78 live,
79 report,
80 |variable, name| variable.name == name,
81 |name, value, actual, _| {
82 let mut diff = FieldDiff::default();
83 let changed = diff.compare("value", actual.map(|variable| &variable.value), value);
84 let (scope, name, value) = (scope.clone(), name.to_owned(), (*value).clone());
85 let operation = match actual {
86 _ if !changed => None,
87 Some(_) => Some(Operation::Update { scope, name, value }),
88 None => Some(Operation::Create { scope, name, value }),
89 };
90 (diff, operation.into_iter().collect())
91 },
92 |variable, _| {
93 Some((
94 variable.name.clone(),
95 Operation::Delete {
96 scope: scope.clone(),
97 name: variable.name.clone(),
98 },
99 ))
100 },
101 )
102}