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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use crate::error::ToolError;
use config::{Config, ConfigError, Environment, File};
use serde::{Deserialize, Serialize};
use std::str::FromStr;

pub const DEFAULT_TERRAFORM_VERSION: &str = "1.5.7";

#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub enum Operators {
    Equals,
    NotEquals,
    Contains,
    NotContains,
}

impl FromStr for Operators {
    type Err = ToolError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "==" => Ok(Operators::Equals),
            "!=" => Ok(Operators::NotEquals),
            "~=" => Ok(Operators::Contains),
            "!~=" => Ok(Operators::NotContains),
            _ => Err(ToolError::InvalidQueryOperator(s.to_string())),
        }
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Variable {
    pub key: String,
    pub operator: Operators,
    pub value: String,
}

impl FromStr for Variable {
    type Err = ToolError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split(':').collect();
        if parts.len() != 3 {
            return Err(ToolError::InvalidVariableQuery(s.to_string()));
        }
        let operator = Operators::from_str(parts[1])?;
        Ok(Variable {
            key: parts[0].to_string(),
            operator,
            value: parts[2].to_string(),
        })
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Tag {
    pub operator: Operators,
    pub name: String,
}

impl FromStr for Tag {
    type Err = ToolError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split(':').collect();
        if parts.len() != 2 {
            return Err(ToolError::InvalidTagQuery(s.to_string()));
        }
        let operator = Operators::from_str(parts[0])?;
        Ok(Tag { operator, name: parts[1].to_string() })
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Pagination {
    pub start_page: String,
    pub max_depth: String,
    pub page_size: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Query {
    pub name: Option<String>,
    pub wildcard_name: Option<String>,
    pub variables: Option<Vec<Variable>>,
    pub tags: Option<Vec<Tag>>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Workspaces {
    pub query: Option<Query>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct Core {
    pub log: String,
    pub token: String,
    pub org: String,
    pub project: Option<String>,
    pub output: String,
    pub save_output: bool,
    pub pagination: Pagination,
    pub workspaces: Workspaces,
    pub terraform_version: String,
}

impl Core {
    pub fn new() -> Result<Self, ConfigError> {
        let s = Config::builder()
            // Set defaults
            .set_default("log", "info".to_string())?
            .set_default("token", "".to_string())?
            .set_default("org", "".to_string())?
            .set_default("output", "report.json".to_string())?
            .set_default("save_output", false)?
            .set_default("pagination.start_page", "1".to_string())?
            .set_default("pagination.max_depth", "1".to_string())?
            .set_default("pagination.page_size", "20".to_string())?
            .set_default(
                "terraform_version",
                DEFAULT_TERRAFORM_VERSION.to_string(),
            )?
            .set_default("workspaces.query", None::<String>)?
            // Start off by merging in the "default" configuration file
            .add_source(File::with_name("settings.toml").required(false))
            // Add in settings from the environment
            // Eg.. `DEBUG=1 ./target/app` would set the `debug` key
            .add_source(Environment::default())
            .build()?;
        s.try_deserialize()
    }
}