use std::{collections::BTreeMap, net::SocketAddr};
use serde::Deserialize;
use crate::core::{
ConfigFeatureWarning, CoreError, CoreResult, DatabaseSection, LogConfig, LogSection,
RpcClientSection, ServiceConfig, dependency_feature_warnings, load_config,
};
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(default, deny_unknown_fields)]
pub struct RpcServiceConfig {
pub name: String,
pub mode: String,
pub server: RpcServerSection,
pub log: LogSection,
pub middlewares: RpcMiddlewaresSection,
pub rpc_clients: BTreeMap<String, RpcClientSection>,
pub database: Option<DatabaseSection>,
}
impl Default for RpcServiceConfig {
fn default() -> Self {
let service = ServiceConfig::default();
Self {
name: service.name,
mode: service.mode,
server: RpcServerSection::default(),
log: service.log,
middlewares: RpcMiddlewaresSection::default(),
rpc_clients: BTreeMap::new(),
database: None,
}
}
}
impl RpcServiceConfig {
pub fn load(basename: &str, env_prefix: &str) -> Result<Self, config::ConfigError> {
load_config(basename, env_prefix)
}
pub fn addr(&self) -> CoreResult<SocketAddr> {
format!("{}:{}", self.server.host, self.server.port)
.parse()
.map_err(|error| {
config::ConfigError::Message(format!("invalid RPC listen address: {error}")).into()
})
}
pub fn log_config(&self) -> LogConfig {
self.log.to_log_config(&self.name)
}
pub fn validate_features(&self) -> Vec<ConfigFeatureWarning> {
let mut warnings = Vec::new();
if self.middlewares.resilience && !cfg!(feature = "resil") {
warnings.push(ConfigFeatureWarning::ignored(
"middlewares.resilience",
"resil",
));
}
if self.middlewares.streaming && !cfg!(feature = "resil") {
warnings.push(ConfigFeatureWarning::ignored(
"middlewares.streaming",
"resil",
));
}
warnings.extend(dependency_feature_warnings(
&self.rpc_clients,
self.database.as_ref(),
));
warnings
}
pub fn rpc_server_config(&self) -> CoreResult<crate::rpc::RpcServerConfig> {
let mut config = if self.middlewares.resilience {
crate::rpc::RpcServerConfig::production_defaults(self.name.clone(), self.addr()?)
} else {
crate::rpc::RpcServerConfig::new(self.name.clone(), self.addr()?)
};
if !self.middlewares.streaming {
config.streaming = crate::rpc::RpcStreamingConfig::default();
}
Ok(config)
}
pub fn rpc_client(&self, name: &str) -> CoreResult<&RpcClientSection> {
self.rpc_clients.get(name).ok_or_else(|| {
CoreError::Config(config::ConfigError::Message(format!(
"missing rpc client config: {name}"
)))
})
}
#[cfg(feature = "rpc")]
pub fn rpc_client_config(&self, name: &str) -> CoreResult<crate::rpc::RpcClientConfig> {
self.rpc_client(name)?.to_rpc_client_config()
}
#[cfg(feature = "db")]
pub fn database_config(&self) -> Option<crate::db::DatabaseConfig> {
self.database
.as_ref()
.map(DatabaseSection::to_database_config)
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(default, deny_unknown_fields)]
pub struct RpcServerSection {
pub host: String,
pub port: u16,
pub timeout_ms: u64,
pub health: bool,
}
impl Default for RpcServerSection {
fn default() -> Self {
Self {
host: "127.0.0.1".to_string(),
port: 50051,
timeout_ms: 5000,
health: true,
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(default, deny_unknown_fields)]
pub struct RpcMiddlewaresSection {
pub resilience: bool,
pub streaming: bool,
}
impl Default for RpcMiddlewaresSection {
fn default() -> Self {
Self {
resilience: true,
streaming: true,
}
}
}
#[cfg(test)]
mod tests {
use super::RpcServiceConfig;
#[test]
fn validate_features_reflects_compile_time_features() {
let warnings = RpcServiceConfig::default().validate_features();
assert_eq!(
warnings
.iter()
.any(|warning| warning.option == "middlewares.resilience"),
!cfg!(feature = "resil")
);
assert_eq!(
warnings
.iter()
.any(|warning| warning.option == "middlewares.streaming"),
!cfg!(feature = "resil")
);
}
}