systemd_lsp/
constants.rs

1use std::collections::HashMap;
2
3pub struct SystemdConstants;
4
5impl SystemdConstants {
6    pub fn valid_sections() -> Vec<&'static str> {
7        include_str!("../docs/sections.txt").lines().collect()
8    }
9
10    /*
11    // Lists containing valid directives for each section
12    // this is stored in docs and _should_ be generated from
13    // from the parent documentation to keep it up to date
14    // and not prone to human error.
15    */
16    pub fn section_directives() -> HashMap<&'static str, Vec<&'static str>> {
17        let mut map = HashMap::new();
18
19        map.insert(
20            "Unit",
21            include_str!("../docs/directives/unit.txt")
22                .lines()
23                .collect(),
24        );
25        map.insert(
26            "Service",
27            include_str!("../docs/directives/service.txt")
28                .lines()
29                .collect(),
30        );
31        map.insert(
32            "Install",
33            include_str!("../docs/directives/install.txt")
34                .lines()
35                .collect(),
36        );
37        map.insert(
38            "Timer",
39            include_str!("../docs/directives/timer.txt")
40                .lines()
41                .collect(),
42        );
43        map.insert(
44            "Socket",
45            include_str!("../docs/directives/socket.txt")
46                .lines()
47                .collect(),
48        );
49        map.insert(
50            "Mount",
51            include_str!("../docs/directives/mount.txt")
52                .lines()
53                .collect(),
54        );
55        map.insert(
56            "Path",
57            include_str!("../docs/directives/path.txt")
58                .lines()
59                .collect(),
60        );
61        map.insert(
62            "Swap",
63            include_str!("../docs/directives/swap.txt")
64                .lines()
65                .collect(),
66        );
67        map.insert(
68            "Automount",
69            include_str!("../docs/directives/automount.txt")
70                .lines()
71                .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
72                .collect(),
73        );
74        map.insert(
75            "Slice",
76            include_str!("../docs/directives/slice.txt")
77                .lines()
78                .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
79                .collect(),
80        );
81        map.insert(
82            "Scope",
83            include_str!("../docs/directives/scope.txt")
84                .lines()
85                .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
86                .collect(),
87        );
88        map.insert(
89            "Container",
90            include_str!("../docs/directives/container.txt")
91                .lines()
92                .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
93                .collect(),
94        );
95        map.insert(
96            "Pod",
97            include_str!("../docs/directives/pod.txt")
98                .lines()
99                .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
100                .collect(),
101        );
102        map.insert(
103            "Volume",
104            include_str!("../docs/directives/volume.txt")
105                .lines()
106                .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
107                .collect(),
108        );
109        map.insert(
110            "Network",
111            include_str!("../docs/directives/network.txt")
112                .lines()
113                .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
114                .collect(),
115        );
116        map.insert(
117            "Kube",
118            include_str!("../docs/directives/kube.txt")
119                .lines()
120                .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
121                .collect(),
122        );
123        map.insert(
124            "Build",
125            include_str!("../docs/directives/build.txt")
126                .lines()
127                .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
128                .collect(),
129        );
130        map.insert(
131            "Image",
132            include_str!("../docs/directives/image.txt")
133                .lines()
134                .filter(|line| !line.starts_with('#') && !line.trim().is_empty())
135                .collect(),
136        );
137
138        map
139    }
140
141    pub fn directive_descriptions() -> HashMap<(&'static str, &'static str), &'static str> {
142        let mut map = HashMap::new();
143
144        // Unit directives
145        map.insert(
146            ("Unit", "Description"),
147            include_str!("../docs/directives/unit/description.txt"),
148        );
149        map.insert(
150            ("Unit", "Wants"),
151            include_str!("../docs/directives/unit/wants.txt"),
152        );
153
154        // Service directives
155        map.insert(
156            ("Service", "Type"),
157            include_str!("../docs/directives/service/type.txt"),
158        );
159        map.insert(
160            ("Service", "ExecStart"),
161            include_str!("../docs/directives/service/execstart.txt"),
162        );
163
164        // Install directives
165        map.insert(
166            ("Install", "WantedBy"),
167            include_str!("../docs/directives/install/wantedby.txt"),
168        );
169
170        map
171    }
172
173    pub fn valid_values() -> HashMap<&'static str, &'static [&'static str]> {
174        let mut map = HashMap::new();
175
176        // Note: Type= is now handled context-sensitively in valid_values_for_section()
177        map.insert(
178            "Restart",
179            &[
180                "no",
181                "on-success",
182                "on-failure",
183                "on-abnormal",
184                "on-watchdog",
185                "on-abort",
186                "always",
187            ] as &[&str],
188        );
189        map.insert(
190            "ProtectSystem",
191            &["true", "false", "strict", "full", "yes", "no"] as &[&str],
192        );
193        map.insert(
194            "ProtectHome",
195            &["true", "false", "read-only", "tmpfs", "yes", "no"] as &[&str],
196        );
197
198        // Boolean values for security directives
199        let boolean_values = &["true", "false", "yes", "no", "1", "0"] as &[&str];
200        map.insert("NoNewPrivileges", boolean_values);
201        map.insert("PrivateDevices", boolean_values);
202        map.insert("PrivateNetwork", boolean_values);
203        map.insert("PrivateUsers", boolean_values);
204        map.insert("PrivateMounts", boolean_values);
205        map.insert("ProtectKernelTunables", boolean_values);
206        map.insert("ProtectKernelModules", boolean_values);
207        map.insert("ProtectKernelLogs", boolean_values);
208        map.insert("ProtectControlGroups", boolean_values);
209        map.insert("RestrictRealtime", boolean_values);
210        map.insert("RestrictSUIDSGID", boolean_values);
211        map.insert("RemoveIPC", boolean_values);
212        map.insert("DynamicUser", boolean_values);
213        map.insert("MountAPIVFS", boolean_values);
214        map.insert("LockPersonality", boolean_values);
215        map.insert("MemoryDenyWriteExecute", boolean_values);
216        map.insert("ProtectHostname", boolean_values);
217        map.insert("ProtectClock", boolean_values);
218        map.insert("PrivatePIDs", boolean_values);
219
220        // PrivateTmp with additional 'disconnected' value
221        map.insert(
222            "PrivateTmp",
223            &["true", "false", "yes", "no", "1", "0", "disconnected"] as &[&str],
224        );
225
226        // DevicePolicy values
227        map.insert("DevicePolicy", &["auto", "closed", "strict"] as &[&str]);
228
229        // ProtectProc values
230        map.insert(
231            "ProtectProc",
232            &["noaccess", "invisible", "ptraceable", "default"] as &[&str],
233        );
234
235        // NotifyAccess values
236        map.insert("NotifyAccess", &["none", "main", "exec", "all"] as &[&str]);
237
238        let standard_io_values = &[
239            "inherit",
240            "null",
241            "tty",
242            "journal",
243            "kmsg",
244            "journal+console",
245            "kmsg+console",
246            "file:",
247            "append:",
248            "truncate:",
249            "socket",
250        ] as &[&str];
251        map.insert("StandardOutput", standard_io_values);
252        map.insert("StandardError", standard_io_values);
253
254        map
255    }
256
257    pub fn valid_values_for_section(
258        section: &str,
259        directive: &str,
260    ) -> Option<&'static [&'static str]> {
261        match (section, directive) {
262            ("Service", "Type") => Some(&[
263                "simple",
264                "exec",
265                "forking",
266                "oneshot",
267                "dbus",
268                "notify",
269                "notify-reload",
270                "idle",
271            ]),
272            ("Mount", "Type") => Some(&[
273                "ext4", "ext3", "ext2", "xfs", "btrfs", "vfat", "ntfs", "exfat", "iso9660",
274                "tmpfs", "proc", "sysfs", "devpts", "nfs", "nfs4", "cifs", "sshfs", "bind",
275                "overlay", "squashfs", "fuse", "none", "auto",
276            ]),
277            _ => {
278                // Fall back to global valid_values for other directives
279                let global_values = Self::valid_values();
280                global_values.get(directive).copied()
281            }
282        }
283    }
284
285    pub fn section_documentation() -> HashMap<&'static str, &'static str> {
286        let mut map = HashMap::new();
287
288        // Use comprehensive markdown files for detailed documentation
289        map.insert("Unit", include_str!("../docs/unit.md"));
290        map.insert("Service", include_str!("../docs/service.md"));
291        map.insert("Install", include_str!("../docs/install.md"));
292        map.insert("Timer", include_str!("../docs/timer.md"));
293        map.insert("Socket", include_str!("../docs/socket.md"));
294        map.insert("Mount", include_str!("../docs/mount.md"));
295        map.insert("Path", include_str!("../docs/path.md"));
296        map.insert("Swap", include_str!("../docs/swap.md"));
297        map.insert("Container", include_str!("../docs/container.md"));
298        map.insert("Pod", include_str!("../docs/pod.md"));
299        map.insert("Volume", include_str!("../docs/volume.md"));
300        map.insert("Network", include_str!("../docs/network.md"));
301        map.insert("Kube", include_str!("../docs/kube.md"));
302        map.insert("Build", include_str!("../docs/build.md"));
303        map.insert("Image", include_str!("../docs/image.md"));
304
305        map.insert("Automount", include_str!("../docs/automount.md"));
306        map.insert("Slice", include_str!("../docs/slice.md"));
307        map.insert("Scope", include_str!("../docs/scope.md"));
308
309        map
310    }
311
312    /// Get shared documentation files (exec, kill, resource-control)
313    /// These contain directives that are shared across multiple section types
314    pub fn shared_documentation() -> HashMap<&'static str, &'static str> {
315        let mut map = HashMap::new();
316        map.insert("exec", include_str!("../docs/exec.md"));
317        map.insert("kill", include_str!("../docs/kill.md"));
318        map.insert("resource-control", include_str!("../docs/resource-control.md"));
319        map
320    }
321
322    /// Returns the list of shared documentation keys that apply to a given section
323    /// This mapping determines which additional directive sets are available in each section
324    pub fn section_shared_docs(section: &str) -> Vec<&'static str> {
325        match section.to_lowercase().as_str() {
326            "service" => vec!["exec", "kill", "resource-control"],
327            "socket" => vec!["exec", "kill", "resource-control"],
328            "mount" => vec!["exec", "kill", "resource-control"],
329            "swap" => vec!["exec", "kill", "resource-control"],
330            "scope" => vec!["kill", "resource-control"],
331            "slice" => vec!["resource-control"],
332            _ => vec![],
333        }
334    }
335
336    pub const APP_NAME: &'static str = "systemdls";
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn test_valid_sections_not_empty() {
345        let sections = SystemdConstants::valid_sections();
346        assert!(!sections.is_empty());
347
348        // Check that common sections are present
349        assert!(sections.contains(&"Unit"));
350        assert!(sections.contains(&"Service"));
351        assert!(sections.contains(&"Install"));
352    }
353
354    #[test]
355    fn test_section_directives_contains_expected_sections() {
356        let directives = SystemdConstants::section_directives();
357
358        // Check that main sections are present
359        assert!(directives.contains_key("Unit"));
360        assert!(directives.contains_key("Service"));
361        assert!(directives.contains_key("Install"));
362        assert!(directives.contains_key("Timer"));
363        assert!(directives.contains_key("Socket"));
364        assert!(directives.contains_key("Mount"));
365        assert!(directives.contains_key("Path"));
366        assert!(directives.contains_key("Swap"));
367        assert!(directives.contains_key("Automount"));
368        assert!(directives.contains_key("Slice"));
369        assert!(directives.contains_key("Scope"));
370    }
371
372    #[test]
373    fn test_section_directives_contain_valid_directives() {
374        let directives = SystemdConstants::section_directives();
375
376        // Test Unit section directives
377        let unit_directives = directives.get("Unit").unwrap();
378        assert!(!unit_directives.is_empty());
379        assert!(unit_directives.contains(&"Description"));
380
381        // Test Service section directives
382        let service_directives = directives.get("Service").unwrap();
383        assert!(!service_directives.is_empty());
384        assert!(service_directives.contains(&"Type"));
385        assert!(service_directives.contains(&"ExecStart"));
386
387        // Test Install section directives
388        let install_directives = directives.get("Install").unwrap();
389        assert!(!install_directives.is_empty());
390        assert!(install_directives.contains(&"WantedBy"));
391    }
392
393    #[test]
394    fn test_directive_descriptions_contain_expected_entries() {
395        let descriptions = SystemdConstants::directive_descriptions();
396
397        // Test that key directive descriptions exist
398        assert!(descriptions.contains_key(&("Unit", "Description")));
399        assert!(descriptions.contains_key(&("Unit", "Wants")));
400        assert!(descriptions.contains_key(&("Service", "Type")));
401        assert!(descriptions.contains_key(&("Service", "ExecStart")));
402        assert!(descriptions.contains_key(&("Install", "WantedBy")));
403
404        // Test that descriptions are not empty
405        assert!(!descriptions[&("Unit", "Description")].is_empty());
406        assert!(!descriptions[&("Service", "Type")].is_empty());
407    }
408
409    #[test]
410    fn test_valid_values_for_type_directive() {
411        // Test Service Type directive
412        let service_type_values =
413            SystemdConstants::valid_values_for_section("Service", "Type").unwrap();
414        assert!(service_type_values.contains(&"simple"));
415        assert!(service_type_values.contains(&"exec"));
416        assert!(service_type_values.contains(&"forking"));
417        assert!(service_type_values.contains(&"oneshot"));
418        assert!(service_type_values.contains(&"dbus"));
419        assert!(service_type_values.contains(&"notify"));
420        assert!(service_type_values.contains(&"idle"));
421
422        // Test Mount Type directive
423        let mount_type_values =
424            SystemdConstants::valid_values_for_section("Mount", "Type").unwrap();
425        assert!(mount_type_values.contains(&"ext4"));
426        assert!(mount_type_values.contains(&"exfat"));
427        assert!(mount_type_values.contains(&"ntfs"));
428        assert!(mount_type_values.contains(&"tmpfs"));
429    }
430
431    #[test]
432    fn test_valid_values_for_restart_directive() {
433        let valid_values = SystemdConstants::valid_values();
434
435        let restart_values = valid_values.get("Restart").unwrap();
436        assert!(restart_values.contains(&"no"));
437        assert!(restart_values.contains(&"on-success"));
438        assert!(restart_values.contains(&"on-failure"));
439        assert!(restart_values.contains(&"always"));
440    }
441
442    #[test]
443    fn test_valid_values_for_boolean_directives() {
444        let valid_values = SystemdConstants::valid_values();
445
446        let boolean_directives = [
447            "NoNewPrivileges",
448            "PrivateTmp",
449            "PrivateDevices",
450            "PrivateNetwork",
451            "DynamicUser",
452        ];
453
454        for directive in &boolean_directives {
455            let values = valid_values.get(directive).unwrap();
456            assert!(values.contains(&"true"));
457            assert!(values.contains(&"false"));
458            assert!(values.contains(&"yes"));
459            assert!(values.contains(&"no"));
460            assert!(values.contains(&"1"));
461            assert!(values.contains(&"0"));
462        }
463    }
464
465    #[test]
466    fn test_valid_values_for_standard_io() {
467        let valid_values = SystemdConstants::valid_values();
468
469        let standard_output = valid_values.get("StandardOutput").unwrap();
470        assert!(standard_output.contains(&"inherit"));
471        assert!(standard_output.contains(&"null"));
472        assert!(standard_output.contains(&"journal"));
473        assert!(standard_output.contains(&"file:"));
474
475        let standard_error = valid_values.get("StandardError").unwrap();
476        assert!(standard_error.contains(&"inherit"));
477        assert!(standard_error.contains(&"null"));
478        assert!(standard_error.contains(&"journal"));
479    }
480
481    #[test]
482    fn test_section_documentation_not_empty() {
483        let docs = SystemdConstants::section_documentation();
484
485        // Check that main sections have documentation
486        assert!(docs.contains_key("Unit"));
487        assert!(docs.contains_key("Service"));
488        assert!(docs.contains_key("Install"));
489        assert!(docs.contains_key("Automount"));
490        assert!(docs.contains_key("Slice"));
491        assert!(docs.contains_key("Scope"));
492
493        // Check that documentation is not empty
494        assert!(!docs["Unit"].is_empty());
495        assert!(!docs["Service"].is_empty());
496        assert!(!docs["Install"].is_empty());
497        assert!(!docs["Automount"].is_empty());
498        assert!(!docs["Slice"].is_empty());
499        assert!(!docs["Scope"].is_empty());
500    }
501
502    #[test]
503    fn test_app_name_constant() {
504        assert_eq!(SystemdConstants::APP_NAME, "systemdls");
505    }
506
507    #[test]
508    fn test_protect_system_values() {
509        let valid_values = SystemdConstants::valid_values();
510        let protect_system = valid_values.get("ProtectSystem").unwrap();
511
512        assert!(protect_system.contains(&"true"));
513        assert!(protect_system.contains(&"false"));
514        assert!(protect_system.contains(&"strict"));
515        assert!(protect_system.contains(&"full"));
516        assert!(protect_system.contains(&"yes"));
517        assert!(protect_system.contains(&"no"));
518    }
519
520    #[test]
521    fn test_protect_home_values() {
522        let valid_values = SystemdConstants::valid_values();
523        let protect_home = valid_values.get("ProtectHome").unwrap();
524
525        assert!(protect_home.contains(&"true"));
526        assert!(protect_home.contains(&"false"));
527        assert!(protect_home.contains(&"read-only"));
528        assert!(protect_home.contains(&"tmpfs"));
529        assert!(protect_home.contains(&"yes"));
530        assert!(protect_home.contains(&"no"));
531    }
532}