durable-actors 0.8.1

Standalone regional durable-actors control plane, host, and durability runtime
use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Deserializer, Serialize, de::Error};
use serde_json::{
    Value,
    value::{RawValue, to_raw_value},
};
use std::collections::BTreeMap;

#[async_trait]
pub trait ActorDatabase: Send + Sync {
    async fn call(&self, call: DatabaseCall) -> Result<DatabaseResult>;
}

#[derive(Debug, Serialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum DatabaseCall {
    Exec {
        sql: String,
        #[serde(default)]
        bindings: Vec<SqlValue>,
        #[serde(default)]
        immediate: bool,
    },
    Fields,
    InitializeFields {
        fields: BTreeMap<String, Box<RawValue>>,
    },
    PersistField {
        name: String,
        value: Box<RawValue>,
        #[serde(default)]
        immediate: bool,
    },
    DeleteField {
        name: String,
        #[serde(default)]
        immediate: bool,
    },
    ReplaceFields {
        fields: BTreeMap<String, Box<RawValue>>,
    },
    Alarm,
    SetAlarm {
        timestamp_ms: i64,
        #[serde(default)]
        immediate: bool,
    },
    DeleteAlarm {
        #[serde(default)]
        immediate: bool,
    },
    Commit,
    Rollback,
}

#[derive(Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
enum DatabaseCallWire {
    Exec {
        sql: String,
        #[serde(default)]
        bindings: Vec<SqlValue>,
        #[serde(default)]
        immediate: bool,
    },
    Fields,
    InitializeFields {
        fields: BTreeMap<String, Value>,
    },
    PersistField {
        name: String,
        value: Value,
        #[serde(default)]
        immediate: bool,
    },
    DeleteField {
        name: String,
        #[serde(default)]
        immediate: bool,
    },
    ReplaceFields {
        fields: BTreeMap<String, Value>,
    },
    Alarm,
    SetAlarm {
        timestamp_ms: i64,
        #[serde(default)]
        immediate: bool,
    },
    DeleteAlarm {
        #[serde(default)]
        immediate: bool,
    },
    Commit,
    Rollback,
}

impl<'de> Deserialize<'de> for DatabaseCall {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        Ok(match DatabaseCallWire::deserialize(deserializer)? {
            DatabaseCallWire::Exec {
                sql,
                bindings,
                immediate,
            } => Self::Exec {
                sql,
                bindings,
                immediate,
            },
            DatabaseCallWire::Fields => Self::Fields,
            DatabaseCallWire::InitializeFields { fields } => Self::InitializeFields {
                fields: raw_fields(fields)?,
            },
            DatabaseCallWire::PersistField {
                name,
                value,
                immediate,
            } => Self::PersistField {
                name,
                value: to_raw_value(&value).map_err(D::Error::custom)?,
                immediate,
            },
            DatabaseCallWire::DeleteField { name, immediate } => {
                Self::DeleteField { name, immediate }
            }
            DatabaseCallWire::ReplaceFields { fields } => Self::ReplaceFields {
                fields: raw_fields(fields)?,
            },
            DatabaseCallWire::Alarm => Self::Alarm,
            DatabaseCallWire::SetAlarm {
                timestamp_ms,
                immediate,
            } => Self::SetAlarm {
                timestamp_ms,
                immediate,
            },
            DatabaseCallWire::DeleteAlarm { immediate } => Self::DeleteAlarm { immediate },
            DatabaseCallWire::Commit => Self::Commit,
            DatabaseCallWire::Rollback => Self::Rollback,
        })
    }
}

fn raw_fields<E: Error>(
    fields: BTreeMap<String, Value>,
) -> Result<BTreeMap<String, Box<RawValue>>, E> {
    fields
        .into_iter()
        .map(|(name, value)| Ok((name, to_raw_value(&value).map_err(E::custom)?)))
        .collect()
}

impl DatabaseCall {
    pub(crate) fn immediate(&self) -> bool {
        matches!(
            self,
            Self::Exec {
                immediate: true,
                ..
            } | Self::PersistField {
                immediate: true,
                ..
            } | Self::DeleteField {
                immediate: true,
                ..
            } | Self::SetAlarm {
                immediate: true,
                ..
            } | Self::DeleteAlarm {
                immediate: true,
                ..
            }
        )
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum DatabaseResult {
    Rows {
        columns: Vec<String>,
        rows: Vec<Vec<SqlValue>>,
    },
    Fields {
        fields: BTreeMap<String, Box<RawValue>>,
    },
    Alarm {
        timestamp_ms: Option<i64>,
    },
    Done,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
pub enum SqlValue {
    Null,
    Integer(#[serde(with = "integer")] i64),
    Real(f64),
    Text(String),
    Blob(#[serde(with = "blob")] Vec<u8>),
}

mod integer {
    use serde::{Deserialize, Deserializer, Serializer, de::Error};

    pub(super) fn serialize<S: Serializer>(value: &i64, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.collect_str(value)
    }

    pub(super) fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<i64, D::Error> {
        String::deserialize(deserializer)?
            .parse()
            .map_err(D::Error::custom)
    }
}

mod blob {
    use base64::{Engine, engine::general_purpose::STANDARD};
    use serde::{Deserialize, Deserializer, Serializer, de::Error};

    pub(super) fn serialize<S: Serializer>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(&STANDARD.encode(value))
    }

    pub(super) fn deserialize<'de, D: Deserializer<'de>>(
        deserializer: D,
    ) -> Result<Vec<u8>, D::Error> {
        STANDARD
            .decode(String::deserialize(deserializer)?)
            .map_err(D::Error::custom)
    }
}