1use 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#[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
71pub struct Observer {
73 etcd_watch_stream: Option<WatchStream>,
75}
76
77impl Observer {
78 pub async fn watch(&mut self) -> Result<bool> {
80 let sleep_time = Duration::from_secs(30);
81 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 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 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::*;