plugx_config/
entity.rs

1//! Configuration entity for each plugin.
2//!
3//! ### Example usage
4//! ```rust
5//! use std::env::set_var;
6//! use plugx_config::{
7//!     ext::{url::Url, plugx_input::Input},
8//!     entity::ConfigurationEntity,
9//!     loader::{Loader, env::Env as EnvLoader},
10//!     parser::{Parser, env::Env as EnvParser},
11//! };
12//!
13//! let url = "env://".parse::<Url>().expect("Valid URL");
14//! let plugin_name = "foo";
15//! let loader = EnvLoader::new().with_prefix("MY_APP_NAME").with_separator("__");
16//! set_var("MY_APP_NAME__FOO__BAR__BAZ", "3.14");
17//! set_var("MY_APP_NAME__FOO__QUX", "false");
18//! let loaded = loader.load(&url, None, false).unwrap();
19//! let (_, foo_entity) = loaded.iter().find(|(plugin_name, _)| plugin_name == "foo").expect("`foo` plugin config");
20//! // Above `loader` actually does this:
21//! let loader_name = format!("{loader}");
22//! let mut foo_entity2 = ConfigurationEntity::new("MY_APP_NAME__*", url.clone(), plugin_name, loader_name)
23//!     .with_format("env")
24//!     .with_contents("BAR__BAZ=\"3.14\"\nQUX=\"false\"");
25//!
26//! assert_eq!(&foo_entity2, foo_entity);
27//!
28//! // We can pass a list of `ConfigurationParser` to an entity to parse its contents.
29//! let parser = EnvParser::new().with_key_separator("__");
30//! let parser_list: Vec<Box<dyn Parser>> = vec![Box::new(parser)];
31//! let input = foo_entity2.parse_contents_mut(&parser_list).unwrap();
32//! assert_eq!(input.as_map().get("qux").expect("`qux` value"), &false.into());
33//! ```
34use crate::parser::{Error, Parser};
35use plugx_input::Input;
36use std::fmt::{Display, Formatter};
37use url::Url;
38
39/// A configuration entity for each plugin.
40#[derive(Debug, Clone, PartialEq)]
41pub struct ConfigurationEntity {
42    item: String,
43    loader_name: String,
44    url: Url,
45    plugin_name: String,
46    maybe_format: Option<String>,
47    maybe_contents: Option<String>,
48    maybe_parsed: Option<Input>,
49}
50
51impl ConfigurationEntity {
52    /// Constructs a new [ConfigurationEntity].
53    ///
54    /// It's better to set the format (via [Self::set_format] or [Self::with_format]) and if we
55    /// don't and try to parse its  contents, All parsers that support
56    /// [Parser::is_format_supported] method try to validate the
57    /// contents to pick it up for future parsing!
58    pub fn new<I, P, L>(item: I, url: Url, plugin_name: P, loader_name: L) -> Self
59    where
60        I: AsRef<str>,
61        P: AsRef<str>,
62        L: AsRef<str>,
63    {
64        Self {
65            url,
66            item: item.as_ref().to_string(),
67            plugin_name: plugin_name.as_ref().to_string(),
68            loader_name: loader_name.as_ref().to_string(),
69            maybe_format: Default::default(),
70            maybe_contents: Default::default(),
71            maybe_parsed: Default::default(),
72        }
73    }
74
75    pub fn set_format<F: AsRef<str>>(&mut self, format: F) {
76        self.maybe_format = Some(format.as_ref().to_string());
77    }
78
79    pub fn with_format<F: AsRef<str>>(mut self, format: F) -> Self {
80        self.set_format(format);
81        self
82    }
83
84    pub fn set_contents<C: AsRef<str>>(&mut self, contents: C) {
85        self.maybe_contents = Some(contents.as_ref().to_string());
86    }
87
88    pub fn with_contents<C: AsRef<str>>(mut self, contents: C) -> Self {
89        self.set_contents(contents);
90        self
91    }
92
93    pub fn set_parsed_contents<I: Into<Input>>(&mut self, contents: I) {
94        self.maybe_parsed = Some(contents.into());
95    }
96
97    pub fn with_parsed_contents<I: Into<Input>>(mut self, contents: I) -> Self {
98        self.set_parsed_contents(contents);
99        self
100    }
101
102    pub fn item(&self) -> &String {
103        &self.item
104    }
105
106    pub fn item_mut(&mut self) -> &mut String {
107        &mut self.item
108    }
109
110    pub fn url(&self) -> &Url {
111        &self.url
112    }
113
114    pub fn url_mut(&mut self) -> &mut Url {
115        &mut self.url
116    }
117
118    pub fn plugin_name(&self) -> &String {
119        &self.plugin_name
120    }
121
122    pub fn plugin_name_mut(&mut self) -> &mut String {
123        &mut self.plugin_name
124    }
125
126    pub fn maybe_format(&self) -> Option<&String> {
127        self.maybe_format.as_ref()
128    }
129
130    pub fn maybe_format_mut(&mut self) -> &mut Option<String> {
131        &mut self.maybe_format
132    }
133
134    pub fn maybe_contents(&self) -> Option<&String> {
135        self.maybe_contents.as_ref()
136    }
137
138    pub fn maybe_contents_mut(&mut self) -> &mut Option<String> {
139        &mut self.maybe_contents
140    }
141
142    pub fn maybe_parsed_contents(&self) -> Option<&Input> {
143        self.maybe_parsed.as_ref()
144    }
145
146    pub fn maybe_parsed_contents_mut(&mut self) -> &mut Option<Input> {
147        &mut self.maybe_parsed
148    }
149
150    /// We have to call it after calling [Self::set_contents] or [Self::with_contents] and If no
151    /// contents is set, It yields [None] too.
152    pub fn guess_format(&self, parser_list: &[Box<dyn Parser>]) -> Option<String> {
153        let contents = self.maybe_contents()?;
154        let bytes = contents.as_bytes();
155        if let Some(parser) = parser_list
156            .iter()
157            .find(|parser| parser.is_format_supported(bytes).unwrap_or_default())
158        {
159            parser.supported_format_list().iter().last().cloned()
160        } else {
161            None
162        }
163    }
164
165    pub fn parse_contents(&self, parser_list: &[Box<dyn Parser>]) -> Result<Input, Error> {
166        let contents = if let Some(contents) = self.maybe_contents() {
167            contents
168        } else {
169            return Ok(Input::new_map());
170        };
171        let format = if let Some(format) = self.maybe_format() {
172            format.clone()
173        } else if let Some(format) = self.guess_format(parser_list) {
174            format
175        } else {
176            return Err(Error::ParserNotFound {
177                format: "<unknown>".into(),
178            });
179        };
180        if let Some(parser) = parser_list
181            .iter()
182            .find(|parser| parser.supported_format_list().contains(&format))
183        {
184            parser.parse(contents.as_bytes())
185        } else {
186            Err(Error::ParserNotFound { format })
187        }
188    }
189
190    pub fn parse_contents_mut(
191        &mut self,
192        parser_list: &[Box<dyn Parser>],
193    ) -> Result<&mut Input, Error> {
194        let input = self.parse_contents(parser_list)?;
195        self.set_parsed_contents(input);
196        Ok(self
197            .maybe_parsed_contents_mut()
198            .as_mut()
199            .expect("input has been set!"))
200    }
201}
202
203impl Display for ConfigurationEntity {
204    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
205        f.write_str(format!("Configuration entity for {}", self.plugin_name).as_str())
206    }
207}