use serde::{Deserialize, Serialize};
use core::default::Default;
use std::prelude::v1::*;
use std::collections::BTreeMap;
use product_os_configuration::{ProductOSConfig, ConfigError};
#[cfg(feature = "core")]
use product_os_configuration::logging::LogLevel;
use product_os_configuration::logging::Logging;
use product_os_net::SocketAddr;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ServerConfig {
pub environment: String,
pub root_path: String,
pub network: Network,
pub logging: Logging,
pub certificate: Option<Certificate>,
pub compression: Option<Compression>,
pub command_control: Option<serde_json::Value>,
pub security: Option<serde_json::Value>,
pub oidc: Option<serde_json::Value>,
pub store: Option<serde_json::Value>,
pub authentication: Option<serde_json::Value>,
pub content: Option<serde_json::Value>,
pub proxy: Option<serde_json::Value>,
pub browser: Option<serde_json::Value>,
pub crawler: Option<serde_json::Value>,
pub vpn: Option<serde_json::Value>,
pub connectors: Option<BTreeMap<String, serde_json::Value>>,
}
impl Default for ServerConfig {
fn default() -> Self {
Self::new()
}
}
impl ServerConfig {
#[must_use]
pub fn new() -> Self {
Self {
environment: String::from("development"),
root_path: String::from("/"),
network: Network::default(),
logging: Logging::default(),
certificate: None,
compression: Some(Compression {
enable: false,
gzip: true,
deflate: false,
brotli: true,
}),
command_control: None,
security: None,
oidc: None,
store: None,
authentication: None,
content: None,
proxy: None,
browser: None,
crawler: None,
vpn: None,
connectors: None,
}
}
#[must_use]
pub fn environment(&self) -> &str {
&self.environment
}
#[cfg(feature = "core")]
#[must_use]
pub fn log_level(&self) -> tracing::Level {
match self.logging.level {
LogLevel::WARN => tracing::Level::WARN,
LogLevel::INFO => tracing::Level::INFO,
LogLevel::DEBUG => tracing::Level::DEBUG,
LogLevel::TRACE => tracing::Level::TRACE,
LogLevel::ERROR | LogLevel::OFF => tracing::Level::ERROR,
}
}
#[must_use]
pub fn get_host(&self) -> String {
self.network.host.clone()
}
#[must_use]
#[allow(clippy::panic)]
#[deprecated(since = "0.0.53", note = "Use try_url_address which returns Result instead of panicking")]
pub fn url_address(&self) -> String {
self.try_url_address()
.unwrap_or_else(|e| ::std::panic!("Failed to build URL from config: {}", e))
}
pub fn try_url_address(&self) -> crate::error::Result<String> {
if self.network.host.is_empty() {
return Err(crate::error::ProductOSServerError::ConfigurationError {
component: "url_address".to_string(),
message: "Host must not be empty".to_string(),
});
}
if self.network.protocol.is_empty() {
return Err(crate::error::ProductOSServerError::ConfigurationError {
component: "url_address".to_string(),
message: "Protocol must not be empty".to_string(),
});
}
let root = self.root_path.strip_suffix('/').unwrap_or(&self.root_path);
let url_string = alloc::format!(
"{}://{}:{}{}",
self.network.protocol,
self.network.host,
self.network.port,
root,
);
Ok(url_string)
}
#[must_use]
pub fn socket_address(&self, port: Option<u16>, default_all: bool) -> SocketAddr {
let socket_port = port.unwrap_or(self.network.port);
Self::get_socket_address(&self.network.host, socket_port, default_all)
}
#[must_use]
pub fn get_socket_address(host: &str, port: u16, default_all: bool) -> SocketAddr {
product_os_utilities::ProductOSUtilities::get_socket_address(host, port, default_all)
}
#[must_use]
pub fn is_secure(&self) -> bool {
self.network.secure
}
#[must_use]
pub fn all_insecure(&self) -> bool {
self.network.allow_insecure
}
#[must_use]
pub fn insecure_port(&self) -> u16 {
self.network.insecure_port
}
#[must_use]
pub fn insecure_force_secure(&self) -> bool {
self.network.insecure_force_secure
}
#[must_use]
pub fn is_compression_gzip(&self) -> bool {
self.compression.as_ref().map_or(false, |c| c.gzip)
}
#[must_use]
pub fn is_compression_deflate(&self) -> bool {
self.compression.as_ref().map_or(false, |c| c.deflate)
}
#[must_use]
pub fn is_compression_brotli(&self) -> bool {
self.compression.as_ref().map_or(false, |c| c.brotli)
}
}
impl ProductOSConfig for ServerConfig {
const SECTION_KEY: &'static str = "server";
fn validate(&self) -> Result<(), ConfigError> {
let mut errors = Vec::new();
if self.network.host.is_empty() && self.network.port == 0 {
errors.push("network host and port must be configured".into());
}
if errors.is_empty() {
Ok(())
} else {
Err(ConfigError::ValidationError(errors))
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct Network {
pub protocol: String,
pub secure: bool,
pub host: String,
pub port: u16,
pub listen_all_interfaces: bool,
pub allow_insecure: bool,
pub insecure_port: u16,
pub insecure_use_different_port: bool,
pub insecure_force_secure: bool,
#[serde(default)]
pub connect_info: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct Certificate {
pub files: Option<CertificateFiles>,
pub file_kind: Option<CertificateFilesKind>,
pub entries: Option<Vec<BTreeMap<String, serde_json::Value>>>,
pub names: Option<Vec<String>>,
pub serial: Option<u32>,
pub valid_for: Option<u32>,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct CertificateFiles {
pub cert_file: String,
pub key_file: String,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
#[serde(rename_all = "lowercase")]
pub enum CertificateFilesKind {
#[default]
SelfSigned,
CA,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct Compression {
pub enable: bool,
pub gzip: bool,
pub deflate: bool,
pub brotli: bool,
}