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 #[cfg(feature = "leptos-ssr")]
96 pub frontend_dist_path: Option<PathBuf>,
104 #[cfg(feature = "leptos-ssr")]
105 pub leptos_output_name: Option<String>,
108 #[cfg(feature = "leptos-ssr")]
109 pub leptos_server_fn_prefix: Option<String>,
112 #[cfg(feature = "leptos-ssr")]
113 pub leptos_seo: Option<crate::leptos_ssr::SeoDefaults>,
115
116 #[serde(default)]
120 pub security_headers: crate::security_headers::SecurityHeadersOptions,
121
122 #[serde(flatten)]
123 pub tracing_options: TracingOptions,
125}
126
127#[derive(Clone)]
129pub struct GenericServerState {
130 pub startup_variant: StartupVariant,
132 pub _guards: Arc<TracingGuards>,
134}
135
136pub async fn load_generic_config<T: DeserializeOwned + GenericSetup + Default>(app_name: &str) -> MResult<T> {
138 let mut file = std::fs::File::open(format!("{app_name}.yaml"));
139 if file.is_err() {
140 file = std::fs::File::open(format!("/etc/{app_name}.yaml"));
141 }
142 let mut file = file.map_err(|e| {
143 ServerError::from_private(e)
144 .with_public("The server configuration could not be found.")
145 .with_500()
146 })?;
147
148 let mut buffer = String::new();
149 file.read_to_string(&mut buffer).map_err(|e| {
150 ServerError::from_private(e)
151 .with_public("Failed to read the contents of the server configuration file.")
152 .with_500()
153 })?;
154 let mut config: T = serde_pretty_yaml::from_str(&buffer).map_err(|e| {
155 ServerError::from_private(e)
156 .with_public("Failed to parse the contents of the server configuration file.")
157 .with_500()
158 })?;
159
160 let data = config.generic_values_mut();
161 data.app_name = app_name.to_string();
162
163 #[cfg(feature = "oapi")]
164 if data.allow_oapi_access.is_some_and(|v| v) {
165 if data.oapi_name.is_none() {
166 ServerError::from_public("The API name for OAPI is not specified.")
167 .with_500()
168 .bail()?;
169 }
170 if data.oapi_ver.is_none() {
171 ServerError::from_public("The API version for OAPI is not specified.")
172 .with_500()
173 .bail()?;
174 }
175 if data.oapi_api_addr.is_none() {
176 ServerError::from_public("The path to OAPI was not specified.")
177 .with_500()
178 .bail()?;
179 }
180 }
181
182 if let Some(achiever) = &data.server_port_achiever {
183 let port = port_file_watcher(achiever.as_path()).await?;
184 data.server_port = Some(port);
185 }
186
187 Ok(config)
188}
189
190pub async fn load_generic_state<T: GenericSetup>(setup: &T, init_logging: bool) -> MResult<GenericServerState> {
194 let data = setup.generic_values();
195
196 let guards = if init_logging {
197 data.tracing_options.init(&data.app_name)?
198 } else {
199 Default::default()
200 };
201
202 let state = GenericServerState {
203 startup_variant: match &*data.startup_type {
204 "http_localhost" => {
205 if data.server_host.is_some() {
206 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()?;
207 }
208 StartupVariant::HttpLocalhost
209 }
210 "unsafe_http" => {
211 if data.server_host.is_none() {
212 ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
213 .with_500()
214 .bail()?;
215 }
216 StartupVariant::UnsafeHttp
217 }
218 #[cfg(feature = "acme")]
219 "https_acme" => {
220 if data.server_host.is_none() {
221 ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
222 .with_500()
223 .bail()?;
224 }
225 if data.acme_domain.is_none() {
226 ServerError::from_public("Choose ACME's domain!").with_500().bail()?;
227 }
228 StartupVariant::HttpsAcme
229 }
230 "https_only" => {
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::HttpsOnly
243 }
244 #[cfg(all(feature = "http3", feature = "acme"))]
245 "quinn_acme" => {
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.acme_domain.is_none() {
252 ServerError::from_public("Choose ACME's domain!").with_500().bail()?;
253 }
254 StartupVariant::QuinnAcme
255 }
256 #[cfg(feature = "http3")]
257 "quinn" => {
258 if data.server_host.is_none() {
259 ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
260 .with_500()
261 .bail()?;
262 }
263 if data.ssl_key_path.is_none() {
264 ServerError::from_public("Choose SSL key path.").with_500().bail()?;
265 }
266 if data.ssl_crt_path.is_none() {
267 ServerError::from_public("Choose SSL cert path.").with_500().bail()?;
268 }
269 StartupVariant::Quinn
270 }
271 #[cfg(feature = "http3")]
272 "quinn_only" => {
273 if data.server_host.is_none() {
274 ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
275 .with_500()
276 .bail()?;
277 }
278 if data.ssl_key_path.is_none() {
279 ServerError::from_public("Choose SSL key path.").with_500().bail()?;
280 }
281 if data.ssl_crt_path.is_none() {
282 ServerError::from_public("Choose SSL cert path.").with_500().bail()?;
283 }
284 StartupVariant::QuinnOnly
285 }
286 _ => ServerError::from_public(
287 "The server deployment method could not be determined. Read the documentation on the `startup_variant` field.",
288 )
289 .with_500()
290 .bail()?,
291 },
292 _guards: Arc::new(guards),
293 };
294 Ok(state)
295}