use std::net::SocketAddr;
use std::path::PathBuf;
use std::time::Duration;
use crate::ServerError;
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ServerConfig {
pub listen_address: SocketAddr,
pub health_listen_address: SocketAddr,
pub drain_timeout_ms: u64,
pub channels: Vec<ChannelDef>,
pub routing_rules: Vec<RoutingRuleDef>,
pub persistence_path: Option<PathBuf>,
pub cluster: Option<ClusterConfig>,
#[serde(default)]
pub auth: Option<AuthConfig>,
#[serde(default)]
pub services: ServicesConfig,
#[serde(default)]
pub limits: LimitsConfig,
}
impl ServerConfig {
#[must_use]
pub const fn drain_timeout(&self) -> Duration {
Duration::from_millis(self.drain_timeout_ms)
}
}
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ChannelDef {
pub name: String,
#[serde(default)]
pub schema_ref: Option<PathBuf>,
pub durable: bool,
#[serde(skip)]
pub loaded_schema: Option<LoadedSchema>,
}
#[derive(Debug, Clone)]
pub struct LoadedSchema {
pub bytes: Vec<u8>,
pub document: serde_json::Value,
}
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RoutingRuleDef {
pub source_channel: String,
pub target_channel: String,
pub predicate: Option<String>,
}
pub const DEFAULT_COOKIE: &str = "beamr-cookie";
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ClusterConfig {
pub node_name: String,
pub listen_address: SocketAddr,
pub seed_nodes: Vec<SocketAddr>,
#[serde(default = "default_cookie")]
pub cookie: String,
}
fn default_cookie() -> String {
DEFAULT_COOKIE.to_owned()
}
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AuthConfig {
pub token: String,
}
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ServicesConfig {
#[serde(default = "default_service_profile")]
pub profile: String,
}
impl Default for ServicesConfig {
fn default() -> Self {
Self {
profile: default_service_profile(),
}
}
}
impl ServicesConfig {
pub fn profile(&self) -> Result<ServiceProfile, ServerError> {
ServiceProfile::parse(&self.profile)
}
}
fn default_service_profile() -> String {
ServiceProfile::FULL.to_owned()
}
#[derive(Debug, Clone, Copy, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LimitsConfig {
#[serde(default = "default_max_connections")]
pub max_connections: usize,
#[serde(default = "default_max_subscriptions_per_connection")]
pub max_subscriptions_per_connection: usize,
#[serde(default = "default_max_conversations_per_connection")]
pub max_conversations_per_connection: usize,
#[serde(default = "default_max_pending_pushes_per_connection")]
pub max_pending_pushes_per_connection: usize,
#[serde(default = "default_max_pending_conversation_replies_per_connection")]
pub max_pending_conversation_replies_per_connection: usize,
#[serde(default = "default_max_pending_replies_per_conversation")]
pub max_pending_replies_per_conversation: usize,
#[serde(default = "default_max_connection_inbox_bytes")]
pub max_connection_inbox_bytes: usize,
#[serde(default = "default_max_subscription_inbox_depth")]
pub max_subscription_inbox_depth: usize,
}
impl LimitsConfig {
pub const DEFAULT_MAX_CONNECTIONS: usize = 256;
pub const DEFAULT_MAX_SUBSCRIPTIONS_PER_CONNECTION: usize = 32;
pub const DEFAULT_MAX_CONVERSATIONS_PER_CONNECTION: usize = 32;
pub const DEFAULT_MAX_PENDING_PUSHES_PER_CONNECTION: usize = 32;
pub const DEFAULT_MAX_PENDING_CONVERSATION_REPLIES_PER_CONNECTION: usize = 32;
pub const DEFAULT_MAX_PENDING_REPLIES_PER_CONVERSATION: usize = 8;
pub const DEFAULT_MAX_CONNECTION_INBOX_BYTES: usize = 4 * 1024 * 1024;
pub const DEFAULT_MAX_SUBSCRIPTION_INBOX_DEPTH: usize = 256;
pub(crate) fn collect_errors(&self, errors: &mut Vec<String>) {
let checks: [(&str, usize); 8] = [
("max_connections", self.max_connections),
(
"max_subscriptions_per_connection",
self.max_subscriptions_per_connection,
),
(
"max_conversations_per_connection",
self.max_conversations_per_connection,
),
(
"max_pending_pushes_per_connection",
self.max_pending_pushes_per_connection,
),
(
"max_pending_conversation_replies_per_connection",
self.max_pending_conversation_replies_per_connection,
),
(
"max_pending_replies_per_conversation",
self.max_pending_replies_per_conversation,
),
(
"max_connection_inbox_bytes",
self.max_connection_inbox_bytes,
),
(
"max_subscription_inbox_depth",
self.max_subscription_inbox_depth,
),
];
for (field, value) in checks {
if value == 0 {
errors.push(format!(
"limits.{field}: must be greater than zero (a zero cap would be \
unlimited-by-silence, which §5 forbids)"
));
}
}
}
}
impl Default for LimitsConfig {
fn default() -> Self {
Self {
max_connections: default_max_connections(),
max_subscriptions_per_connection: default_max_subscriptions_per_connection(),
max_conversations_per_connection: default_max_conversations_per_connection(),
max_pending_pushes_per_connection: default_max_pending_pushes_per_connection(),
max_pending_conversation_replies_per_connection:
default_max_pending_conversation_replies_per_connection(),
max_pending_replies_per_conversation: default_max_pending_replies_per_conversation(),
max_connection_inbox_bytes: default_max_connection_inbox_bytes(),
max_subscription_inbox_depth: default_max_subscription_inbox_depth(),
}
}
}
const fn default_max_connections() -> usize {
LimitsConfig::DEFAULT_MAX_CONNECTIONS
}
const fn default_max_subscriptions_per_connection() -> usize {
LimitsConfig::DEFAULT_MAX_SUBSCRIPTIONS_PER_CONNECTION
}
const fn default_max_conversations_per_connection() -> usize {
LimitsConfig::DEFAULT_MAX_CONVERSATIONS_PER_CONNECTION
}
const fn default_max_pending_pushes_per_connection() -> usize {
LimitsConfig::DEFAULT_MAX_PENDING_PUSHES_PER_CONNECTION
}
const fn default_max_pending_conversation_replies_per_connection() -> usize {
LimitsConfig::DEFAULT_MAX_PENDING_CONVERSATION_REPLIES_PER_CONNECTION
}
const fn default_max_pending_replies_per_conversation() -> usize {
LimitsConfig::DEFAULT_MAX_PENDING_REPLIES_PER_CONVERSATION
}
const fn default_max_connection_inbox_bytes() -> usize {
LimitsConfig::DEFAULT_MAX_CONNECTION_INBOX_BYTES
}
const fn default_max_subscription_inbox_depth() -> usize {
LimitsConfig::DEFAULT_MAX_SUBSCRIPTION_INBOX_DEPTH
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ServiceProfile {
Full,
WorkerFrontDoor,
}
impl ServiceProfile {
pub const FULL: &'static str = "full";
pub const WORKER_FRONT_DOOR: &'static str = "worker-front-door";
pub fn parse(value: &str) -> Result<Self, ServerError> {
match value {
Self::FULL => Ok(Self::Full),
Self::WORKER_FRONT_DOOR => Ok(Self::WorkerFrontDoor),
other => Err(ServerError::ConfigValidation {
message: format!(
"services.profile: unknown profile '{other}'; expected \"{}\" or \"{}\"",
Self::FULL,
Self::WORKER_FRONT_DOOR
),
}),
}
}
}