acts-package-state 0.20.1

acts package for state store
Documentation
use crate::config::StateConfig;
use acts::{
    ActError, ActPackage, ActPackageCatalog, ActPackageDefinition, ActResource, ActRunAs, Result,
    Vars,
};
use serde::{Deserialize, Serialize};
use serde_json::json;

const CONFIG_NAME: &str = "state";

#[derive(Debug, Clone)]
pub struct StatePackage {
    client: redis::Client,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatePackageParams {
    op: String,
    params: Vars,
}

impl ActPackage for StatePackage {
    fn definition() -> acts::ActPackageDefinition {
        ActPackageDefinition {
            id: "acts.app.state",
            name: "State",
            desc: "get or set state to redis",
            version: "0.1.0",
            icon: "icon-app-state",
            doc: "",
            schema: json!({
                "type": "object",
                "properties": {
                    "op": { "type": "string", "enum": ["GET", "SET" ] },
                    "key": { "type": "string" },
                    "value": { "type": ["number", "string", "boolean", "array", "object"] },
                },
                "required": ["op", "key"],
            }),
            options: Some(json!({
                "ui:order": ["op", "key", "value"],
                "op": {
                    "ui:widget": "select",
                    "ui:options": {
                        "label": false,
                        "placeholder": "Select an operation"
                    }
                },
                "key": {
                    "ui:widget": "text",
                    "ui:options": {
                        "label": false,
                        "placeholder": "Enter the key"
                    }
                },
                "value": {
                    "ui:widget": "textarea",
                    "ui:options": {
                        "label": false,
                        "placeholder": "Enter the value"
                    }
                }
            })),
            run_as: ActRunAs::Irq,
            resources: vec![
                ActResource {
                    name: "Get state store".to_string(),
                    desc: "get a state from the state store".to_string(),
                    value: json!({ "op": "GET"}),
                },
                ActResource {
                    name: "Set state store".to_string(),
                    desc: "set a state from the state store".to_string(),
                    value: json!({ "op": "SET"}),
                },
            ],
            catalog: ActPackageCatalog::App,
        }
    }

    fn new(config: &acts::Config) -> Result<Self>
    where
        Self: Sized,
    {
        if !config.has(CONFIG_NAME) {
            return Err(acts::ActError::Config(
                "missing 'state' section in config file".to_string(),
            ));
        }
        let config = config
            .get::<StateConfig>(CONFIG_NAME)
            .map_err(|err| acts::ActError::Config(format!("get state config error: {err}")))?;

        let mut client = redis::Client::open(config.database_uri.as_str())
            .map_err(|err| acts::ActError::Config(format!("create redis client error: {err}")))?;

        redis::cmd("PING")
            .exec(&mut client)
            .map_err(|err| acts::ActError::Config(format!("ping redis error: {err}")))?;
        Ok(Self { client })
    }

    fn execute(&self, ctx: &acts::Context, params: &serde_json::Value) -> Result<Option<Vars>> {
        let mut conn = self
            .client
            .get_connection()
            .map_err(|err| ActError::Package(format!("error happend to get connection: {err}")))?;

        let pid = ctx.task().pid.to_string();
        let params = serde_json::from_value::<StatePackageParams>(params.clone()).map_err(|e| {
            ActError::Package(format!(
                "invalid ActPackage({}) params: {}",
                Self::definition().id,
                e
            ))
        })?;
        match params.op.as_str() {
            "GET" => {
                let key = params
                    .params
                    .get::<String>("key")
                    .ok_or(ActError::Package("missing 'key' in params".to_string()))?
                    .to_string();

                let ret = redis::cmd("GET")
                    .arg(format!("{pid}:{key}"))
                    .query::<String>(&mut conn)
                    .map_err(|err| {
                        ActError::Package(format!("error happend to set value: {err}"))
                    })?;

                let mut vars = Vars::new();
                vars.insert(
                    key,
                    serde_json::from_str(&ret).map_err(|err| {
                        ActError::Package(format!("error happend to parse value: {err}"))
                    })?,
                );

                Ok(Some(vars))
            }
            "SET" => {
                let key = params
                    .params
                    .get::<String>("key")
                    .ok_or(ActError::Package("missing 'key' in params".to_string()))?
                    .to_string();

                let value: serde_json::Value = params
                    .params
                    .get("value")
                    .ok_or(ActError::Package("missing 'value' in params".to_string()))?;

                let v = serde_json::to_string(&value).map_err(|err| {
                    ActError::Package(format!("error happend to parse value: {err}"))
                })?;

                redis::cmd("SET")
                    .arg(format!("{pid}:{key}"))
                    .arg(v.as_str())
                    .query::<String>(&mut conn)
                    .map_err(|err| {
                        ActError::Package(format!("error happend to set value: {err}"))
                    })?;
                Ok(None)
            }
            _ => Err(ActError::Package(format!(
                "invalid operation: {}",
                params.op
            ))),
        }
    }
}