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)]
351pub struct Source {
352 pub(crate) source: String,
353 pub(crate) options: Options,
354 pub(crate) resource: String,
355 pub(crate) ignore_errors: bool,
356 pub(crate) resource_colon: bool,
357}
358
359impl Source {
360 pub fn parse(input: &str) -> Result<Self, ParseError> {
361 parse::parse(input)
362 }
363
364 pub fn source(&self) -> &str {
365 self.source.as_str()
366 }
367
368 pub fn source_mut(&mut self) -> &mut String {
369 &mut self.source
370 }
371
372 pub fn set_source(&mut self, source: impl Into<String>) {
373 self.source = source.into();
374 }
375
376 pub fn with_source(mut self, source: impl Into<String>) -> Self {
377 self.source = source.into();
378 self
379 }
380
381 pub fn options(&self) -> &Options {
382 &self.options
383 }
384
385 pub fn options_mut(&mut self) -> &mut Options {
386 &mut self.options
387 }
388
389 pub fn set_options(&mut self, options: Options) {
390 self.options = options;
391 }
392
393 pub fn with_options(mut self, options: Options) -> Self {
394 self.options = options;
395 self
396 }
397
398 pub fn set_option<K: Into<String>, V: Into<OptionValue>>(&mut self, key: K, value: V) {
399 self.options.insert(key, value);
400 }
401
402 pub fn with_option<K: Into<String>, V: Into<OptionValue>>(mut self, key: K, value: V) -> Self {
403 self.options.insert(key, value);
404 self
405 }
406
407 pub fn resource(&self) -> &str {
408 self.resource.as_str()
409 }
410
411 pub fn resource_mut(&mut self) -> &mut String {
412 &mut self.resource
413 }
414
415 pub fn set_resource(&mut self, resource: impl Into<String>) {
416 self.resource = resource.into();
417 if !self.resource.is_empty() {
418 self.resource_colon = true;
419 }
420 }
421
422 pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
423 self.resource = resource.into();
424 if !self.resource.is_empty() {
425 self.resource_colon = true;
426 }
427 self
428 }
429
430 pub fn ignore_errors(&self) -> bool {
431 self.ignore_errors
432 }
433
434 pub fn set_ignore_errors(&mut self, ignore_errors: bool) {
435 self.ignore_errors = ignore_errors;
436 }
437
438 pub fn with_ignore_errors(mut self, ignore_errors: bool) -> Self {
439 self.ignore_errors = ignore_errors;
440 self
441 }
442
443 pub fn resource_colon(&self) -> bool {
444 self.resource_colon
445 }
446
447 pub fn set_resource_colon(&mut self, resource_colon: bool) {
448 self.resource_colon = resource_colon;
449 }
450
451 pub fn with_resource_colon(mut self, resource_colon: bool) -> Self {
452 self.resource_colon = resource_colon;
453 self
454 }
455}
456
457#[derive(Debug, Clone, PartialEq, Default)]
459pub struct SourceBuilder {
460 source: Option<String>,
461 options: Options,
462 resource: String,
463 ignore_errors: bool,
464 resource_colon: bool,
465}
466
467impl SourceBuilder {
468 pub fn new() -> Self {
469 Self::default()
470 }
471
472 pub fn with_source(mut self, source: impl Into<String>) -> Self {
473 self.source = Some(source.into());
474 self
475 }
476
477 pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
478 self.resource = resource.into();
479 self
480 }
481
482 pub fn with_options(mut self, options: Options) -> Self {
483 self.options = options;
484 self
485 }
486
487 pub fn with_option<K: Into<String>, V: Into<OptionValue>>(mut self, key: K, value: V) -> Self {
488 self.options.insert(key, value);
489 self
490 }
491
492 pub fn with_ignore_errors(mut self, ignore_errors: bool) -> Self {
493 self.ignore_errors = ignore_errors;
494 self
495 }
496
497 pub fn with_resource_colon(mut self, resource_colon: bool) -> Self {
498 self.resource_colon = resource_colon;
499 self
500 }
501
502 pub fn build(self) -> Result<Source, Error> {
503 let source = self.source.ok_or(Error::MissingSource)?;
504 if source.is_empty() {
505 return Err(Error::MissingSource);
506 }
507 let resource_colon = self.resource_colon || !self.resource.is_empty();
508 Ok(Source {
509 source,
510 options: self.options,
511 resource: self.resource,
512 ignore_errors: self.ignore_errors,
513 resource_colon,
514 })
515 }
516}
517
518#[cfg(test)]
519mod tests {
520 use super::*;
521
522 #[test]
523 fn builder_requires_source() {
524 let error = SourceBuilder::new().build().unwrap_err();
525 assert!(matches!(error, Error::MissingSource));
526
527 let error = SourceBuilder::new().with_source("").build().unwrap_err();
528 assert!(matches!(error, Error::MissingSource));
529 }
530
531 #[test]
532 fn builder_with_option_and_into_string() {
533 let source = SourceBuilder::new()
534 .with_source("env")
535 .with_resource("")
536 .with_option("prefix", "APP")
537 .with_option("timeout", 30_i64)
538 .with_option("retry", true)
539 .build()
540 .unwrap();
541
542 assert_eq!(source.source(), "env");
543 assert_eq!(source.resource(), "");
544 assert_eq!(
545 source.options().get("prefix"),
546 Some(&OptionValue::String("APP".into()))
547 );
548 assert_eq!(
549 source.options().get("timeout"),
550 Some(&OptionValue::Integer(30))
551 );
552 assert_eq!(
553 source.options().get("retry"),
554 Some(&OptionValue::Bool(true))
555 );
556 }
557
558 #[test]
559 fn options_last_key_wins() {
560 let mut options = Options::new();
561 options.insert("prefix", "OLD");
562 options.insert("prefix", "NEW");
563 assert_eq!(options.len(), 1);
564 assert_eq!(
565 options.get("prefix"),
566 Some(&OptionValue::String("NEW".into()))
567 );
568 }
569
570 #[test]
571 fn option_value_accessors_and_type_name() {
572 let value = OptionValue::from(vec!["a", "b"]);
573 assert!(value.is_list());
574 assert_eq!(value.type_name(), OptionValueType::List);
575 assert_eq!(value.as_list().unwrap().len(), 2);
576
577 let mut map = OptionValue::new_map();
578 map.map_mut()
579 .unwrap()
580 .insert("enabled", OptionValue::from(true));
581 assert_eq!(map.type_name(), OptionValueType::Map);
582 }
583
584 #[test]
585 fn config_source_setters() {
586 let mut source = SourceBuilder::new()
587 .with_source("file")
588 .with_resource("/etc/app")
589 .build()
590 .unwrap();
591
592 source.set_source("http");
593 source.set_resource("https://example.com/config.json");
594 source.set_option("timeout", 5_u32);
595
596 assert_eq!(source.source(), "http");
597 assert_eq!(source.resource(), "https://example.com/config.json");
598 assert_eq!(
599 source.options().get("timeout"),
600 Some(&OptionValue::Integer(5))
601 );
602 }
603}