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}
25
26#[derive(Debug, Clone, Deserialize, Serialize)]
27#[serde(default)]
28pub struct SecurityConfig {
29 pub publicize_fhir_metadata: bool,
30 pub mfa: MFAConfig,
31 pub encryption: SecretProviderConfig,
32 pub aes_key: Option<String>,
33 pub certification_key: Option<String>,
34}
35
36#[derive(Debug, Clone, Deserialize, Serialize)]
37#[serde(default)]
38pub struct MFAConfig {
39 pub max_credentials_per_user: usize,
40}
41
42#[derive(Debug, Clone, Deserialize, Serialize)]
43#[serde(default)]
44pub struct MonitoringConfig {
45 pub audit_enabled: bool,
46 pub ip_source: IpSource,
47}
48
49#[derive(Debug, Clone, Deserialize, Serialize)]
50#[serde(tag = "type", rename_all = "snake_case")]
51pub enum SecretProviderConfig {
52 Environment { prefix: Option<String> },
53 GCP { project_id: String },
54 AWS { region: String },
55}
56
57#[derive(Debug, Clone, Deserialize, Serialize)]
58#[serde(default)]
59pub struct FHIRConfig {
60 pub delete_limit: usize,
62}
63
64#[derive(Debug, Clone, Deserialize, Serialize)]
66#[serde(tag = "backend", rename_all = "snake_case")]
67pub enum RepoConfig {
68 Postgres(PostgresConfig),
69}
70
71#[derive(Derivative, Clone, Deserialize, Serialize)]
72#[derivative(Debug)]
73pub struct PostgresConfig {
74 #[derivative(Debug = "ignore")]
75 pub database_url: String,
76 pub max_connections: u32,
77}
78
79#[derive(Debug, Clone, Deserialize, Serialize)]
81#[serde(tag = "backend", rename_all = "snake_case")]
82pub enum SearchConfig {
83 Elasticsearch(ElasticsearchConfig),
84}
85
86#[derive(Derivative, Clone, Deserialize, Serialize)]
87#[derivative(Debug)]
88pub struct ElasticsearchConfig {
89 pub url: String,
90 #[derivative(Debug = "ignore")]
91 pub username: String,
92 #[derivative(Debug = "ignore")]
93 pub password: String,
94}
95
96#[derive(Derivative, Clone, Deserialize, Serialize)]
97#[derivative(Debug)]
98#[serde(tag = "backend", rename_all = "snake_case")]
99pub enum EmailConfig {
100 SendGrid {
101 #[derivative(Debug = "ignore")]
102 api_key: String,
103 #[derivative(Debug = "ignore")]
104 from_address: String,
105 },
106}
107
108#[derive(Debug, Clone, Deserialize, Serialize)]
109#[serde(default)]
110pub struct RateLimitsConfig {
111 pub rate_limit_subscription_tiers: Option<[usize; 4]>,
112 pub rate_limit_window_seconds: u64,
113 pub rate_limit_operation_points: u32,
114}
115
116#[derive(Debug, Clone, Deserialize, Serialize, Default)]
117#[serde(rename_all = "snake_case")]
118pub enum IpSource {
119 #[default]
120 ConnectInfo,
121 CfConnectingIp,
122 XRealIp,
123}
124
125impl Default for FHIRConfig {
126 fn default() -> Self {
127 Self { delete_limit: 100 }
128 }
129}
130
131impl Default for ServerConfig {
132 fn default() -> Self {
133 Self {
134 allow_artifact_mutations: false,
135 certification_dir: PathBuf::from("certifications"),
136 api_uri: "http://localhost:3000".into(),
137 admin_app_redirect_uri: "http://*.localhost:3001".into(),
138 fhir: FHIRConfig::default(),
139 repo: RepoConfig::default(),
140 search: SearchConfig::default(),
141 email: None,
142 max_request_body_size: 4 * 1024 * 1024,
143 rate_limits: RateLimitsConfig::default(),
144 monitoring: MonitoringConfig::default(),
145 security: SecurityConfig::default(),
146 }
147 }
148}
149impl Default for RepoConfig {
150 fn default() -> Self {
151 RepoConfig::Postgres(PostgresConfig::default())
152 }
153}
154impl Default for PostgresConfig {
155 fn default() -> Self {
156 Self {
157 database_url: "postgresql://postgres:postgres@localhost:5432/haste_health".into(),
158 max_connections: 10,
159 }
160 }
161}
162impl Default for SearchConfig {
163 fn default() -> Self {
164 SearchConfig::Elasticsearch(ElasticsearchConfig::default())
165 }
166}
167impl Default for ElasticsearchConfig {
168 fn default() -> Self {
169 Self {
170 url: "http://localhost:9200".into(),
171 username: "elastic".into(),
172 password: "elastic".into(),
173 }
174 }
175}
176
177impl Default for RateLimitsConfig {
178 fn default() -> Self {
179 Self {
180 rate_limit_subscription_tiers: None,
181 rate_limit_window_seconds: 60 * 60 * 24, rate_limit_operation_points: 100,
183 }
184 }
185}
186
187impl Default for MonitoringConfig {
188 fn default() -> Self {
189 Self {
190 audit_enabled: false,
191 ip_source: IpSource::default(),
192 }
193 }
194}
195
196impl Default for SecurityConfig {
197 fn default() -> Self {
198 Self {
199 publicize_fhir_metadata: true,
200 mfa: MFAConfig::default(),
201 encryption: SecretProviderConfig::default(),
202 aes_key: None,
203 certification_key: None,
204 }
205 }
206}
207
208impl Default for MFAConfig {
209 fn default() -> Self {
210 Self {
211 max_credentials_per_user: 1,
212 }
213 }
214}
215
216impl Default for SecretProviderConfig {
217 fn default() -> Self {
218 Self::Environment {
219 prefix: Some("HASTE_SECRET_".to_string()),
220 }
221 }
222}