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: &str = Deserialize::deserialize(deserializer)?;
227    // if s is "false", return false, otherwise return true
228    match s {
229        "false" => Ok(false),
230        _ => Ok(true),
231    }
232}
233
234#[derive(Deserialize, Default, Debug)]
235struct ConfigManagerParams {
236    #[serde(default, deserialize_with = "bool_from_str")]
237    separation: bool,
238    #[serde(default)]
239    enable_history: bool,
240}
241
242pub fn new_file_config_manager(path: &str) -> Result<ConfigManager> {
243    let (file, query) = path.split_once('?').unwrap_or((path, ""));
244    let file = resolve_path(file);
245    let filepath = Path::new(&file);
246    let (mode, enable_history) = if filepath.is_dir() {
247        let params: ConfigManagerParams =
248            serde_qs::from_str(query).map_err(|e| Error::Invalid {
249                message: e.to_string(),
250            })?;
251        if params.separation {
252            (ConfigMode::MultiByItem, params.enable_history)
253        } else {
254            (ConfigMode::MultiByType, false)
255        }
256    } else {
257        (ConfigMode::Single, false)
258    };
259
260    let mut storage = FileStorage::new(&file)?;
261    if enable_history {
262        storage.with_history_path(&format!("{file}-history"))?;
263    }
264    Ok(ConfigManager::new(Arc::new(storage), mode))
265}
266
267pub fn new_etcd_config_manager(path: &str) -> Result<ConfigManager> {
268    let storage = EtcdStorage::new(path)?;
269    Ok(ConfigManager::new(
270        Arc::new(storage),
271        ConfigMode::MultiByItem,
272    ))
273}
274
275pub struct ConfigManager {
276    storage: Arc<dyn Storage>,
277    mode: ConfigMode,
278    current_config: ArcSwap<PingapConfig>,
279}
280
281impl ConfigManager {
282    pub fn new(storage: Arc<dyn Storage>, mode: ConfigMode) -> Self {
283        Self {
284            storage,
285            mode,
286            current_config: ArcSwap::from_pointee(PingapConfig::default()),
287        }
288    }
289    pub fn support_observer(&self) -> bool {
290        self.storage.support_observer()
291    }
292    pub async fn observe(&self) -> Result<Observer> {
293        self.storage.observe().await
294    }
295
296    pub fn get_current_config(&self) -> Arc<PingapConfig> {
297        self.current_config.load().clone()
298    }
299    pub fn set_current_config(&self, config: PingapConfig) {
300        self.current_config.store(Arc::new(config));
301    }
302
303    /// get storage key
304    fn get_key(&self, category: &Category, name: &str) -> String {
305        match self.mode {
306            ConfigMode::Single => SINGLE_KEY.to_string(),
307            ConfigMode::MultiByType => {
308                format!("{}.toml", format_category(category))
309            },
310            ConfigMode::MultiByItem => {
311                if *category == Category::Basic {
312                    format!("{}.toml", format_category(category))
313                } else {
314                    format!("{}/{}.toml", format_category(category), name)
315                }
316            },
317        }
318    }
319
320    pub async fn load_all(&self) -> Result<PingapTomlConfig> {
321        let data = self.storage.fetch("").await?;
322        toml::from_str(&data).map_err(|e| Error::De { source: e })
323    }
324    pub async fn save_all(&self, config: &PingapTomlConfig) -> Result<()> {
325        match self.mode {
326            ConfigMode::Single => {
327                self.storage
328                    .save(
329                        &self.get_key(&Category::Basic, ""),
330                        &to_string_pretty(config)?,
331                    )
332                    .await?;
333            },
334            ConfigMode::MultiByType => {
335                for category in [
336                    Category::Basic,
337                    Category::Server,
338                    Category::Location,
339                    Category::Upstream,
340                    Category::Plugin,
341                    Category::Certificate,
342                    Category::Storage,
343                ]
344                .iter()
345                {
346                    let value = config.get_category_toml(category)?;
347                    self.storage
348                        .save(&self.get_key(category, ""), &value)
349                        .await?;
350                }
351            },
352            ConfigMode::MultiByItem => {
353                let basic_config = config.get_toml(&Category::Basic, "")?;
354                self.storage
355                    .save(&self.get_key(&Category::Basic, ""), &basic_config)
356                    .await?;
357
358                for (category, value) in [
359                    (Category::Server, config.servers.clone()),
360                    (Category::Location, config.locations.clone()),
361                    (Category::Upstream, config.upstreams.clone()),
362                    (Category::Plugin, config.plugins.clone()),
363                    (Category::Certificate, config.certificates.clone()),
364                    (Category::Storage, config.storages.clone()),
365                ] {
366                    let Some(value) = value else {
367                        continue;
368                    };
369                    for name in value.keys() {
370                        let value = config.get_toml(&category, name)?;
371                        self.storage
372                            .save(&self.get_key(&category, name), &value)
373                            .await?;
374                    }
375                }
376            },
377        }
378
379        Ok(())
380    }
381    pub async fn update<T: Serialize + Send + Sync>(
382        &self,
383        category: Category,
384        name: &str,
385        value: &T,
386    ) -> Result<()> {
387        let key = self.get_key(&category, name);
388        let value = toml::to_string_pretty(value)
389            .map_err(|e| Error::Ser { source: e })?;
390        // update by item
391        if self.mode == ConfigMode::MultiByItem {
392            let value: Value =
393                toml::from_str(&value).map_err(|e| Error::De { source: e })?;
394            let value = format_item_toml_config(Some(value), &category, name)?;
395            return self.storage.save(&key, &value).await;
396        }
397        // load all config
398        let mut config = self.load_all().await?;
399        let value: Value =
400            toml::from_str(&value).map_err(|e| Error::De { source: e })?;
401        config.update(&category, name, value);
402        // update by type
403        let value = if self.mode == ConfigMode::MultiByType {
404            config.get_category_toml(&category)?
405        } else {
406            to_string_pretty(&config)?
407        };
408
409        self.storage.save(&key, &value).await?;
410        Ok(())
411    }
412    pub async fn get<T: DeserializeOwned + Send>(
413        &self,
414        category: Category,
415        name: &str,
416    ) -> Result<Option<T>> {
417        let key = self.get_key(&category, name);
418        let data = self.storage.fetch(&key).await?;
419        let config: PingapTomlConfig =
420            toml::from_str(&data).map_err(|e| Error::De { source: e })?;
421
422        if let Some(value) = config.get(&category, name) {
423            let value = to_string_pretty(&value)?;
424            let value =
425                toml::from_str(&value).map_err(|e| Error::De { source: e })?;
426            Ok(Some(value))
427        } else {
428            Ok(None)
429        }
430    }
431    pub async fn delete(&self, category: Category, name: &str) -> Result<()> {
432        let key = self.get_key(&category, name);
433
434        let mut current_config = (*self.get_current_config()).clone();
435        current_config.remove(category.to_string().as_str(), name)?;
436
437        if self.mode == ConfigMode::MultiByItem {
438            return self.storage.delete(&key).await;
439        }
440        let mut config = self.load_all().await?;
441        config.delete(&category, name);
442        let value = if self.mode == ConfigMode::MultiByType {
443            config.get_category_toml(&category)?
444        } else {
445            to_string_pretty(&config)?
446        };
447        self.storage.save(&key, &value).await
448    }
449    pub fn support_history(&self) -> bool {
450        self.storage.support_history()
451    }
452    pub async fn history(
453        &self,
454        category: Category,
455        name: &str,
456    ) -> Result<Option<Vec<History>>> {
457        if !self.storage.support_history() {
458            return Ok(None);
459        }
460        let key = self.get_key(&category, name);
461        self.storage.fetch_history(&key).await
462    }
463}
464
465#[cfg(test)]
466mod tests {
467    use super::*;
468    use nanoid::nanoid;
469    use pretty_assertions::assert_eq;
470
471    fn new_pingap_config() -> PingapTomlConfig {
472        let basic_config = r#"auto_restart_check_interval = "1s"
473        name = "pingap"
474        pid_file = "/tmp/pingap.pid"
475        "#;
476
477        let server_config = r#"[server1]
478        addr = "127.0.0.1:8080"
479        locations = ["location1"]
480        threads = 1
481        
482        [server2]
483        addr = "127.0.0.1:8081"
484        locations = ["location2"]
485        threads = 2
486        "#;
487
488        let upstream_config = r#"[upstream1]
489        addrs = ["127.0.0.1:7080"]
490        
491        [upstream2]
492        addrs = ["127.0.0.1:7081"]
493        "#;
494
495        let location_config = r#"[location1]
496        upstream = "upstream1"
497        
498        [location2]
499        upstream = "upstream2"
500        "#;
501
502        let plugin_config = r#"[plugin1]
503        value = "/plugin1"
504        category = "plugin1"
505        
506        [plugin2]
507        value = "/plugin2"
508        category = "plugin2"
509        "#;
510
511        let certificate_config = r#"[certificate1]
512        cert = "/certificate1"
513        key = "/key1"
514        
515        [certificate2]
516        cert = "/certificate2"
517        key = "/key2"
518        "#;
519        let storage_config = r#"[storage1]
520        value = "/storage1"
521        category = "storage1"
522        
523        [storage2]
524        value = "/storage2"
525        category = "storage2"
526        "#;
527
528        PingapTomlConfig {
529            basic: Some(toml::from_str(basic_config).unwrap()),
530            servers: Some(toml::from_str(server_config).unwrap()),
531            upstreams: Some(toml::from_str(upstream_config).unwrap()),
532            locations: Some(toml::from_str(location_config).unwrap()),
533            plugins: Some(toml::from_str(plugin_config).unwrap()),
534            certificates: Some(toml::from_str(certificate_config).unwrap()),
535            storages: Some(toml::from_str(storage_config).unwrap()),
536        }
537    }
538
539    async fn test_config_manger(manager: ConfigManager, mode: ConfigMode) {
540        assert_eq!(true, mode == manager.mode);
541
542        let config = new_pingap_config();
543
544        manager.save_all(&config).await.unwrap();
545
546        // get all data from file
547        let data = manager.storage.fetch("").await.unwrap();
548        let new_config = toml::from_str::<PingapTomlConfig>(&data).unwrap();
549
550        assert_eq!(toml::to_string(&config), toml::to_string(&new_config));
551
552        let current_config = manager.load_all().await.unwrap();
553        assert_eq!(
554            toml::to_string(&config).unwrap(),
555            toml::to_string(&current_config).unwrap()
556        );
557
558        // ----- basic config test start ----- //
559        // get basic config
560        let value: Value =
561            manager.get(Category::Basic, "").await.unwrap().unwrap();
562        assert_eq!(
563            r#"auto_restart_check_interval = "1s"
564name = "pingap"
565pid_file = "/tmp/pingap.pid"
566"#,
567            toml::to_string(&value).unwrap()
568        );
569        // update basic config
570        let new_basic_config: Value = toml::from_str(
571            r#"auto_restart_check_interval = "2s"
572name = "pingap2"
573pid_file = "/tmp/pingap2.pid"
574"#,
575        )
576        .unwrap();
577        manager
578            .update(Category::Basic, "", &new_basic_config)
579            .await
580            .unwrap();
581        // get new basic config
582        let value: Value =
583            manager.get(Category::Basic, "").await.unwrap().unwrap();
584        assert_eq!(
585            toml::to_string(&new_basic_config).unwrap(),
586            toml::to_string(&value).unwrap()
587        );
588        // ----- basic config test end ----- //
589
590        // ----- server config test start ----- //
591        // get server config
592        let value: Value = manager
593            .get(Category::Server, "server1")
594            .await
595            .unwrap()
596            .unwrap();
597        assert_eq!(
598            r#"addr = "127.0.0.1:8080"
599locations = ["location1"]
600threads = 1
601"#,
602            toml::to_string(&value).unwrap()
603        );
604        // update server config
605        let new_server_config: Value = toml::from_str(
606            r#"addr = "192.186.1.1:8080"
607locations = ["location1"]
608threads = 1
609"#,
610        )
611        .unwrap();
612        manager
613            .update(Category::Server, "server2", &new_server_config)
614            .await
615            .unwrap();
616        // get new server config
617        let value: Value = manager
618            .get(Category::Server, "server2")
619            .await
620            .unwrap()
621            .unwrap();
622        assert_eq!(
623            toml::to_string(&new_server_config).unwrap(),
624            toml::to_string(&value).unwrap()
625        );
626        // ----- server config test end ----- //
627
628        // ----- upstream config test start ----- //
629        // get upstream config
630        let value: Value = manager
631            .get(Category::Upstream, "upstream2")
632            .await
633            .unwrap()
634            .unwrap();
635        assert_eq!(
636            r#"addrs = ["127.0.0.1:7081"]
637"#,
638            toml::to_string(&value).unwrap()
639        );
640        // update upstream config
641        let new_upstream_config: Value = toml::from_str(
642            r#"addrs = ["192.168.1.1:7081"]
643"#,
644        )
645        .unwrap();
646        manager
647            .update(Category::Upstream, "upstream2", &new_upstream_config)
648            .await
649            .unwrap();
650
651        // get new upstream config
652        let value: Value = manager
653            .get(Category::Upstream, "upstream2")
654            .await
655            .unwrap()
656            .unwrap();
657        assert_eq!(
658            toml::to_string(&new_upstream_config).unwrap(),
659            toml::to_string(&value).unwrap()
660        );
661
662        // ----- upstream config test end ----- //
663
664        // ----- location config test start ----- //
665        // get location config
666        let value: Value = manager
667            .get(Category::Location, "location2")
668            .await
669            .unwrap()
670            .unwrap();
671        assert_eq!(
672            r#"upstream = "upstream2"
673"#,
674            toml::to_string(&value).unwrap()
675        );
676
677        // update location config
678        let new_location_config: Value = toml::from_str(
679            r#"upstream = "upstream22"
680"#,
681        )
682        .unwrap();
683        manager
684            .update(Category::Location, "location2", &new_location_config)
685            .await
686            .unwrap();
687
688        // get new location config
689        let value: Value = manager
690            .get(Category::Location, "location2")
691            .await
692            .unwrap()
693            .unwrap();
694        assert_eq!(
695            toml::to_string(&new_location_config).unwrap(),
696            toml::to_string(&value).unwrap()
697        );
698
699        // ----- location config test end ----- //
700
701        // ----- plugin config test start ----- //
702        // get plugin config
703        let value: Value = manager
704            .get(Category::Plugin, "plugin2")
705            .await
706            .unwrap()
707            .unwrap();
708        assert_eq!(
709            r#"category = "plugin2"
710value = "/plugin2"
711"#,
712            toml::to_string(&value).unwrap()
713        );
714
715        // update plugin config
716        let new_plugin_config: Value = toml::from_str(
717            r#"category = "plugin22"
718value = "/plugin22"
719"#,
720        )
721        .unwrap();
722        manager
723            .update(Category::Plugin, "plugin2", &new_plugin_config)
724            .await
725            .unwrap();
726        // get new plugin config
727        let value: Value = manager
728            .get(Category::Plugin, "plugin2")
729            .await
730            .unwrap()
731            .unwrap();
732        assert_eq!(
733            toml::to_string(&new_plugin_config).unwrap(),
734            toml::to_string(&value).unwrap()
735        );
736
737        // ----- plugin config test end ----- //
738
739        // ----- certificate config test start ----- //
740        // get certificate config
741        let value: Value = manager
742            .get(Category::Certificate, "certificate2")
743            .await
744            .unwrap()
745            .unwrap();
746        assert_eq!(
747            r#"cert = "/certificate2"
748key = "/key2"
749"#,
750            toml::to_string(&value).unwrap()
751        );
752
753        // update certificate config
754        let new_certificate_config: Value = toml::from_str(
755            r#"cert = "/certificate22"
756key = "/key22"
757"#,
758        )
759        .unwrap();
760        manager
761            .update(
762                Category::Certificate,
763                "certificate2",
764                &new_certificate_config,
765            )
766            .await
767            .unwrap();
768        // get new certificate config
769        let value: Value = manager
770            .get(Category::Certificate, "certificate2")
771            .await
772            .unwrap()
773            .unwrap();
774        assert_eq!(
775            toml::to_string(&new_certificate_config).unwrap(),
776            toml::to_string(&value).unwrap()
777        );
778        // ----- certificate config test end ----- //
779
780        // ----- storage config test start ----- //
781        // get storage config
782        let value: Value = manager
783            .get(Category::Storage, "storage2")
784            .await
785            .unwrap()
786            .unwrap();
787        assert_eq!(
788            r#"category = "storage2"
789value = "/storage2"
790"#,
791            toml::to_string(&value).unwrap()
792        );
793        // update storage config
794        let new_storage_config: Value = toml::from_str(
795            r#"category = "storage22"
796value = "/storage22"
797"#,
798        )
799        .unwrap();
800        manager
801            .update(Category::Storage, "storage2", &new_storage_config)
802            .await
803            .unwrap();
804        // get new storage config
805        let value: Value = manager
806            .get(Category::Storage, "storage2")
807            .await
808            .unwrap()
809            .unwrap();
810        assert_eq!(
811            toml::to_string(&new_storage_config).unwrap(),
812            toml::to_string(&value).unwrap()
813        );
814        // ----- storage config test end ----- //
815
816        // ----- delete config test start ----- //
817
818        // delete basic config
819        manager.delete(Category::Basic, "").await.unwrap();
820        let basic_config: Option<Value> =
821            manager.get(Category::Basic, "").await.unwrap();
822        assert_eq!(None, basic_config);
823
824        // delete server config
825        manager.delete(Category::Server, "server1").await.unwrap();
826        let server_config: Option<Value> =
827            manager.get(Category::Server, "server1").await.unwrap();
828        assert_eq!(None, server_config);
829
830        // delete location config
831        manager
832            .delete(Category::Location, "location1")
833            .await
834            .unwrap();
835        let location_config: Option<Value> =
836            manager.get(Category::Location, "location1").await.unwrap();
837        assert_eq!(None, location_config);
838
839        // delete upstream config
840        manager
841            .delete(Category::Upstream, "upstream1")
842            .await
843            .unwrap();
844        let upstream_config: Option<Value> =
845            manager.get(Category::Upstream, "upstream1").await.unwrap();
846        assert_eq!(None, upstream_config);
847
848        // delete plugin config
849        manager.delete(Category::Plugin, "plugin1").await.unwrap();
850        let plugin_config: Option<Value> =
851            manager.get(Category::Plugin, "plugin1").await.unwrap();
852        assert_eq!(None, plugin_config);
853
854        // delete certificate config
855        manager
856            .delete(Category::Certificate, "certificate1")
857            .await
858            .unwrap();
859        let certificate_config: Option<Value> = manager
860            .get(Category::Certificate, "certificate1")
861            .await
862            .unwrap();
863        assert_eq!(None, certificate_config);
864
865        // delete storage config
866        manager.delete(Category::Storage, "storage1").await.unwrap();
867        let storage_config: Option<Value> =
868            manager.get(Category::Storage, "storage1").await.unwrap();
869        assert_eq!(None, storage_config);
870
871        let current_config = manager.load_all().await.unwrap();
872        assert_eq!(
873            r#"[servers.server2]
874addr = "192.186.1.1:8080"
875locations = ["location1"]
876threads = 1
877
878[upstreams.upstream2]
879addrs = ["192.168.1.1:7081"]
880
881[locations.location2]
882upstream = "upstream22"
883
884[plugins.plugin2]
885category = "plugin22"
886value = "/plugin22"
887
888[certificates.certificate2]
889cert = "/certificate22"
890key = "/key22"
891
892[storages.storage2]
893category = "storage22"
894value = "/storage22"
895"#,
896            toml::to_string(&current_config).unwrap()
897        );
898
899        // ----- delete config test end ----- //
900    }
901
902    #[tokio::test]
903    async fn test_single_config_manger() {
904        let file = tempfile::NamedTempFile::with_suffix(".toml").unwrap();
905
906        let manager =
907            new_file_config_manager(&file.path().to_string_lossy()).unwrap();
908        test_config_manger(manager, ConfigMode::Single).await;
909    }
910
911    #[tokio::test]
912    async fn test_multi_by_type_config_manger() {
913        let file = tempfile::TempDir::new().unwrap();
914
915        let manager =
916            new_file_config_manager(&file.path().to_string_lossy()).unwrap();
917        test_config_manger(manager, ConfigMode::MultiByType).await;
918    }
919
920    #[tokio::test]
921    async fn test_multi_by_item_config_manger() {
922        let file = tempfile::TempDir::new().unwrap();
923
924        let manager = new_file_config_manager(&format!(
925            "{}?separation",
926            file.path().to_string_lossy()
927        ))
928        .unwrap();
929        test_config_manger(manager, ConfigMode::MultiByItem).await;
930    }
931
932    #[tokio::test]
933    async fn test_etcd_config_manger() {
934        let url = format!(
935            "etcd://127.0.0.1:2379/{}?timeout=10s&connect_timeout=5s",
936            nanoid!(16)
937        );
938        let manager = new_etcd_config_manager(&url).unwrap();
939        test_config_manger(manager, ConfigMode::MultiByItem).await;
940    }
941}