Skip to main content

pingap_config/
manager.rs

1// Copyright 2024-2025 Tree xie.
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 crate::PingapConfig;
16use crate::convert_pingap_config;
17use crate::etcd_storage::EtcdStorage;
18use crate::file_storage::FileStorage;
19use crate::storage::{History, Storage};
20use crate::{Category, Error, Observer};
21use arc_swap::ArcSwap;
22use pingap_util::resolve_path;
23use serde::{Deserialize, Deserializer, Serialize, de::DeserializeOwned};
24use std::path::Path;
25use std::sync::Arc;
26use toml::{Value, map::Map};
27
28type Result<T, E = Error> = std::result::Result<T, E>;
29
30pub fn format_category(category: &Category) -> &str {
31    match category {
32        Category::Basic => "basic",
33        Category::Server => "servers",
34        Category::Location => "locations",
35        Category::Upstream => "upstreams",
36        Category::Plugin => "plugins",
37        Category::Certificate => "certificates",
38        Category::Storage => "storages",
39    }
40}
41
42fn to_string_pretty<T>(value: &T) -> Result<String>
43where
44    T: serde::ser::Serialize + ?Sized,
45{
46    toml::to_string_pretty(value).map_err(|e| Error::Ser { source: e })
47}
48
49#[derive(Deserialize, Debug, Serialize)]
50pub struct PingapTomlConfig {
51    pub basic: Option<Value>,
52    pub servers: Option<Map<String, Value>>,
53    pub upstreams: Option<Map<String, Value>>,
54    pub locations: Option<Map<String, Value>>,
55    pub plugins: Option<Map<String, Value>>,
56    pub certificates: Option<Map<String, Value>>,
57    pub storages: Option<Map<String, Value>>,
58}
59
60fn format_item_toml_config(
61    value: Option<Value>,
62    category: &Category,
63    name: &str,
64) -> Result<String> {
65    if let Some(value) = value {
66        let mut wrapper = Map::new();
67        if name.is_empty() {
68            wrapper.insert(format_category(category).to_string(), value);
69        } else {
70            let mut inner = Map::new();
71            inner.insert(name.to_string(), value);
72            wrapper.insert(
73                format_category(category).to_string(),
74                Value::Table(inner),
75            );
76        }
77
78        to_string_pretty(&wrapper)
79    } else {
80        Ok("".to_string())
81    }
82}
83
84impl PingapTomlConfig {
85    pub fn to_toml(&self) -> Result<String> {
86        to_string_pretty(self)
87    }
88    pub fn to_pingap_config(
89        &self,
90        replace_include: bool,
91    ) -> Result<PingapConfig> {
92        let value = self.to_toml()?;
93        convert_pingap_config(value.as_bytes(), replace_include)
94    }
95
96    fn get_toml(&self, category: &Category, name: &str) -> Result<String> {
97        let value = self.get(category, name);
98        format_item_toml_config(value, category, name)
99    }
100    fn get_category_toml(&self, category: &Category) -> Result<String> {
101        let wrapper = |value: Option<Value>| {
102            if let Some(value) = value {
103                let mut wrapper = Map::new();
104                wrapper.insert(format_category(category).to_string(), value);
105                to_string_pretty(&wrapper)
106            } else {
107                Ok("".to_string())
108            }
109        };
110        match category {
111            Category::Basic => wrapper(self.basic.clone()),
112            Category::Server => wrapper(self.servers.clone().map(Value::Table)),
113            Category::Location => {
114                wrapper(self.locations.clone().map(Value::Table))
115            },
116            Category::Upstream => {
117                wrapper(self.upstreams.clone().map(Value::Table))
118            },
119            Category::Plugin => wrapper(self.plugins.clone().map(Value::Table)),
120            Category::Certificate => {
121                wrapper(self.certificates.clone().map(Value::Table))
122            },
123            Category::Storage => {
124                wrapper(self.storages.clone().map(Value::Table))
125            },
126        }
127    }
128    fn update(&mut self, category: &Category, name: &str, value: Value) {
129        let name = name.to_string();
130        match category {
131            Category::Basic => {
132                self.basic = Some(value);
133            },
134            Category::Server => {
135                self.servers.get_or_insert_default().insert(name, value);
136            },
137            Category::Location => {
138                self.locations.get_or_insert_default().insert(name, value);
139            },
140            Category::Upstream => {
141                self.upstreams.get_or_insert_default().insert(name, value);
142            },
143            Category::Plugin => {
144                self.plugins.get_or_insert_default().insert(name, value);
145            },
146            Category::Certificate => {
147                self.certificates
148                    .get_or_insert_default()
149                    .insert(name, value);
150            },
151            Category::Storage => {
152                self.storages.get_or_insert_default().insert(name, value);
153            },
154        };
155    }
156    fn get(&self, category: &Category, name: &str) -> Option<Value> {
157        match category {
158            Category::Basic => self.basic.clone(),
159            Category::Server => self
160                .servers
161                .as_ref()
162                .and_then(|servers| servers.get(name).cloned()),
163            Category::Location => self
164                .locations
165                .as_ref()
166                .and_then(|locations| locations.get(name).cloned()),
167            Category::Upstream => self
168                .upstreams
169                .as_ref()
170                .and_then(|upstreams| upstreams.get(name).cloned()),
171            Category::Plugin => self
172                .plugins
173                .as_ref()
174                .and_then(|plugins| plugins.get(name).cloned()),
175            Category::Certificate => self
176                .certificates
177                .as_ref()
178                .and_then(|certificates| certificates.get(name).cloned()),
179            Category::Storage => self
180                .storages
181                .as_ref()
182                .and_then(|storages| storages.get(name).cloned()),
183        }
184    }
185    fn delete(&mut self, category: &Category, name: &str) {
186        match category {
187            Category::Basic => self.basic = None,
188            Category::Server => {
189                self.servers.get_or_insert_default().remove(name);
190            },
191            Category::Location => {
192                self.locations.get_or_insert_default().remove(name);
193            },
194            Category::Upstream => {
195                self.upstreams.get_or_insert_default().remove(name);
196            },
197            Category::Plugin => {
198                self.plugins.get_or_insert_default().remove(name);
199            },
200            Category::Certificate => {
201                self.certificates.get_or_insert_default().remove(name);
202            },
203            Category::Storage => {
204                self.storages.get_or_insert_default().remove(name);
205            },
206        };
207    }
208}
209
210#[derive(PartialEq, Clone, Debug)]
211pub enum ConfigMode {
212    /// single mode (e.g., pingap.toml)
213    Single,
214    /// multi by type (e.g., servers.toml, locations.toml)
215    MultiByType,
216    /// multi by item (e.g., servers/web.toml)
217    MultiByItem,
218}
219
220static SINGLE_KEY: &str = "pingap.toml";
221
222fn bool_from_str<'de, D>(deserializer: D) -> Result<bool, D::Error>
223where
224    D: Deserializer<'de>,
225{
226    let s: Option<&str> = Deserialize::deserialize(deserializer)?;
227    match s {
228        Some("false") => Ok(false),
229        _ => Ok(true),
230    }
231}
232
233#[derive(Deserialize, Default, Debug)]
234struct ConfigManagerParams {
235    #[serde(default, deserialize_with = "bool_from_str")]
236    separation: bool,
237    #[serde(default)]
238    enable_history: bool,
239}
240
241pub fn new_file_config_manager(path: &str) -> Result<ConfigManager> {
242    let (file, query) = path.split_once('?').unwrap_or((path, ""));
243    let file = resolve_path(file);
244    let filepath = Path::new(&file);
245    let (mode, enable_history) = if filepath.is_dir() {
246        let params: ConfigManagerParams =
247            serde_qs::from_str(query).map_err(|e| Error::Invalid {
248                message: e.to_string(),
249            })?;
250        if params.separation {
251            (ConfigMode::MultiByItem, params.enable_history)
252        } else {
253            (ConfigMode::MultiByType, false)
254        }
255    } else {
256        (ConfigMode::Single, false)
257    };
258
259    let mut storage = FileStorage::new(&file)?;
260    if enable_history {
261        storage.with_history_path(&format!("{file}-history"))?;
262    }
263    Ok(ConfigManager::new(Arc::new(storage), mode))
264}
265
266pub fn new_etcd_config_manager(path: &str) -> Result<ConfigManager> {
267    let storage = EtcdStorage::new(path)?;
268    Ok(ConfigManager::new(
269        Arc::new(storage),
270        ConfigMode::MultiByItem,
271    ))
272}
273
274pub struct ConfigManager {
275    storage: Arc<dyn Storage>,
276    mode: ConfigMode,
277    current_config: ArcSwap<PingapConfig>,
278}
279
280impl ConfigManager {
281    pub fn new(storage: Arc<dyn Storage>, mode: ConfigMode) -> Self {
282        Self {
283            storage,
284            mode,
285            current_config: ArcSwap::from_pointee(PingapConfig::default()),
286        }
287    }
288    pub fn support_observer(&self) -> bool {
289        self.storage.support_observer()
290    }
291    pub async fn observe(&self) -> Result<Observer> {
292        self.storage.observe().await
293    }
294
295    pub fn get_current_config(&self) -> Arc<PingapConfig> {
296        self.current_config.load().clone()
297    }
298    pub fn set_current_config(&self, config: PingapConfig) {
299        self.current_config.store(Arc::new(config));
300    }
301
302    /// get storage key
303    fn get_key(&self, category: &Category, name: &str) -> String {
304        match self.mode {
305            ConfigMode::Single => SINGLE_KEY.to_string(),
306            ConfigMode::MultiByType => {
307                format!("{}.toml", format_category(category))
308            },
309            ConfigMode::MultiByItem => {
310                if *category == Category::Basic {
311                    format!("{}.toml", format_category(category))
312                } else {
313                    format!("{}/{}.toml", format_category(category), name)
314                }
315            },
316        }
317    }
318
319    pub async fn load_all(&self) -> Result<PingapTomlConfig> {
320        let data = self.storage.fetch("").await?;
321        toml::from_str(&data).map_err(|e| Error::De { source: e })
322    }
323    pub async fn save_all(&self, config: &PingapTomlConfig) -> Result<()> {
324        match self.mode {
325            ConfigMode::Single => {
326                self.storage
327                    .save(
328                        &self.get_key(&Category::Basic, ""),
329                        &to_string_pretty(config)?,
330                    )
331                    .await?;
332            },
333            ConfigMode::MultiByType => {
334                for category in [
335                    Category::Basic,
336                    Category::Server,
337                    Category::Location,
338                    Category::Upstream,
339                    Category::Plugin,
340                    Category::Certificate,
341                    Category::Storage,
342                ]
343                .iter()
344                {
345                    let value = config.get_category_toml(category)?;
346                    self.storage
347                        .save(&self.get_key(category, ""), &value)
348                        .await?;
349                }
350            },
351            ConfigMode::MultiByItem => {
352                let basic_config = config.get_toml(&Category::Basic, "")?;
353                self.storage
354                    .save(&self.get_key(&Category::Basic, ""), &basic_config)
355                    .await?;
356
357                for (category, value) in [
358                    (Category::Server, config.servers.clone()),
359                    (Category::Location, config.locations.clone()),
360                    (Category::Upstream, config.upstreams.clone()),
361                    (Category::Plugin, config.plugins.clone()),
362                    (Category::Certificate, config.certificates.clone()),
363                    (Category::Storage, config.storages.clone()),
364                ] {
365                    let Some(value) = value else {
366                        continue;
367                    };
368                    for name in value.keys() {
369                        let value = config.get_toml(&category, name)?;
370                        self.storage
371                            .save(&self.get_key(&category, name), &value)
372                            .await?;
373                    }
374                }
375            },
376        }
377
378        Ok(())
379    }
380    pub async fn update<T: Serialize + Send + Sync>(
381        &self,
382        category: Category,
383        name: &str,
384        value: &T,
385    ) -> Result<()> {
386        let key = self.get_key(&category, name);
387        let value = toml::to_string_pretty(value)
388            .map_err(|e| Error::Ser { source: e })?;
389        // update by item
390        if self.mode == ConfigMode::MultiByItem {
391            let value: Value =
392                toml::from_str(&value).map_err(|e| Error::De { source: e })?;
393            let value = format_item_toml_config(Some(value), &category, name)?;
394            return self.storage.save(&key, &value).await;
395        }
396        // load all config
397        let mut config = self.load_all().await?;
398        let value: Value =
399            toml::from_str(&value).map_err(|e| Error::De { source: e })?;
400        config.update(&category, name, value);
401        // update by type
402        let value = if self.mode == ConfigMode::MultiByType {
403            config.get_category_toml(&category)?
404        } else {
405            to_string_pretty(&config)?
406        };
407
408        self.storage.save(&key, &value).await?;
409        Ok(())
410    }
411    pub async fn get<T: DeserializeOwned + Send>(
412        &self,
413        category: Category,
414        name: &str,
415    ) -> Result<Option<T>> {
416        let key = self.get_key(&category, name);
417        let data = self.storage.fetch(&key).await?;
418        let config: PingapTomlConfig =
419            toml::from_str(&data).map_err(|e| Error::De { source: e })?;
420
421        if let Some(value) = config.get(&category, name) {
422            let value = to_string_pretty(&value)?;
423            let value =
424                toml::from_str(&value).map_err(|e| Error::De { source: e })?;
425            Ok(Some(value))
426        } else {
427            Ok(None)
428        }
429    }
430    pub async fn delete(&self, category: Category, name: &str) -> Result<()> {
431        let key = self.get_key(&category, name);
432
433        let mut current_config = (*self.get_current_config()).clone();
434        current_config.remove(category.to_string().as_str(), name)?;
435
436        if self.mode == ConfigMode::MultiByItem {
437            return self.storage.delete(&key).await;
438        }
439        let mut config = self.load_all().await?;
440        config.delete(&category, name);
441        let value = if self.mode == ConfigMode::MultiByType {
442            config.get_category_toml(&category)?
443        } else {
444            to_string_pretty(&config)?
445        };
446        self.storage.save(&key, &value).await
447    }
448    pub fn support_history(&self) -> bool {
449        self.storage.support_history()
450    }
451    pub async fn history(
452        &self,
453        category: Category,
454        name: &str,
455    ) -> Result<Option<Vec<History>>> {
456        if !self.storage.support_history() {
457            return Ok(None);
458        }
459        let key = self.get_key(&category, name);
460        self.storage.fetch_history(&key).await
461    }
462}
463
464#[cfg(test)]
465mod tests {
466    use super::*;
467    use nanoid::nanoid;
468    use pretty_assertions::assert_eq;
469
470    fn new_pingap_config() -> PingapTomlConfig {
471        let basic_config = r#"auto_restart_check_interval = "1s"
472        name = "pingap"
473        pid_file = "/tmp/pingap.pid"
474        "#;
475
476        let server_config = r#"[server1]
477        addr = "127.0.0.1:8080"
478        locations = ["location1"]
479        threads = 1
480        
481        [server2]
482        addr = "127.0.0.1:8081"
483        locations = ["location2"]
484        threads = 2
485        "#;
486
487        let upstream_config = r#"[upstream1]
488        addrs = ["127.0.0.1:7080"]
489        
490        [upstream2]
491        addrs = ["127.0.0.1:7081"]
492        "#;
493
494        let location_config = r#"[location1]
495        upstream = "upstream1"
496        
497        [location2]
498        upstream = "upstream2"
499        "#;
500
501        let plugin_config = r#"[plugin1]
502        value = "/plugin1"
503        category = "plugin1"
504        
505        [plugin2]
506        value = "/plugin2"
507        category = "plugin2"
508        "#;
509
510        let certificate_config = r#"[certificate1]
511        cert = "/certificate1"
512        key = "/key1"
513        
514        [certificate2]
515        cert = "/certificate2"
516        key = "/key2"
517        "#;
518        let storage_config = r#"[storage1]
519        value = "/storage1"
520        category = "storage1"
521        
522        [storage2]
523        value = "/storage2"
524        category = "storage2"
525        "#;
526
527        PingapTomlConfig {
528            basic: Some(toml::from_str(basic_config).unwrap()),
529            servers: Some(toml::from_str(server_config).unwrap()),
530            upstreams: Some(toml::from_str(upstream_config).unwrap()),
531            locations: Some(toml::from_str(location_config).unwrap()),
532            plugins: Some(toml::from_str(plugin_config).unwrap()),
533            certificates: Some(toml::from_str(certificate_config).unwrap()),
534            storages: Some(toml::from_str(storage_config).unwrap()),
535        }
536    }
537
538    async fn test_config_manger(manager: ConfigManager, mode: ConfigMode) {
539        assert_eq!(true, mode == manager.mode);
540
541        let config = new_pingap_config();
542
543        manager.save_all(&config).await.unwrap();
544
545        // get all data from file
546        let data = manager.storage.fetch("").await.unwrap();
547        let new_config = toml::from_str::<PingapTomlConfig>(&data).unwrap();
548
549        assert_eq!(toml::to_string(&config), toml::to_string(&new_config));
550
551        let current_config = manager.load_all().await.unwrap();
552        assert_eq!(
553            toml::to_string(&config).unwrap(),
554            toml::to_string(&current_config).unwrap()
555        );
556
557        // ----- basic config test start ----- //
558        // get basic config
559        let value: Value =
560            manager.get(Category::Basic, "").await.unwrap().unwrap();
561        assert_eq!(
562            r#"auto_restart_check_interval = "1s"
563name = "pingap"
564pid_file = "/tmp/pingap.pid"
565"#,
566            toml::to_string(&value).unwrap()
567        );
568        // update basic config
569        let new_basic_config: Value = toml::from_str(
570            r#"auto_restart_check_interval = "2s"
571name = "pingap2"
572pid_file = "/tmp/pingap2.pid"
573"#,
574        )
575        .unwrap();
576        manager
577            .update(Category::Basic, "", &new_basic_config)
578            .await
579            .unwrap();
580        // get new basic config
581        let value: Value =
582            manager.get(Category::Basic, "").await.unwrap().unwrap();
583        assert_eq!(
584            toml::to_string(&new_basic_config).unwrap(),
585            toml::to_string(&value).unwrap()
586        );
587        // ----- basic config test end ----- //
588
589        // ----- server config test start ----- //
590        // get server config
591        let value: Value = manager
592            .get(Category::Server, "server1")
593            .await
594            .unwrap()
595            .unwrap();
596        assert_eq!(
597            r#"addr = "127.0.0.1:8080"
598locations = ["location1"]
599threads = 1
600"#,
601            toml::to_string(&value).unwrap()
602        );
603        // update server config
604        let new_server_config: Value = toml::from_str(
605            r#"addr = "192.186.1.1:8080"
606locations = ["location1"]
607threads = 1
608"#,
609        )
610        .unwrap();
611        manager
612            .update(Category::Server, "server2", &new_server_config)
613            .await
614            .unwrap();
615        // get new server config
616        let value: Value = manager
617            .get(Category::Server, "server2")
618            .await
619            .unwrap()
620            .unwrap();
621        assert_eq!(
622            toml::to_string(&new_server_config).unwrap(),
623            toml::to_string(&value).unwrap()
624        );
625        // ----- server config test end ----- //
626
627        // ----- upstream config test start ----- //
628        // get upstream config
629        let value: Value = manager
630            .get(Category::Upstream, "upstream2")
631            .await
632            .unwrap()
633            .unwrap();
634        assert_eq!(
635            r#"addrs = ["127.0.0.1:7081"]
636"#,
637            toml::to_string(&value).unwrap()
638        );
639        // update upstream config
640        let new_upstream_config: Value = toml::from_str(
641            r#"addrs = ["192.168.1.1:7081"]
642"#,
643        )
644        .unwrap();
645        manager
646            .update(Category::Upstream, "upstream2", &new_upstream_config)
647            .await
648            .unwrap();
649
650        // get new upstream config
651        let value: Value = manager
652            .get(Category::Upstream, "upstream2")
653            .await
654            .unwrap()
655            .unwrap();
656        assert_eq!(
657            toml::to_string(&new_upstream_config).unwrap(),
658            toml::to_string(&value).unwrap()
659        );
660
661        // ----- upstream config test end ----- //
662
663        // ----- location config test start ----- //
664        // get location config
665        let value: Value = manager
666            .get(Category::Location, "location2")
667            .await
668            .unwrap()
669            .unwrap();
670        assert_eq!(
671            r#"upstream = "upstream2"
672"#,
673            toml::to_string(&value).unwrap()
674        );
675
676        // update location config
677        let new_location_config: Value = toml::from_str(
678            r#"upstream = "upstream22"
679"#,
680        )
681        .unwrap();
682        manager
683            .update(Category::Location, "location2", &new_location_config)
684            .await
685            .unwrap();
686
687        // get new location config
688        let value: Value = manager
689            .get(Category::Location, "location2")
690            .await
691            .unwrap()
692            .unwrap();
693        assert_eq!(
694            toml::to_string(&new_location_config).unwrap(),
695            toml::to_string(&value).unwrap()
696        );
697
698        // ----- location config test end ----- //
699
700        // ----- plugin config test start ----- //
701        // get plugin config
702        let value: Value = manager
703            .get(Category::Plugin, "plugin2")
704            .await
705            .unwrap()
706            .unwrap();
707        assert_eq!(
708            r#"category = "plugin2"
709value = "/plugin2"
710"#,
711            toml::to_string(&value).unwrap()
712        );
713
714        // update plugin config
715        let new_plugin_config: Value = toml::from_str(
716            r#"category = "plugin22"
717value = "/plugin22"
718"#,
719        )
720        .unwrap();
721        manager
722            .update(Category::Plugin, "plugin2", &new_plugin_config)
723            .await
724            .unwrap();
725        // get new plugin config
726        let value: Value = manager
727            .get(Category::Plugin, "plugin2")
728            .await
729            .unwrap()
730            .unwrap();
731        assert_eq!(
732            toml::to_string(&new_plugin_config).unwrap(),
733            toml::to_string(&value).unwrap()
734        );
735
736        // ----- plugin config test end ----- //
737
738        // ----- certificate config test start ----- //
739        // get certificate config
740        let value: Value = manager
741            .get(Category::Certificate, "certificate2")
742            .await
743            .unwrap()
744            .unwrap();
745        assert_eq!(
746            r#"cert = "/certificate2"
747key = "/key2"
748"#,
749            toml::to_string(&value).unwrap()
750        );
751
752        // update certificate config
753        let new_certificate_config: Value = toml::from_str(
754            r#"cert = "/certificate22"
755key = "/key22"
756"#,
757        )
758        .unwrap();
759        manager
760            .update(
761                Category::Certificate,
762                "certificate2",
763                &new_certificate_config,
764            )
765            .await
766            .unwrap();
767        // get new certificate config
768        let value: Value = manager
769            .get(Category::Certificate, "certificate2")
770            .await
771            .unwrap()
772            .unwrap();
773        assert_eq!(
774            toml::to_string(&new_certificate_config).unwrap(),
775            toml::to_string(&value).unwrap()
776        );
777        // ----- certificate config test end ----- //
778
779        // ----- storage config test start ----- //
780        // get storage config
781        let value: Value = manager
782            .get(Category::Storage, "storage2")
783            .await
784            .unwrap()
785            .unwrap();
786        assert_eq!(
787            r#"category = "storage2"
788value = "/storage2"
789"#,
790            toml::to_string(&value).unwrap()
791        );
792        // update storage config
793        let new_storage_config: Value = toml::from_str(
794            r#"category = "storage22"
795value = "/storage22"
796"#,
797        )
798        .unwrap();
799        manager
800            .update(Category::Storage, "storage2", &new_storage_config)
801            .await
802            .unwrap();
803        // get new storage config
804        let value: Value = manager
805            .get(Category::Storage, "storage2")
806            .await
807            .unwrap()
808            .unwrap();
809        assert_eq!(
810            toml::to_string(&new_storage_config).unwrap(),
811            toml::to_string(&value).unwrap()
812        );
813        // ----- storage config test end ----- //
814
815        // ----- delete config test start ----- //
816
817        // delete basic config
818        manager.delete(Category::Basic, "").await.unwrap();
819        let basic_config: Option<Value> =
820            manager.get(Category::Basic, "").await.unwrap();
821        assert_eq!(None, basic_config);
822
823        // delete server config
824        manager.delete(Category::Server, "server1").await.unwrap();
825        let server_config: Option<Value> =
826            manager.get(Category::Server, "server1").await.unwrap();
827        assert_eq!(None, server_config);
828
829        // delete location config
830        manager
831            .delete(Category::Location, "location1")
832            .await
833            .unwrap();
834        let location_config: Option<Value> =
835            manager.get(Category::Location, "location1").await.unwrap();
836        assert_eq!(None, location_config);
837
838        // delete upstream config
839        manager
840            .delete(Category::Upstream, "upstream1")
841            .await
842            .unwrap();
843        let upstream_config: Option<Value> =
844            manager.get(Category::Upstream, "upstream1").await.unwrap();
845        assert_eq!(None, upstream_config);
846
847        // delete plugin config
848        manager.delete(Category::Plugin, "plugin1").await.unwrap();
849        let plugin_config: Option<Value> =
850            manager.get(Category::Plugin, "plugin1").await.unwrap();
851        assert_eq!(None, plugin_config);
852
853        // delete certificate config
854        manager
855            .delete(Category::Certificate, "certificate1")
856            .await
857            .unwrap();
858        let certificate_config: Option<Value> = manager
859            .get(Category::Certificate, "certificate1")
860            .await
861            .unwrap();
862        assert_eq!(None, certificate_config);
863
864        // delete storage config
865        manager.delete(Category::Storage, "storage1").await.unwrap();
866        let storage_config: Option<Value> =
867            manager.get(Category::Storage, "storage1").await.unwrap();
868        assert_eq!(None, storage_config);
869
870        let current_config = manager.load_all().await.unwrap();
871        assert_eq!(
872            r#"[servers.server2]
873addr = "192.186.1.1:8080"
874locations = ["location1"]
875threads = 1
876
877[upstreams.upstream2]
878addrs = ["192.168.1.1:7081"]
879
880[locations.location2]
881upstream = "upstream22"
882
883[plugins.plugin2]
884category = "plugin22"
885value = "/plugin22"
886
887[certificates.certificate2]
888cert = "/certificate22"
889key = "/key22"
890
891[storages.storage2]
892category = "storage22"
893value = "/storage22"
894"#,
895            toml::to_string(&current_config).unwrap()
896        );
897
898        // ----- delete config test end ----- //
899    }
900
901    #[tokio::test]
902    async fn test_single_config_manger() {
903        let file = tempfile::NamedTempFile::with_suffix(".toml").unwrap();
904
905        let manager =
906            new_file_config_manager(&file.path().to_string_lossy()).unwrap();
907        test_config_manger(manager, ConfigMode::Single).await;
908    }
909
910    #[tokio::test]
911    async fn test_multi_by_type_config_manger() {
912        let file = tempfile::TempDir::new().unwrap();
913
914        let manager =
915            new_file_config_manager(&file.path().to_string_lossy()).unwrap();
916        test_config_manger(manager, ConfigMode::MultiByType).await;
917    }
918
919    #[tokio::test]
920    async fn test_multi_by_item_config_manger() {
921        let file = tempfile::TempDir::new().unwrap();
922
923        let manager = new_file_config_manager(&format!(
924            "{}?separation",
925            file.path().to_string_lossy()
926        ))
927        .unwrap();
928        test_config_manger(manager, ConfigMode::MultiByItem).await;
929    }
930
931    #[tokio::test]
932    async fn test_etcd_config_manger() {
933        let url = format!(
934            "etcd://127.0.0.1:2379/{}?timeout=10s&connect_timeout=5s",
935            nanoid!(16)
936        );
937        let manager = new_etcd_config_manager(&url).unwrap();
938        test_config_manger(manager, ConfigMode::MultiByItem).await;
939    }
940}