hq_rs/
delete.rs

1//! use the [`hcl-edit`][hcl_edit] crate to remove values from HCL documents
2
3use std::error::Error;
4
5use hcl_edit::{
6    structure::{Body, Structure},
7    visit_mut::VisitMut,
8};
9
10use crate::parser::Field;
11
12struct HclDeleter {
13    fields: Vec<Field>,
14    current_index: usize,
15    current: Option<Field>,
16    error: Option<Box<dyn Error>>,
17}
18
19impl HclDeleter {
20    fn new(fields: Vec<Field>) -> Self {
21        let current = fields.first().cloned();
22        HclDeleter {
23            fields,
24            current_index: 0,
25            current,
26            error: None,
27        }
28    }
29
30    fn next_field(&mut self) {
31        self.current_index += 1;
32        self.current = self.fields.get(self.current_index).cloned();
33    }
34
35    fn previous_field(&mut self) {
36        self.current_index -= 1;
37        self.current = self.fields.get(self.current_index).cloned();
38    }
39
40    fn should_remove(&self) -> bool {
41        self.current_index >= self.fields.len() - 1
42    }
43}
44
45impl VisitMut for HclDeleter {
46    fn visit_body_mut(&mut self, node: &mut Body) {
47        if let Some(current) = self.current.clone() {
48            let mut matching_attr_keys = Vec::new();
49            let mut matching_block_idents = Vec::new();
50            for item in node.iter() {
51                match item {
52                    Structure::Attribute(attr) => {
53                        if attr.key.as_str() == current.name {
54                            matching_attr_keys.push(attr.key.to_string());
55                        }
56                    }
57                    Structure::Block(block) => {
58                        if block.ident.as_str() == current.name {
59                            if current.labels.is_empty() {
60                                matching_block_idents.push(block.ident.to_string());
61                            } else {
62                                for filter_label in &current.labels {
63                                    for block_label in &block.labels {
64                                        if block_label.as_str() == filter_label {
65                                            matching_block_idents.push(block.ident.to_string());
66                                        }
67                                    }
68                                }
69                            }
70                        }
71                    }
72                }
73            }
74
75            for key in matching_attr_keys {
76                if self.should_remove() {
77                    node.remove_attribute(&key);
78                } else {
79                    self.next_field();
80                    // Key was gotten iterating over the node, so it must be a non-None value.
81                    self.visit_attr_mut(node.get_attribute_mut(&key).unwrap());
82                    self.previous_field();
83                }
84            }
85
86            for ident in matching_block_idents {
87                if self.should_remove() {
88                    node.remove_blocks(&ident);
89                } else {
90                    for block in node.get_blocks_mut(&ident) {
91                        self.next_field();
92                        self.visit_block_mut(block);
93                        self.previous_field();
94                    }
95                }
96            }
97        }
98    }
99
100    fn visit_object_mut(&mut self, node: &mut hcl_edit::expr::Object) {
101        if let Some(current) = self.current.clone() {
102            let mut matches = Vec::new();
103            for (key, _) in node.iter() {
104                // some objects are keyed with an Ident
105                if let Some(id) = key.as_ident() {
106                    if id.as_str() == current.name {
107                        matches.push(key.clone());
108                    }
109                }
110                // some objects are keyed with a String Expression
111                if let Some(expr) = key.as_expr() {
112                    if let Some(expr) = expr.as_str() {
113                        if expr == current.name {
114                            matches.push(key.clone());
115                        }
116                    }
117                }
118            }
119
120            for key in matches {
121                if self.should_remove() {
122                    node.remove(&key);
123                } else if let Some(val) = node.get_mut(&key) {
124                    // If we haven't reached the end of the query, we need to traverse further into
125                    // the AST to determine what needs to be deleted.
126                    self.next_field();
127                    self.visit_object_value_mut(val);
128                    self.previous_field();
129                } else {
130                    // Every key in this vec was gotten by iterating over this object, so the value
131                    // should exist and this branch should not be reachable.
132                    unreachable!();
133                }
134            }
135        }
136    }
137}
138
139/// given a vector of [`Field`]s, delete the [`Expression`] value that matches that filter
140pub fn delete(fields: Vec<Field>, body: &mut Body) -> Result<(), Box<dyn Error>> {
141    let mut visitor = HclDeleter::new(fields);
142    visitor.visit_body_mut(body);
143    if let Some(err) = visitor.error {
144        return Err(err);
145    }
146    Ok(())
147}