static_credstore_plugin/
config.rs1use serde::Deserialize;
2use uuid::Uuid;
3
4use credstore_sdk::SharingMode;
5
6#[derive(Debug, Clone, Deserialize)]
8#[serde(default, deny_unknown_fields)]
9pub struct StaticCredStorePluginConfig {
10 pub vendor: String,
12
13 pub priority: i16,
15
16 pub secrets: Vec<SecretConfig>,
18}
19
20impl Default for StaticCredStorePluginConfig {
21 fn default() -> Self {
22 Self {
23 vendor: "hyperspot".to_owned(),
24 priority: 100,
25 secrets: Vec::new(),
26 }
27 }
28}
29
30#[derive(Clone, Deserialize)]
32#[serde(deny_unknown_fields)]
33pub struct SecretConfig {
34 pub tenant_id: Option<Uuid>,
46
47 pub owner_id: Option<Uuid>,
60
61 pub key: String,
63
64 pub value: String,
66
67 pub sharing: Option<SharingMode>,
73}
74
75impl SecretConfig {
76 #[must_use]
79 pub fn resolve_sharing(&self) -> SharingMode {
80 self.sharing
81 .unwrap_or(match (self.tenant_id, self.owner_id) {
82 (None, _) => SharingMode::Shared,
83 (Some(_), None) => SharingMode::Tenant,
84 (Some(_), Some(_)) => SharingMode::Private,
85 })
86 }
87}
88
89impl core::fmt::Debug for SecretConfig {
90 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
91 f.debug_struct("SecretConfig")
92 .field("tenant_id", &self.tenant_id)
93 .field("owner_id", &self.owner_id)
94 .field("key", &self.key)
95 .field("value", &"<redacted>")
96 .field("sharing", &self.resolve_sharing())
97 .finish()
98 }
99}
100
101#[cfg(test)]
102#[cfg_attr(coverage_nightly, coverage(off))]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn config_defaults_are_applied() {
108 let yaml = r#"
109secrets:
110 - tenant_id: "00000000-0000-0000-0000-000000000001"
111 owner_id: "00000000-0000-0000-0000-000000000002"
112 key: "openai_api_key"
113 value: "sk-test-123"
114"#;
115
116 let cfg: StaticCredStorePluginConfig = serde_saphyr::from_str(yaml).unwrap();
117
118 assert_eq!(cfg.vendor, "hyperspot");
119 assert_eq!(cfg.priority, 100);
120 assert_eq!(cfg.secrets.len(), 1);
121 assert!(cfg.secrets[0].sharing.is_none());
122 assert_eq!(cfg.secrets[0].resolve_sharing(), SharingMode::Private);
123 }
124
125 #[test]
126 fn config_allows_omitted_tenant_and_owner() {
127 let yaml = r#"
128secrets:
129 - key: "global_api_key"
130 value: "sk-global"
131"#;
132
133 let cfg: StaticCredStorePluginConfig = serde_saphyr::from_str(yaml).unwrap();
134 assert_eq!(cfg.secrets.len(), 1);
135 assert!(cfg.secrets[0].tenant_id.is_none());
136 assert!(cfg.secrets[0].owner_id.is_none());
137 assert!(cfg.secrets[0].sharing.is_none());
138 assert_eq!(cfg.secrets[0].resolve_sharing(), SharingMode::Shared);
139 }
140
141 #[test]
142 fn config_allows_partial_tenant_only() {
143 let yaml = r#"
144secrets:
145 - tenant_id: "00000000-0000-0000-0000-000000000001"
146 key: "scoped_key"
147 value: "val"
148"#;
149
150 let cfg: StaticCredStorePluginConfig = serde_saphyr::from_str(yaml).unwrap();
151 assert!(cfg.secrets[0].tenant_id.is_some());
152 assert!(cfg.secrets[0].owner_id.is_none());
153 assert!(cfg.secrets[0].sharing.is_none());
154 assert_eq!(cfg.secrets[0].resolve_sharing(), SharingMode::Tenant);
155 }
156
157 #[test]
158 fn config_explicit_sharing_overrides_default() {
159 let yaml = r#"
161secrets:
162 - tenant_id: "00000000-0000-0000-0000-000000000001"
163 key: "key"
164 value: "val"
165 sharing: "shared"
166"#;
167
168 let cfg: StaticCredStorePluginConfig = serde_saphyr::from_str(yaml).unwrap();
169 assert_eq!(cfg.secrets[0].sharing, Some(SharingMode::Shared));
170 assert_eq!(cfg.secrets[0].resolve_sharing(), SharingMode::Shared);
171 }
172
173 #[test]
174 fn config_rejects_unknown_fields() {
175 let yaml = r#"
176vendor: "hyperspot"
177priority: 100
178unexpected: true
179"#;
180
181 let parsed: Result<StaticCredStorePluginConfig, _> = serde_saphyr::from_str(yaml);
182 assert!(parsed.is_err());
183 }
184
185 #[test]
186 fn config_allows_empty_secrets() {
187 let parsed: Result<StaticCredStorePluginConfig, _> = serde_saphyr::from_str("{}");
188 assert!(parsed.is_ok());
189
190 let cfg = match parsed {
191 Ok(cfg) => cfg,
192 Err(e) => panic!("failed to parse config: {e}"),
193 };
194 assert!(cfg.secrets.is_empty());
195 assert_eq!(cfg.vendor, "hyperspot");
196 assert_eq!(cfg.priority, 100);
197 }
198}