1use derivative::Derivative;
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, Deserialize, Serialize)]
6#[serde(default)]
7pub struct ServerConfig {
8 pub allow_artifact_mutations: bool,
9 pub certification_dir: PathBuf,
11 pub api_uri: String,
13 pub admin_app_redirect_uri: String,
15
16 pub fhir: FHIRConfig,
17 pub repo: RepoConfig,
18 pub search: SearchConfig,
19 pub email: Option<EmailConfig>,
20 pub rate_limits: RateLimitsConfig,
21 pub max_request_body_size: usize,
22 pub monitoring: MonitoringConfig,
23 pub security: SecurityConfig,
24 pub operations: OperationsConfig,
25}
26
27#[derive(Debug, Clone, Deserialize, Serialize)]
28#[serde(default)]
29pub struct SecurityConfig {
30 pub publicize_fhir_metadata: bool,
31 pub mfa: MFAConfig,
32 pub encryption: SecretProviderConfig,
33 pub aes_key: Option<String>,
34 pub certification_key: Option<String>,
35}
36
37#[derive(Debug, Clone, Deserialize, Serialize)]
38#[serde(default)]
39pub struct MFAConfig {
40 pub max_credentials_per_user: usize,
41}
42
43#[derive(Debug, Clone, Deserialize, Serialize)]
44#[serde(default)]
45pub struct OperationsConfig {
46 pub deno_pool_threads: usize,
53}
54
55#[derive(Debug, Clone, Deserialize, Serialize)]
56#[serde(default)]
57#[derive(Default)]
58pub struct MonitoringConfig {
59 pub audit_enabled: bool,
60 pub ip_source: IpSource,
61}
62
63#[derive(Debug, Clone, Deserialize, Serialize)]
64#[serde(tag = "type", rename_all = "snake_case")]
65pub enum SecretProviderConfig {
66 Environment { prefix: Option<String> },
67 GCP { project_id: String },
68 AWS { region: String },
69}
70
71#[derive(Debug, Clone, Deserialize, Serialize)]
72#[serde(default)]
73pub struct FHIRConfig {
74 pub delete_limit: u64,
76}
77
78#[derive(Debug, Clone, Deserialize, Serialize)]
80#[serde(tag = "backend", rename_all = "snake_case")]
81pub enum RepoConfig {
82 Postgres(PostgresConfig),
83}
84
85#[derive(Derivative, Clone, Deserialize, Serialize)]
86#[derivative(Debug)]
87pub struct PostgresConfig {
88 #[derivative(Debug = "ignore")]
89 pub database_url: String,
90 pub max_connections: u32,
91}
92
93#[derive(Debug, Clone, Deserialize, Serialize)]
95#[serde(tag = "backend", rename_all = "snake_case")]
96pub enum SearchConfig {
97 Elasticsearch(ElasticsearchConfig),
98 Postgres(PostgresSearchConfig),
99}
100
101#[derive(Derivative, Clone, Deserialize, Serialize)]
102#[derivative(Debug)]
103pub struct PostgresSearchConfig {
104 #[derivative(Debug = "ignore")]
105 pub database_url: String,
106 pub max_connections: u32,
107}
108
109#[derive(Derivative, Clone, Deserialize, Serialize)]
110#[derivative(Debug)]
111pub struct ElasticsearchConfig {
112 pub url: String,
113 #[derivative(Debug = "ignore")]
114 pub username: String,
115 #[derivative(Debug = "ignore")]
116 pub password: String,
117 #[serde(default)]
123 pub prune_removed_search_parameters: bool,
124}
125
126#[derive(Derivative, Clone, Deserialize, Serialize)]
127#[derivative(Debug)]
128#[serde(tag = "backend", rename_all = "snake_case")]
129pub enum EmailConfig {
130 SendGrid {
131 #[derivative(Debug = "ignore")]
132 api_key: String,
133 #[derivative(Debug = "ignore")]
134 from_address: String,
135 },
136}
137
138#[derive(Debug, Clone, Deserialize, Serialize)]
139#[serde(default)]
140pub struct RateLimitsConfig {
141 pub rate_limit_subscription_tiers: Option<[usize; 4]>,
142 pub rate_limit_window_seconds: u64,
143 pub rate_limit_operation_points: u32,
144}
145
146#[derive(Debug, Clone, Deserialize, Serialize, Default)]
147#[serde(rename_all = "snake_case")]
148pub enum IpSource {
149 #[default]
150 ConnectInfo,
151 CfConnectingIp,
152 XRealIp,
153}
154
155impl Default for FHIRConfig {
156 fn default() -> Self {
157 Self { delete_limit: 100 }
158 }
159}
160
161impl Default for ServerConfig {
162 fn default() -> Self {
163 Self {
164 allow_artifact_mutations: false,
165 certification_dir: PathBuf::from("certifications"),
166 api_uri: "http://localhost:3000".into(),
167 admin_app_redirect_uri: "http://*.localhost:3001".into(),
168 fhir: FHIRConfig::default(),
169 repo: RepoConfig::default(),
170 search: SearchConfig::default(),
171 email: None,
172 max_request_body_size: 4 * 1024 * 1024,
173 rate_limits: RateLimitsConfig::default(),
174 monitoring: MonitoringConfig::default(),
175 security: SecurityConfig::default(),
176 operations: OperationsConfig::default(),
177 }
178 }
179}
180impl Default for RepoConfig {
181 fn default() -> Self {
182 RepoConfig::Postgres(PostgresConfig::default())
183 }
184}
185impl Default for PostgresConfig {
186 fn default() -> Self {
187 Self {
188 database_url: "postgresql://postgres:postgres@localhost:5432/haste_health".into(),
189 max_connections: 10,
190 }
191 }
192}
193impl Default for SearchConfig {
194 fn default() -> Self {
195 SearchConfig::Elasticsearch(ElasticsearchConfig::default())
196 }
197}
198impl Default for ElasticsearchConfig {
199 fn default() -> Self {
200 Self {
201 url: "http://localhost:9200".into(),
202 username: "elastic".into(),
203 password: "elastic".into(),
204 prune_removed_search_parameters: false,
205 }
206 }
207}
208
209impl Default for PostgresSearchConfig {
210 fn default() -> Self {
211 Self {
212 database_url: "postgresql://postgres:postgres@localhost:5432/haste_search".into(),
213 max_connections: 10,
214 }
215 }
216}
217
218impl Default for RateLimitsConfig {
219 fn default() -> Self {
220 Self {
221 rate_limit_subscription_tiers: None,
222 rate_limit_window_seconds: 60 * 60 * 24, rate_limit_operation_points: 100,
224 }
225 }
226}
227
228impl Default for SecurityConfig {
229 fn default() -> Self {
230 Self {
231 publicize_fhir_metadata: true,
232 mfa: MFAConfig::default(),
233 encryption: SecretProviderConfig::default(),
234 aes_key: None,
235 certification_key: None,
236 }
237 }
238}
239
240impl Default for MFAConfig {
241 fn default() -> Self {
242 Self {
243 max_credentials_per_user: 1,
244 }
245 }
246}
247
248impl Default for OperationsConfig {
249 fn default() -> Self {
250 let available = std::thread::available_parallelism()
251 .map(std::num::NonZeroUsize::get)
252 .unwrap_or(4);
253
254 Self {
255 deno_pool_threads: (available / 2).max(2),
256 }
257 }
258}
259
260impl Default for SecretProviderConfig {
261 fn default() -> Self {
262 Self::Environment {
263 prefix: Some("HASTE_SECRET_".to_string()),
264 }
265 }
266}