Skip to main content

nginx_discovery/types/
server.rs

1//! NGINX server block representation
2//!
3//! This module provides types for representing NGINX `server` blocks,
4//! including listen directives, server names, locations, and associated logs.
5
6// src/types/server.rs
7use crate::types::{AccessLog, ErrorLog, ListenDirective, Location};
8use std::path::PathBuf;
9// ... rest of file
10
11/// Represents an NGINX server block
12#[derive(Debug, Clone, PartialEq)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct Server {
15    /// Server names (from `server_name` directive)
16    pub server_names: Vec<String>,
17
18    /// Listen directives
19    pub listen: Vec<ListenDirective>,
20
21    /// Root directory
22    pub root: Option<PathBuf>,
23
24    /// Location blocks
25    pub locations: Vec<Location>,
26
27    /// Access logs specific to this server
28    pub access_logs: Vec<AccessLog>,
29
30    /// Error logs specific to this server
31    pub error_logs: Vec<ErrorLog>,
32
33    /// Index files
34    pub index: Vec<String>,
35}
36
37impl Default for Server {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl Server {
44    /// Create a new server
45    #[must_use]
46    pub fn new() -> Self {
47        Self {
48            server_names: Vec::new(),
49            listen: Vec::new(),
50            root: None,
51            locations: Vec::new(),
52            access_logs: Vec::new(),
53            error_logs: Vec::new(),
54            index: Vec::new(),
55        }
56    }
57
58    /// Add a server name
59    #[must_use]
60    pub fn with_server_name(mut self, name: impl Into<String>) -> Self {
61        self.server_names.push(name.into());
62        self
63    }
64
65    /// Add a listen directive
66    #[must_use]
67    pub fn with_listen(mut self, listen: ListenDirective) -> Self {
68        self.listen.push(listen);
69        self
70    }
71
72    /// Set root directory
73    #[must_use]
74    pub fn with_root(mut self, root: impl Into<PathBuf>) -> Self {
75        self.root = Some(root.into());
76        self
77    }
78
79    /// Check if server has SSL enabled
80    #[must_use]
81    pub fn has_ssl(&self) -> bool {
82        self.listen.iter().any(|l| l.ssl)
83    }
84
85    /// Check if server is default
86    #[must_use]
87    pub fn is_default_server(&self) -> bool {
88        self.listen.iter().any(|l| l.default_server)
89    }
90
91    /// Get primary server name
92    #[must_use]
93    pub fn primary_name(&self) -> Option<&str> {
94        self.server_names.first().map(String::as_str)
95    }
96
97    /// Add an index file
98    #[must_use]
99    pub fn with_index(mut self, index: impl Into<String>) -> Self {
100        self.index.push(index.into());
101        self
102    }
103
104    /// Add a location block
105    #[must_use]
106    pub fn with_location(mut self, location: Location) -> Self {
107        self.locations.push(location);
108        self
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use crate::types::LocationModifier;
116
117    #[test]
118    fn test_server_new() {
119        let server = Server::new();
120        assert!(server.server_names.is_empty());
121        assert!(server.listen.is_empty());
122        assert!(server.root.is_none());
123        assert!(server.locations.is_empty());
124        assert!(server.access_logs.is_empty());
125        assert!(server.error_logs.is_empty());
126        assert!(server.index.is_empty());
127    }
128
129    #[test]
130    fn test_server_default() {
131        let server = Server::default();
132        assert!(server.server_names.is_empty());
133        assert!(server.listen.is_empty());
134    }
135
136    #[test]
137    fn test_with_server_name() {
138        let server = Server::new()
139            .with_server_name("example.com")
140            .with_server_name("www.example.com");
141
142        assert_eq!(server.server_names.len(), 2);
143        assert_eq!(server.server_names[0], "example.com");
144        assert_eq!(server.server_names[1], "www.example.com");
145    }
146
147    #[test]
148    fn test_with_listen() {
149        let listen = ListenDirective::new("0.0.0.0", 80);
150        let server = Server::new().with_listen(listen.clone());
151
152        assert_eq!(server.listen.len(), 1);
153        assert_eq!(server.listen[0].port, 80);
154        assert_eq!(server.listen[0].address, "0.0.0.0");
155    }
156
157    #[test]
158    fn test_with_root() {
159        let server = Server::new().with_root("/var/www/html");
160
161        assert!(server.root.is_some());
162        assert_eq!(server.root.unwrap(), PathBuf::from("/var/www/html"));
163    }
164
165    #[test]
166    fn test_with_index() {
167        let server = Server::new()
168            .with_index("index.html")
169            .with_index("index.php");
170
171        assert_eq!(server.index.len(), 2);
172        assert_eq!(server.index[0], "index.html");
173        assert_eq!(server.index[1], "index.php");
174    }
175
176    #[test]
177    fn test_with_location() {
178        let location = Location::new("/", LocationModifier::None);
179        let server = Server::new().with_location(location);
180
181        assert_eq!(server.locations.len(), 1);
182        assert_eq!(server.locations[0].path, "/");
183    }
184
185    #[test]
186    fn test_has_ssl_true() {
187        let mut listen_ssl = ListenDirective::new("0.0.0.0", 443);
188        listen_ssl.ssl = true;
189        let server = Server::new().with_listen(listen_ssl);
190
191        assert!(server.has_ssl());
192    }
193
194    #[test]
195    fn test_has_ssl_false() {
196        let listen = ListenDirective::new("0.0.0.0", 80);
197        let server = Server::new().with_listen(listen);
198
199        assert!(!server.has_ssl());
200    }
201
202    #[test]
203    fn test_has_ssl_mixed() {
204        let listen_http = ListenDirective::new("0.0.0.0", 80);
205        let mut listen_https = ListenDirective::new("0.0.0.0", 443);
206        listen_https.ssl = true;
207
208        let server = Server::new()
209            .with_listen(listen_http)
210            .with_listen(listen_https);
211
212        assert!(server.has_ssl());
213    }
214
215    #[test]
216    fn test_is_default_server_true() {
217        let mut listen = ListenDirective::new("0.0.0.0", 80);
218        listen.default_server = true;
219        let server = Server::new().with_listen(listen);
220
221        assert!(server.is_default_server());
222    }
223
224    #[test]
225    fn test_is_default_server_false() {
226        let listen = ListenDirective::new("0.0.0.0", 80);
227        let server = Server::new().with_listen(listen);
228
229        assert!(!server.is_default_server());
230    }
231
232    #[test]
233    fn test_primary_name_some() {
234        let server = Server::new()
235            .with_server_name("example.com")
236            .with_server_name("www.example.com");
237
238        assert_eq!(server.primary_name(), Some("example.com"));
239    }
240
241    #[test]
242    fn test_primary_name_none() {
243        let server = Server::new();
244        assert_eq!(server.primary_name(), None);
245    }
246
247    #[test]
248    fn test_builder_pattern_complete() {
249        let mut listen = ListenDirective::new("0.0.0.0", 443);
250        listen.ssl = true;
251        let location = Location::new("/api", LocationModifier::None);
252
253        let server = Server::new()
254            .with_server_name("example.com")
255            .with_listen(listen)
256            .with_root("/var/www")
257            .with_index("index.html")
258            .with_location(location);
259
260        assert_eq!(server.server_names.len(), 1);
261        assert_eq!(server.listen.len(), 1);
262        assert!(server.root.is_some());
263        assert_eq!(server.index.len(), 1);
264        assert_eq!(server.locations.len(), 1);
265        assert!(server.has_ssl());
266    }
267}