Skip to main content

genja_core/settings/
inventory_loading.rs

1use super::InventoryConfig;
2use crate::inventory::{Defaults, Groups, Hosts};
3use crate::{InventoryFileKind, InventoryLoadError};
4use serde::de::DeserializeOwned;
5
6impl InventoryConfig {
7    /// Loads inventory data from configured file paths.
8    ///
9    /// This method reads and deserializes inventory files (hosts, groups, and defaults)
10    /// based on the paths specified in the `options` field. If a file path is not
11    /// provided for a particular inventory component, a default or empty value is used.
12    ///
13    /// # Returns
14    ///
15    /// Returns a `Result` containing a tuple of:
16    /// * `Hosts` - The loaded hosts inventory. If no hosts file is specified, returns
17    ///   an empty `Hosts` instance.
18    /// * `Option<Groups>` - The loaded groups inventory, or `None` if no groups file
19    ///   is specified.
20    /// * `Option<Defaults>` - The loaded defaults inventory, or `None` if no defaults
21    ///   file is specified.
22    ///
23    /// # Errors
24    ///
25    /// Returns [`InventoryLoadError`] if:
26    /// * Any specified file cannot be read
27    /// * Any file contains invalid JSON or YAML syntax
28    /// * A file has an unsupported format (not .json, .yaml, or .yml)
29    ///
30    /// # Examples
31    ///
32    /// ```no_run
33    /// use genja_core::settings::InventoryConfig;
34    ///
35    /// let config = InventoryConfig::default();
36    /// match config.load_inventory_files() {
37    ///     Ok((hosts, groups, defaults)) => {
38    ///         println!("Loaded {} hosts", hosts.len());
39    ///     }
40    ///     Err(e) => eprintln!("Failed to load inventory: {}", e),
41    /// }
42    /// ```
43    pub fn load_inventory_files(
44        &self,
45    ) -> Result<(Hosts, Option<Groups>, Option<Defaults>), InventoryLoadError> {
46        let hosts = match self.options.hosts_file.as_deref() {
47            Some(path) => Self::load_from_file::<Hosts>(InventoryFileKind::Hosts, path)?,
48            None => Hosts::new(),
49        };
50
51        let groups = match self.options.groups_file.as_deref() {
52            Some(path) => Some(Self::load_from_file::<Groups>(
53                InventoryFileKind::Groups,
54                path,
55            )?),
56            None => None,
57        };
58
59        let defaults = match self.options.defaults_file.as_deref() {
60            Some(path) => Some(Self::load_from_file::<Defaults>(
61                InventoryFileKind::Defaults,
62                path,
63            )?),
64            None => None,
65        };
66
67        Ok((hosts, groups, defaults))
68    }
69
70    /// Loads and deserializes data from a file.
71    ///
72    /// This helper method reads a file from the filesystem and deserializes its contents
73    /// based on the file extension. Supports JSON (.json) and YAML (.yaml, .yml) formats.
74    ///
75    /// # Type Parameters
76    ///
77    /// * `T` - The type to deserialize the file contents into. Must implement `DeserializeOwned`.
78    ///
79    /// # Parameters
80    ///
81    /// * `path` - The file path to read and deserialize. The file extension determines
82    ///   the deserialization format.
83    ///
84    /// # Returns
85    ///
86    /// Returns a `Result` containing the deserialized data of type `T`.
87    ///
88    /// # Errors
89    ///
90    /// Returns [`InventoryLoadError`] if:
91    /// * The file cannot be read (e.g., doesn't exist, permission denied)
92    /// * The file contents cannot be parsed as valid JSON or YAML
93    /// * The file has an unsupported extension (not .json, .yaml, or .yml)
94    fn load_from_file<T>(kind: InventoryFileKind, path: &str) -> Result<T, InventoryLoadError>
95    where
96        T: DeserializeOwned,
97    {
98        let contents = std::fs::read_to_string(path).map_err(|e| InventoryLoadError::Read {
99            kind,
100            path: path.to_string(),
101            message: e.to_string(),
102        })?;
103
104        if path.ends_with(".json") {
105            serde_json::from_str(&contents).map_err(|e| InventoryLoadError::ParseJson {
106                kind,
107                path: path.to_string(),
108                message: e.to_string(),
109            })
110        } else if path.ends_with(".yaml") || path.ends_with(".yml") {
111            serde_yaml::from_str(&contents).map_err(|e| InventoryLoadError::ParseYaml {
112                kind,
113                path: path.to_string(),
114                message: e.to_string(),
115            })
116        } else {
117            Err(InventoryLoadError::UnsupportedFormat {
118                kind,
119                path: path.to_string(),
120            })
121        }
122    }
123}