Skip to main content

terraform_wrapper/commands/
fmt.rs

1use crate::Terraform;
2use crate::command::TerraformCommand;
3use crate::error::Result;
4use crate::exec::{self, CommandOutput};
5
6/// Command for formatting Terraform configuration files.
7///
8/// By default, rewrites files in-place. Use `.check()` to only verify
9/// formatting without modifying files, or `.diff()` to display differences.
10///
11/// ```no_run
12/// # async fn example() -> terraform_wrapper::error::Result<()> {
13/// use terraform_wrapper::{Terraform, TerraformCommand};
14/// use terraform_wrapper::commands::fmt::FmtCommand;
15///
16/// let tf = Terraform::builder().working_dir("/tmp/infra").build()?;
17///
18/// // Check formatting without modifying files
19/// let output = FmtCommand::new().check().execute(&tf).await?;
20/// # Ok(())
21/// # }
22/// ```
23#[derive(Debug, Clone, Default)]
24pub struct FmtCommand {
25    check: bool,
26    diff: bool,
27    recursive: bool,
28    write: Option<bool>,
29    raw_args: Vec<String>,
30}
31
32impl FmtCommand {
33    /// Create a new fmt command with default options.
34    #[must_use]
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    /// Check if files are formatted without modifying them (`-check`).
40    ///
41    /// Returns exit code 0 if all files are formatted, exit code 3 if not.
42    #[must_use]
43    pub fn check(mut self) -> Self {
44        self.check = true;
45        self
46    }
47
48    /// Display diffs of formatting changes (`-diff`).
49    #[must_use]
50    pub fn diff(mut self) -> Self {
51        self.diff = true;
52        self
53    }
54
55    /// Process files in subdirectories recursively (`-recursive`).
56    #[must_use]
57    pub fn recursive(mut self) -> Self {
58        self.recursive = true;
59        self
60    }
61
62    /// Control whether to write changes to files (`-write`).
63    #[must_use]
64    pub fn write(mut self, enabled: bool) -> Self {
65        self.write = Some(enabled);
66        self
67    }
68
69    /// Add a raw argument (escape hatch for unsupported options).
70    #[must_use]
71    pub fn arg(mut self, arg: impl Into<String>) -> Self {
72        self.raw_args.push(arg.into());
73        self
74    }
75}
76
77impl TerraformCommand for FmtCommand {
78    type Output = CommandOutput;
79
80    fn args(&self) -> Vec<String> {
81        let mut args = vec!["fmt".to_string()];
82        if self.check {
83            args.push("-check".to_string());
84        }
85        if self.diff {
86            args.push("-diff".to_string());
87        }
88        if self.recursive {
89            args.push("-recursive".to_string());
90        }
91        if let Some(write) = self.write {
92            args.push(format!("-write={write}"));
93        }
94        args.extend(self.raw_args.clone());
95        args
96    }
97
98    async fn execute(&self, tf: &Terraform) -> Result<CommandOutput> {
99        // fmt -check returns exit code 3 when files need formatting
100        exec::run_terraform_allow_exit_codes(tf, self.args(), &[0, 3]).await
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn default_args() {
110        let cmd = FmtCommand::new();
111        assert_eq!(cmd.args(), vec!["fmt"]);
112    }
113
114    #[test]
115    fn check_and_diff() {
116        let cmd = FmtCommand::new().check().diff();
117        let args = cmd.args();
118        assert!(args.contains(&"-check".to_string()));
119        assert!(args.contains(&"-diff".to_string()));
120    }
121
122    #[test]
123    fn recursive() {
124        let cmd = FmtCommand::new().recursive();
125        assert!(cmd.args().contains(&"-recursive".to_string()));
126    }
127
128    #[test]
129    fn write_false() {
130        let cmd = FmtCommand::new().write(false);
131        assert!(cmd.args().contains(&"-write=false".to_string()));
132    }
133}