Skip to main content

pingap_config/
lib.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
15// External crate imports for async operations, etcd client, and error handling
16use etcd_client::WatchStream;
17use glob::glob;
18use snafu::Snafu;
19use std::str::FromStr;
20use std::sync::Arc;
21use std::time::Duration;
22use tokio::fs;
23use tracing::debug;
24
25mod common;
26mod etcd_storage;
27mod file_storage;
28mod manager;
29mod storage;
30
31// Error enum for all possible configuration-related errors
32#[derive(Debug, Snafu)]
33pub enum Error {
34    #[snafu(display("Invalid error {message}"))]
35    Invalid { message: String },
36    #[snafu(display("Glob pattern error {source}, {path}"))]
37    Pattern {
38        source: glob::PatternError,
39        path: String,
40    },
41    #[snafu(display("Glob error {source}"))]
42    Glob { source: glob::GlobError },
43    #[snafu(display("Io error {source}, {file}"))]
44    Io {
45        source: std::io::Error,
46        file: String,
47    },
48    #[snafu(display("Toml de error {source}"))]
49    De { source: toml::de::Error },
50    #[snafu(display("Toml ser error {source}"))]
51    Ser { source: toml::ser::Error },
52    #[snafu(display("Url parse error {source}, {url}"))]
53    UrlParse {
54        source: url::ParseError,
55        url: String,
56    },
57    #[snafu(display("Addr parse error {source}, {addr}"))]
58    AddrParse {
59        source: std::net::AddrParseError,
60        addr: String,
61    },
62    #[snafu(display("Base64 decode error {source}"))]
63    Base64Decode { source: base64::DecodeError },
64    #[snafu(display("Regex error {source}"))]
65    Regex { source: regex::Error },
66    #[snafu(display("Etcd error {source}"))]
67    Etcd { source: Box<etcd_client::Error> },
68}
69type Result<T, E = Error> = std::result::Result<T, E>;
70
71// Observer struct for watching configuration changes
72pub struct Observer {
73    // Optional watch stream for etcd-based configuration
74    etcd_watch_stream: Option<WatchStream>,
75}
76
77impl Observer {
78    // Watches for configuration changes, returns true if changes detected
79    pub async fn watch(&mut self) -> Result<bool> {
80        let sleep_time = Duration::from_secs(30);
81        // no watch stream, just sleep a moment
82        let Some(stream) = self.etcd_watch_stream.as_mut() else {
83            tokio::time::sleep(sleep_time).await;
84            return Ok(false);
85        };
86        let resp = stream.message().await.map_err(|e| Error::Etcd {
87            source: Box::new(e),
88        })?;
89
90        Ok(resp.is_some())
91    }
92}
93
94#[derive(PartialEq, Clone, Debug)]
95pub enum Category {
96    Basic,
97    Server,
98    Location,
99    Upstream,
100    Plugin,
101    Certificate,
102    Storage,
103}
104
105impl std::fmt::Display for Category {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        // 使用 match 来为每个变体指定其字符串表示
108        // write! 宏将字符串写入格式化器
109        match self {
110            Category::Basic => write!(f, "basic"),
111            Category::Server => write!(f, "server"),
112            Category::Location => write!(f, "location"),
113            Category::Upstream => write!(f, "upstream"),
114            Category::Plugin => write!(f, "plugin"),
115            Category::Certificate => write!(f, "certificate"),
116            Category::Storage => write!(f, "storage"),
117        }
118    }
119}
120impl FromStr for Category {
121    type Err = Error;
122
123    fn from_str(s: &str) -> Result<Self, Self::Err> {
124        match s.to_lowercase().as_str() {
125            "basic" => Ok(Category::Basic),
126            "server" => Ok(Category::Server),
127            "location" => Ok(Category::Location),
128            "upstream" => Ok(Category::Upstream),
129            "plugin" => Ok(Category::Plugin),
130            "certificate" => Ok(Category::Certificate),
131            "storage" => Ok(Category::Storage),
132            _ => Err(Error::Invalid {
133                message: format!("invalid category: {s}"),
134            }),
135        }
136    }
137}
138
139pub fn new_config_manager(value: &str) -> Result<ConfigManager> {
140    if value.starts_with(etcd_storage::ETCD_PROTOCOL) {
141        new_etcd_config_manager(value)
142    } else {
143        new_file_config_manager(value)
144    }
145}
146
147pub async fn read_all_toml_files(dir: &str) -> Result<Vec<u8>> {
148    let mut data = vec![];
149    for entry in
150        glob(&format!("{dir}/**/*.toml")).map_err(|e| Error::Pattern {
151            source: e,
152            path: dir.to_string(),
153        })?
154    {
155        let f = entry.map_err(|e| Error::Glob { source: e })?;
156        let mut buf = fs::read(&f).await.map_err(|e| Error::Io {
157            source: e,
158            file: f.to_string_lossy().to_string(),
159        })?;
160        debug!(filename = format!("{f:?}"), "read toml file");
161        // Append file contents and newline
162        data.append(&mut buf);
163        data.push(0x0a);
164    }
165    Ok(data)
166}
167
168pub async fn sync_to_path(
169    config_manager: Arc<ConfigManager>,
170    path: &str,
171) -> Result<()> {
172    let config = config_manager.get_current_config();
173    let config = toml::to_string_pretty(&config.as_ref().clone())
174        .map_err(|e| Error::Ser { source: e })?;
175    let config = toml::from_str::<PingapTomlConfig>(&config)
176        .map_err(|e| Error::De { source: e })?;
177    let new_config_manager = new_config_manager(path)?;
178    new_config_manager.save_all(&config).await?;
179    Ok(())
180}
181
182pub use common::*;
183pub use etcd_storage::ETCD_PROTOCOL;
184pub use manager::*;
185pub use storage::*;