Skip to main content

liminal_server/config/
types.rs

1use std::net::SocketAddr;
2use std::path::PathBuf;
3use std::time::Duration;
4
5/// Declarative configuration for the standalone liminal server wrapper.
6#[derive(Debug, Clone, serde::Deserialize)]
7#[serde(deny_unknown_fields)]
8pub struct ServerConfig {
9    /// Socket address where the standalone server will listen for client traffic.
10    pub listen_address: SocketAddr,
11    /// Socket address where the health endpoint server will listen for probes.
12    pub health_listen_address: SocketAddr,
13    /// Maximum time to allow existing connections to drain during graceful shutdown.
14    pub drain_timeout_ms: u64,
15    /// Channel topology definitions declared by the operator.
16    pub channels: Vec<ChannelDef>,
17    /// Declarative routing rules that connect configured channels.
18    pub routing_rules: Vec<RoutingRuleDef>,
19    /// Optional filesystem location for durable server state.
20    pub persistence_path: Option<PathBuf>,
21    /// Optional beamr distribution cluster membership configuration.
22    pub cluster: Option<ClusterConfig>,
23    /// Optional connection authentication configuration.
24    ///
25    /// When present, every client `Connect` handshake must carry a matching
26    /// `auth_token`; when absent the server is open (byte-identical to the
27    /// pre-auth behaviour). Not an ACL system — a single shared bearer token.
28    #[serde(default)]
29    pub auth: Option<AuthConfig>,
30}
31
32impl ServerConfig {
33    /// Returns the configured graceful-shutdown drain timeout.
34    #[must_use]
35    pub const fn drain_timeout(&self) -> Duration {
36        Duration::from_millis(self.drain_timeout_ms)
37    }
38}
39
40/// Declarative channel definition loaded from server configuration.
41#[derive(Debug, Clone, serde::Deserialize)]
42#[serde(deny_unknown_fields)]
43pub struct ChannelDef {
44    /// Unique channel name used by routing rules and operators.
45    pub name: String,
46    /// Filesystem path to a JSON Schema document that validates every message
47    /// published to this channel.
48    ///
49    /// The path is resolved relative to the directory containing the config file
50    /// (absolute paths are used verbatim). Config validation reads and parses the
51    /// referenced document and stores the result in [`Self::loaded_schema`]; a
52    /// missing file, an unreadable file, or a file that is not valid JSON is an
53    /// accumulated validation error that stops startup.
54    ///
55    /// `None` means the channel has no schema: it keeps the permissive empty
56    /// schema (`{}`) that accepts any JSON payload.
57    #[serde(default)]
58    pub schema_ref: Option<PathBuf>,
59    /// Whether this channel requires durable persistence.
60    pub durable: bool,
61    /// Schema document loaded and parsed from [`Self::schema_ref`] during config
62    /// validation. Populated only by [`crate::config::validate`]; a directly
63    /// constructed [`ChannelDef`] that skips validation carries `None` here and is
64    /// therefore built with the permissive empty schema regardless of
65    /// [`Self::schema_ref`]. Never deserialized from the config file.
66    #[serde(skip)]
67    pub loaded_schema: Option<LoadedSchema>,
68}
69
70/// A channel's JSON Schema document as loaded from disk during config validation.
71///
72/// Carries both the parsed document (fed to the validation engine when the channel
73/// is built) and the raw file bytes (hashed into the protocol schema id advertised
74/// at subscribe time, so an SDK deriving ids from the same schema bytes converges).
75#[derive(Debug, Clone)]
76pub struct LoadedSchema {
77    /// Raw bytes of the schema file, hashed to derive the protocol schema id.
78    pub bytes: Vec<u8>,
79    /// Parsed JSON Schema document, fed to the channel's validation engine.
80    pub document: serde_json::Value,
81}
82
83/// Declarative routing rule definition loaded from server configuration.
84#[derive(Debug, Clone, serde::Deserialize)]
85#[serde(deny_unknown_fields)]
86pub struct RoutingRuleDef {
87    /// Source channel name from which messages are routed.
88    pub source_channel: String,
89    /// Target channel name to which matching messages are routed.
90    pub target_channel: String,
91    /// Optional predicate expression that filters routed messages.
92    pub predicate: Option<String>,
93}
94
95/// Default beamr distribution handshake cookie, used when the operator does not
96/// configure one. Mirrors beamr's own [`beamr::distribution::DEFAULT_COOKIE`].
97pub const DEFAULT_COOKIE: &str = "beamr-cookie";
98
99/// Beamr distribution cluster configuration for standalone deployment.
100#[derive(Debug, Clone, serde::Deserialize)]
101#[serde(deny_unknown_fields)]
102pub struct ClusterConfig {
103    /// Unique node name advertised to the beamr distribution cluster.
104    pub node_name: String,
105    /// Socket address this node binds for inbound distribution links from peers.
106    ///
107    /// This is distinct from [`ServerConfig::listen_address`] (the client wire
108    /// port): a clustered node listens on two ports — one for clients, one for
109    /// peer distribution traffic.
110    pub listen_address: SocketAddr,
111    /// Seed node socket addresses used to join an existing cluster.
112    pub seed_nodes: Vec<SocketAddr>,
113    /// Shared distribution handshake cookie. Every node in a cluster MUST use the
114    /// same cookie or the OTP handshake is rejected. Defaults to
115    /// [`DEFAULT_COOKIE`] when omitted.
116    #[serde(default = "default_cookie")]
117    pub cookie: String,
118}
119
120fn default_cookie() -> String {
121    DEFAULT_COOKIE.to_owned()
122}
123
124/// Connection authentication configuration.
125///
126/// A single shared bearer token compared (constant-time) against the `auth_token`
127/// carried on every client `Connect` handshake. This is the table-stakes access
128/// gate, not an ACL system: one token grants full access, its absence (no `[auth]`
129/// section) leaves the server open.
130#[derive(Debug, Clone, serde::Deserialize)]
131#[serde(deny_unknown_fields)]
132pub struct AuthConfig {
133    /// Shared secret token a client must present in its `Connect` handshake. Must
134    /// be non-empty when the `[auth]` section is present (an empty token is a
135    /// config validation error, since it would gate nothing).
136    pub token: String,
137}