use crate::viewsets::metadata::{ActionMetadata, FunctionActionHandler, get_actions_for_viewset};
use reinhardt_http::{Request, Response, Result};
use std::collections::HashMap;
use std::future::Future;
use std::sync::{Arc, RwLock};
use tracing;
pub struct ManualActionRegistry {
actions: RwLock<HashMap<String, Vec<ActionMetadata>>>,
}
impl ManualActionRegistry {
fn new() -> Self {
Self {
actions: RwLock::new(HashMap::new()),
}
}
pub fn register(&self, viewset_type: &str, action: ActionMetadata) {
let mut actions = self.actions.write().unwrap_or_else(|e| {
tracing::warn!("Action registry RwLock was poisoned, recovering");
e.into_inner()
});
actions
.entry(viewset_type.to_string())
.or_default()
.push(action);
}
pub fn get_actions(&self, viewset_type: &str) -> Vec<ActionMetadata> {
let actions = self.actions.read().unwrap_or_else(|e| {
tracing::warn!("Action registry RwLock was poisoned, recovering");
e.into_inner()
});
actions.get(viewset_type).cloned().unwrap_or_default()
}
pub fn clear(&self) {
let mut actions = self.actions.write().unwrap_or_else(|e| {
tracing::warn!("Action registry RwLock was poisoned, recovering");
e.into_inner()
});
actions.clear();
}
}
lazy_static::lazy_static! {
static ref GLOBAL_REGISTRY: ManualActionRegistry = ManualActionRegistry::new();
}
pub fn register_action(viewset_type: &str, action: ActionMetadata) {
GLOBAL_REGISTRY.register(viewset_type, action);
}
pub fn get_registered_actions(viewset_type: &str) -> Vec<ActionMetadata> {
GLOBAL_REGISTRY.get_actions(viewset_type)
}
pub fn clear_actions() {
GLOBAL_REGISTRY.clear();
}
pub fn bridge_marker_actions_to_viewset(marker_type: &str, viewset_type: &str) {
if marker_type == viewset_type {
return;
}
let existing: std::collections::HashSet<(Option<String>, String, bool)> =
get_registered_actions(viewset_type)
.into_iter()
.map(|a| (a.url_name.clone(), a.name.clone(), a.detail))
.collect();
let mut already_bridged = existing;
let mut bridge_one = |action: ActionMetadata| {
let key = (action.url_name.clone(), action.name.clone(), action.detail);
if already_bridged.insert(key) {
register_action(viewset_type, action);
}
};
for action in get_registered_actions(marker_type) {
bridge_one(action);
}
for action in get_actions_for_viewset(marker_type) {
bridge_one(action);
}
}
pub fn action<F, Fut>(name: impl Into<String>, detail: bool, handler: F) -> ActionMetadata
where
F: Fn(Request) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Response>> + Send + 'static,
{
let handler_fn = Arc::new(handler);
ActionMetadata::new(name)
.with_detail(detail)
.with_handler(FunctionActionHandler::new(move |req| {
let h = handler_fn.clone();
Box::pin(async move { h(req).await })
}))
}
#[macro_export]
macro_rules! register_viewset_actions {
($viewset_type:ty => {
$(
$action_name:ident ( $detail:expr $(, $attr:ident = $value:expr )* ) => $handler:expr
),* $(,)?
}) => {
{
let viewset_type = std::any::type_name::<$viewset_type>();
$(
let mut action = $crate::viewsets::metadata::ActionMetadata::new(stringify!($action_name))
.with_detail($detail);
$(
action = register_viewset_actions!(@attr action, $attr, $value);
)*
let handler = $handler;
action = action.with_handler($crate::viewsets::metadata::FunctionActionHandler::new(
move |req| Box::pin(handler(req))
));
$crate::viewsets::registry::register_action(viewset_type, action);
)*
}
};
(@attr $action:expr, name, $value:expr) => {
$action.with_custom_name($value)
};
(@attr $action:expr, suffix, $value:expr) => {
$action.with_suffix($value)
};
(@attr $action:expr, url_path, $value:expr) => {
$action.with_url_path($value)
};
(@attr $action:expr, url_name, $value:expr) => {
$action.with_url_name($value)
};
(@attr $action:expr, methods, $value:expr) => {
$action.with_methods($value)
};
}