omni_orchestrator/config/mod.rs
1use lazy_static::lazy_static;
2use serde::{Deserialize, Serialize};
3use std::sync::Arc;
4
5/// Configuration for the OmniOrchestrator server application.
6///
7/// This structure defines all the configurable parameters for the server,
8/// including network settings and behavior options. It supports serialization
9/// to and deserialization from JSON for persistent configuration.
10///
11/// The configuration can be loaded from a file or generated with default
12/// values if no configuration file exists.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ServerConfig {
15 /// The port number on which the server will listen
16 pub port: u16,
17
18 /// The IP address to which the server will bind
19 pub address: String,
20
21 /// Whether to apply syntax highlighting to SQL logs
22 pub highlight_sql: bool,
23
24 /// List of other server instances in the cluster
25 pub instances: Vec<Instance>,
26}
27
28/// Represents an instance of the server in the cluster.
29///
30/// This structure contains the network location information for a server
31/// instance that is part of the OmniOrchestrator cluster. It's used for
32/// peer discovery and communication between cluster nodes.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct Instance {
35 /// The port number on which the instance is listening
36 pub port: u16,
37
38 /// The hostname or IP address of the instance
39 pub address: String,
40}
41
42/// Default implementation for ServerConfig.
43///
44/// Provides reasonable default values for a server configuration to be
45/// used when no custom configuration is provided or when initializing
46/// a new configuration file.
47impl Default for ServerConfig {
48 fn default() -> Self {
49 Self {
50 port: 8000,
51 address: "127.0.0.1".to_string(),
52 highlight_sql: true,
53 instances: vec![Instance {
54 port: 8000,
55 address: "example.com".to_string(),
56 }],
57 }
58 }
59}
60
61/// Possible errors that can occur during configuration operations.
62///
63/// This enum represents the various error conditions that might arise
64/// when reading from or writing to the configuration file.
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub enum ConfigError {
67 /// Indicates that the configuration file could not be found
68 FileNotFound,
69
70 /// Indicates that writing to the configuration file failed
71 FailedToWrite,
72
73 /// Indicates that parsing the configuration file content failed
74 ParseError,
75}
76
77/// Global static reference to the server configuration.
78///
79/// This lazy_static provides thread-safe access to the server configuration
80/// throughout the application. It is initialized when first accessed,
81/// reading from the configuration file or creating default settings if
82/// no configuration exists.
83///
84/// # Panics
85///
86/// Panics if the configuration cannot be read or written, which would
87/// prevent the server from starting properly.
88lazy_static! {
89 pub static ref SERVER_CONFIG: Arc<ServerConfig> =
90 Arc::new(ServerConfig::read().expect("Failed to initalize server config"));
91}
92
93impl ServerConfig {
94 /// Reads the server configuration from the config file.
95 ///
96 /// Attempts to load the configuration from "config.json" in the current
97 /// directory. If the file doesn't exist or can't be read, it creates a new
98 /// configuration file with default values and returns those defaults.
99 ///
100 /// # Returns
101 ///
102 /// * `Ok(ServerConfig)` - Successfully loaded or created configuration
103 /// * `Err(ConfigError)` - Failed to parse existing configuration
104 ///
105 /// # Error Handling
106 ///
107 /// - If the file doesn't exist, creates a default configuration
108 /// - If the file exists but can't be parsed, returns a ParseError
109 pub fn read() -> Result<Self, ConfigError> {
110 let config_path = "config.json";
111 let config_content = match std::fs::read_to_string(config_path) {
112 Ok(content) => content,
113 Err(_) => {
114 // If file doesn't exist, create a default configuration
115 Self::write_default().expect("Failed to write default config");
116 return Ok(ServerConfig::default());
117 }
118 };
119
120 // Parse the configuration from JSON
121 let config: ServerConfig = match serde_json5::from_str(&config_content) {
122 Ok(config) => config,
123 Err(_) => return Err(ConfigError::ParseError),
124 };
125
126 Ok(config)
127 }
128
129 /// Writes the current configuration to the config file.
130 ///
131 /// Serializes the configuration to JSON and writes it to "config.json"
132 /// in the current directory. This allows configuration changes to persist
133 /// across server restarts.
134 ///
135 /// # Returns
136 ///
137 /// * `Ok(())` - Successfully wrote configuration to file
138 /// * `Err(ConfigError)` - Failed to serialize or write configuration
139 ///
140 /// # Error Handling
141 ///
142 /// - Returns ParseError if serialization to JSON fails
143 /// - Returns FailedToWrite if writing to the file fails
144 pub fn write(&self) -> Result<(), ConfigError> {
145 let config_path = "config.json";
146
147 // Serialize the configuration to pretty-printed JSON
148 let config_content = match serde_json::to_string_pretty(&self) {
149 Ok(content) => content,
150 Err(_) => return Err(ConfigError::ParseError),
151 };
152
153 // Write the JSON to the configuration file
154 match std::fs::write(config_path, config_content) {
155 Ok(_) => Ok(()),
156 Err(_) => Err(ConfigError::FailedToWrite),
157 }
158 }
159
160 /// Creates and writes a default configuration to the config file.
161 ///
162 /// This is a convenience method that creates a ServerConfig with default
163 /// values and writes it to the configuration file. It's typically used
164 /// when no configuration file exists yet.
165 ///
166 /// # Returns
167 ///
168 /// * `Ok(())` - Successfully wrote default configuration to file
169 /// * `Err(ConfigError)` - Failed to write default configuration
170 pub fn write_default() -> Result<(), ConfigError> {
171 let config = ServerConfig::default();
172 config.write()
173 }
174}