Skip to main content

nginx_discovery/ast/
mod.rs

1//! Abstract Syntax Tree (AST) types for NGINX configurations
2//!
3//! This module provides the core AST types that represent parsed NGINX configurations.
4//!
5//! # Examples
6//!
7//! ```rust
8//! use nginx_discovery::ast::*;
9//!
10//! // Create a simple directive: user nginx;
11//! let user_directive = Directive::simple("user", vec!["nginx".to_string()]);
12//!
13//! // Create a server block
14//! let server = Directive::block(
15//!     "server",
16//!     vec![],
17//!     vec![
18//!         Directive::simple("listen", vec!["80".to_string()]),
19//!         Directive::simple("server_name", vec!["example.com".to_string()]),
20//!     ],
21//! );
22//!
23//! // Create a config
24//! let config = Config::with_directives(vec![user_directive, server]);
25//! ```
26
27mod directive;
28mod span;
29mod value;
30
31pub use directive::{Directive, DirectiveItem};
32pub use span::{Span, Spanned};
33pub use value::Value;
34
35/// Root configuration node
36///
37/// Represents a complete NGINX configuration file or a logical section.
38#[derive(Debug, Clone, PartialEq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40pub struct Config {
41    /// Top-level directives
42    pub directives: Vec<Directive>,
43}
44
45impl Config {
46    /// Create a new empty configuration
47    ///
48    /// # Examples
49    ///
50    /// ```rust
51    /// use nginx_discovery::ast::Config;
52    ///
53    /// let config = Config::new();
54    /// assert!(config.directives.is_empty());
55    /// ```
56    #[must_use]
57    pub fn new() -> Self {
58        Self {
59            directives: Vec::new(),
60        }
61    }
62
63    /// Create a configuration with directives
64    ///
65    /// # Examples
66    ///
67    /// ```rust
68    /// use nginx_discovery::ast::{Config, Directive};
69    ///
70    /// let directives = vec![
71    ///     Directive::simple("user", vec!["nginx".to_string()]),
72    /// ];
73    /// let config = Config::with_directives(directives);
74    /// assert_eq!(config.directives.len(), 1);
75    /// ```
76    #[must_use]
77    pub fn with_directives(directives: Vec<Directive>) -> Self {
78        Self { directives }
79    }
80
81    /// Add a directive to the configuration
82    pub fn add_directive(&mut self, directive: Directive) {
83        self.directives.push(directive);
84    }
85
86    /// Find all directives with a given name (non-recursive)
87    ///
88    /// # Examples
89    ///
90    /// ```rust
91    /// use nginx_discovery::ast::{Config, Directive};
92    ///
93    /// let config = Config::with_directives(vec![
94    ///     Directive::simple("user", vec!["nginx".to_string()]),
95    ///     Directive::simple("worker_processes", vec!["auto".to_string()]),
96    /// ]);
97    ///
98    /// let users = config.find_directives("user");
99    /// assert_eq!(users.len(), 1);
100    /// ```
101    #[must_use]
102    pub fn find_directives(&self, name: &str) -> Vec<&Directive> {
103        self.directives
104            .iter()
105            .filter(|d| d.name() == name)
106            .collect()
107    }
108
109    /// Find all directives with a given name (mutable, non-recursive)
110    pub fn find_directives_mut(&mut self, name: &str) -> Vec<&mut Directive> {
111        self.directives
112            .iter_mut()
113            .filter(|d| d.name() == name)
114            .collect()
115    }
116
117    /// Recursively find all directives with a given name
118    ///
119    /// This searches through the entire configuration tree, including
120    /// directives nested in blocks.
121    ///
122    /// # Examples
123    ///
124    /// ```rust
125    /// use nginx_discovery::ast::{Config, Directive};
126    ///
127    /// let server = Directive::block(
128    ///     "server",
129    ///     vec![],
130    ///     vec![
131    ///         Directive::simple("access_log", vec!["/var/log/nginx/access.log".to_string()]),
132    ///     ],
133    /// );
134    ///
135    /// let config = Config::with_directives(vec![
136    ///     Directive::simple("access_log", vec!["/var/log/nginx/main.log".to_string()]),
137    ///     server,
138    /// ]);
139    ///
140    /// let logs = config.find_directives_recursive("access_log");
141    /// assert_eq!(logs.len(), 2); // Found both
142    /// ```
143    #[must_use]
144    pub fn find_directives_recursive(&self, name: &str) -> Vec<&Directive> {
145        let mut result = Vec::new();
146        self.find_directives_recursive_impl(name, &mut result);
147        result
148    }
149
150    fn find_directives_recursive_impl<'a>(&'a self, name: &str, result: &mut Vec<&'a Directive>) {
151        for directive in &self.directives {
152            // Check if current directive matches
153            if directive.name() == name {
154                result.push(directive);
155            }
156            // Recursively search in children if this is a block
157            if let Some(children) = directive.children() {
158                for child in children {
159                    Self::find_directive_recursive(child, name, result);
160                }
161            }
162        }
163    }
164
165    // Helper to recursively search within a single directive
166    fn find_directive_recursive<'a>(
167        directive: &'a Directive,
168        name: &str,
169        result: &mut Vec<&'a Directive>,
170    ) {
171        if directive.name() == name {
172            result.push(directive);
173        }
174        if let Some(children) = directive.children() {
175            for child in children {
176                Self::find_directive_recursive(child, name, result);
177            }
178        }
179    }
180
181    /// Count total number of directives (including nested)
182    #[must_use]
183    pub fn count_directives(&self) -> usize {
184        let mut count = self.directives.len();
185        for directive in &self.directives {
186            if let Some(children) = directive.children() {
187                let child_config = Config {
188                    directives: children.to_vec(),
189                };
190                count += child_config.count_directives();
191            }
192        }
193        count
194    }
195
196    /// Check if the configuration is empty
197    #[must_use]
198    pub fn is_empty(&self) -> bool {
199        self.directives.is_empty()
200    }
201}
202
203impl Default for Config {
204    fn default() -> Self {
205        Self::new()
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_config_new() {
215        let config = Config::new();
216        assert!(config.directives.is_empty());
217        assert!(config.is_empty());
218    }
219
220    #[test]
221    fn test_config_with_directives() {
222        let directives = vec![
223            Directive::simple("user", vec!["nginx".to_string()]),
224            Directive::simple("worker_processes", vec!["auto".to_string()]),
225        ];
226        let config = Config::with_directives(directives);
227        assert_eq!(config.directives.len(), 2);
228        assert!(!config.is_empty());
229    }
230
231    #[test]
232    fn test_config_add_directive() {
233        let mut config = Config::new();
234        config.add_directive(Directive::simple("user", vec!["nginx".to_string()]));
235        assert_eq!(config.directives.len(), 1);
236    }
237
238    #[test]
239    fn test_find_directives() {
240        let config = Config::with_directives(vec![
241            Directive::simple("user", vec!["nginx".to_string()]),
242            Directive::simple("worker_processes", vec!["auto".to_string()]),
243            Directive::simple("user", vec!["www-data".to_string()]),
244        ]);
245
246        let users = config.find_directives("user");
247        assert_eq!(users.len(), 2);
248    }
249
250    #[test]
251    fn test_find_directives_recursive() {
252        let location = Directive::block(
253            "location",
254            vec!["/".to_string()],
255            vec![Directive::simple(
256                "access_log",
257                vec!["/var/log/1.log".to_string()],
258            )],
259        );
260
261        let server = Directive::block("server", vec![], vec![location]);
262
263        let config = Config::with_directives(vec![
264            Directive::simple("access_log", vec!["/var/log/main.log".to_string()]),
265            server,
266        ]);
267
268        let logs = config.find_directives_recursive("access_log");
269        assert_eq!(logs.len(), 2);
270    }
271
272    #[test]
273    fn test_count_directives() {
274        let location = Directive::block(
275            "location",
276            vec!["/".to_string()],
277            vec![Directive::simple(
278                "proxy_pass",
279                vec!["http://backend".to_string()],
280            )],
281        );
282
283        let server = Directive::block("server", vec![], vec![location]);
284
285        let config = Config::with_directives(vec![
286            Directive::simple("user", vec!["nginx".to_string()]),
287            server,
288        ]);
289
290        // user + server + location + proxy_pass = 4
291        assert_eq!(config.count_directives(), 4);
292    }
293
294    #[test]
295    fn test_config_default() {
296        let config = Config::default();
297        assert!(config.is_empty());
298    }
299}