winterbaume-chatbot 0.2.0

Chatbot service implementation for winterbaume
Documentation
use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use winterbaume_core::{StateChangeNotifier, StateViewError, StatefulService};

use crate::handlers::ChatbotService;
use crate::state::ChatbotState;
use crate::types::{ChimeConfig, SlackConfig, TeamsConfig};

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChatbotStateView {
    #[serde(default)]
    pub slack_configs: HashMap<String, SlackConfigView>,
    #[serde(default)]
    pub chime_configs: HashMap<String, ChimeConfigView>,
    #[serde(default)]
    pub teams_configs: HashMap<String, TeamsConfigView>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlackConfigView {
    pub arn: String,
    pub configuration_name: String,
    pub slack_team_id: String,
    pub slack_channel_id: String,
    pub slack_channel_name: Option<String>,
    pub iam_role_arn: String,
    pub sns_topic_arns: Vec<String>,
    pub logging_level: Option<String>,
    pub guardrail_policy_arns: Vec<String>,
    pub user_authorization_required: Option<bool>,
    pub tags: HashMap<String, String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChimeConfigView {
    pub arn: String,
    pub configuration_name: String,
    pub webhook_url: String,
    pub webhook_description: String,
    pub iam_role_arn: String,
    pub sns_topic_arns: Vec<String>,
    pub logging_level: Option<String>,
    pub tags: HashMap<String, String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamsConfigView {
    pub arn: String,
    pub configuration_name: String,
    pub team_id: String,
    pub team_name: Option<String>,
    pub tenant_id: String,
    pub channel_id: String,
    pub channel_name: Option<String>,
    pub iam_role_arn: String,
    pub sns_topic_arns: Vec<String>,
    pub logging_level: Option<String>,
    pub guardrail_policy_arns: Vec<String>,
    pub user_authorization_required: Option<bool>,
    pub tags: HashMap<String, String>,
}

impl From<&SlackConfig> for SlackConfigView {
    fn from(c: &SlackConfig) -> Self {
        Self {
            arn: c.arn.clone(),
            configuration_name: c.configuration_name.clone(),
            slack_team_id: c.slack_team_id.clone(),
            slack_channel_id: c.slack_channel_id.clone(),
            slack_channel_name: c.slack_channel_name.clone(),
            iam_role_arn: c.iam_role_arn.clone(),
            sns_topic_arns: c.sns_topic_arns.clone(),
            logging_level: c.logging_level.clone(),
            guardrail_policy_arns: c.guardrail_policy_arns.clone(),
            user_authorization_required: c.user_authorization_required,
            tags: c.tags.clone(),
        }
    }
}

impl From<SlackConfigView> for SlackConfig {
    fn from(v: SlackConfigView) -> Self {
        Self {
            arn: v.arn,
            configuration_name: v.configuration_name,
            slack_team_id: v.slack_team_id,
            slack_channel_id: v.slack_channel_id,
            slack_channel_name: v.slack_channel_name,
            iam_role_arn: v.iam_role_arn,
            sns_topic_arns: v.sns_topic_arns,
            logging_level: v.logging_level,
            guardrail_policy_arns: v.guardrail_policy_arns,
            user_authorization_required: v.user_authorization_required,
            tags: v.tags,
        }
    }
}

impl From<&ChimeConfig> for ChimeConfigView {
    fn from(c: &ChimeConfig) -> Self {
        Self {
            arn: c.arn.clone(),
            configuration_name: c.configuration_name.clone(),
            webhook_url: c.webhook_url.clone(),
            webhook_description: c.webhook_description.clone(),
            iam_role_arn: c.iam_role_arn.clone(),
            sns_topic_arns: c.sns_topic_arns.clone(),
            logging_level: c.logging_level.clone(),
            tags: c.tags.clone(),
        }
    }
}

impl From<ChimeConfigView> for ChimeConfig {
    fn from(v: ChimeConfigView) -> Self {
        Self {
            arn: v.arn,
            configuration_name: v.configuration_name,
            webhook_url: v.webhook_url,
            webhook_description: v.webhook_description,
            iam_role_arn: v.iam_role_arn,
            sns_topic_arns: v.sns_topic_arns,
            logging_level: v.logging_level,
            tags: v.tags,
        }
    }
}

impl From<&TeamsConfig> for TeamsConfigView {
    fn from(c: &TeamsConfig) -> Self {
        Self {
            arn: c.arn.clone(),
            configuration_name: c.configuration_name.clone(),
            team_id: c.team_id.clone(),
            team_name: c.team_name.clone(),
            tenant_id: c.tenant_id.clone(),
            channel_id: c.channel_id.clone(),
            channel_name: c.channel_name.clone(),
            iam_role_arn: c.iam_role_arn.clone(),
            sns_topic_arns: c.sns_topic_arns.clone(),
            logging_level: c.logging_level.clone(),
            guardrail_policy_arns: c.guardrail_policy_arns.clone(),
            user_authorization_required: c.user_authorization_required,
            tags: c.tags.clone(),
        }
    }
}

impl From<TeamsConfigView> for TeamsConfig {
    fn from(v: TeamsConfigView) -> Self {
        Self {
            arn: v.arn,
            configuration_name: v.configuration_name,
            team_id: v.team_id,
            team_name: v.team_name,
            tenant_id: v.tenant_id,
            channel_id: v.channel_id,
            channel_name: v.channel_name,
            iam_role_arn: v.iam_role_arn,
            sns_topic_arns: v.sns_topic_arns,
            logging_level: v.logging_level,
            guardrail_policy_arns: v.guardrail_policy_arns,
            user_authorization_required: v.user_authorization_required,
            tags: v.tags,
        }
    }
}

impl From<&ChatbotState> for ChatbotStateView {
    fn from(state: &ChatbotState) -> Self {
        Self {
            slack_configs: state
                .slack_configs
                .iter()
                .map(|(k, v)| (k.clone(), SlackConfigView::from(v)))
                .collect(),
            chime_configs: state
                .chime_configs
                .iter()
                .map(|(k, v)| (k.clone(), ChimeConfigView::from(v)))
                .collect(),
            teams_configs: state
                .teams_configs
                .iter()
                .map(|(k, v)| (k.clone(), TeamsConfigView::from(v)))
                .collect(),
        }
    }
}

impl From<ChatbotStateView> for ChatbotState {
    fn from(view: ChatbotStateView) -> Self {
        Self {
            slack_configs: view
                .slack_configs
                .into_iter()
                .map(|(k, v)| (k, SlackConfig::from(v)))
                .collect(),
            chime_configs: view
                .chime_configs
                .into_iter()
                .map(|(k, v)| (k, ChimeConfig::from(v)))
                .collect(),
            teams_configs: view
                .teams_configs
                .into_iter()
                .map(|(k, v)| (k, TeamsConfig::from(v)))
                .collect(),
        }
    }
}

impl StatefulService for ChatbotService {
    type StateView = ChatbotStateView;

    async fn snapshot(&self, account_id: &str, region: &str) -> Self::StateView {
        let state = self.state.get(account_id, region);
        let guard = state.read().await;
        ChatbotStateView::from(&*guard)
    }

    async fn restore(
        &self,
        account_id: &str,
        region: &str,
        view: Self::StateView,
    ) -> Result<(), StateViewError> {
        let state = self.state.get(account_id, region);
        {
            let mut guard = state.write().await;
            *guard = ChatbotState::from(view);
        }
        self.notify_state_changed(account_id, region).await;
        Ok(())
    }

    async fn merge(
        &self,
        account_id: &str,
        region: &str,
        view: Self::StateView,
    ) -> Result<(), StateViewError> {
        let state = self.state.get(account_id, region);
        {
            let mut guard = state.write().await;
            for (k, v) in view.slack_configs {
                guard.slack_configs.insert(k, SlackConfig::from(v));
            }
            for (k, v) in view.chime_configs {
                guard.chime_configs.insert(k, ChimeConfig::from(v));
            }
            for (k, v) in view.teams_configs {
                guard.teams_configs.insert(k, TeamsConfig::from(v));
            }
        }
        self.notify_state_changed(account_id, region).await;
        Ok(())
    }

    fn notifier(&self) -> &StateChangeNotifier<Self::StateView> {
        &self.notifier
    }
}