1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
use seaplane::api::compute::v2::Formation as FormationModel;

use crate::{
    context::flight::FlightCtx,
    error::{CliError, CliErrorKind, Context, Result},
    ops::{formation::FormationNameId, generate_name},
    printer::Color,
};

pub fn no_matching_flight(flight: &str) -> CliError {
    CliErrorKind::NoMatchingItem(flight.to_string())
        .into_err()
        .context("(hint: or try fetching remote definitions with '")
        .color_context(Color::Green, "seaplane formation fetch-remote")
        .context("')\n")
}

/// Represents the "Source of Truth" i.e. it combines all the CLI options, ENV vars, and config
/// values into a single structure that can be used later to build models for the API or local
/// structs for serializing
///
/// A somewhat counter-intuitive thing about Formations and their models is the there is no
/// "Formation Model" only a "Formation Configuration Model" This is because a "Formation" so to
/// speak is really just a named collection of configurations and info about their traffic
/// weights/activation statuses.
// TODO: we may not want to derive this we implement circular references
#[derive(Debug, Clone)]
pub struct FormationCtx {
    pub name_id: Option<FormationNameId>,
    pub launch: bool,
    pub remote: bool,
    pub local: bool,
    pub flights: Vec<FlightCtx>,
    pub gateway_flight: Option<String>,
    // Used internally to pass already gathered DB indices between operations
    pub indices: Option<Vec<usize>>,
}

impl Default for FormationCtx {
    fn default() -> Self {
        Self {
            name_id: None,
            launch: false,
            remote: false,
            local: true,
            flights: Vec::new(),
            indices: None,
            gateway_flight: None,
        }
    }
}

impl FormationCtx {
    /// Creates a new seaplane::api::compute::v2::Formation from the contained values
    pub fn model(&self) -> Result<FormationModel> {
        // Create the new Formation model from the CLI inputs
        let mut f_model = FormationModel::builder();

        if let Some(FormationNameId::Name(name)) = &self.name_id {
            f_model = f_model.name(name);
        } else {
            f_model = f_model.name(generate_name());
        }

        for flight in &self.flights {
            f_model = f_model.add_flight(flight.model());
        }

        if let Some(gw) = &self.gateway_flight {
            f_model = f_model.gateway_flight(gw);
        }

        // TODO: probably match and check errors
        f_model.build().map_err(Into::into)
    }
}