tremor_config/
lib.rs

1// Copyright 2020-2021, The Tremor Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Tremor shared configuration
16
17#![deny(warnings)]
18#![deny(missing_docs)]
19#![recursion_limit = "1024"]
20#![deny(
21    clippy::all,
22    clippy::unwrap_used,
23    clippy::unnecessary_unwrap,
24    clippy::pedantic,
25    clippy::mod_module_files
26)]
27
28use serde::Deserialize;
29use tremor_value::prelude::*;
30
31/// Named key value pair with optional config
32#[derive(Clone, Debug, Default)]
33#[allow(clippy::module_name_repetitions)]
34pub struct NameWithConfig {
35    /// Name
36    pub name: String,
37    /// Config (optional)
38    pub config: Option<Value<'static>>,
39}
40
41impl<'v> serde::Deserialize<'v> for NameWithConfig {
42    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
43    where
44        D: serde::Deserializer<'v>,
45    {
46        // This makes little sense for some reason having three tuple variants is required
47        // where two with optional config should be enough.
48        // They are in the tests but here they are not and we couldn't figure out why :.(
49        #[derive(Deserialize, Debug)]
50        #[serde(bound(deserialize = "'de: 'v, 'v: 'de"), untagged)]
51        enum Variants<'v> {
52            // json: "json"
53            Name(String),
54            // json: { "name": "json", "config": { ... } }
55            NameAndConfig { name: String, config: Value<'v> },
56            // json: { "name": "json" }
57            NameAndNoConfig { name: String },
58        }
59
60        let var = Variants::deserialize(deserializer)?;
61
62        match var {
63            Variants::NameAndConfig { name, config } => Ok(NameWithConfig {
64                name,
65                config: Some(config.into_static()),
66            }),
67            Variants::NameAndNoConfig { name } | Variants::Name(name) => {
68                Ok(NameWithConfig { name, config: None })
69            }
70        }
71    }
72}
73
74impl<'v> TryFrom<&Value<'v>> for NameWithConfig {
75    type Error = Error;
76
77    fn try_from(value: &Value) -> Result<Self, Error> {
78        if let Some(name) = value.as_str() {
79            Ok(Self::from(name))
80        } else if let Some(name) = value.get_str("name") {
81            Ok(Self {
82                name: name.to_string(),
83                config: value.get("config").map(Value::clone_static),
84            })
85        } else {
86            Err(Error::InvalidConfig(value.encode()))
87        }
88    }
89}
90
91/// Error for confdig
92#[derive(Debug, Clone, PartialEq)]
93pub enum Error {
94    /// malformed configuration
95    InvalidConfig(String),
96}
97
98impl std::fmt::Display for Error {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
100        match self {
101            Self::InvalidConfig(v) => write!(f, "Invalid config: {v}"),
102        }
103    }
104}
105
106impl std::error::Error for Error {}
107
108impl From<&str> for NameWithConfig {
109    fn from(name: &str) -> Self {
110        Self {
111            name: name.to_string(),
112            config: None,
113        }
114    }
115}
116impl From<&String> for NameWithConfig {
117    fn from(name: &String) -> Self {
118        name.clone().into()
119    }
120}
121impl From<String> for NameWithConfig {
122    fn from(name: String) -> Self {
123        Self { name, config: None }
124    }
125}
126
127/// Trait for detecting errors in config and the key names are included in errors
128pub trait Impl {
129    /// deserialises the config into a struct and returns nice errors
130    /// this doesn't need to be overwritten in most cases.
131    ///
132    /// # Errors
133    /// if the Configuration is invalid
134    fn new(config: &tremor_value::Value) -> Result<Self, tremor_value::Error>
135    where
136        Self: serde::de::Deserialize<'static>,
137    {
138        tremor_value::structurize(config.clone_static())
139    }
140}
141
142/// A configuration map
143pub type Map = Option<tremor_value::Value<'static>>;
144
145#[cfg(test)]
146mod test {
147    use std::collections::HashMap;
148
149    use super::*;
150    #[test]
151    fn name_with_config() {
152        let v = literal!({"name": "json", "config": {"mode": "sorted"}});
153        let nac = NameWithConfig::deserialize(v).expect("could structurize two element struct");
154        assert_eq!(nac.name, "json");
155        assert!(nac.config.as_object().is_some());
156        let v = literal!({"name": "yaml"});
157        let nac = NameWithConfig::deserialize(v).expect("could structurize one element struct");
158        assert_eq!(nac.name, "yaml");
159        assert_eq!(nac.config, None);
160        let v = literal!("name");
161        let nac = NameWithConfig::deserialize(v).expect("could structurize string");
162        assert_eq!(nac.name, "name");
163        assert_eq!(nac.config, None);
164    }
165
166    #[test]
167    fn name_with_config_in_a_hatemap() {
168        let codec = "json";
169        let data = literal!( {
170            "application/json": {"name": "json", "config": {"mode": "sorted"}},
171            "application/yaml": {"name": "yaml"},
172            "*/*": codec,
173        });
174        let nac = HashMap::<String, NameWithConfig>::deserialize(data)
175            .expect("could structurize two element struct");
176
177        assert_eq!(nac.len(), 3);
178    }
179
180    #[test]
181    fn name_with_config_in_a_hatemap_in_struct() {
182        #[derive(Deserialize, Debug, Clone)]
183        #[serde(deny_unknown_fields)]
184        struct Config {
185            mime_mapping: Option<HashMap<String, NameWithConfig>>,
186        }
187        let codec = "json";
188        let data = literal!({ "mime_mapping": {
189            "application/json": {"name": "json", "config": {"mode": "sorted"}},
190            "application/yaml": {"name": "yaml"},
191            "*/*": codec,
192        }});
193        let nac = Config::deserialize(data).expect("could structurize two element struct");
194
195        assert_eq!(nac.mime_mapping.map(|h| h.len()).unwrap_or_default(), 3);
196    }
197
198    // Test if the invlaid configs give errors
199    #[test]
200    fn name_with_config_invalid() {
201        let v = literal!({"name": "json", "config": {"mode": "sorted"}});
202        let nac = NameWithConfig::try_from(&v).expect("could structurize two element struct");
203        assert_eq!(nac.name, "json");
204        assert!(nac.config.as_object().is_some());
205        let v = literal!({"name": "yaml"});
206        let nac = NameWithConfig::try_from(&v).expect("could structurize one element struct");
207        assert_eq!(nac.name, "yaml");
208        assert_eq!(nac.config, None);
209        let v = literal!("name");
210        let nac = NameWithConfig::try_from(&v).expect("could structurize string");
211        assert_eq!(nac.name, "name");
212        assert_eq!(nac.config, None);
213        let v = literal!({"snot": "yaml"});
214        let nac = NameWithConfig::try_from(&v);
215        assert!(nac.is_err());
216        assert_eq!(
217            nac.err().map(|e| e.to_string()).expect("err"),
218            r#"Invalid config: {"snot":"yaml"}"#
219        );
220    }
221}