Skip to main content

plugins/
options.rs

1// Copyright 2023 Greptime 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 License.
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
15use common_options::plugin_options::{PluginOptionsDeserializer, PluginOptionsSerializer};
16use serde::{Deserialize, Serialize};
17use serde_json::Value;
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct DummyOptions;
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub enum PluginOptions {
24    Dummy(DummyOptions),
25}
26
27pub struct PluginOptionsList(pub Vec<PluginOptions>);
28
29impl PluginOptionsSerializer for PluginOptionsList {
30    fn serialize(&self) -> Result<String, serde_json::Error> {
31        if self.0.is_empty() {
32            Ok(String::new())
33        } else {
34            serde_json::to_string(&self.0)
35        }
36    }
37}
38
39pub struct PluginOptionsDeserializerImpl;
40
41impl PluginOptionsDeserializer<Vec<PluginOptions>> for PluginOptionsDeserializerImpl {
42    fn deserialize(&self, payload: &str) -> Result<Vec<PluginOptions>, serde_json::Error> {
43        if payload.is_empty() {
44            Ok(vec![])
45        } else {
46            let values: Vec<Value> = serde_json::from_str(payload)?;
47            common_options::plugin_options::filter_known_plugin_options(values)
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use std::sync::{Mutex, OnceLock};
55
56    use super::*;
57
58    // The dropped-tag buffer is process-global, so tests that touch it run serialized.
59    fn lock() -> std::sync::MutexGuard<'static, ()> {
60        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
61        LOCK.get_or_init(|| Mutex::new(())).lock().unwrap()
62    }
63
64    fn filter(values: Vec<Value>) -> Result<Vec<PluginOptions>, serde_json::Error> {
65        common_options::plugin_options::filter_known_plugin_options(values)
66    }
67
68    #[test]
69    fn test_drops_unknown_keeps_known() {
70        let _g = lock();
71        // `null` is the serialized form of the unit-struct `DummyOptions`.
72        let payload = r#"[
73            {"Dummy": null},
74            {"SomeEnterpriseThing": {"foo": "bar", "num": 5}},
75            {"AnotherUnknown": {}}
76        ]"#;
77        let values: Vec<Value> = serde_json::from_str(payload).unwrap();
78        let kept = filter(values).unwrap();
79        assert_eq!(kept, vec![PluginOptions::Dummy(DummyOptions)]);
80        // Unknown tags were buffered for deferred logging.
81        assert_eq!(
82            common_options::plugin_options::take_dropped_plugin_warnings(),
83            vec![
84                "SomeEnterpriseThing".to_string(),
85                "AnotherUnknown".to_string()
86            ]
87        );
88    }
89
90    #[test]
91    fn test_unknown_variant_tag_is_not_in_payload_warning() {
92        let _g = lock();
93        // Privacy (#5): only the tag is recorded, never the raw payload.
94        let values: Vec<Value> =
95            serde_json::from_str(r#"[{"SecretPlugin": {"token": "hunter2"}}]"#).unwrap();
96        filter(values).unwrap();
97        let recorded = common_options::plugin_options::take_dropped_plugin_warnings();
98        assert_eq!(recorded, vec!["SecretPlugin".to_string()]);
99    }
100
101    #[test]
102    fn test_known_variant_with_malformed_payload_errors() {
103        let _g = lock();
104        // Regression (#3): a known variant with a bad payload must NOT be
105        // silently dropped.
106        for bad in [r#"[{"Dummy": 1}]"#, r#"[{"Dummy": {"x": 1}}]"#] {
107            let values: Vec<Value> = serde_json::from_str(bad).unwrap();
108            let err = filter(values).unwrap_err();
109            assert!(
110                err.to_string().contains("Dummy"),
111                "expected the error to reference the Dummy variant, got: {err}"
112            );
113        }
114        // Nothing should have been buffered.
115        assert!(common_options::plugin_options::take_dropped_plugin_warnings().is_empty());
116    }
117
118    #[test]
119    fn test_unrecognized_shape_errors() {
120        let _g = lock();
121        // Not a recognizable plugin entry: surface the error.
122        let values: Vec<Value> = serde_json::from_str(r#"[123]"#).unwrap();
123        assert!(filter(values).is_err());
124    }
125
126    #[test]
127    fn test_deserializer_impl_is_lenient_for_unknown() {
128        let _g = lock();
129        let payload = r#"[{"Dummy": null}, {"UnknownPlugin": {"x": 1}}]"#;
130        let kept = PluginOptionsDeserializerImpl.deserialize(payload).unwrap();
131        assert_eq!(kept, vec![PluginOptions::Dummy(DummyOptions)]);
132        common_options::plugin_options::take_dropped_plugin_warnings();
133    }
134
135    #[test]
136    fn test_deserializer_impl_errors_on_bad_known_payload() {
137        let _g = lock();
138        let payload = r#"[{"Dummy": 1}]"#;
139        assert!(PluginOptionsDeserializerImpl.deserialize(payload).is_err());
140    }
141}