Skip to main content

systemprompt_models/profile/
secrets.rs

1//! Profile `secrets:` block naming the secret source and its parameters.
2//!
3//! `secrets_path` is only meaningful for [`SecretsSource::File`] and
4//! [`SecretsSource::Env`] (which falls back to the file when run outside a
5//! deployment host), so it is optional and reached through
6//! [`SecretsConfig::secrets_path`], which reports the misconfiguration rather
7//! than substituting an empty path.
8//!
9//! Copyright (c) systemprompt.io — Business Source License 1.1.
10//! See <https://systemprompt.io> for licensing details.
11
12use serde::{Deserialize, Serialize};
13
14use super::ProfileError;
15use super::vault::VaultSecretsConfig;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
18#[serde(rename_all = "lowercase")]
19pub enum SecretsSource {
20    File,
21    Env,
22    Vault,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
26#[serde(deny_unknown_fields)]
27pub struct SecretsConfig {
28    pub source: SecretsSource,
29
30    #[serde(default)]
31    pub validation: SecretsValidationMode,
32
33    #[serde(default)]
34    pub secrets_path: Option<String>,
35
36    #[serde(default)]
37    pub vault: Option<VaultSecretsConfig>,
38}
39
40impl SecretsConfig {
41    pub fn validate(&self) -> Result<(), ProfileError> {
42        match self.source {
43            SecretsSource::File | SecretsSource::Env => {
44                if matches!(self.source, SecretsSource::File)
45                    && self.secrets_path.as_deref().is_none_or(str::is_empty)
46                {
47                    return Err(ProfileError::SecretsPathRequired {
48                        secrets_source: self.source_name(),
49                    });
50                }
51                if self.vault.is_some() {
52                    return Err(ProfileError::VaultBlockUnexpected {
53                        secrets_source: self.source_name(),
54                    });
55                }
56            },
57            SecretsSource::Vault => {
58                if self.vault.is_none() {
59                    return Err(ProfileError::VaultBlockRequired);
60                }
61            },
62        }
63        Ok(())
64    }
65
66    pub fn secrets_path(&self) -> Result<&str, ProfileError> {
67        self.secrets_path
68            .as_deref()
69            .filter(|p| !p.is_empty())
70            .ok_or_else(|| ProfileError::SecretsPathRequired {
71                secrets_source: self.source_name(),
72            })
73    }
74
75    #[must_use]
76    pub const fn source_name(&self) -> &'static str {
77        match self.source {
78            SecretsSource::File => "file",
79            SecretsSource::Env => "env",
80            SecretsSource::Vault => "vault",
81        }
82    }
83}
84
85#[derive(
86    Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, schemars::JsonSchema,
87)]
88#[serde(rename_all = "lowercase")]
89pub enum SecretsValidationMode {
90    Strict,
91
92    #[default]
93    Warn,
94
95    Skip,
96}