terraform_wrapper/commands/
output.rs1use std::collections::HashMap;
2use std::fmt;
3
4use crate::Terraform;
5use crate::command::TerraformCommand;
6use crate::error::Result;
7use crate::exec;
8
9#[derive(Debug, Clone)]
16pub enum OutputResult {
17 Raw(String),
19 #[cfg(feature = "json")]
21 Json(HashMap<String, crate::types::output::OutputValue>),
22 #[cfg(feature = "json")]
24 Single(crate::types::output::OutputValue),
25 Plain(exec::CommandOutput),
27}
28
29impl fmt::Display for OutputResult {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 OutputResult::Raw(s) => write!(f, "{s}"),
33 #[cfg(feature = "json")]
34 OutputResult::Single(v) => {
35 let pretty = serde_json::to_string_pretty(v).map_err(|_| fmt::Error)?;
36 write!(f, "{pretty}")
37 }
38 #[cfg(feature = "json")]
39 OutputResult::Json(map) => {
40 let pretty = serde_json::to_string_pretty(map).map_err(|_| fmt::Error)?;
41 write!(f, "{pretty}")
42 }
43 OutputResult::Plain(output) => write!(f, "{output}"),
44 }
45 }
46}
47
48#[derive(Debug, Clone, Default)]
70pub struct OutputCommand {
71 name: Option<String>,
72 json: bool,
73 raw: bool,
74 raw_args: Vec<String>,
75}
76
77impl OutputCommand {
78 #[must_use]
80 pub fn new() -> Self {
81 Self::default()
82 }
83
84 #[must_use]
86 pub fn name(mut self, name: &str) -> Self {
87 self.name = Some(name.to_string());
88 self
89 }
90
91 #[must_use]
93 pub fn json(mut self) -> Self {
94 self.json = true;
95 self
96 }
97
98 #[must_use]
100 pub fn raw(mut self) -> Self {
101 self.raw = true;
102 self
103 }
104
105 #[must_use]
107 pub fn arg(mut self, arg: impl Into<String>) -> Self {
108 self.raw_args.push(arg.into());
109 self
110 }
111}
112
113impl TerraformCommand for OutputCommand {
114 type Output = OutputResult;
115
116 fn args(&self) -> Vec<String> {
117 let mut args = vec!["output".to_string()];
118 if self.json {
119 args.push("-json".to_string());
120 }
121 if self.raw {
122 args.push("-raw".to_string());
123 }
124 args.extend(self.raw_args.clone());
125 if let Some(ref name) = self.name {
126 args.push(name.clone());
127 }
128 args
129 }
130
131 async fn execute(&self, tf: &Terraform) -> Result<OutputResult> {
132 let output = exec::run_terraform(tf, self.args()).await?;
133
134 if self.raw {
135 return Ok(OutputResult::Raw(output.stdout.trim_end().to_string()));
136 }
137
138 #[cfg(feature = "json")]
139 if self.json {
140 if self.name.is_some() {
141 let value: crate::types::output::OutputValue = serde_json::from_str(&output.stdout)
142 .map_err(|e| crate::error::Error::Json {
143 message: "failed to parse output json".to_string(),
144 source: e,
145 })?;
146 return Ok(OutputResult::Single(value));
147 }
148 let values: HashMap<String, crate::types::output::OutputValue> =
149 serde_json::from_str(&output.stdout).map_err(|e| crate::error::Error::Json {
150 message: "failed to parse output json".to_string(),
151 source: e,
152 })?;
153 return Ok(OutputResult::Json(values));
154 }
155
156 Ok(OutputResult::Plain(output))
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn default_args() {
166 let cmd = OutputCommand::new();
167 assert_eq!(cmd.args(), vec!["output"]);
168 }
169
170 #[test]
171 fn json_all_outputs() {
172 let cmd = OutputCommand::new().json();
173 assert_eq!(cmd.args(), vec!["output", "-json"]);
174 }
175
176 #[test]
177 fn raw_named_output() {
178 let cmd = OutputCommand::new().name("public_ip").raw();
179 let args = cmd.args();
180 assert_eq!(args, vec!["output", "-raw", "public_ip"]);
181 }
182
183 #[test]
184 fn json_named_output() {
185 let cmd = OutputCommand::new().name("vpc_id").json();
186 let args = cmd.args();
187 assert_eq!(args, vec!["output", "-json", "vpc_id"]);
188 }
189
190 #[test]
191 fn name_at_end() {
192 let cmd = OutputCommand::new().name("endpoint").arg("-no-color");
193 let args = cmd.args();
194 assert_eq!(args.last().unwrap(), "endpoint");
196 }
197
198 #[test]
199 fn display_raw() {
200 let result = OutputResult::Raw("10.0.1.5".to_string());
201 assert_eq!(result.to_string(), "10.0.1.5");
202 }
203
204 #[test]
205 fn display_plain() {
206 let output = exec::CommandOutput {
207 stdout: "some output\n".to_string(),
208 stderr: String::new(),
209 exit_code: 0,
210 success: true,
211 };
212 let result = OutputResult::Plain(output);
213 assert_eq!(result.to_string(), "some output");
214 }
215
216 #[cfg(feature = "json")]
217 #[test]
218 fn display_single() {
219 let value = crate::types::output::OutputValue {
220 sensitive: false,
221 output_type: serde_json::json!("string"),
222 value: serde_json::json!("10.0.1.5"),
223 };
224 let result = OutputResult::Single(value);
225 let displayed = result.to_string();
226 assert!(displayed.contains("\"value\": \"10.0.1.5\""));
227 assert!(displayed.contains("\"sensitive\": false"));
228 }
229
230 #[cfg(feature = "json")]
231 #[test]
232 fn display_json_map() {
233 let mut map = HashMap::new();
234 map.insert(
235 "ip".to_string(),
236 crate::types::output::OutputValue {
237 sensitive: false,
238 output_type: serde_json::json!("string"),
239 value: serde_json::json!("1.2.3.4"),
240 },
241 );
242 let result = OutputResult::Json(map);
243 let displayed = result.to_string();
244 assert!(displayed.contains("\"ip\""));
245 assert!(displayed.contains("\"value\": \"1.2.3.4\""));
246 }
247}