1#![doc = include_str!("../README.md")]
2
3mod impls;
4mod parse;
5
6pub use parse::{ParseError, parse};
7
8#[cfg(feature = "serde")]
9mod serde;
10
11use std::fmt::{Debug, Display, Formatter};
12
13#[derive(Debug, thiserror::Error)]
15pub enum Error {
16 #[error("configuration source is required")]
18 MissingSource,
19 #[error(transparent)]
21 Parse(#[from] ParseError),
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum OptionValueType {
27 Bool,
28 Integer,
29 Float,
30 String,
31 Map,
32 List,
33}
34
35impl Display for OptionValueType {
36 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
37 f.write_str(match self {
38 Self::Bool => "boolean",
39 Self::Integer => "integer",
40 Self::Float => "float",
41 Self::String => "string",
42 Self::Map => "map",
43 Self::List => "list",
44 })
45 }
46}
47
48#[derive(Debug, Clone, PartialEq)]
50pub enum OptionValue {
51 Bool(bool),
52 Integer(i64),
53 Float(f64),
54 String(String),
55 List(Vec<OptionValue>),
56 Map(Options),
57}
58
59impl OptionValue {
60 pub fn new_map() -> Self {
61 Self::Map(Options::default())
62 }
63
64 pub fn new_list() -> Self {
65 Self::List(Vec::new())
66 }
67
68 pub fn new_string() -> Self {
69 Self::String(String::new())
70 }
71
72 pub fn is_bool(&self) -> bool {
73 matches!(self, Self::Bool(_))
74 }
75
76 pub fn as_bool(&self) -> Option<bool> {
77 match self {
78 Self::Bool(value) => Some(*value),
79 _ => None,
80 }
81 }
82
83 pub fn into_bool(self) -> Option<bool> {
84 match self {
85 Self::Bool(value) => Some(value),
86 _ => None,
87 }
88 }
89
90 pub fn bool_mut(&mut self) -> Option<&mut bool> {
91 match self {
92 Self::Bool(value) => Some(value),
93 _ => None,
94 }
95 }
96
97 pub fn is_integer(&self) -> bool {
98 matches!(self, Self::Integer(_))
99 }
100
101 pub fn as_integer(&self) -> Option<i64> {
102 match self {
103 Self::Integer(value) => Some(*value),
104 _ => None,
105 }
106 }
107
108 pub fn into_integer(self) -> Option<i64> {
109 match self {
110 Self::Integer(value) => Some(value),
111 _ => None,
112 }
113 }
114
115 pub fn integer_mut(&mut self) -> Option<&mut i64> {
116 match self {
117 Self::Integer(value) => Some(value),
118 _ => None,
119 }
120 }
121
122 pub fn is_float(&self) -> bool {
123 matches!(self, Self::Float(_))
124 }
125
126 pub fn as_float(&self) -> Option<f64> {
127 match self {
128 Self::Float(value) => Some(*value),
129 _ => None,
130 }
131 }
132
133 pub fn into_float(self) -> Option<f64> {
134 match self {
135 Self::Float(value) => Some(value),
136 _ => None,
137 }
138 }
139
140 pub fn float_mut(&mut self) -> Option<&mut f64> {
141 match self {
142 Self::Float(value) => Some(value),
143 _ => None,
144 }
145 }
146
147 pub fn is_string(&self) -> bool {
148 matches!(self, Self::String(_))
149 }
150
151 pub fn as_string(&self) -> Option<&String> {
152 match self {
153 Self::String(value) => Some(value),
154 _ => None,
155 }
156 }
157
158 pub fn into_string(self) -> Option<String> {
159 match self {
160 Self::String(value) => Some(value),
161 _ => None,
162 }
163 }
164
165 pub fn string_mut(&mut self) -> Option<&mut String> {
166 match self {
167 Self::String(value) => Some(value),
168 _ => None,
169 }
170 }
171
172 pub fn is_list(&self) -> bool {
173 matches!(self, Self::List(_))
174 }
175
176 pub fn as_list(&self) -> Option<&Vec<OptionValue>> {
177 match self {
178 Self::List(value) => Some(value),
179 _ => None,
180 }
181 }
182
183 pub fn into_list(self) -> Option<Vec<OptionValue>> {
184 match self {
185 Self::List(value) => Some(value),
186 _ => None,
187 }
188 }
189
190 pub fn list_mut(&mut self) -> Option<&mut Vec<OptionValue>> {
191 match self {
192 Self::List(value) => Some(value),
193 _ => None,
194 }
195 }
196
197 pub fn is_map(&self) -> bool {
198 matches!(self, Self::Map(_))
199 }
200
201 pub fn as_map(&self) -> Option<&Options> {
202 match self {
203 Self::Map(value) => Some(value),
204 _ => None,
205 }
206 }
207
208 pub fn into_map(self) -> Option<Options> {
209 match self {
210 Self::Map(value) => Some(value),
211 _ => None,
212 }
213 }
214
215 pub fn map_mut(&mut self) -> Option<&mut Options> {
216 match self {
217 Self::Map(value) => Some(value),
218 _ => None,
219 }
220 }
221
222 pub fn type_name(&self) -> OptionValueType {
223 match self {
224 Self::Bool(_) => OptionValueType::Bool,
225 Self::Integer(_) => OptionValueType::Integer,
226 Self::Float(_) => OptionValueType::Float,
227 Self::String(_) => OptionValueType::String,
228 Self::List(_) => OptionValueType::List,
229 Self::Map(_) => OptionValueType::Map,
230 }
231 }
232}
233
234impl Display for OptionValue {
235 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
236 match self {
237 Self::Bool(value) => write!(f, "{value}"),
238 Self::Integer(value) => write!(f, "{value}"),
239 Self::Float(value) => write!(f, "{value}"),
240 Self::String(value) => write!(f, "{value:?}"),
241 Self::List(value) => {
242 write!(f, "[")?;
243 for (index, inner_value) in value.iter().enumerate() {
244 if index > 0 {
245 write!(f, ", ")?;
246 }
247 write!(f, "{inner_value}")?;
248 }
249 write!(f, "]")
250 }
251 Self::Map(value) => write!(f, "{value}"),
252 }
253 }
254}
255
256#[derive(Debug, Clone, PartialEq, Default)]
258pub struct Options {
259 entries: Vec<(String, OptionValue)>,
260}
261
262impl Options {
263 pub fn new() -> Self {
264 Self::default()
265 }
266
267 pub fn len(&self) -> usize {
268 self.entries.len()
269 }
270
271 pub fn is_empty(&self) -> bool {
272 self.entries.is_empty()
273 }
274
275 pub fn contains_key(&self, key: &str) -> bool {
276 self.entries.iter().any(|(entry_key, _)| entry_key == key)
277 }
278
279 pub fn get(&self, key: &str) -> Option<&OptionValue> {
280 self.entries
281 .iter()
282 .rfind(|(entry_key, _)| entry_key == key)
283 .map(|(_, value)| value)
284 }
285
286 pub fn get_mut(&mut self, key: &str) -> Option<&mut OptionValue> {
287 let index = self
288 .entries
289 .iter()
290 .rposition(|(entry_key, _)| entry_key == key)?;
291 Some(&mut self.entries[index].1)
292 }
293
294 pub fn insert<K: Into<String>, V: Into<OptionValue>>(
295 &mut self,
296 key: K,
297 value: V,
298 ) -> Option<OptionValue> {
299 let key = key.into();
300 let value = value.into();
301 let old = self.remove(&key);
302 self.entries.push((key, value));
303 old
304 }
305
306 pub fn remove(&mut self, key: &str) -> Option<OptionValue> {
307 let index = self
308 .entries
309 .iter()
310 .rposition(|(entry_key, _)| entry_key == key)?;
311 Some(self.entries.remove(index).1)
312 }
313
314 pub fn iter(&self) -> impl Iterator<Item = (&str, &OptionValue)> {
315 self.entries
316 .iter()
317 .map(|(key, value)| (key.as_str(), value))
318 }
319
320 pub fn keys(&self) -> impl Iterator<Item = &str> {
321 self.entries.iter().map(|(key, _)| key.as_str())
322 }
323
324 pub fn values(&self) -> impl Iterator<Item = &OptionValue> {
325 self.entries.iter().map(|(_, value)| value)
326 }
327
328 pub(crate) fn entries(&self) -> &[(String, OptionValue)] {
329 &self.entries
330 }
331}
332
333impl Display for Options {
334 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
335 write!(f, "{{")?;
336 for (index, (key, value)) in self.entries.iter().enumerate() {
337 if index > 0 {
338 write!(f, ", ")?;
339 }
340 write!(f, "{key:?}: {value}")?;
341 }
342 write!(f, "}}")
343 }
344}
345
346#[derive(Debug, Clone, PartialEq)]
348pub struct Source {
349 pub(crate) source: String,
350 pub(crate) options: Options,
351 pub(crate) resource: String,
352 pub(crate) skip_errors: bool,
353 pub(crate) resource_colon: bool,
354}
355
356impl Source {
357 pub fn parse(input: &str) -> Result<Self, ParseError> {
358 parse::parse(input)
359 }
360
361 pub fn source(&self) -> &str {
362 self.source.as_str()
363 }
364
365 pub fn source_mut(&mut self) -> &mut String {
366 &mut self.source
367 }
368
369 pub fn set_source(&mut self, source: impl Into<String>) {
370 self.source = source.into();
371 }
372
373 pub fn options(&self) -> &Options {
374 &self.options
375 }
376
377 pub fn options_mut(&mut self) -> &mut Options {
378 &mut self.options
379 }
380
381 pub fn set_options(&mut self, options: Options) {
382 self.options = options;
383 }
384
385 pub fn set_option<K: Into<String>, V: Into<OptionValue>>(&mut self, key: K, value: V) {
386 self.options.insert(key, value);
387 }
388
389 pub fn resource(&self) -> &str {
390 self.resource.as_str()
391 }
392
393 pub fn resource_mut(&mut self) -> &mut String {
394 &mut self.resource
395 }
396
397 pub fn set_resource(&mut self, resource: impl Into<String>) {
398 self.resource = resource.into();
399 if !self.resource.is_empty() {
400 self.resource_colon = true;
401 }
402 }
403
404 pub fn skip_errors(&self) -> bool {
405 self.skip_errors
406 }
407
408 pub fn set_skip_errors(&mut self, skip_errors: bool) {
409 self.skip_errors = skip_errors;
410 }
411
412 pub fn resource_colon(&self) -> bool {
413 self.resource_colon
414 }
415
416 pub fn set_resource_colon(&mut self, resource_colon: bool) {
417 self.resource_colon = resource_colon;
418 }
419}
420
421#[derive(Debug, Clone, PartialEq, Default)]
423pub struct SourceBuilder {
424 source: Option<String>,
425 options: Options,
426 resource: String,
427 skip_errors: bool,
428 resource_colon: bool,
429}
430
431impl SourceBuilder {
432 pub fn new() -> Self {
433 Self::default()
434 }
435
436 pub fn with_source(mut self, source: impl Into<String>) -> Self {
437 self.source = Some(source.into());
438 self
439 }
440
441 pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
442 self.resource = resource.into();
443 self
444 }
445
446 pub fn with_options(mut self, options: Options) -> Self {
447 self.options = options;
448 self
449 }
450
451 pub fn with_option<K: Into<String>, V: Into<OptionValue>>(mut self, key: K, value: V) -> Self {
452 self.options.insert(key, value);
453 self
454 }
455
456 pub fn with_skip_errors(mut self, skip_errors: bool) -> Self {
457 self.skip_errors = skip_errors;
458 self
459 }
460
461 pub fn with_resource_colon(mut self, resource_colon: bool) -> Self {
462 self.resource_colon = resource_colon;
463 self
464 }
465
466 pub fn build(self) -> Result<Source, Error> {
467 let source = self.source.ok_or(Error::MissingSource)?;
468 if source.is_empty() {
469 return Err(Error::MissingSource);
470 }
471 let resource_colon = self.resource_colon || !self.resource.is_empty();
472 Ok(Source {
473 source,
474 options: self.options,
475 resource: self.resource,
476 skip_errors: self.skip_errors,
477 resource_colon,
478 })
479 }
480}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485
486 #[test]
487 fn builder_requires_source() {
488 let error = SourceBuilder::new().build().unwrap_err();
489 assert!(matches!(error, Error::MissingSource));
490
491 let error = SourceBuilder::new().with_source("").build().unwrap_err();
492 assert!(matches!(error, Error::MissingSource));
493 }
494
495 #[test]
496 fn builder_with_option_and_into_string() {
497 let source = SourceBuilder::new()
498 .with_source("env")
499 .with_resource("")
500 .with_option("prefix", "APP")
501 .with_option("timeout", 30_i64)
502 .with_option("retry", true)
503 .build()
504 .unwrap();
505
506 assert_eq!(source.source(), "env");
507 assert_eq!(source.resource(), "");
508 assert_eq!(
509 source.options().get("prefix"),
510 Some(&OptionValue::String("APP".into()))
511 );
512 assert_eq!(
513 source.options().get("timeout"),
514 Some(&OptionValue::Integer(30))
515 );
516 assert_eq!(
517 source.options().get("retry"),
518 Some(&OptionValue::Bool(true))
519 );
520 }
521
522 #[test]
523 fn options_last_key_wins() {
524 let mut options = Options::new();
525 options.insert("prefix", "OLD");
526 options.insert("prefix", "NEW");
527 assert_eq!(options.len(), 1);
528 assert_eq!(
529 options.get("prefix"),
530 Some(&OptionValue::String("NEW".into()))
531 );
532 }
533
534 #[test]
535 fn option_value_accessors_and_type_name() {
536 let value = OptionValue::from(vec!["a", "b"]);
537 assert!(value.is_list());
538 assert_eq!(value.type_name(), OptionValueType::List);
539 assert_eq!(value.as_list().unwrap().len(), 2);
540
541 let mut map = OptionValue::new_map();
542 map.map_mut()
543 .unwrap()
544 .insert("enabled", OptionValue::from(true));
545 assert_eq!(map.type_name(), OptionValueType::Map);
546 }
547
548 #[test]
549 fn config_source_setters() {
550 let mut source = SourceBuilder::new()
551 .with_source("file")
552 .with_resource("/etc/app")
553 .build()
554 .unwrap();
555
556 source.set_source("http");
557 source.set_resource("https://example.com/config.json");
558 source.set_option("timeout", 5_u32);
559
560 assert_eq!(source.source(), "http");
561 assert_eq!(source.resource(), "https://example.com/config.json");
562 assert_eq!(
563 source.options().get("timeout"),
564 Some(&OptionValue::Integer(5))
565 );
566 }
567}