use serde::Deserialize;
use serde::de::DeserializeOwned;
use std::io::Read;
use std::path::PathBuf;
use std::sync::Arc;
use impulse_utils::prelude::*;
pub mod port_achiever;
pub mod tracing_init;
use crate::setup::port_achiever::port_file_watcher;
use crate::setup::tracing_init::{TracingGuards, TracingOptions};
pub trait GenericSetup {
fn generic_values(&self) -> &GenericValues;
fn generic_values_mut(&mut self) -> &mut GenericValues;
}
#[derive(Clone, Eq, PartialEq)]
pub enum StartupVariant {
HttpLocalhost,
UnsafeHttp,
#[cfg(feature = "acme")]
HttpsAcme,
#[cfg(all(feature = "http3", feature = "acme"))]
QuinnAcme,
HttpsOnly,
#[cfg(feature = "http3")]
Quinn,
#[cfg(feature = "http3")]
QuinnOnly,
}
#[derive(Clone, Deserialize, Default)]
pub struct GenericValues {
#[serde(skip)]
pub app_name: String,
pub startup_type: String,
pub server_host: Option<String>,
pub server_port: Option<u16>,
pub acme_domain: Option<String>,
pub ssl_key_path: Option<String>,
pub ssl_crt_path: Option<String>,
pub auto_migrate_bin: Option<String>,
pub server_port_achiever: Option<PathBuf>,
#[cfg(feature = "cors")]
pub allow_cors_domain: Option<String>,
#[cfg(feature = "oapi")]
pub allow_oapi_access: Option<bool>,
#[cfg(feature = "oapi")]
pub oapi_frontend_type: Option<String>,
#[cfg(feature = "oapi")]
pub oapi_name: Option<String>,
#[cfg(feature = "oapi")]
pub oapi_ver: Option<String>,
#[cfg(feature = "oapi")]
pub oapi_api_addr: Option<String>,
#[cfg(feature = "leptos-ssr")]
pub frontend_dist_path: Option<PathBuf>,
#[cfg(feature = "leptos-ssr")]
pub leptos_output_name: Option<String>,
#[cfg(feature = "leptos-ssr")]
pub leptos_server_fn_prefix: Option<String>,
#[cfg(feature = "leptos-ssr")]
pub leptos_seo: Option<crate::leptos_ssr::SeoDefaults>,
#[serde(default)]
pub security_headers: crate::security_headers::SecurityHeadersOptions,
#[serde(flatten)]
pub tracing_options: TracingOptions,
}
#[derive(Clone)]
pub struct GenericServerState {
pub startup_variant: StartupVariant,
pub _guards: Arc<TracingGuards>,
}
pub async fn load_generic_config<T: DeserializeOwned + GenericSetup + Default>(app_name: &str) -> MResult<T> {
let mut file = std::fs::File::open(format!("{app_name}.yaml"));
if file.is_err() {
file = std::fs::File::open(format!("/etc/{app_name}.yaml"));
}
let mut file = file.map_err(|e| {
ServerError::from_private(e)
.with_public("The server configuration could not be found.")
.with_500()
})?;
let mut buffer = String::new();
file.read_to_string(&mut buffer).map_err(|e| {
ServerError::from_private(e)
.with_public("Failed to read the contents of the server configuration file.")
.with_500()
})?;
let mut config: T = serde_pretty_yaml::from_str(&buffer).map_err(|e| {
ServerError::from_private(e)
.with_public("Failed to parse the contents of the server configuration file.")
.with_500()
})?;
let data = config.generic_values_mut();
data.app_name = app_name.to_string();
#[cfg(feature = "oapi")]
if data.allow_oapi_access.is_some_and(|v| v) {
if data.oapi_name.is_none() {
ServerError::from_public("The API name for OAPI is not specified.")
.with_500()
.bail()?;
}
if data.oapi_ver.is_none() {
ServerError::from_public("The API version for OAPI is not specified.")
.with_500()
.bail()?;
}
if data.oapi_api_addr.is_none() {
ServerError::from_public("The path to OAPI was not specified.")
.with_500()
.bail()?;
}
}
if let Some(achiever) = &data.server_port_achiever {
let port = port_file_watcher(achiever.as_path()).await?;
data.server_port = Some(port);
}
Ok(config)
}
pub async fn load_generic_state<T: GenericSetup>(setup: &T, init_logging: bool) -> MResult<GenericServerState> {
let data = setup.generic_values();
let guards = if init_logging {
data.tracing_options.init(&data.app_name)?
} else {
Default::default()
};
let state = GenericServerState {
startup_variant: match &*data.startup_type {
"http_localhost" => {
if data.server_host.is_some() {
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()?;
}
StartupVariant::HttpLocalhost
}
"unsafe_http" => {
if data.server_host.is_none() {
ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
.with_500()
.bail()?;
}
StartupVariant::UnsafeHttp
}
#[cfg(feature = "acme")]
"https_acme" => {
if data.server_host.is_none() {
ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
.with_500()
.bail()?;
}
if data.acme_domain.is_none() {
ServerError::from_public("Choose ACME's domain!").with_500().bail()?;
}
StartupVariant::HttpsAcme
}
"https_only" => {
if data.server_host.is_none() {
ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
.with_500()
.bail()?;
}
if data.ssl_key_path.is_none() {
ServerError::from_public("Choose SSL key path.").with_500().bail()?;
}
if data.ssl_crt_path.is_none() {
ServerError::from_public("Choose SSL cert path.").with_500().bail()?;
}
StartupVariant::HttpsOnly
}
#[cfg(all(feature = "http3", feature = "acme"))]
"quinn_acme" => {
if data.server_host.is_none() {
ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
.with_500()
.bail()?;
}
if data.acme_domain.is_none() {
ServerError::from_public("Choose ACME's domain!").with_500().bail()?;
}
StartupVariant::QuinnAcme
}
#[cfg(feature = "http3")]
"quinn" => {
if data.server_host.is_none() {
ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
.with_500()
.bail()?;
}
if data.ssl_key_path.is_none() {
ServerError::from_public("Choose SSL key path.").with_500().bail()?;
}
if data.ssl_crt_path.is_none() {
ServerError::from_public("Choose SSL cert path.").with_500().bail()?;
}
StartupVariant::Quinn
}
#[cfg(feature = "http3")]
"quinn_only" => {
if data.server_host.is_none() {
ServerError::from_public("Choose server's host, e.g. `0.0.0.0`.")
.with_500()
.bail()?;
}
if data.ssl_key_path.is_none() {
ServerError::from_public("Choose SSL key path.").with_500().bail()?;
}
if data.ssl_crt_path.is_none() {
ServerError::from_public("Choose SSL cert path.").with_500().bail()?;
}
StartupVariant::QuinnOnly
}
_ => ServerError::from_public(
"The server deployment method could not be determined. Read the documentation on the `startup_variant` field.",
)
.with_500()
.bail()?,
},
_guards: Arc::new(guards),
};
Ok(state)
}