impulse_server_kit/setup/
mod.rs1use serde::Deserialize;
4use serde::de::DeserializeOwned;
5use std::io::Read;
6use std::path::PathBuf;
7use std::sync::Arc;
8
9use impulse_utils::prelude::*;
10
11pub mod port_achiever;
12pub mod tracing_init;
13
14use crate::setup::port_achiever::port_file_watcher;
15use crate::setup::tracing_init::{TracingGuards, TracingOptions};
16
17pub trait GenericSetup {
19 fn generic_values(&self) -> &GenericValues;
21 fn generic_values_mut(&mut self) -> &mut GenericValues;
23}
24
25#[derive(Clone, Eq, PartialEq)]
29pub enum StartupVariant {
30 HttpLocalhost,
32 UnsafeHttp,
34 #[cfg(feature = "acme")]
35 HttpsAcme,
37 #[cfg(all(feature = "http3", feature = "acme"))]
38 QuinnAcme,
40 HttpsOnly,
42 #[cfg(feature = "http3")]
43 Quinn,
45 #[cfg(feature = "http3")]
46 QuinnOnly,
48}
49
50#[derive(Clone, Deserialize, Default)]
52pub struct GenericValues {
53 #[serde(skip)]
57 pub app_name: String,
58 pub startup_type: String,
60 pub server_host: Option<String>,
62 pub server_port: Option<u16>,
64 pub acme_domain: Option<String>,
66 pub ssl_key_path: Option<String>,
68 pub ssl_crt_path: Option<String>,
70 pub auto_migrate_bin: Option<String>,
72 pub server_port_achiever: Option<PathBuf>,
74
75 #[cfg(feature = "cors")]
76 pub allow_cors_domain: Option<String>,
78
79 #[cfg(feature = "oapi")]
80 pub allow_oapi_access: Option<bool>,
82 #[cfg(feature = "oapi")]
83 pub oapi_frontend_type: Option<String>,
85 #[cfg(feature = "oapi")]
86 pub oapi_name: Option<String>,
88 #[cfg(feature = "oapi")]
89 pub oapi_ver: Option<String>,
91 #[cfg(feature = "oapi")]
92 pub oapi_api_addr: Option<String>,
94
95 #[serde(flatten)]
96 pub tracing_options: TracingOptions,
98}
99
100#[derive(Clone)]
102pub struct GenericServerState {
103 pub startup_variant: StartupVariant,
105 pub _guards: Arc<TracingGuards>,
107}
108
109pub async fn load_generic_config<T: DeserializeOwned + GenericSetup + Default>(app_name: &str) -> MResult<T> {
111 let mut file = std::fs::File::open(format!("{app_name}.yaml"));
112 if file.is_err() {
113 file = std::fs::File::open(format!("/etc/{app_name}.yaml"));
114 }
115 let mut file = file.map_err(|e| {
116 ServerError::from_private(e)
117 .with_public("The server configuration could not be found.")
118 .with_500()
119 })?;
120
121 let mut buffer = String::new();
122 file.read_to_string(&mut buffer).map_err(|e| {
123 ServerError::from_private(e)
124 .with_public("Failed to read the contents of the server configuration file.")
125 .with_500()
126 })?;
127 let mut config: T = serde_pretty_yaml::from_str(&buffer).map_err(|e| {
128 ServerError::from_private(e)
129 .with_public("Failed to parse the contents of the server configuration file.")
130 .with_500()
131 })?;
132
133 let data = config.generic_values_mut();
134 data.app_name = app_name.to_string();
135
136 #[cfg(feature = "oapi")]
137 if data.allow_oapi_access.is_some_and(|v| v) {
138 if data.oapi_name.is_none() {
139 ServerError::from_public("The API name for OAPI is not specified.")
140 .with_500()
141 .bail()?;
142 }
143 if data.oapi_ver.is_none() {
144 ServerError::from_public("The API version for OAPI is not specified.")
145 .with_500()
146 .bail()?;
147 }
148 if data.oapi_api_addr.is_none() {
149 ServerError::from_public("The path to OAPI was not specified.")
150 .with_500()
151 .bail()?;
152 }
153 }
154
155 if let Some(achiever) = &data.server_port_achiever {
156 let port = port_file_watcher(achiever.as_path()).await?;
157 data.server_port = Some(port);
158 }
159
160 Ok(config)
161}
162
163pub async fn load_generic_state<T: GenericSetup>(setup: &T, init_logging: bool) -> MResult<GenericServerState> {
167 let data = setup.generic_values();
168
169 let guards = if init_logging {
170 data.tracing_options.init(&data.app_name)?
171 } else {
172 Default::default()
173 };
174
175 let state = GenericServerState {
176 startup_variant: match &*data.startup_type {
177 "http_localhost" => {
178 if data.server_host.is_some() {
179 ServerError::from_public("Server will only listen `127.0.0.1` address because of `http_localhost` startup variant. Consider to move to `https_only` or `quinn`.").with_500().bail()?;
180 }
181 StartupVariant::HttpLocalhost
182 }
183 "unsafe_http" => {
184 if data.server_host.is_none() {
185 ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
186 .with_500()
187 .bail()?;
188 }
189 StartupVariant::UnsafeHttp
190 }
191 #[cfg(feature = "acme")]
192 "https_acme" => {
193 if data.server_host.is_none() {
194 ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
195 .with_500()
196 .bail()?;
197 }
198 if data.acme_domain.is_none() {
199 ServerError::from_public("Choose ACME's domain!").with_500().bail()?;
200 }
201 StartupVariant::HttpsAcme
202 }
203 "https_only" => {
204 if data.server_host.is_none() {
205 ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
206 .with_500()
207 .bail()?;
208 }
209 if data.ssl_key_path.is_none() {
210 ServerError::from_public("Choose SSL key path.").with_500().bail()?;
211 }
212 if data.ssl_crt_path.is_none() {
213 ServerError::from_public("Choose SSL cert path.").with_500().bail()?;
214 }
215 StartupVariant::HttpsOnly
216 }
217 #[cfg(all(feature = "http3", feature = "acme"))]
218 "quinn_acme" => {
219 if data.server_host.is_none() {
220 ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
221 .with_500()
222 .bail()?;
223 }
224 if data.acme_domain.is_none() {
225 ServerError::from_public("Choose ACME's domain!").with_500().bail()?;
226 }
227 StartupVariant::QuinnAcme
228 }
229 #[cfg(feature = "http3")]
230 "quinn" => {
231 if data.server_host.is_none() {
232 ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
233 .with_500()
234 .bail()?;
235 }
236 if data.ssl_key_path.is_none() {
237 ServerError::from_public("Choose SSL key path.").with_500().bail()?;
238 }
239 if data.ssl_crt_path.is_none() {
240 ServerError::from_public("Choose SSL cert path.").with_500().bail()?;
241 }
242 StartupVariant::Quinn
243 }
244 #[cfg(feature = "http3")]
245 "quinn_only" => {
246 if data.server_host.is_none() {
247 ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
248 .with_500()
249 .bail()?;
250 }
251 if data.ssl_key_path.is_none() {
252 ServerError::from_public("Choose SSL key path.").with_500().bail()?;
253 }
254 if data.ssl_crt_path.is_none() {
255 ServerError::from_public("Choose SSL cert path.").with_500().bail()?;
256 }
257 StartupVariant::QuinnOnly
258 }
259 _ => ServerError::from_public(
260 "The server deployment method could not be determined. Read the documentation on the `startup_variant` field.",
261 )
262 .with_500()
263 .bail()?,
264 },
265 _guards: Arc::new(guards),
266 };
267 Ok(state)
268}