use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Activity {
#[serde(default)]
pub(crate) kind: ActivityKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
tenant: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pack_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
flow_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
flow_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
session_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
provider_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
user_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
channel_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
conversation_id: Option<String>,
#[serde(default)]
payload: Value,
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ActivityKind {
#[default]
Message,
Custom {
action: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
flow_type: Option<String>,
},
}
impl Activity {
pub fn text(text: impl Into<String>) -> Self {
Self {
kind: ActivityKind::Message,
tenant: None,
pack_id: None,
flow_id: None,
flow_type: Some("messaging".into()),
session_id: None,
provider_id: None,
user_id: None,
channel_id: None,
conversation_id: None,
payload: json!({ "text": text.into() }),
}
}
pub fn custom(action: impl Into<String>, payload: Value) -> Self {
Self {
kind: ActivityKind::Custom {
action: action.into(),
flow_type: None,
},
tenant: None,
pack_id: None,
flow_id: None,
flow_type: None,
session_id: None,
provider_id: None,
user_id: None,
channel_id: None,
conversation_id: None,
payload,
}
}
pub fn with_tenant(mut self, tenant: impl Into<String>) -> Self {
self.tenant = Some(tenant.into());
self
}
pub fn with_flow(mut self, flow_id: impl Into<String>) -> Self {
self.flow_id = Some(flow_id.into());
self
}
pub fn with_pack(mut self, pack_id: impl Into<String>) -> Self {
self.pack_id = Some(pack_id.into());
self
}
pub fn with_flow_type(mut self, flow_type: impl Into<String>) -> Self {
let flow_type = flow_type.into();
self.flow_type = Some(flow_type.clone());
if let ActivityKind::Custom {
flow_type: inner, ..
} = &mut self.kind
{
*inner = Some(flow_type);
}
self
}
#[cfg(feature = "greentic-x-provider")]
pub(crate) fn with_payload_field(mut self, key: impl Into<String>, value: Value) -> Self {
match &mut self.payload {
Value::Object(object) => {
object.insert(key.into(), value);
}
existing => {
let original = std::mem::replace(existing, Value::Null);
*existing = json!({
key.into(): value,
"value": original,
});
}
}
self
}
pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
self.session_id = Some(session_id.into());
self
}
pub fn with_provider(mut self, provider: impl Into<String>) -> Self {
self.provider_id = Some(provider.into());
self
}
pub fn from_user(mut self, user: impl Into<String>) -> Self {
self.user_id = Some(user.into());
self
}
pub fn in_channel(mut self, channel: impl Into<String>) -> Self {
self.channel_id = Some(channel.into());
self
}
pub fn in_conversation(mut self, conversation: impl Into<String>) -> Self {
self.conversation_id = Some(conversation.into());
self
}
pub fn tenant(&self) -> Option<&str> {
self.tenant.as_deref()
}
pub fn pack_id(&self) -> Option<&str> {
self.pack_id.as_deref()
}
pub fn flow_id(&self) -> Option<&str> {
self.flow_id.as_deref()
}
pub fn flow_type(&self) -> Option<&str> {
self.flow_type
.as_deref()
.or_else(|| self.kind.flow_type_hint())
}
pub fn session_id(&self) -> Option<&str> {
self.session_id.as_deref()
}
pub fn provider_id(&self) -> Option<&str> {
self.provider_id.as_deref()
}
pub fn user(&self) -> Option<&str> {
self.user_id.as_deref()
}
pub fn channel(&self) -> Option<&str> {
self.channel_id.as_deref()
}
pub fn conversation(&self) -> Option<&str> {
self.conversation_id.as_deref()
}
pub fn payload(&self) -> &Value {
&self.payload
}
pub(crate) fn action(&self) -> Option<&str> {
self.kind.action_hint()
}
pub(crate) fn into_payload(self) -> Value {
self.payload
}
pub(crate) fn ensure_tenant(mut self, tenant: &str) -> Self {
if self.tenant.is_none() {
self.tenant = Some(tenant.to_string());
}
self
}
pub(crate) fn from_output(payload: Value, tenant: &str) -> Self {
Activity::custom("response", payload).ensure_tenant(tenant)
}
}
impl ActivityKind {
fn flow_type_hint(&self) -> Option<&str> {
match self {
ActivityKind::Message => Some("messaging"),
ActivityKind::Custom { flow_type, .. } => flow_type.as_deref(),
}
}
fn action_hint(&self) -> Option<&str> {
match self {
ActivityKind::Message => Some("messaging"),
ActivityKind::Custom { action, .. } => Some(action.as_str()),
}
}
}