greentic_config/
loaders.rs

1use crate::paths::DefaultPaths;
2use greentic_config_types::ConfigVersion;
3use greentic_types::{ConnectionKind, DeploymentCtx, EnvId};
4use serde::{Deserialize, Serialize};
5use std::fs;
6use std::path::{Path, PathBuf};
7
8const ENV_PREFIX: &str = "GREENTIC_";
9pub const DEFAULT_DEPLOYER_BASE_DOMAIN: &str = "deploy.greentic.ai";
10
11#[derive(Debug, Clone, Copy)]
12pub enum ConfigFileFormat {
13    Toml,
14    Json,
15}
16
17#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
18pub struct ConfigLayer {
19    #[serde(default)]
20    pub schema_version: Option<ConfigVersion>,
21    #[serde(default)]
22    pub environment: Option<EnvironmentLayer>,
23    #[serde(default)]
24    pub paths: Option<PathsLayer>,
25    #[serde(default)]
26    pub services: Option<ServicesLayer>,
27    #[serde(default)]
28    pub events: Option<EventsLayer>,
29    #[serde(default)]
30    pub runtime: Option<RuntimeLayer>,
31    #[serde(default)]
32    pub telemetry: Option<TelemetryLayer>,
33    #[serde(default)]
34    pub network: Option<NetworkLayer>,
35    #[serde(default)]
36    pub deployer: Option<DeployerLayer>,
37    #[serde(default)]
38    pub secrets: Option<SecretsBackendRefLayer>,
39    #[serde(default)]
40    pub packs: Option<PacksLayer>,
41    #[serde(default)]
42    pub dev: Option<DevLayer>,
43}
44
45#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
46pub struct EnvironmentLayer {
47    #[serde(default)]
48    pub env_id: Option<EnvId>,
49    #[serde(default)]
50    pub deployment: Option<DeploymentCtx>,
51    #[serde(default)]
52    pub connection: Option<ConnectionKind>,
53    #[serde(default)]
54    pub region: Option<String>,
55}
56
57#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
58pub struct PathsLayer {
59    #[serde(default)]
60    pub greentic_root: Option<PathBuf>,
61    #[serde(default)]
62    pub state_dir: Option<PathBuf>,
63    #[serde(default)]
64    pub cache_dir: Option<PathBuf>,
65    #[serde(default)]
66    pub logs_dir: Option<PathBuf>,
67}
68
69#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
70pub struct ServicesLayer {
71    #[serde(default)]
72    pub events: Option<ServiceEndpointLayer>,
73    #[serde(default)]
74    pub runner: Option<ServiceLayer>,
75    #[serde(default)]
76    pub deployer: Option<ServiceLayer>,
77    #[serde(default)]
78    pub events_transport: Option<ServiceLayer>,
79    #[serde(default)]
80    pub source: Option<ServiceLayer>,
81    #[serde(default)]
82    pub publish: Option<ServiceLayer>,
83    #[serde(default)]
84    pub metadata: Option<ServiceLayer>,
85    #[serde(default)]
86    pub oauth_broker: Option<ServiceLayer>,
87}
88
89#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
90pub struct ServiceEndpointLayer {
91    #[serde(default)]
92    pub url: Option<url::Url>,
93    #[serde(default)]
94    pub headers: Option<std::collections::BTreeMap<String, String>>,
95}
96
97#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
98pub struct EventsLayer {
99    #[serde(default)]
100    pub reconnect: Option<ReconnectLayer>,
101    #[serde(default)]
102    pub backoff: Option<BackoffLayer>,
103}
104
105#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
106pub struct ServiceTransportLayer {
107    #[serde(default)]
108    pub kind: Option<String>,
109    #[serde(default)]
110    pub url: Option<url::Url>,
111    #[serde(default)]
112    pub headers: Option<std::collections::BTreeMap<String, String>>,
113    #[serde(default)]
114    pub subject_prefix: Option<String>,
115}
116
117#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
118pub struct ServiceLayer {
119    #[serde(default, flatten)]
120    pub transport: ServiceTransportLayer,
121    #[serde(default)]
122    pub service: Option<ServiceConfigLayer>,
123}
124
125#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
126pub struct ServiceConfigLayer {
127    #[serde(default)]
128    pub bind_addr: Option<String>,
129    #[serde(default)]
130    pub port: Option<u16>,
131    #[serde(default)]
132    pub public_base_url: Option<String>,
133    #[serde(default)]
134    pub metrics: Option<MetricsLayer>,
135}
136
137#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
138pub struct MetricsLayer {
139    #[serde(default)]
140    pub enabled: Option<bool>,
141    #[serde(default)]
142    pub bind_addr: Option<String>,
143    #[serde(default)]
144    pub port: Option<u16>,
145    #[serde(default)]
146    pub path: Option<String>,
147}
148
149#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
150pub struct ReconnectLayer {
151    #[serde(default)]
152    pub enabled: Option<bool>,
153    #[serde(default)]
154    pub max_retries: Option<u32>,
155}
156
157#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
158pub struct BackoffLayer {
159    #[serde(default)]
160    pub initial_ms: Option<u64>,
161    #[serde(default)]
162    pub max_ms: Option<u64>,
163    #[serde(default)]
164    pub multiplier: Option<f64>,
165    #[serde(default)]
166    pub jitter: Option<bool>,
167}
168
169#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
170pub struct RuntimeLayer {
171    #[serde(default)]
172    pub max_concurrency: Option<u32>,
173    #[serde(default)]
174    pub task_timeout_ms: Option<u64>,
175    #[serde(default)]
176    pub shutdown_grace_ms: Option<u64>,
177    #[serde(default)]
178    pub admin_endpoints: Option<AdminEndpointsLayer>,
179}
180
181#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
182pub struct AdminEndpointsLayer {
183    #[serde(default)]
184    pub secrets_explain_enabled: Option<bool>,
185}
186
187#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
188pub struct TelemetryLayer {
189    #[serde(default)]
190    pub enabled: Option<bool>,
191    #[serde(default)]
192    pub exporter: Option<String>,
193    #[serde(default)]
194    pub endpoint: Option<String>,
195    #[serde(default)]
196    pub sampling: Option<f32>,
197}
198
199#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
200pub struct NetworkLayer {
201    #[serde(default)]
202    pub proxy_url: Option<String>,
203    #[serde(default)]
204    pub tls_mode: Option<String>,
205    #[serde(default)]
206    pub connect_timeout_ms: Option<u64>,
207    #[serde(default)]
208    pub read_timeout_ms: Option<u64>,
209}
210
211#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
212pub struct DeployerLayer {
213    #[serde(default)]
214    pub base_domain: Option<String>,
215    #[serde(default)]
216    pub provider: Option<DeployerProviderDefaultsLayer>,
217}
218
219#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
220pub struct DeployerProviderDefaultsLayer {
221    #[serde(default)]
222    pub provider_kind: Option<String>,
223    #[serde(default)]
224    pub region: Option<String>,
225}
226
227#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
228pub struct SecretsBackendRefLayer {
229    #[serde(default)]
230    pub kind: Option<String>,
231    #[serde(default)]
232    pub reference: Option<String>,
233}
234
235#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
236pub struct PacksLayer {
237    #[serde(default)]
238    pub source: Option<PackSourceLayer>,
239    #[serde(default)]
240    pub cache_dir: Option<PathBuf>,
241    #[serde(default)]
242    pub index_cache_ttl_secs: Option<u64>,
243    #[serde(default)]
244    pub trust: Option<PackTrustLayer>,
245}
246
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
248#[serde(tag = "type", rename_all = "snake_case")]
249pub enum PackSourceLayer {
250    LocalIndex {
251        #[serde(default)]
252        path: Option<PathBuf>,
253    },
254    HttpIndex {
255        #[serde(default)]
256        url: Option<String>,
257    },
258    OciRegistry {
259        #[serde(default)]
260        reference: Option<String>,
261    },
262}
263
264impl Default for PackSourceLayer {
265    fn default() -> Self {
266        Self::LocalIndex { path: None }
267    }
268}
269
270#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
271pub struct PackTrustLayer {
272    #[serde(default)]
273    pub public_keys: Option<Vec<String>>,
274    #[serde(default)]
275    pub require_signatures: Option<bool>,
276}
277
278#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
279pub struct DevLayer {
280    #[serde(default)]
281    pub default_env: Option<EnvId>,
282    #[serde(default)]
283    pub default_tenant: Option<String>,
284    #[serde(default)]
285    pub default_team: Option<String>,
286}
287
288pub fn default_layer(root: &Path, defaults: &DefaultPaths) -> ConfigLayer {
289    ConfigLayer {
290        schema_version: Some(ConfigVersion::v1()),
291        environment: Some(EnvironmentLayer {
292            env_id: Some(default_env_id()),
293            deployment: None,
294            connection: None,
295            region: None,
296        }),
297        paths: Some(PathsLayer {
298            greentic_root: Some(root.to_path_buf()),
299            state_dir: Some(defaults.state_dir.clone()),
300            cache_dir: Some(defaults.cache_dir.clone()),
301            logs_dir: Some(defaults.logs_dir.clone()),
302        }),
303        services: None,
304        events: Some(EventsLayer {
305            reconnect: Some(ReconnectLayer {
306                enabled: Some(true),
307                max_retries: Some(50),
308            }),
309            backoff: Some(BackoffLayer {
310                initial_ms: Some(250),
311                max_ms: Some(30_000),
312                multiplier: Some(2.0),
313                jitter: Some(true),
314            }),
315        }),
316        runtime: Some(RuntimeLayer::default()),
317        telemetry: Some(TelemetryLayer {
318            enabled: Some(true),
319            exporter: Some("none".to_string()),
320            endpoint: None,
321            sampling: Some(1.0),
322        }),
323        network: Some(NetworkLayer {
324            proxy_url: None,
325            tls_mode: Some("system".to_string()),
326            connect_timeout_ms: None,
327            read_timeout_ms: None,
328        }),
329        deployer: Some(DeployerLayer {
330            base_domain: Some(DEFAULT_DEPLOYER_BASE_DOMAIN.to_string()),
331            provider: None,
332        }),
333        secrets: Some(SecretsBackendRefLayer {
334            kind: Some("none".to_string()),
335            reference: None,
336        }),
337        packs: None,
338        dev: None,
339    }
340}
341
342pub fn load_user_config() -> anyhow::Result<ConfigLayer> {
343    let Some(dirs) = directories::ProjectDirs::from("com", "greentic", "greentic") else {
344        return Ok(ConfigLayer::default());
345    };
346    let path = dirs.config_dir().join("config.toml");
347    load_config_file_if_exists(&path)
348}
349
350pub fn load_user_config_with_origin() -> anyhow::Result<(ConfigLayer, Option<PathBuf>)> {
351    let Some(dirs) = directories::ProjectDirs::from("com", "greentic", "greentic") else {
352        return Ok((ConfigLayer::default(), None));
353    };
354    let path = dirs.config_dir().join("config.toml");
355    let layer = load_config_file_if_exists(&path)?;
356    let abs = crate::paths::absolute_path(&path)?;
357    Ok((layer, Some(abs)))
358}
359
360pub fn load_project_config(project_root: &Path) -> anyhow::Result<ConfigLayer> {
361    let path = project_root.join(".greentic").join("config.toml");
362    load_config_file_if_exists(&path)
363}
364
365pub fn load_project_config_with_origin(
366    project_root: &Path,
367) -> anyhow::Result<(ConfigLayer, PathBuf)> {
368    let path = project_root.join(".greentic").join("config.toml");
369    let layer = load_project_config(project_root)?;
370    let abs = crate::paths::absolute_path(&path)?;
371    Ok((layer, abs))
372}
373
374pub fn load_config_file_required(path: &Path) -> anyhow::Result<ConfigLayer> {
375    let abs = crate::paths::absolute_path(path)?;
376    if !abs.exists() {
377        return Err(anyhow::anyhow!(
378            "explicit config file not found: {}\nHint: pass an existing file to --config / with_config_path()",
379            abs.display()
380        ));
381    }
382    let contents = fs::read_to_string(&abs)?;
383    let format = match abs.extension().and_then(|s| s.to_str()) {
384        Some("json") => ConfigFileFormat::Json,
385        _ => ConfigFileFormat::Toml,
386    };
387    parse_layer(&contents, format)
388}
389
390fn load_config_file_if_exists(path: &Path) -> anyhow::Result<ConfigLayer> {
391    if !path.exists() {
392        return Ok(ConfigLayer::default());
393    }
394    let contents = fs::read_to_string(path)?;
395    let format = match path.extension().and_then(|s| s.to_str()) {
396        Some("json") => ConfigFileFormat::Json,
397        _ => ConfigFileFormat::Toml,
398    };
399    let layer = parse_layer(&contents, format)?;
400    Ok(layer)
401}
402
403fn parse_layer(contents: &str, format: ConfigFileFormat) -> anyhow::Result<ConfigLayer> {
404    let layer = match format {
405        ConfigFileFormat::Toml => toml::from_str::<ConfigLayer>(contents)?,
406        ConfigFileFormat::Json => serde_json::from_str::<ConfigLayer>(contents)?,
407    };
408    Ok(layer)
409}
410
411pub fn load_env_layer() -> (ConfigLayer, Vec<String>) {
412    let mut layer = ConfigLayer::default();
413    let mut warnings = Vec::new();
414    for (key, value) in std::env::vars() {
415        if !key.starts_with(ENV_PREFIX) {
416            continue;
417        }
418        apply_env_var(&key, &value, &mut layer, &mut warnings);
419    }
420    (layer, warnings)
421}
422
423pub fn load_env_layers_detailed() -> Vec<(ConfigLayer, String, Vec<String>)> {
424    load_env_layers_detailed_from(std::env::vars())
425}
426
427pub fn load_env_layers_detailed_from<I>(vars: I) -> Vec<(ConfigLayer, String, Vec<String>)>
428where
429    I: IntoIterator<Item = (String, String)>,
430{
431    let mut layers = Vec::new();
432    for (key, value) in vars {
433        if !key.starts_with(ENV_PREFIX) {
434            continue;
435        }
436        let mut layer = ConfigLayer::default();
437        let mut warnings = Vec::new();
438        if apply_env_var(&key, &value, &mut layer, &mut warnings) {
439            layers.push((layer, key, warnings));
440        }
441    }
442    layers
443}
444
445fn apply_env_var(
446    key: &str,
447    value: &str,
448    layer: &mut ConfigLayer,
449    warnings: &mut Vec<String>,
450) -> bool {
451    match key {
452        "GREENTIC_SCHEMA_VERSION" => layer.schema_version = Some(ConfigVersion(value.to_string())),
453        "GREENTIC_ENVIRONMENT_ENV_ID" => {
454            layer
455                .environment
456                .get_or_insert_with(Default::default)
457                .env_id = parse_string_as::<EnvId>(value)
458        }
459        "GREENTIC_ENVIRONMENT_DEPLOYMENT" => {
460            layer
461                .environment
462                .get_or_insert_with(Default::default)
463                .deployment = parse_string_as::<DeploymentCtx>(value)
464        }
465        "GREENTIC_ENVIRONMENT_CONNECTION" => {
466            layer
467                .environment
468                .get_or_insert_with(Default::default)
469                .connection = parse_string_as::<ConnectionKind>(value)
470        }
471        "GREENTIC_ENVIRONMENT_REGION" => {
472            layer
473                .environment
474                .get_or_insert_with(Default::default)
475                .region = Some(value.to_string())
476        }
477        "GREENTIC_PATHS_GREENTIC_ROOT" => {
478            layer
479                .paths
480                .get_or_insert_with(Default::default)
481                .greentic_root = Some(PathBuf::from(value))
482        }
483        "GREENTIC_PATHS_STATE_DIR" => {
484            layer.paths.get_or_insert_with(Default::default).state_dir = Some(PathBuf::from(value))
485        }
486        "GREENTIC_PATHS_CACHE_DIR" => {
487            layer.paths.get_or_insert_with(Default::default).cache_dir = Some(PathBuf::from(value))
488        }
489        "GREENTIC_PATHS_LOGS_DIR" => {
490            layer.paths.get_or_insert_with(Default::default).logs_dir = Some(PathBuf::from(value))
491        }
492        "GREENTIC_SERVICES_EVENTS_URL" => {
493            layer
494                .services
495                .get_or_insert_with(Default::default)
496                .events
497                .get_or_insert_with(Default::default)
498                .url = parse_string_as::<url::Url>(value)
499        }
500        "GREENTIC_SERVICES_RUNNER_KIND" => {
501            layer
502                .services
503                .get_or_insert_with(Default::default)
504                .runner
505                .get_or_insert_with(Default::default)
506                .transport
507                .kind = Some(value.to_lowercase())
508        }
509        "GREENTIC_SERVICES_RUNNER_URL" => {
510            layer
511                .services
512                .get_or_insert_with(Default::default)
513                .runner
514                .get_or_insert_with(Default::default)
515                .transport
516                .url = parse_string_as::<url::Url>(value)
517        }
518        "GREENTIC_SERVICES_RUNNER_BIND_ADDR" => {
519            layer
520                .services
521                .get_or_insert_with(Default::default)
522                .runner
523                .get_or_insert_with(Default::default)
524                .service
525                .get_or_insert_with(Default::default)
526                .bind_addr = Some(value.to_string());
527        }
528        "GREENTIC_SERVICES_RUNNER_PORT" => {
529            layer
530                .services
531                .get_or_insert_with(Default::default)
532                .runner
533                .get_or_insert_with(Default::default)
534                .service
535                .get_or_insert_with(Default::default)
536                .port = parse_u16_warn(value, key, warnings);
537        }
538        "GREENTIC_SERVICES_RUNNER_PUBLIC_BASE_URL" => {
539            layer
540                .services
541                .get_or_insert_with(Default::default)
542                .runner
543                .get_or_insert_with(Default::default)
544                .service
545                .get_or_insert_with(Default::default)
546                .public_base_url = Some(value.to_string());
547        }
548        "GREENTIC_SERVICES_RUNNER_METRICS_ENABLED" => {
549            layer
550                .services
551                .get_or_insert_with(Default::default)
552                .runner
553                .get_or_insert_with(Default::default)
554                .service
555                .get_or_insert_with(Default::default)
556                .metrics
557                .get_or_insert_with(Default::default)
558                .enabled = parse_bool(value);
559        }
560        "GREENTIC_SERVICES_RUNNER_METRICS_BIND_ADDR" => {
561            layer
562                .services
563                .get_or_insert_with(Default::default)
564                .runner
565                .get_or_insert_with(Default::default)
566                .service
567                .get_or_insert_with(Default::default)
568                .metrics
569                .get_or_insert_with(Default::default)
570                .bind_addr = Some(value.to_string());
571        }
572        "GREENTIC_SERVICES_RUNNER_METRICS_PORT" => {
573            layer
574                .services
575                .get_or_insert_with(Default::default)
576                .runner
577                .get_or_insert_with(Default::default)
578                .service
579                .get_or_insert_with(Default::default)
580                .metrics
581                .get_or_insert_with(Default::default)
582                .port = parse_u16_warn(value, key, warnings);
583        }
584        "GREENTIC_SERVICES_RUNNER_METRICS_PATH" => {
585            layer
586                .services
587                .get_or_insert_with(Default::default)
588                .runner
589                .get_or_insert_with(Default::default)
590                .service
591                .get_or_insert_with(Default::default)
592                .metrics
593                .get_or_insert_with(Default::default)
594                .path = Some(value.to_string());
595        }
596        "GREENTIC_SERVICES_DEPLOYER_KIND" => {
597            layer
598                .services
599                .get_or_insert_with(Default::default)
600                .deployer
601                .get_or_insert_with(Default::default)
602                .transport
603                .kind = Some(value.to_lowercase())
604        }
605        "GREENTIC_SERVICES_DEPLOYER_URL" => {
606            layer
607                .services
608                .get_or_insert_with(Default::default)
609                .deployer
610                .get_or_insert_with(Default::default)
611                .transport
612                .url = parse_string_as::<url::Url>(value)
613        }
614        "GREENTIC_SERVICES_DEPLOYER_BIND_ADDR" => {
615            layer
616                .services
617                .get_or_insert_with(Default::default)
618                .deployer
619                .get_or_insert_with(Default::default)
620                .service
621                .get_or_insert_with(Default::default)
622                .bind_addr = Some(value.to_string());
623        }
624        "GREENTIC_SERVICES_DEPLOYER_PORT" => {
625            layer
626                .services
627                .get_or_insert_with(Default::default)
628                .deployer
629                .get_or_insert_with(Default::default)
630                .service
631                .get_or_insert_with(Default::default)
632                .port = parse_u16_warn(value, key, warnings);
633        }
634        "GREENTIC_SERVICES_DEPLOYER_PUBLIC_BASE_URL" => {
635            layer
636                .services
637                .get_or_insert_with(Default::default)
638                .deployer
639                .get_or_insert_with(Default::default)
640                .service
641                .get_or_insert_with(Default::default)
642                .public_base_url = Some(value.to_string());
643        }
644        "GREENTIC_SERVICES_DEPLOYER_METRICS_ENABLED" => {
645            layer
646                .services
647                .get_or_insert_with(Default::default)
648                .deployer
649                .get_or_insert_with(Default::default)
650                .service
651                .get_or_insert_with(Default::default)
652                .metrics
653                .get_or_insert_with(Default::default)
654                .enabled = parse_bool(value);
655        }
656        "GREENTIC_SERVICES_DEPLOYER_METRICS_BIND_ADDR" => {
657            layer
658                .services
659                .get_or_insert_with(Default::default)
660                .deployer
661                .get_or_insert_with(Default::default)
662                .service
663                .get_or_insert_with(Default::default)
664                .metrics
665                .get_or_insert_with(Default::default)
666                .bind_addr = Some(value.to_string());
667        }
668        "GREENTIC_SERVICES_DEPLOYER_METRICS_PORT" => {
669            layer
670                .services
671                .get_or_insert_with(Default::default)
672                .deployer
673                .get_or_insert_with(Default::default)
674                .service
675                .get_or_insert_with(Default::default)
676                .metrics
677                .get_or_insert_with(Default::default)
678                .port = parse_u16_warn(value, key, warnings);
679        }
680        "GREENTIC_SERVICES_DEPLOYER_METRICS_PATH" => {
681            layer
682                .services
683                .get_or_insert_with(Default::default)
684                .deployer
685                .get_or_insert_with(Default::default)
686                .service
687                .get_or_insert_with(Default::default)
688                .metrics
689                .get_or_insert_with(Default::default)
690                .path = Some(value.to_string());
691        }
692        "GREENTIC_SERVICES_EVENTS_TRANSPORT_KIND" => {
693            layer
694                .services
695                .get_or_insert_with(Default::default)
696                .events_transport
697                .get_or_insert_with(Default::default)
698                .transport
699                .kind = Some(value.to_lowercase())
700        }
701        "GREENTIC_SERVICES_EVENTS_TRANSPORT_URL" => {
702            layer
703                .services
704                .get_or_insert_with(Default::default)
705                .events_transport
706                .get_or_insert_with(Default::default)
707                .transport
708                .url = parse_string_as::<url::Url>(value)
709        }
710        "GREENTIC_SERVICES_EVENTS_TRANSPORT_BIND_ADDR" => {
711            layer
712                .services
713                .get_or_insert_with(Default::default)
714                .events_transport
715                .get_or_insert_with(Default::default)
716                .service
717                .get_or_insert_with(Default::default)
718                .bind_addr = Some(value.to_string());
719        }
720        "GREENTIC_SERVICES_EVENTS_TRANSPORT_PORT" => {
721            layer
722                .services
723                .get_or_insert_with(Default::default)
724                .events_transport
725                .get_or_insert_with(Default::default)
726                .service
727                .get_or_insert_with(Default::default)
728                .port = parse_u16_warn(value, key, warnings);
729        }
730        "GREENTIC_SERVICES_EVENTS_TRANSPORT_PUBLIC_BASE_URL" => {
731            layer
732                .services
733                .get_or_insert_with(Default::default)
734                .events_transport
735                .get_or_insert_with(Default::default)
736                .service
737                .get_or_insert_with(Default::default)
738                .public_base_url = Some(value.to_string());
739        }
740        "GREENTIC_SERVICES_EVENTS_TRANSPORT_METRICS_ENABLED" => {
741            layer
742                .services
743                .get_or_insert_with(Default::default)
744                .events_transport
745                .get_or_insert_with(Default::default)
746                .service
747                .get_or_insert_with(Default::default)
748                .metrics
749                .get_or_insert_with(Default::default)
750                .enabled = parse_bool(value);
751        }
752        "GREENTIC_SERVICES_EVENTS_TRANSPORT_METRICS_BIND_ADDR" => {
753            layer
754                .services
755                .get_or_insert_with(Default::default)
756                .events_transport
757                .get_or_insert_with(Default::default)
758                .service
759                .get_or_insert_with(Default::default)
760                .metrics
761                .get_or_insert_with(Default::default)
762                .bind_addr = Some(value.to_string());
763        }
764        "GREENTIC_SERVICES_EVENTS_TRANSPORT_METRICS_PORT" => {
765            layer
766                .services
767                .get_or_insert_with(Default::default)
768                .events_transport
769                .get_or_insert_with(Default::default)
770                .service
771                .get_or_insert_with(Default::default)
772                .metrics
773                .get_or_insert_with(Default::default)
774                .port = parse_u16_warn(value, key, warnings);
775        }
776        "GREENTIC_SERVICES_EVENTS_TRANSPORT_METRICS_PATH" => {
777            layer
778                .services
779                .get_or_insert_with(Default::default)
780                .events_transport
781                .get_or_insert_with(Default::default)
782                .service
783                .get_or_insert_with(Default::default)
784                .metrics
785                .get_or_insert_with(Default::default)
786                .path = Some(value.to_string());
787        }
788        "GREENTIC_SERVICES_SOURCE_KIND" => {
789            layer
790                .services
791                .get_or_insert_with(Default::default)
792                .source
793                .get_or_insert_with(Default::default)
794                .transport
795                .kind = Some(value.to_lowercase())
796        }
797        "GREENTIC_SERVICES_SOURCE_URL" => {
798            layer
799                .services
800                .get_or_insert_with(Default::default)
801                .source
802                .get_or_insert_with(Default::default)
803                .transport
804                .url = parse_string_as::<url::Url>(value)
805        }
806        "GREENTIC_SERVICES_SOURCE_BIND_ADDR" => {
807            layer
808                .services
809                .get_or_insert_with(Default::default)
810                .source
811                .get_or_insert_with(Default::default)
812                .service
813                .get_or_insert_with(Default::default)
814                .bind_addr = Some(value.to_string());
815        }
816        "GREENTIC_SERVICES_SOURCE_PORT" => {
817            layer
818                .services
819                .get_or_insert_with(Default::default)
820                .source
821                .get_or_insert_with(Default::default)
822                .service
823                .get_or_insert_with(Default::default)
824                .port = parse_u16_warn(value, key, warnings);
825        }
826        "GREENTIC_SERVICES_SOURCE_PUBLIC_BASE_URL" => {
827            layer
828                .services
829                .get_or_insert_with(Default::default)
830                .source
831                .get_or_insert_with(Default::default)
832                .service
833                .get_or_insert_with(Default::default)
834                .public_base_url = Some(value.to_string());
835        }
836        "GREENTIC_SERVICES_SOURCE_METRICS_ENABLED" => {
837            layer
838                .services
839                .get_or_insert_with(Default::default)
840                .source
841                .get_or_insert_with(Default::default)
842                .service
843                .get_or_insert_with(Default::default)
844                .metrics
845                .get_or_insert_with(Default::default)
846                .enabled = parse_bool(value);
847        }
848        "GREENTIC_SERVICES_SOURCE_METRICS_BIND_ADDR" => {
849            layer
850                .services
851                .get_or_insert_with(Default::default)
852                .source
853                .get_or_insert_with(Default::default)
854                .service
855                .get_or_insert_with(Default::default)
856                .metrics
857                .get_or_insert_with(Default::default)
858                .bind_addr = Some(value.to_string());
859        }
860        "GREENTIC_SERVICES_SOURCE_METRICS_PORT" => {
861            layer
862                .services
863                .get_or_insert_with(Default::default)
864                .source
865                .get_or_insert_with(Default::default)
866                .service
867                .get_or_insert_with(Default::default)
868                .metrics
869                .get_or_insert_with(Default::default)
870                .port = parse_u16_warn(value, key, warnings);
871        }
872        "GREENTIC_SERVICES_SOURCE_METRICS_PATH" => {
873            layer
874                .services
875                .get_or_insert_with(Default::default)
876                .source
877                .get_or_insert_with(Default::default)
878                .service
879                .get_or_insert_with(Default::default)
880                .metrics
881                .get_or_insert_with(Default::default)
882                .path = Some(value.to_string());
883        }
884        "GREENTIC_SERVICES_PUBLISH_KIND" => {
885            layer
886                .services
887                .get_or_insert_with(Default::default)
888                .publish
889                .get_or_insert_with(Default::default)
890                .transport
891                .kind = Some(value.to_lowercase())
892        }
893        "GREENTIC_SERVICES_PUBLISH_URL" => {
894            layer
895                .services
896                .get_or_insert_with(Default::default)
897                .publish
898                .get_or_insert_with(Default::default)
899                .transport
900                .url = parse_string_as::<url::Url>(value)
901        }
902        "GREENTIC_SERVICES_PUBLISH_BIND_ADDR" => {
903            layer
904                .services
905                .get_or_insert_with(Default::default)
906                .publish
907                .get_or_insert_with(Default::default)
908                .service
909                .get_or_insert_with(Default::default)
910                .bind_addr = Some(value.to_string());
911        }
912        "GREENTIC_SERVICES_PUBLISH_PORT" => {
913            layer
914                .services
915                .get_or_insert_with(Default::default)
916                .publish
917                .get_or_insert_with(Default::default)
918                .service
919                .get_or_insert_with(Default::default)
920                .port = parse_u16_warn(value, key, warnings);
921        }
922        "GREENTIC_SERVICES_PUBLISH_PUBLIC_BASE_URL" => {
923            layer
924                .services
925                .get_or_insert_with(Default::default)
926                .publish
927                .get_or_insert_with(Default::default)
928                .service
929                .get_or_insert_with(Default::default)
930                .public_base_url = Some(value.to_string());
931        }
932        "GREENTIC_SERVICES_PUBLISH_METRICS_ENABLED" => {
933            layer
934                .services
935                .get_or_insert_with(Default::default)
936                .publish
937                .get_or_insert_with(Default::default)
938                .service
939                .get_or_insert_with(Default::default)
940                .metrics
941                .get_or_insert_with(Default::default)
942                .enabled = parse_bool(value);
943        }
944        "GREENTIC_SERVICES_PUBLISH_METRICS_BIND_ADDR" => {
945            layer
946                .services
947                .get_or_insert_with(Default::default)
948                .publish
949                .get_or_insert_with(Default::default)
950                .service
951                .get_or_insert_with(Default::default)
952                .metrics
953                .get_or_insert_with(Default::default)
954                .bind_addr = Some(value.to_string());
955        }
956        "GREENTIC_SERVICES_PUBLISH_METRICS_PORT" => {
957            layer
958                .services
959                .get_or_insert_with(Default::default)
960                .publish
961                .get_or_insert_with(Default::default)
962                .service
963                .get_or_insert_with(Default::default)
964                .metrics
965                .get_or_insert_with(Default::default)
966                .port = parse_u16_warn(value, key, warnings);
967        }
968        "GREENTIC_SERVICES_PUBLISH_METRICS_PATH" => {
969            layer
970                .services
971                .get_or_insert_with(Default::default)
972                .publish
973                .get_or_insert_with(Default::default)
974                .service
975                .get_or_insert_with(Default::default)
976                .metrics
977                .get_or_insert_with(Default::default)
978                .path = Some(value.to_string());
979        }
980        "GREENTIC_SERVICES_METADATA_KIND" => {
981            layer
982                .services
983                .get_or_insert_with(Default::default)
984                .metadata
985                .get_or_insert_with(Default::default)
986                .transport
987                .kind = Some(value.to_lowercase())
988        }
989        "GREENTIC_SERVICES_METADATA_URL" => {
990            layer
991                .services
992                .get_or_insert_with(Default::default)
993                .metadata
994                .get_or_insert_with(Default::default)
995                .transport
996                .url = parse_string_as::<url::Url>(value)
997        }
998        "GREENTIC_SERVICES_METADATA_BIND_ADDR" => {
999            layer
1000                .services
1001                .get_or_insert_with(Default::default)
1002                .metadata
1003                .get_or_insert_with(Default::default)
1004                .service
1005                .get_or_insert_with(Default::default)
1006                .bind_addr = Some(value.to_string());
1007        }
1008        "GREENTIC_SERVICES_METADATA_PORT" => {
1009            layer
1010                .services
1011                .get_or_insert_with(Default::default)
1012                .metadata
1013                .get_or_insert_with(Default::default)
1014                .service
1015                .get_or_insert_with(Default::default)
1016                .port = parse_u16_warn(value, key, warnings);
1017        }
1018        "GREENTIC_SERVICES_METADATA_PUBLIC_BASE_URL" => {
1019            layer
1020                .services
1021                .get_or_insert_with(Default::default)
1022                .metadata
1023                .get_or_insert_with(Default::default)
1024                .service
1025                .get_or_insert_with(Default::default)
1026                .public_base_url = Some(value.to_string());
1027        }
1028        "GREENTIC_SERVICES_METADATA_METRICS_ENABLED" => {
1029            layer
1030                .services
1031                .get_or_insert_with(Default::default)
1032                .metadata
1033                .get_or_insert_with(Default::default)
1034                .service
1035                .get_or_insert_with(Default::default)
1036                .metrics
1037                .get_or_insert_with(Default::default)
1038                .enabled = parse_bool(value);
1039        }
1040        "GREENTIC_SERVICES_METADATA_METRICS_BIND_ADDR" => {
1041            layer
1042                .services
1043                .get_or_insert_with(Default::default)
1044                .metadata
1045                .get_or_insert_with(Default::default)
1046                .service
1047                .get_or_insert_with(Default::default)
1048                .metrics
1049                .get_or_insert_with(Default::default)
1050                .bind_addr = Some(value.to_string());
1051        }
1052        "GREENTIC_SERVICES_METADATA_METRICS_PORT" => {
1053            layer
1054                .services
1055                .get_or_insert_with(Default::default)
1056                .metadata
1057                .get_or_insert_with(Default::default)
1058                .service
1059                .get_or_insert_with(Default::default)
1060                .metrics
1061                .get_or_insert_with(Default::default)
1062                .port = parse_u16_warn(value, key, warnings);
1063        }
1064        "GREENTIC_SERVICES_METADATA_METRICS_PATH" => {
1065            layer
1066                .services
1067                .get_or_insert_with(Default::default)
1068                .metadata
1069                .get_or_insert_with(Default::default)
1070                .service
1071                .get_or_insert_with(Default::default)
1072                .metrics
1073                .get_or_insert_with(Default::default)
1074                .path = Some(value.to_string());
1075        }
1076        "GREENTIC_SERVICES_OAUTH_BROKER_KIND" => {
1077            layer
1078                .services
1079                .get_or_insert_with(Default::default)
1080                .oauth_broker
1081                .get_or_insert_with(Default::default)
1082                .transport
1083                .kind = Some(value.to_lowercase())
1084        }
1085        "GREENTIC_SERVICES_OAUTH_BROKER_URL" => {
1086            layer
1087                .services
1088                .get_or_insert_with(Default::default)
1089                .oauth_broker
1090                .get_or_insert_with(Default::default)
1091                .transport
1092                .url = parse_string_as::<url::Url>(value)
1093        }
1094        "GREENTIC_SERVICES_OAUTH_BROKER_BIND_ADDR" => {
1095            layer
1096                .services
1097                .get_or_insert_with(Default::default)
1098                .oauth_broker
1099                .get_or_insert_with(Default::default)
1100                .service
1101                .get_or_insert_with(Default::default)
1102                .bind_addr = Some(value.to_string());
1103        }
1104        "GREENTIC_SERVICES_OAUTH_BROKER_PORT" => {
1105            layer
1106                .services
1107                .get_or_insert_with(Default::default)
1108                .oauth_broker
1109                .get_or_insert_with(Default::default)
1110                .service
1111                .get_or_insert_with(Default::default)
1112                .port = parse_u16_warn(value, key, warnings);
1113        }
1114        "GREENTIC_SERVICES_OAUTH_BROKER_PUBLIC_BASE_URL" => {
1115            layer
1116                .services
1117                .get_or_insert_with(Default::default)
1118                .oauth_broker
1119                .get_or_insert_with(Default::default)
1120                .service
1121                .get_or_insert_with(Default::default)
1122                .public_base_url = Some(value.to_string());
1123        }
1124        "GREENTIC_SERVICES_OAUTH_BROKER_METRICS_ENABLED" => {
1125            layer
1126                .services
1127                .get_or_insert_with(Default::default)
1128                .oauth_broker
1129                .get_or_insert_with(Default::default)
1130                .service
1131                .get_or_insert_with(Default::default)
1132                .metrics
1133                .get_or_insert_with(Default::default)
1134                .enabled = parse_bool(value);
1135        }
1136        "GREENTIC_SERVICES_OAUTH_BROKER_METRICS_BIND_ADDR" => {
1137            layer
1138                .services
1139                .get_or_insert_with(Default::default)
1140                .oauth_broker
1141                .get_or_insert_with(Default::default)
1142                .service
1143                .get_or_insert_with(Default::default)
1144                .metrics
1145                .get_or_insert_with(Default::default)
1146                .bind_addr = Some(value.to_string());
1147        }
1148        "GREENTIC_SERVICES_OAUTH_BROKER_METRICS_PORT" => {
1149            layer
1150                .services
1151                .get_or_insert_with(Default::default)
1152                .oauth_broker
1153                .get_or_insert_with(Default::default)
1154                .service
1155                .get_or_insert_with(Default::default)
1156                .metrics
1157                .get_or_insert_with(Default::default)
1158                .port = parse_u16_warn(value, key, warnings);
1159        }
1160        "GREENTIC_SERVICES_OAUTH_BROKER_METRICS_PATH" => {
1161            layer
1162                .services
1163                .get_or_insert_with(Default::default)
1164                .oauth_broker
1165                .get_or_insert_with(Default::default)
1166                .service
1167                .get_or_insert_with(Default::default)
1168                .metrics
1169                .get_or_insert_with(Default::default)
1170                .path = Some(value.to_string());
1171        }
1172        "GREENTIC_RUNTIME_MAX_CONCURRENCY" => {
1173            layer
1174                .runtime
1175                .get_or_insert_with(Default::default)
1176                .max_concurrency = parse_u32(value)
1177        }
1178        "GREENTIC_RUNTIME_TASK_TIMEOUT_MS" => {
1179            layer
1180                .runtime
1181                .get_or_insert_with(Default::default)
1182                .task_timeout_ms = parse_u64(value)
1183        }
1184        "GREENTIC_RUNTIME_SHUTDOWN_GRACE_MS" => {
1185            layer
1186                .runtime
1187                .get_or_insert_with(Default::default)
1188                .shutdown_grace_ms = parse_u64(value)
1189        }
1190        "GREENTIC_RUNTIME_ADMIN_SECRETS_EXPLAIN_ENABLED" => {
1191            layer
1192                .runtime
1193                .get_or_insert_with(Default::default)
1194                .admin_endpoints
1195                .get_or_insert_with(Default::default)
1196                .secrets_explain_enabled = parse_bool(value)
1197        }
1198        "GREENTIC_TELEMETRY_ENABLED" => {
1199            layer.telemetry.get_or_insert_with(Default::default).enabled = parse_bool(value)
1200        }
1201        "GREENTIC_TELEMETRY_EXPORTER" => {
1202            layer
1203                .telemetry
1204                .get_or_insert_with(Default::default)
1205                .exporter = Some(value.to_lowercase())
1206        }
1207        "GREENTIC_TELEMETRY_ENDPOINT" => {
1208            layer
1209                .telemetry
1210                .get_or_insert_with(Default::default)
1211                .endpoint = Some(value.to_string())
1212        }
1213        "GREENTIC_TELEMETRY_SAMPLING" => {
1214            layer
1215                .telemetry
1216                .get_or_insert_with(Default::default)
1217                .sampling = parse_f32(value)
1218        }
1219        "GREENTIC_NETWORK_PROXY_URL" => {
1220            layer.network.get_or_insert_with(Default::default).proxy_url = Some(value.to_string())
1221        }
1222        "GREENTIC_NETWORK_TLS_MODE" => {
1223            layer.network.get_or_insert_with(Default::default).tls_mode = Some(value.to_lowercase())
1224        }
1225        "GREENTIC_NETWORK_CONNECT_TIMEOUT_MS" => {
1226            layer
1227                .network
1228                .get_or_insert_with(Default::default)
1229                .connect_timeout_ms = parse_u64(value)
1230        }
1231        "GREENTIC_NETWORK_READ_TIMEOUT_MS" => {
1232            layer
1233                .network
1234                .get_or_insert_with(Default::default)
1235                .read_timeout_ms = parse_u64(value)
1236        }
1237        "GREENTIC_SECRETS_KIND" => {
1238            layer.secrets.get_or_insert_with(Default::default).kind = Some(value.to_string())
1239        }
1240        "GREENTIC_SECRETS_REFERENCE" => {
1241            layer.secrets.get_or_insert_with(Default::default).reference = Some(value.to_string())
1242        }
1243        "GREENTIC_DEV_DEFAULT_ENV" => {
1244            layer.dev.get_or_insert_with(Default::default).default_env =
1245                parse_string_as::<EnvId>(value)
1246        }
1247        "GREENTIC_DEV_DEFAULT_TENANT" => {
1248            layer
1249                .dev
1250                .get_or_insert_with(Default::default)
1251                .default_tenant = Some(value.to_string())
1252        }
1253        "GREENTIC_DEV_DEFAULT_TEAM" => {
1254            layer.dev.get_or_insert_with(Default::default).default_team = Some(value.to_string())
1255        }
1256        "GREENTIC_EVENTS_RECONNECT_ENABLED" => {
1257            layer
1258                .events
1259                .get_or_insert_with(Default::default)
1260                .reconnect
1261                .get_or_insert_with(Default::default)
1262                .enabled = parse_bool(value)
1263        }
1264        "GREENTIC_EVENTS_RECONNECT_MAX_RETRIES" => {
1265            layer
1266                .events
1267                .get_or_insert_with(Default::default)
1268                .reconnect
1269                .get_or_insert_with(Default::default)
1270                .max_retries = parse_u32(value)
1271        }
1272        "GREENTIC_EVENTS_BACKOFF_INITIAL_MS" => {
1273            layer
1274                .events
1275                .get_or_insert_with(Default::default)
1276                .backoff
1277                .get_or_insert_with(Default::default)
1278                .initial_ms = parse_u64(value)
1279        }
1280        "GREENTIC_EVENTS_BACKOFF_MAX_MS" => {
1281            layer
1282                .events
1283                .get_or_insert_with(Default::default)
1284                .backoff
1285                .get_or_insert_with(Default::default)
1286                .max_ms = parse_u64(value)
1287        }
1288        "GREENTIC_EVENTS_BACKOFF_MULTIPLIER" => {
1289            layer
1290                .events
1291                .get_or_insert_with(Default::default)
1292                .backoff
1293                .get_or_insert_with(Default::default)
1294                .multiplier = parse_f32(value).map(|v| v as f64)
1295        }
1296        "GREENTIC_EVENTS_BACKOFF_JITTER" => {
1297            layer
1298                .events
1299                .get_or_insert_with(Default::default)
1300                .backoff
1301                .get_or_insert_with(Default::default)
1302                .jitter = parse_bool(value)
1303        }
1304        _ => return false,
1305    }
1306    true
1307}
1308
1309fn parse_string_as<T: for<'de> Deserialize<'de>>(value: &str) -> Option<T> {
1310    serde_json::from_str(&format!("\"{value}\"")).ok()
1311}
1312
1313fn parse_bool(value: &str) -> Option<bool> {
1314    match value.to_ascii_lowercase().as_str() {
1315        "1" | "true" | "yes" | "on" => Some(true),
1316        "0" | "false" | "no" | "off" => Some(false),
1317        _ => None,
1318    }
1319}
1320
1321fn parse_u64(value: &str) -> Option<u64> {
1322    value.parse::<u64>().ok()
1323}
1324
1325fn parse_u32(value: &str) -> Option<u32> {
1326    value.parse::<u32>().ok()
1327}
1328
1329fn parse_f32(value: &str) -> Option<f32> {
1330    value.parse::<f32>().ok()
1331}
1332
1333fn parse_u16_warn(value: &str, key: &str, warnings: &mut Vec<String>) -> Option<u16> {
1334    match value.parse::<u16>() {
1335        Ok(v) => Some(v),
1336        Err(_) => {
1337            warnings.push(format!("Ignored {key}: expected u16 but got '{value}'"));
1338            None
1339        }
1340    }
1341}
1342
1343pub(crate) fn default_env_id() -> EnvId {
1344    serde_json::from_str("\"dev\"").expect("EnvId should deserialize from string")
1345}