backtest_server/
config.rs1use qs_service::ServiceEndpoint;
4use serde::Deserialize;
5
6#[derive(Debug, Deserialize)]
8pub struct ServerConfig {
9 pub server: ServerSection,
10 pub database: DatabaseSection,
11 pub symbols: SymbolsSection,
12 pub profiles: ProfilesSection,
13 #[serde(default)]
14 pub jobs: JobsSection,
15 #[serde(default)]
16 pub artifacts: ArtifactsSection,
17 #[serde(default)]
18 pub logging: LoggingSection,
19}
20
21#[derive(Debug, Deserialize)]
23pub struct ServerSection {
24 #[serde(default)]
26 pub endpoint: Option<ServiceEndpoint>,
27 #[serde(default)]
29 pub shm_name: String,
30 #[serde(default = "default_shm_buffer")]
32 pub shm_buffer_size: usize,
33 #[serde(default = "default_max_connections")]
34 pub max_connections: usize,
35 #[serde(default)]
36 pub allow_insecure_non_loopback: bool,
37}
38
39impl ServerSection {
40 pub fn resolved_endpoint(&self) -> crate::error::Result<ServiceEndpoint> {
41 let legacy = if self.shm_name.is_empty() {
42 None
43 } else {
44 Some(
45 ServiceEndpoint::shared_memory(self.shm_name.clone()).map_err(|error| {
46 crate::error::BacktestServerError::Config(error.to_string())
47 })?,
48 )
49 };
50 match (&self.endpoint, legacy) {
51 (Some(endpoint), Some(legacy)) if endpoint != &legacy => {
52 Err(crate::error::BacktestServerError::Config(format!(
53 "conflicting server.endpoint '{}' and deprecated server.shm_name '{}'",
54 endpoint, self.shm_name
55 )))
56 }
57 (Some(endpoint), _) => Ok(endpoint.clone()),
58 (None, Some(endpoint)) => Ok(endpoint),
59 (None, None) => Err(crate::error::BacktestServerError::Config(
60 "server.endpoint is required (or provide deprecated server.shm_name)".to_string(),
61 )),
62 }
63 }
64}
65
66#[derive(Debug, Deserialize)]
68pub struct DatabaseSection {
69 pub data_dir: String,
71}
72
73#[derive(Debug, Deserialize)]
75pub struct SymbolsSection {
76 pub registry_path: String,
77}
78
79#[derive(Debug, Deserialize)]
81pub struct ProfilesSection {
82 pub profiles_path: String,
83}
84
85#[derive(Debug, Deserialize)]
87pub struct JobsSection {
88 #[serde(default = "default_job_retention_secs")]
90 pub retention_secs: u64,
91 #[serde(default = "default_job_cleanup_interval_secs")]
93 pub cleanup_interval_secs: u64,
94 #[serde(default = "default_max_retained_jobs")]
96 pub max_retained_jobs: usize,
97}
98
99impl Default for JobsSection {
100 fn default() -> Self {
101 Self {
102 retention_secs: default_job_retention_secs(),
103 cleanup_interval_secs: default_job_cleanup_interval_secs(),
104 max_retained_jobs: default_max_retained_jobs(),
105 }
106 }
107}
108
109#[derive(Debug, Deserialize)]
111pub struct ArtifactsSection {
112 #[serde(default = "default_artifact_directory")]
114 pub directory: String,
115 #[serde(default = "default_artifact_inline_limit_bytes")]
117 pub inline_limit_bytes: usize,
118 #[serde(default = "default_artifact_chunk_size")]
120 pub chunk_size: usize,
121 #[serde(default = "default_artifact_retention_secs")]
123 pub retention_secs: u64,
124 #[serde(default = "default_artifact_max_total_bytes")]
126 pub max_total_bytes: u64,
127}
128
129impl Default for ArtifactsSection {
130 fn default() -> Self {
131 Self {
132 directory: default_artifact_directory(),
133 inline_limit_bytes: default_artifact_inline_limit_bytes(),
134 chunk_size: default_artifact_chunk_size(),
135 retention_secs: default_artifact_retention_secs(),
136 max_total_bytes: default_artifact_max_total_bytes(),
137 }
138 }
139}
140
141#[derive(Debug, Deserialize)]
143pub struct LoggingSection {
144 #[serde(default = "default_log_level")]
145 pub level: String,
146}
147
148impl Default for LoggingSection {
149 fn default() -> Self {
150 Self {
151 level: default_log_level(),
152 }
153 }
154}
155
156fn default_shm_buffer() -> usize {
157 16 * 1024 * 1024
158}
159
160fn default_max_connections() -> usize {
161 256
162}
163
164fn default_job_retention_secs() -> u64 {
165 3_600
166}
167
168fn default_job_cleanup_interval_secs() -> u64 {
169 60
170}
171
172fn default_max_retained_jobs() -> usize {
173 1_000
174}
175
176fn default_artifact_directory() -> String {
177 "backtest-artifacts".into()
178}
179
180fn default_artifact_inline_limit_bytes() -> usize {
181 12 * 1024 * 1024
182}
183
184fn default_artifact_chunk_size() -> usize {
185 1024 * 1024
186}
187
188fn default_artifact_retention_secs() -> u64 {
189 3_600
190}
191
192fn default_artifact_max_total_bytes() -> u64 {
193 1024 * 1024 * 1024
194}
195
196fn default_log_level() -> String {
197 "info".into()
198}
199
200pub fn load_config(path: &str) -> crate::error::Result<ServerConfig> {
202 let content = std::fs::read_to_string(path)
203 .map_err(|e| crate::error::BacktestServerError::Config(format!("{path}: {e}")))?;
204 toml::from_str(&content)
205 .map_err(|e| crate::error::BacktestServerError::Config(format!("{path}: {e}")))
206}