use std::collections::HashMap;
use async_trait::async_trait;
use serde_json::Value;
use crate::context::RequestContext;
pub type ExtraColumns = HashMap<String, Value>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StorageOperation {
CreateConversation,
GetConversation,
UpdateConversation,
DeleteConversation,
CreateItem,
LinkItem,
LinkItems,
ListItems,
GetItem,
IsItemLinked,
DeleteItem,
StoreResponse,
GetResponse,
DeleteResponse,
GetResponseChain,
ListIdentifierResponses,
DeleteIdentifierResponses,
}
#[derive(Debug)]
pub enum BeforeHookResult {
Continue(ExtraColumns),
Reject(String),
}
impl Default for BeforeHookResult {
fn default() -> Self {
Self::Continue(ExtraColumns::new())
}
}
#[derive(Debug, thiserror::Error)]
pub enum HookError {
#[error("hook rejected: {0}")]
Rejected(String),
#[error("hook error: {0}")]
Internal(String),
}
#[async_trait]
pub trait StorageHook: Send + Sync + 'static {
async fn before(
&self,
operation: StorageOperation,
context: Option<&RequestContext>,
payload: &Value,
) -> Result<BeforeHookResult, HookError>;
async fn after(
&self,
operation: StorageOperation,
context: Option<&RequestContext>,
payload: &Value,
result: &Value,
extra: &ExtraColumns,
) -> Result<ExtraColumns, HookError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn before_hook_result_default_is_continue_with_empty_extra() {
let result = BeforeHookResult::default();
match result {
BeforeHookResult::Continue(extra) => assert!(extra.is_empty()),
BeforeHookResult::Reject(_) => panic!("expected Continue"),
}
}
#[test]
fn storage_operation_is_copy() {
let op = StorageOperation::CreateConversation;
let op2 = op; assert_eq!(op, op2);
}
#[test]
fn hook_error_display() {
let err = HookError::Rejected("bad input".to_string());
assert_eq!(err.to_string(), "hook rejected: bad input");
let err = HookError::Internal("timeout".to_string());
assert_eq!(err.to_string(), "hook error: timeout");
}
}