gsm-core 0.4.40

Core types and platform abstractions for the Greentic messaging runtime.
Documentation
#![allow(dead_code)]

use std::collections::HashMap;
use std::sync::{Arc, Mutex};

use greentic_secrets_spec::record_from_plain;
use greentic_types::{EnvId, TeamId, TenantCtx, TenantId};
use gsm_core::platforms::webchat::config::Config;
use gsm_core::platforms::webchat::provider::WebChatProvider;
use secrets_core::{Scope, SecretUri, SecretVersion, SecretsBackend, VersionedSecret};

#[derive(Clone, Default)]
pub struct TestSecretsBackend {
    inner: Arc<Mutex<HashMap<String, VersionedSecret>>>,
}

impl TestSecretsBackend {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn insert_secret(&self, scope: Scope, category: &str, name: &str, value: &str) {
        let uri = SecretUri::new(scope, category.to_string(), name.to_string())
            .expect("valid secret uri");
        let record = record_from_plain(value.to_string());
        let secret = VersionedSecret {
            version: 1,
            deleted: false,
            record: Some(record),
        };
        self.inner
            .lock()
            .expect("lock secrets map")
            .insert(uri.to_string(), secret);
    }

    pub fn backend_arc(self) -> Arc<Self> {
        Arc::new(self)
    }
}

impl SecretsBackend for TestSecretsBackend {
    fn put(
        &self,
        record: secrets_core::SecretRecord,
    ) -> secrets_core::Result<secrets_core::SecretVersion> {
        self.inner.lock().expect("lock secrets map").insert(
            record.meta.uri.to_string(),
            VersionedSecret {
                version: 1,
                deleted: false,
                record: Some(record),
            },
        );
        Ok(secrets_core::SecretVersion {
            version: 1,
            deleted: false,
        })
    }

    fn get(
        &self,
        uri: &SecretUri,
        _version: Option<u64>,
    ) -> secrets_core::Result<Option<VersionedSecret>> {
        Ok(self
            .inner
            .lock()
            .expect("lock secrets map")
            .get(&uri.to_string())
            .cloned())
    }

    fn list(
        &self,
        scope: &Scope,
        category_prefix: Option<&str>,
        name_prefix: Option<&str>,
    ) -> secrets_core::Result<Vec<secrets_core::SecretListItem>> {
        let items = self
            .inner
            .lock()
            .expect("lock secrets map")
            .values()
            .filter_map(|secret| secret.record.as_ref())
            .filter(|record| {
                record.meta.uri.scope() == scope
                    && category_prefix.is_none_or(|p| record.meta.uri.category().starts_with(p))
                    && name_prefix.is_none_or(|p| record.meta.uri.name().starts_with(p))
            })
            .map(|record| secrets_core::SecretListItem::from_meta(&record.meta, Some("1".into())))
            .collect();
        Ok(items)
    }

    fn delete(&self, uri: &SecretUri) -> secrets_core::Result<SecretVersion> {
        let removed = self
            .inner
            .lock()
            .expect("lock secrets map")
            .remove(&uri.to_string());
        match removed {
            Some(secret) => Ok(SecretVersion {
                version: secret.version,
                deleted: secret.deleted,
            }),
            None => Err(secrets_core::Error::NotFound {
                entity: uri.to_string(),
            }),
        }
    }

    fn versions(&self, uri: &SecretUri) -> secrets_core::Result<Vec<SecretVersion>> {
        Ok(self
            .inner
            .lock()
            .expect("lock secrets map")
            .get(&uri.to_string())
            .map(|secret| {
                vec![SecretVersion {
                    version: secret.version,
                    deleted: secret.deleted,
                }]
            })
            .unwrap_or_default())
    }

    fn exists(&self, uri: &SecretUri) -> secrets_core::Result<bool> {
        Ok(self
            .inner
            .lock()
            .expect("lock secrets map")
            .contains_key(&uri.to_string()))
    }
}

pub fn tenant_scope(env: &str, tenant: &str, team: Option<&str>) -> Scope {
    Scope::new(
        env.to_ascii_lowercase(),
        tenant.to_ascii_lowercase(),
        team.map(|value| value.to_ascii_lowercase()),
    )
    .expect("valid tenant scope")
}

pub fn signing_scope() -> Scope {
    Scope::new("global", "webchat", None).expect("valid signing scope")
}

pub fn tenant_ctx(env: &str, tenant: &str, team: Option<&str>) -> TenantCtx {
    let mut ctx = TenantCtx::new(EnvId(env.to_string()), TenantId(tenant.to_string()));
    if let Some(team) = team {
        ctx = ctx.with_team(Some(TeamId(team.to_string())));
    }
    ctx
}

pub fn provider_with_secrets(
    config: Config,
    signing_scope: Scope,
    secrets: &[(&Scope, &str, &str, &str)],
) -> WebChatProvider {
    let backend = TestSecretsBackend::new();
    backend.insert_secret(
        signing_scope.clone(),
        "webchat",
        "jwt_signing_key",
        "test-signing-key",
    );
    for (scope, category, name, value) in secrets {
        backend.insert_secret((**scope).clone(), category, name, value);
    }
    let provider = WebChatProvider::new(config, backend.backend_arc());
    provider.with_signing_scope(signing_scope)
}