Skip to main content

ephemeris_forge_github/plan/
variables.rs

1use 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}