use std::{net::SocketAddr, path::PathBuf, time::Duration};
use serde::Deserialize;
use crate::error::ServerError;
use super::{
config_error,
defaults::{
CLUSTER_BROADCAST_CAPACITY_REQUIRED, DEFAULT_GRPC_ADDRESS, DEFAULT_HTTP_ADDRESS,
DEFAULT_MAX_IN_FLIGHT_ACTIVITIES, DEFAULT_OBSERVABILITY_MAX_EVENT_BYTES,
DEFAULT_OBSERVABILITY_MAX_STREAM_EVENTS, EVENT_BROADCAST_CAPACITY_REQUIRED,
OBSERVABILITY_MAX_EVENT_BYTES_REQUIRED, OBSERVABILITY_MAX_STREAM_EVENTS_REQUIRED,
},
};
#[derive(Clone, Debug, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct ServerSection {
pub listen_address: SocketAddr,
pub grpc_address: SocketAddr,
#[serde(default)]
pub cors_allowed_origins: Vec<String>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum StoreBackend {
Memory,
LibSql,
Haematite,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct StoreConfig {
pub backend: StoreBackend,
pub url: Option<String>,
pub owned_shards: Vec<usize>,
pub data_dir: Option<String>,
pub shard_count: usize,
pub cluster: Option<ClusterConfig>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct ClusterConfig {
pub node_id: String,
pub bind_address: SocketAddr,
#[serde(default)]
pub members: Vec<String>,
#[serde(default)]
pub peers: Vec<ClusterPeer>,
#[serde(default)]
pub failover_poll_interval_ms: Option<u64>,
#[serde(default)]
pub failover_confirmations: Option<u32>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct ClusterPeer {
pub name: String,
pub address: SocketAddr,
#[serde(default)]
pub grpc_address: Option<SocketAddr>,
#[serde(default)]
pub owned_shards: Vec<usize>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct RuntimeSection {
pub scheduler_threads: usize,
pub query_timeout_ms: Option<u64>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct DrainConfig {
pub timeout_seconds: u64,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct AuthConfig {
pub enabled: bool,
pub jwks_url: Option<String>,
pub jwks_refresh_seconds: u64,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct MetricsConfig {
pub enabled: bool,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct NamespacesConfig {
pub default: String,
pub auto_create: AutoCreate,
pub max_in_flight_activities: u32,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AutoCreate {
#[default]
Open,
Closed,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct ListenConfig {
pub grpc: SocketAddr,
pub http: SocketAddr,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TlsConfig {
pub certificate_chain_path: PathBuf,
pub private_key_path: PathBuf,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct OpsConsoleConfig {
pub source: OpsConsoleAssetSource,
}
#[derive(Clone, Debug, Deserialize)]
pub enum OpsConsoleAssetSource {
FileSystem {
asset_path: PathBuf,
},
Embedded,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct NamespaceConfig {
pub mode: NamespaceMode,
}
#[derive(Clone, Debug, Deserialize)]
pub enum NamespaceMode {
SharedEngine,
SingleTenant {
namespace: String,
},
}
#[derive(Clone, Debug, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct WorkerConfig {
#[serde(with = "duration_millis")]
pub heartbeat_window: Duration,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct WebSocketConfig {
pub outbound_buffer_bound: usize,
pub event_broadcast_capacity: Option<usize>,
pub cluster_broadcast_capacity: Option<usize>,
}
impl WebSocketConfig {
pub(super) fn validate(&self) -> Result<(), ServerError> {
if self.outbound_buffer_bound == 0 {
return config_error("websocket.outbound_buffer_bound must be greater than zero");
}
match self.event_broadcast_capacity {
None | Some(0) => return config_error(EVENT_BROADCAST_CAPACITY_REQUIRED),
Some(_) => {}
}
match self.cluster_broadcast_capacity {
None | Some(0) => return config_error(CLUSTER_BROADCAST_CAPACITY_REQUIRED),
Some(_) => {}
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
#[serde(default, deny_unknown_fields)]
pub struct ObservabilityConfig {
pub max_event_bytes: usize,
pub max_stream_events: u64,
}
impl ObservabilityConfig {
pub(super) fn validate(&self) -> Result<(), ServerError> {
if self.max_event_bytes == 0 {
return config_error(OBSERVABILITY_MAX_EVENT_BYTES_REQUIRED);
}
if self.max_stream_events == 0 {
return config_error(OBSERVABILITY_MAX_STREAM_EVENTS_REQUIRED);
}
Ok(())
}
}
impl Default for ObservabilityConfig {
fn default() -> Self {
Self {
max_event_bytes: DEFAULT_OBSERVABILITY_MAX_EVENT_BYTES,
max_stream_events: DEFAULT_OBSERVABILITY_MAX_STREAM_EVENTS,
}
}
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct DeployConfig {
pub enabled: bool,
pub max_archive_bytes: Option<u64>,
pub max_inflated_bytes: Option<u64>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct DevConfig {
pub enabled: bool,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct OutboxConfig {
pub enabled: bool,
pub poll_interval_ms: Option<u64>,
pub batch_size: Option<u32>,
pub max_attempts: Option<u32>,
pub backoff_base_ms: Option<u64>,
pub backoff_multiplier: Option<u32>,
pub backoff_max_ms: Option<u64>,
pub reconcile_interval_ms: Option<u64>,
pub reconcile_stale_after_ms: Option<u64>,
pub transport: OutboxTransport,
pub liminal_listen_address: Option<String>,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OutboxTransport {
#[cfg_attr(not(feature = "liminal-transport"), default)]
Grpc,
#[cfg_attr(feature = "liminal-transport", default)]
Liminal,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct AuthoringConfig {
pub gleam_path: Option<PathBuf>,
pub project_root: Option<PathBuf>,
pub workspace_dir: Option<PathBuf>,
}
impl Default for ServerSection {
fn default() -> Self {
Self {
listen_address: DEFAULT_HTTP_ADDRESS,
grpc_address: DEFAULT_GRPC_ADDRESS,
cors_allowed_origins: Vec::new(),
}
}
}
impl Default for StoreConfig {
fn default() -> Self {
Self {
backend: StoreBackend::Haematite,
url: None,
owned_shards: Vec::new(),
data_dir: None,
shard_count: 64,
cluster: None,
}
}
}
impl Default for RuntimeSection {
fn default() -> Self {
Self {
scheduler_threads: 1,
query_timeout_ms: None,
}
}
}
impl Default for DrainConfig {
fn default() -> Self {
Self {
timeout_seconds: 30,
}
}
}
impl Default for AuthConfig {
fn default() -> Self {
Self {
enabled: false,
jwks_url: None,
jwks_refresh_seconds: 300,
}
}
}
impl Default for MetricsConfig {
fn default() -> Self {
Self { enabled: true }
}
}
impl Default for NamespacesConfig {
fn default() -> Self {
Self {
default: "default".to_owned(),
auto_create: AutoCreate::default(),
max_in_flight_activities: DEFAULT_MAX_IN_FLIGHT_ACTIVITIES,
}
}
}
impl Default for ListenConfig {
fn default() -> Self {
Self {
grpc: DEFAULT_GRPC_ADDRESS,
http: DEFAULT_HTTP_ADDRESS,
}
}
}
impl Default for OpsConsoleConfig {
fn default() -> Self {
Self {
source: OpsConsoleAssetSource::Embedded,
}
}
}
impl Default for NamespaceConfig {
fn default() -> Self {
Self {
mode: NamespaceMode::SharedEngine,
}
}
}
impl Default for WorkerConfig {
fn default() -> Self {
Self {
heartbeat_window: Duration::from_secs(30),
}
}
}
impl Default for WebSocketConfig {
fn default() -> Self {
Self {
outbound_buffer_bound: 32,
event_broadcast_capacity: None,
cluster_broadcast_capacity: None,
}
}
}
mod duration_millis {
use std::time::Duration;
use serde::{Deserialize, Deserializer};
pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let millis = u64::deserialize(deserializer)?;
Ok(Duration::from_millis(millis))
}
}