1#![deny(missing_docs)]
2use async_trait::async_trait;
9use layer0::secret::SecretSource;
10use neuron_auth::AuthProvider;
11use neuron_secret::{SecretError, SecretLease, SecretResolver};
12use std::sync::Arc;
13
14pub struct GcpResolver {
16 _auth: Arc<dyn AuthProvider>,
17}
18
19impl GcpResolver {
20 pub fn new(auth: Arc<dyn AuthProvider>) -> Self {
22 Self { _auth: auth }
23 }
24}
25
26#[async_trait]
27impl SecretResolver for GcpResolver {
28 async fn resolve(&self, source: &SecretSource) -> Result<SecretLease, SecretError> {
29 match source {
30 SecretSource::GcpSecretManager { project, secret_id } => {
31 Err(SecretError::BackendError(format!(
32 "GcpResolver is a stub — would resolve {project}/{secret_id}"
33 )))
34 }
35 _ => Err(SecretError::NoResolver("gcp".into())),
36 }
37 }
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43 use neuron_auth::{AuthError, AuthRequest, AuthToken};
44
45 struct StubAuth;
46 #[async_trait]
47 impl AuthProvider for StubAuth {
48 async fn provide(&self, _request: &AuthRequest) -> Result<AuthToken, AuthError> {
49 Ok(AuthToken::permanent(b"stub".to_vec()))
50 }
51 }
52
53 fn _assert_send_sync<T: Send + Sync>() {}
54
55 #[test]
56 fn object_safety() {
57 _assert_send_sync::<Box<dyn SecretResolver>>();
58 _assert_send_sync::<Arc<dyn SecretResolver>>();
59 let auth: Arc<dyn AuthProvider> = Arc::new(StubAuth);
60 let _: Arc<dyn SecretResolver> = Arc::new(GcpResolver::new(auth));
61 }
62
63 #[tokio::test]
64 async fn matches_gcp_source() {
65 let auth: Arc<dyn AuthProvider> = Arc::new(StubAuth);
66 let resolver = GcpResolver::new(auth);
67 let source = SecretSource::GcpSecretManager {
68 project: "my-project".into(),
69 secret_id: "api-key".into(),
70 };
71 let err = resolver.resolve(&source).await.unwrap_err();
72 assert!(matches!(err, SecretError::BackendError(_)));
73 assert!(err.to_string().contains("stub"));
74 }
75
76 #[tokio::test]
77 async fn rejects_wrong_source() {
78 let auth: Arc<dyn AuthProvider> = Arc::new(StubAuth);
79 let resolver = GcpResolver::new(auth);
80 let source = SecretSource::Vault {
81 mount: "secret".into(),
82 path: "data/key".into(),
83 };
84 let err = resolver.resolve(&source).await.unwrap_err();
85 assert!(matches!(err, SecretError::NoResolver(_)));
86 }
87}