use async_trait::async_trait;
use parking_lot::RwLock;
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct HookContext {
pub name: String,
pub data: Value,
pub proceed: bool,
pub result: Option<Value>,
}
impl HookContext {
pub fn new(name: &str, data: Value) -> Self {
Self {
name: name.to_string(),
data,
proceed: true,
result: None,
}
}
pub fn set_result(&mut self, result: Value) {
self.result = Some(result);
}
pub fn stop(&mut self) {
self.proceed = false;
}
}
#[async_trait]
pub trait Hook: Send + Sync {
fn name(&self) -> &str;
fn priority(&self) -> i32 {
0
}
async fn execute(&self, ctx: &mut HookContext);
}
pub struct HookManager {
hooks: RwLock<HashMap<String, Vec<Arc<dyn Hook>>>>,
}
impl HookManager {
pub fn new() -> Self {
Self {
hooks: RwLock::new(HashMap::new()),
}
}
pub fn register<H: Hook + 'static>(&self, hook_name: &str, hook: H) {
let mut hooks = self.hooks.write();
let list = hooks.entry(hook_name.to_string()).or_default();
list.push(Arc::new(hook));
list.sort_by_key(|h| h.priority());
}
pub async fn execute(&self, hook_name: &str, data: Value) -> HookContext {
let mut ctx = HookContext::new(hook_name, data);
let hooks = {
let hooks = self.hooks.read();
hooks.get(hook_name).cloned().unwrap_or_default()
};
for hook in hooks {
if !ctx.proceed {
break;
}
hook.execute(&mut ctx).await;
}
ctx
}
pub fn remove(&self, hook_name: &str) {
let mut hooks = self.hooks.write();
hooks.remove(hook_name);
}
pub fn clear(&self) {
let mut hooks = self.hooks.write();
hooks.clear();
}
pub fn list(&self, hook_name: &str) -> Vec<String> {
let hooks = self.hooks.read();
hooks
.get(hook_name)
.map(|list| list.iter().map(|h| h.name().to_string()).collect())
.unwrap_or_default()
}
}
impl Default for HookManager {
fn default() -> Self {
Self::new()
}
}
static GLOBAL_HOOK_MANAGER: once_cell::sync::Lazy<HookManager> =
once_cell::sync::Lazy::new(HookManager::new);
pub fn global_hook_manager() -> &'static HookManager {
&GLOBAL_HOOK_MANAGER
}
pub mod hook_names {
pub const BEFORE_UPLOAD: &str = "beforeUpload";
pub const AFTER_UPLOAD: &str = "afterUpload";
pub const BEFORE_REQUEST: &str = "beforeRequest";
pub const AFTER_REQUEST: &str = "afterRequest";
pub const BEFORE_DB: &str = "beforeDb";
pub const AFTER_DB: &str = "afterDb";
}