use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum ActionType {
Create,
Update,
Delete,
Navigate,
Refresh,
Toggle,
Custom(String),
}
impl From<&str> for ActionType {
fn from(s: &str) -> Self {
match s.to_lowercase().as_str() {
"create" | "new" | "add" => Self::Create,
"update" | "edit" | "save" => Self::Update,
"delete" | "remove" | "destroy" => Self::Delete,
"navigate" | "goto" | "redirect" => Self::Navigate,
"refresh" | "reload" => Self::Refresh,
"toggle" => Self::Toggle,
other => Self::Custom(other.to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct ParsedAction {
pub action_type: ActionType,
pub collection: Option<String>,
pub id: Option<String>,
pub redirect: Option<String>,
pub target: Option<String>,
pub swap: SwapMode,
pub confirm: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub enum SwapMode {
#[default]
InnerHtml,
OuterHtml,
BeforeBegin,
AfterEnd,
BeforeEnd,
AfterBegin,
None,
}
impl From<&str> for SwapMode {
fn from(s: &str) -> Self {
match s.to_lowercase().as_str() {
"innerhtml" | "inner" => Self::InnerHtml,
"outerhtml" | "outer" => Self::OuterHtml,
"beforebegin" | "before" => Self::BeforeBegin,
"afterend" | "after" => Self::AfterEnd,
"beforeend" | "append" => Self::BeforeEnd,
"afterbegin" | "prepend" => Self::AfterBegin,
"none" | "false" => Self::None,
_ => Self::InnerHtml,
}
}
}
pub struct ActionHandler;
impl ActionHandler {
pub fn parse_attributes(attrs: &HashMap<String, String>) -> ParsedAction {
let action_str = attrs
.get("w-action")
.map(|s| s.as_str())
.unwrap_or("create");
let action_type = ActionType::from(action_str);
ParsedAction {
action_type,
collection: attrs
.get("w-store")
.cloned()
.or_else(|| attrs.get("w-collection").cloned()),
id: attrs.get("w-id").cloned(),
redirect: attrs.get("w-redirect").cloned(),
target: attrs.get("w-target").cloned(),
swap: attrs
.get("w-swap")
.map(|s| SwapMode::from(s.as_str()))
.unwrap_or_default(),
confirm: attrs.get("w-confirm").cloned(),
}
}
pub fn generate_action_url(action: &ParsedAction) -> String {
let mut url = String::from("/w-action");
if let Some(ref collection) = action.collection {
url.push('/');
url.push_str(collection);
if let Some(ref id) = action.id {
url.push('/');
url.push_str(id);
}
}
let mut params = Vec::new();
match action.action_type {
ActionType::Create => params.push("w-action=create".to_string()),
ActionType::Update => params.push("w-action=update".to_string()),
ActionType::Delete => params.push("w-action=delete".to_string()),
ActionType::Custom(ref name) => params.push(format!("w-action={}", name)),
_ => {}
}
if let Some(ref redirect) = action.redirect {
params.push(format!("w-redirect={}", urlencoding::encode(redirect)));
}
if !params.is_empty() {
url.push('?');
url.push_str(¶ms.join("&"));
}
url
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_action_type() {
assert_eq!(ActionType::from("create"), ActionType::Create);
assert_eq!(ActionType::from("update"), ActionType::Update);
assert_eq!(ActionType::from("delete"), ActionType::Delete);
assert_eq!(
ActionType::from("custom_action"),
ActionType::Custom("custom_action".to_string())
);
}
#[test]
fn test_parse_attributes() {
let mut attrs = HashMap::new();
attrs.insert("w-action".to_string(), "create".to_string());
attrs.insert("w-store".to_string(), "posts".to_string());
attrs.insert("w-redirect".to_string(), "/posts".to_string());
let action = ActionHandler::parse_attributes(&attrs);
assert_eq!(action.action_type, ActionType::Create);
assert_eq!(action.collection, Some("posts".to_string()));
assert_eq!(action.redirect, Some("/posts".to_string()));
}
#[test]
fn test_generate_action_url() {
let action = ParsedAction {
action_type: ActionType::Create,
collection: Some("posts".to_string()),
id: None,
redirect: Some("/posts".to_string()),
target: None,
swap: SwapMode::InnerHtml,
confirm: None,
};
let url = ActionHandler::generate_action_url(&action);
assert!(url.starts_with("/w-action/posts"));
assert!(url.contains("w-action=create"));
}
}