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}