1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use crate::is_trace_level_enabled;
use cfg_if::cfg_if;
use plugx_input::Input;
use std::collections::HashMap;
use std::fmt::{Debug, Display};
use thiserror::Error;

#[cfg(feature = "env-parser")]
pub mod env;
#[cfg(feature = "json")]
pub mod json;
#[cfg(feature = "toml")]
pub mod toml;
#[cfg(feature = "yaml")]
pub mod yaml;

pub mod closure;

pub type BoxedModifierFn =
    Box<dyn Fn(&Input) -> anyhow::Result<Option<HashMap<String, Input>>> + Send + Sync>;
const MODIFIER_FN_DEBUG: &str = stringify!(
    Box<dyn Fn(&mut Input) -> Result<()>, ConfigurationParserError>>
);

#[derive(Debug, Error)]
pub enum ConfigurationParserError {
    #[error("{parser} with supported formats {supported_format_list:?} could not parse `{data}`")]
    Parse {
        data: String,
        parser: String,
        supported_format_list: Vec<String>,
        #[source]
        source: anyhow::Error,
    },
    #[error("Could not found parser")]
    ParserNotFound,
}

pub trait ConfigurationParser: Send + Sync + Debug + Display {
    fn maybe_get_modifier(&self) -> Option<&BoxedModifierFn> {
        None
    }

    fn supported_format_list(&self) -> Vec<String>;

    fn parse_and_modify_with_logging(
        &self,
        bytes: &[u8],
    ) -> Result<Input, ConfigurationParserError> {
        self.parse_with_logging(bytes).and_then(|mut input| {
            if let Some(modifier) = self.maybe_get_modifier() {
                let maybe_input_clone = if is_trace_level_enabled!() {
                    Some(input.clone())
                } else {
                    None
                };
                modifier(&mut input).map_err(|error| ConfigurationParserError::Parse {
                    data: String::from_utf8_lossy(bytes).to_string(),
                    parser: self.to_string(),
                    supported_format_list: self.supported_format_list().to_vec(),
                    source: error,
                })?;
                if let Some(old_input) = maybe_input_clone {
                    if input != old_input {
                        cfg_if! {
                            if #[cfg(feature = "tracing")] {
                                tracing::trace!(
                                    parser = self.to_string(),
                                    supported_format_list = ?self.supported_format_list(),
                                    from = old_input.to_string(),
                                    to = input.to_string(),
                                    "Modified parsed contents"
                                );
                            } else if #[cfg(feature = "logging")] {
                                log::trace!(
                                    "parser=\"{self}\" supported_format_list={:?}, from={:?} to={:?} message=\"Modified parsed contents\"",
                                    self.supported_format_list(),
                                    old_input.to_string(),
                                    input.to_string(),
                                );
                            }
                        }
                    }
                }
            };
            Ok(input)
        })
    }

    fn parse_with_logging(&self, bytes: &[u8]) -> Result<Input, ConfigurationParserError> {
        self.parse(bytes)
            .map(|input| {
                if is_trace_level_enabled!() {
                    cfg_if! {
                        if #[cfg(feature = "tracing")] {
                            tracing::trace!(
                                parser = self.to_string(),
                                supported_format_list = ?self.supported_format_list(),
                                from = String::from_utf8_lossy(bytes).to_string(),
                                to = input.to_string(),
                                "Parsed contents"
                            );
                        } else if #[cfg(feature = "logging")] {
                            log::trace!(
                                "parser=\"{self}\" supported_format_list={:?}, from={:?} to={:?} message=\"Parsed contents\"",
                                self.supported_format_list(),
                                String::from_utf8_lossy(bytes).to_string(),
                                input.to_string(),
                            );
                        }
                    }
                };
                input
            })
            .map_err(|error| ConfigurationParserError::Parse {
                data: String::from_utf8_lossy(bytes).to_string(),
                parser: self.to_string(),
                supported_format_list: self.supported_format_list().to_vec(),
                source: error,
            })
    }

    fn parse_and_modify(&self, bytes: &[u8]) -> anyhow::Result<Input> {
        self.parse(bytes).and_then(|mut input| {
            if let Some(modifier) = self.maybe_get_modifier() {
                let _ = modifier(&mut input)?;
            };
            Ok(input)
        })
    }

    fn parse(&self, bytes: &[u8]) -> anyhow::Result<Input>;

    fn is_format_supported(&self, bytes: &[u8]) -> Option<bool>;
}