1extern crate alloc;
11
12use alloc::collections::BTreeMap;
13use alloc::string::{String, ToString};
14use alloc::vec::Vec;
15
16use crate::{ConfigError, Node, Origin};
17
18#[derive(Debug, Clone)]
20pub struct Section<'a> {
21 map: Option<&'a BTreeMap<String, Node>>,
22 prefix: String,
23}
24
25static EMPTY: BTreeMap<String, Node> = BTreeMap::new();
26
27#[must_use]
32pub fn first_origin(node: &Node) -> Origin {
33 fn walk(node: &Node, depth: usize) -> Option<&Origin> {
35 if depth > crate::MAX_NESTING_DEPTH {
36 return None;
37 }
38 match node {
39 Node::Scalar(_, origin) => Some(origin),
40 Node::Seq(items) => items.iter().find_map(|n| walk(n, depth + 1)),
41 Node::Map(map) => map.values().find_map(|n| walk(n, depth + 1)),
42 }
43 }
44 walk(node, 0).cloned().unwrap_or(Origin::Default)
45}
46
47fn invalid(setting: String, origin: Origin, message: impl Into<String>) -> ConfigError {
48 ConfigError::Invalid {
49 setting,
50 origin,
51 message: message.into(),
52 }
53}
54
55impl<'a> Section<'a> {
56 pub fn root(node: Option<&'a Node>) -> Result<Self, ConfigError> {
61 Self::of(node, "")
62 }
63
64 pub fn of(node: Option<&'a Node>, prefix: impl Into<String>) -> Result<Self, ConfigError> {
69 let prefix = prefix.into();
70 match node {
71 None => Ok(Self { map: None, prefix }),
72 Some(Node::Map(map)) => Ok(Self {
73 map: Some(map),
74 prefix,
75 }),
76 Some(other) => Err(invalid(
77 prefix,
78 first_origin(other),
79 "expected a map of settings",
80 )),
81 }
82 }
83
84 fn path(&self, key: &str) -> String {
85 if self.prefix.is_empty() {
86 key.to_string()
87 } else {
88 alloc::format!("{}.{key}", self.prefix)
89 }
90 }
91
92 #[must_use]
94 pub fn path_of(&self, key: &str) -> String {
95 self.path(key)
96 }
97
98 #[must_use]
100 pub fn node(&self, key: &str) -> Option<&'a Node> {
101 self.map.unwrap_or(&EMPTY).get(key)
102 }
103
104 pub fn only(&self, allowed: &[&str]) -> Result<(), ConfigError> {
109 for (key, node) in self.map.unwrap_or(&EMPTY) {
110 if !allowed.contains(&key.as_str()) {
111 return Err(invalid(
112 self.path(key),
113 first_origin(node),
114 alloc::format!("unknown setting; known here: {}", allowed.join(", ")),
115 ));
116 }
117 }
118 Ok(())
119 }
120
121 pub fn section(&self, key: &str) -> Result<Section<'a>, ConfigError> {
126 Section::of(self.node(key), self.path(key))
127 }
128
129 pub fn scalar(&self, key: &str) -> Result<Option<(&'a str, &'a Origin)>, ConfigError> {
134 match self.node(key) {
135 None => Ok(None),
136 Some(Node::Scalar(value, origin)) => Ok(Some((value.as_str(), origin))),
137 Some(other) => Err(invalid(
138 self.path(key),
139 first_origin(other),
140 "expected a single value",
141 )),
142 }
143 }
144
145 pub fn string(&self, key: &str) -> Result<Option<String>, ConfigError> {
150 Ok(self.scalar(key)?.map(|(v, _)| v.to_string()))
151 }
152
153 pub fn parse<T: core::str::FromStr>(
159 &self,
160 key: &str,
161 expect: &str,
162 ) -> Result<Option<T>, ConfigError> {
163 match self.scalar(key)? {
164 None => Ok(None),
165 Some((value, origin)) => value.trim().parse::<T>().map(Some).map_err(|_| {
166 invalid(
167 self.path(key),
168 origin.clone(),
169 alloc::format!("got `{value}`, expected {expect}"),
170 )
171 }),
172 }
173 }
174
175 pub fn boolean(&self, key: &str) -> Result<Option<bool>, ConfigError> {
180 match self.scalar(key)? {
181 None => Ok(None),
182 Some((value, origin)) => match value.trim() {
183 "true" | "1" => Ok(Some(true)),
184 "false" | "0" => Ok(Some(false)),
185 other => Err(invalid(
186 self.path(key),
187 origin.clone(),
188 alloc::format!("got `{other}`, expected true/false"),
189 )),
190 },
191 }
192 }
193
194 pub fn choice(&self, key: &str, choices: &[&str]) -> Result<Option<String>, ConfigError> {
199 match self.scalar(key)? {
200 None => Ok(None),
201 Some((value, origin)) => {
202 let value = value.trim();
203 if choices.contains(&value) {
204 Ok(Some(value.to_string()))
205 } else {
206 Err(invalid(
207 self.path(key),
208 origin.clone(),
209 alloc::format!("got `{value}`, expected one of {}", choices.join(", ")),
210 ))
211 }
212 }
213 }
214 }
215
216 pub fn strings(&self, key: &str) -> Result<Option<Vec<String>>, ConfigError> {
222 match self.node(key) {
223 None => Ok(None),
224 Some(Node::Scalar(value, _)) => Ok(Some(alloc::vec![value.clone()])),
225 Some(Node::Seq(items)) => items
226 .iter()
227 .map(|item| match item {
228 Node::Scalar(value, _) => Ok(value.clone()),
229 other => Err(invalid(
230 self.path(key),
231 first_origin(other),
232 "expected a list of values",
233 )),
234 })
235 .collect::<Result<Vec<_>, _>>()
236 .map(Some),
237 Some(other) => Err(invalid(
238 self.path(key),
239 first_origin(other),
240 "expected a list of values",
241 )),
242 }
243 }
244
245 pub fn items(&self, key: &str) -> Result<Vec<(String, &'a Node)>, ConfigError> {
250 match self.node(key) {
251 None => Ok(Vec::new()),
252 Some(Node::Seq(items)) => Ok(items
253 .iter()
254 .enumerate()
255 .map(|(i, item)| (alloc::format!("{}[{i}]", self.path(key)), item))
256 .collect()),
257 Some(other) => Err(invalid(
258 self.path(key),
259 first_origin(other),
260 "expected a list",
261 )),
262 }
263 }
264}
265
266#[must_use]
271pub fn split_topic_shorthand(text: &str) -> (String, Option<String>) {
272 let bytes = text.as_bytes();
273 for (i, b) in bytes.iter().enumerate() {
274 let lone =
275 *b == b':' && bytes.get(i + 1) != Some(&b':') && (i == 0 || bytes[i - 1] != b':');
276 if lone {
277 return (
278 text[..i].trim().to_string(),
279 Some(text[i + 1..].trim().to_string()),
280 );
281 }
282 }
283 (text.trim().to_string(), None)
284}