use std::fmt;
use crate::error::{AddonLoaderError, AddonLoaderResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResourceAction {
Index,
Create,
Edit,
Read,
Save,
Update,
Delete,
}
impl ResourceAction {
pub fn as_str(&self) -> &'static str {
match self {
ResourceAction::Index => "index",
ResourceAction::Create => "create",
ResourceAction::Edit => "edit",
ResourceAction::Read => "read",
ResourceAction::Save => "save",
ResourceAction::Update => "update",
ResourceAction::Delete => "delete",
}
}
pub fn http_method(&self) -> HttpMethod {
match self {
ResourceAction::Index
| ResourceAction::Create
| ResourceAction::Edit
| ResourceAction::Read => HttpMethod::Get,
ResourceAction::Save => HttpMethod::Post,
ResourceAction::Update => HttpMethod::Put,
ResourceAction::Delete => HttpMethod::Delete,
}
}
pub fn url_suffix(&self) -> &'static str {
match self {
ResourceAction::Index | ResourceAction::Save => "",
ResourceAction::Create => "/create",
ResourceAction::Edit => "/<id>/edit",
ResourceAction::Read | ResourceAction::Update | ResourceAction::Delete => "/<id>",
}
}
pub fn controller_method(&self) -> &'static str {
self.as_str()
}
pub fn all() -> &'static [ResourceAction] {
&[
ResourceAction::Index,
ResourceAction::Create,
ResourceAction::Edit,
ResourceAction::Read,
ResourceAction::Save,
ResourceAction::Update,
ResourceAction::Delete,
]
}
pub fn parse_name(name: &str) -> Option<ResourceAction> {
match name {
"index" => Some(ResourceAction::Index),
"create" => Some(ResourceAction::Create),
"edit" => Some(ResourceAction::Edit),
"read" => Some(ResourceAction::Read),
"save" => Some(ResourceAction::Save),
"update" => Some(ResourceAction::Update),
"delete" => Some(ResourceAction::Delete),
_ => None,
}
}
}
impl std::str::FromStr for ResourceAction {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse_name(s).ok_or_else(|| format!("unknown ResourceAction: {}", s))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpMethod {
Get,
Post,
Put,
Delete,
Patch,
}
impl HttpMethod {
pub fn as_str(&self) -> &'static str {
match self {
HttpMethod::Get => "GET",
HttpMethod::Post => "POST",
HttpMethod::Put => "PUT",
HttpMethod::Delete => "DELETE",
HttpMethod::Patch => "PATCH",
}
}
}
impl fmt::Display for HttpMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResourceRouteEntry {
pub action: ResourceAction,
pub method: HttpMethod,
pub url: String,
pub controller_method: String,
}
#[derive(Debug, Clone, Default)]
pub struct ResourceOptions {
pub only: Vec<ResourceAction>,
pub except: Vec<ResourceAction>,
}
impl ResourceOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_only(mut self, only: Vec<ResourceAction>) -> Self {
self.only = only;
self
}
pub fn with_except(mut self, except: Vec<ResourceAction>) -> Self {
self.except = except;
self
}
pub fn should_include(&self, action: ResourceAction) -> bool {
if !self.only.is_empty() && !self.only.contains(&action) {
return false;
}
if self.except.contains(&action) {
return false;
}
true
}
}
#[derive(Debug, Clone)]
pub struct ResourceRoute {
pub addon: String,
pub controller: String,
pub base_url: String,
pub entries: Vec<ResourceRouteEntry>,
}
impl ResourceRoute {
pub fn get(&self, action: ResourceAction) -> Option<&ResourceRouteEntry> {
self.entries.iter().find(|e| e.action == action)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[tracing::instrument]
pub fn build_resource_routes(
addon: &str,
controller: &str,
options: &ResourceOptions,
) -> AddonLoaderResult<ResourceRoute> {
if addon.is_empty() {
return Err(AddonLoaderError::RouteParse {
url: String::new(),
reason: "addon cannot be empty for resource route".to_string(),
});
}
if controller.is_empty() {
return Err(AddonLoaderError::RouteParse {
url: String::new(),
reason: "controller cannot be empty for resource route".to_string(),
});
}
let base_url = build_resource_base_url(addon, controller);
let mut entries = Vec::new();
for &action in ResourceAction::all() {
if !options.should_include(action) {
continue;
}
let url = format!("{}{}", base_url, action.url_suffix());
entries.push(ResourceRouteEntry {
action,
method: action.http_method(),
url,
controller_method: action.controller_method().to_string(),
});
}
Ok(ResourceRoute {
addon: addon.to_string(),
controller: controller.to_string(),
base_url,
entries,
})
}
fn build_resource_base_url(addon: &str, controller: &str) -> String {
if !controller.contains('.') {
return format!("/addons/{}/{}", addon, controller);
}
let mut parts: Vec<&str> = controller.split('.').collect();
let last = parts.pop().unwrap_or(controller);
let mut segments: Vec<String> = Vec::new();
for val in &parts {
segments.push(format!("{}/<{}_id>", val, val));
}
segments.push(last.to_string());
format!("/addons/{}/{}", addon, segments.join("/"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resource_action_as_str() {
assert_eq!(ResourceAction::Index.as_str(), "index");
assert_eq!(ResourceAction::Create.as_str(), "create");
assert_eq!(ResourceAction::Edit.as_str(), "edit");
assert_eq!(ResourceAction::Read.as_str(), "read");
assert_eq!(ResourceAction::Save.as_str(), "save");
assert_eq!(ResourceAction::Update.as_str(), "update");
assert_eq!(ResourceAction::Delete.as_str(), "delete");
}
#[test]
fn test_resource_action_http_method() {
assert_eq!(ResourceAction::Index.http_method(), HttpMethod::Get);
assert_eq!(ResourceAction::Create.http_method(), HttpMethod::Get);
assert_eq!(ResourceAction::Edit.http_method(), HttpMethod::Get);
assert_eq!(ResourceAction::Read.http_method(), HttpMethod::Get);
assert_eq!(ResourceAction::Save.http_method(), HttpMethod::Post);
assert_eq!(ResourceAction::Update.http_method(), HttpMethod::Put);
assert_eq!(ResourceAction::Delete.http_method(), HttpMethod::Delete);
}
#[test]
fn test_resource_action_url_suffix() {
assert_eq!(ResourceAction::Index.url_suffix(), "");
assert_eq!(ResourceAction::Create.url_suffix(), "/create");
assert_eq!(ResourceAction::Edit.url_suffix(), "/<id>/edit");
assert_eq!(ResourceAction::Read.url_suffix(), "/<id>");
assert_eq!(ResourceAction::Save.url_suffix(), "");
assert_eq!(ResourceAction::Update.url_suffix(), "/<id>");
assert_eq!(ResourceAction::Delete.url_suffix(), "/<id>");
}
#[test]
fn test_resource_action_controller_method() {
assert_eq!(ResourceAction::Index.controller_method(), "index");
assert_eq!(ResourceAction::Save.controller_method(), "save");
assert_eq!(ResourceAction::Delete.controller_method(), "delete");
}
#[test]
fn test_resource_action_all_returns_seven() {
let all = ResourceAction::all();
assert_eq!(all.len(), 7);
assert_eq!(all[0], ResourceAction::Index);
assert_eq!(all[1], ResourceAction::Create);
assert_eq!(all[2], ResourceAction::Edit);
assert_eq!(all[3], ResourceAction::Read);
assert_eq!(all[4], ResourceAction::Save);
assert_eq!(all[5], ResourceAction::Update);
assert_eq!(all[6], ResourceAction::Delete);
}
#[test]
fn test_resource_action_parse_name_valid() {
assert_eq!(
ResourceAction::parse_name("index"),
Some(ResourceAction::Index)
);
assert_eq!(
ResourceAction::parse_name("create"),
Some(ResourceAction::Create)
);
assert_eq!(
ResourceAction::parse_name("edit"),
Some(ResourceAction::Edit)
);
assert_eq!(
ResourceAction::parse_name("read"),
Some(ResourceAction::Read)
);
assert_eq!(
ResourceAction::parse_name("save"),
Some(ResourceAction::Save)
);
assert_eq!(
ResourceAction::parse_name("update"),
Some(ResourceAction::Update)
);
assert_eq!(
ResourceAction::parse_name("delete"),
Some(ResourceAction::Delete)
);
}
#[test]
fn test_resource_action_parse_name_invalid() {
assert_eq!(ResourceAction::parse_name("unknown"), None);
assert_eq!(ResourceAction::parse_name(""), None);
assert_eq!(ResourceAction::parse_name("INDEX"), None);
}
#[test]
fn test_resource_action_from_str_trait() {
use std::str::FromStr;
assert_eq!(ResourceAction::from_str("index"), Ok(ResourceAction::Index));
assert_eq!(
ResourceAction::from_str("delete"),
Ok(ResourceAction::Delete)
);
assert!(ResourceAction::from_str("unknown").is_err());
assert!(ResourceAction::from_str("").is_err());
}
#[test]
fn test_http_method_as_str() {
assert_eq!(HttpMethod::Get.as_str(), "GET");
assert_eq!(HttpMethod::Post.as_str(), "POST");
assert_eq!(HttpMethod::Put.as_str(), "PUT");
assert_eq!(HttpMethod::Delete.as_str(), "DELETE");
assert_eq!(HttpMethod::Patch.as_str(), "PATCH");
}
#[test]
fn test_http_method_display() {
assert_eq!(format!("{}", HttpMethod::Get), "GET");
assert_eq!(format!("{}", HttpMethod::Post), "POST");
assert_eq!(format!("{}", HttpMethod::Put), "PUT");
assert_eq!(format!("{}", HttpMethod::Delete), "DELETE");
assert_eq!(format!("{}", HttpMethod::Patch), "PATCH");
}
#[test]
fn test_resource_options_new() {
let opts = ResourceOptions::new();
assert!(opts.only.is_empty());
assert!(opts.except.is_empty());
}
#[test]
fn test_resource_options_with_only() {
let opts =
ResourceOptions::new().with_only(vec![ResourceAction::Index, ResourceAction::Read]);
assert_eq!(opts.only.len(), 2);
assert!(opts.except.is_empty());
}
#[test]
fn test_resource_options_with_except() {
let opts = ResourceOptions::new().with_except(vec![ResourceAction::Delete]);
assert!(opts.only.is_empty());
assert_eq!(opts.except.len(), 1);
}
#[test]
fn test_resource_options_with_only_and_except() {
let opts = ResourceOptions::new()
.with_only(vec![
ResourceAction::Index,
ResourceAction::Read,
ResourceAction::Delete,
])
.with_except(vec![ResourceAction::Delete]);
assert_eq!(opts.only.len(), 3);
assert_eq!(opts.except.len(), 1);
}
#[test]
fn test_should_include_no_filter() {
let opts = ResourceOptions::new();
for &action in ResourceAction::all() {
assert!(
opts.should_include(action),
"{:?} should be included",
action
);
}
}
#[test]
fn test_should_include_only_filter() {
let opts =
ResourceOptions::new().with_only(vec![ResourceAction::Index, ResourceAction::Read]);
assert!(opts.should_include(ResourceAction::Index));
assert!(opts.should_include(ResourceAction::Read));
assert!(!opts.should_include(ResourceAction::Create));
assert!(!opts.should_include(ResourceAction::Edit));
assert!(!opts.should_include(ResourceAction::Save));
assert!(!opts.should_include(ResourceAction::Update));
assert!(!opts.should_include(ResourceAction::Delete));
}
#[test]
fn test_should_include_except_filter() {
let opts = ResourceOptions::new()
.with_except(vec![ResourceAction::Delete, ResourceAction::Create]);
assert!(opts.should_include(ResourceAction::Index));
assert!(!opts.should_include(ResourceAction::Create));
assert!(opts.should_include(ResourceAction::Edit));
assert!(opts.should_include(ResourceAction::Read));
assert!(opts.should_include(ResourceAction::Save));
assert!(opts.should_include(ResourceAction::Update));
assert!(!opts.should_include(ResourceAction::Delete));
}
#[test]
fn test_should_include_only_and_except_combined() {
let opts = ResourceOptions::new()
.with_only(vec![
ResourceAction::Index,
ResourceAction::Read,
ResourceAction::Delete,
])
.with_except(vec![ResourceAction::Delete]);
assert!(opts.should_include(ResourceAction::Index));
assert!(opts.should_include(ResourceAction::Read));
assert!(!opts.should_include(ResourceAction::Delete));
assert!(!opts.should_include(ResourceAction::Create));
}
#[test]
fn test_should_include_empty_only_includes_all() {
let opts = ResourceOptions::new().with_only(vec![]);
for &action in ResourceAction::all() {
assert!(opts.should_include(action));
}
}
#[test]
fn test_build_resource_base_url_simple() {
let url = build_resource_base_url("operate", "Blog");
assert_eq!(url, "/addons/operate/Blog");
}
#[test]
fn test_build_resource_base_url_nested_two_levels() {
let url = build_resource_base_url("operate", "blog.comment");
assert_eq!(url, "/addons/operate/blog/<blog_id>/comment");
}
#[test]
fn test_build_resource_base_url_nested_three_levels() {
let url = build_resource_base_url("operate", "blog.post.comment");
assert_eq!(url, "/addons/operate/blog/<blog_id>/post/<post_id>/comment");
}
#[test]
fn test_build_resource_base_url_nested_four_levels() {
let url = build_resource_base_url("operate", "a.b.c.d");
assert_eq!(url, "/addons/operate/a/<a_id>/b/<b_id>/c/<c_id>/d");
}
#[test]
fn test_build_resource_routes_default_seven() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
assert_eq!(route.addon, "operate");
assert_eq!(route.controller, "Blog");
assert_eq!(route.base_url, "/addons/operate/Blog");
assert_eq!(route.len(), 7);
assert!(!route.is_empty());
}
#[test]
fn test_build_resource_routes_index_entry() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let entry = route.get(ResourceAction::Index).unwrap();
assert_eq!(entry.action, ResourceAction::Index);
assert_eq!(entry.method, HttpMethod::Get);
assert_eq!(entry.url, "/addons/operate/Blog");
assert_eq!(entry.controller_method, "index");
}
#[test]
fn test_build_resource_routes_create_entry() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let entry = route.get(ResourceAction::Create).unwrap();
assert_eq!(entry.action, ResourceAction::Create);
assert_eq!(entry.method, HttpMethod::Get);
assert_eq!(entry.url, "/addons/operate/Blog/create");
assert_eq!(entry.controller_method, "create");
}
#[test]
fn test_build_resource_routes_edit_entry() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let entry = route.get(ResourceAction::Edit).unwrap();
assert_eq!(entry.action, ResourceAction::Edit);
assert_eq!(entry.method, HttpMethod::Get);
assert_eq!(entry.url, "/addons/operate/Blog/<id>/edit");
assert_eq!(entry.controller_method, "edit");
}
#[test]
fn test_build_resource_routes_read_entry() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let entry = route.get(ResourceAction::Read).unwrap();
assert_eq!(entry.action, ResourceAction::Read);
assert_eq!(entry.method, HttpMethod::Get);
assert_eq!(entry.url, "/addons/operate/Blog/<id>");
assert_eq!(entry.controller_method, "read");
}
#[test]
fn test_build_resource_routes_save_entry() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let entry = route.get(ResourceAction::Save).unwrap();
assert_eq!(entry.action, ResourceAction::Save);
assert_eq!(entry.method, HttpMethod::Post);
assert_eq!(entry.url, "/addons/operate/Blog");
assert_eq!(entry.controller_method, "save");
}
#[test]
fn test_build_resource_routes_update_entry() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let entry = route.get(ResourceAction::Update).unwrap();
assert_eq!(entry.action, ResourceAction::Update);
assert_eq!(entry.method, HttpMethod::Put);
assert_eq!(entry.url, "/addons/operate/Blog/<id>");
assert_eq!(entry.controller_method, "update");
}
#[test]
fn test_build_resource_routes_delete_entry() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let entry = route.get(ResourceAction::Delete).unwrap();
assert_eq!(entry.action, ResourceAction::Delete);
assert_eq!(entry.method, HttpMethod::Delete);
assert_eq!(entry.url, "/addons/operate/Blog/<id>");
assert_eq!(entry.controller_method, "delete");
}
#[test]
fn test_build_resource_routes_all_urls() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let urls: Vec<&str> = route.entries.iter().map(|e| e.url.as_str()).collect();
assert_eq!(
urls,
vec![
"/addons/operate/Blog",
"/addons/operate/Blog/create",
"/addons/operate/Blog/<id>/edit",
"/addons/operate/Blog/<id>",
"/addons/operate/Blog",
"/addons/operate/Blog/<id>",
"/addons/operate/Blog/<id>",
]
);
}
#[test]
fn test_build_resource_routes_all_methods() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let methods: Vec<HttpMethod> = route.entries.iter().map(|e| e.method).collect();
assert_eq!(
methods,
vec![
HttpMethod::Get,
HttpMethod::Get,
HttpMethod::Get,
HttpMethod::Get,
HttpMethod::Post,
HttpMethod::Put,
HttpMethod::Delete,
]
);
}
#[test]
fn test_build_resource_routes_only_index_read() {
let opts =
ResourceOptions::new().with_only(vec![ResourceAction::Index, ResourceAction::Read]);
let route = build_resource_routes("operate", "Blog", &opts).unwrap();
assert_eq!(route.len(), 2);
assert!(route.get(ResourceAction::Index).is_some());
assert!(route.get(ResourceAction::Read).is_some());
assert!(route.get(ResourceAction::Create).is_none());
assert!(route.get(ResourceAction::Edit).is_none());
assert!(route.get(ResourceAction::Save).is_none());
assert!(route.get(ResourceAction::Update).is_none());
assert!(route.get(ResourceAction::Delete).is_none());
}
#[test]
fn test_build_resource_routes_except_delete() {
let opts = ResourceOptions::new().with_except(vec![ResourceAction::Delete]);
let route = build_resource_routes("operate", "Blog", &opts).unwrap();
assert_eq!(route.len(), 6);
assert!(route.get(ResourceAction::Delete).is_none());
assert!(route.get(ResourceAction::Index).is_some());
}
#[test]
fn test_build_resource_routes_only_create_save() {
let opts =
ResourceOptions::new().with_only(vec![ResourceAction::Create, ResourceAction::Save]);
let route = build_resource_routes("operate", "Blog", &opts).unwrap();
assert_eq!(route.len(), 2);
assert_eq!(route.entries[0].action, ResourceAction::Create);
assert_eq!(route.entries[1].action, ResourceAction::Save);
}
#[test]
fn test_build_resource_routes_except_all() {
let opts = ResourceOptions::new().with_except(ResourceAction::all().to_vec());
let route = build_resource_routes("operate", "Blog", &opts).unwrap();
assert_eq!(route.len(), 0);
assert!(route.is_empty());
}
#[test]
fn test_build_resource_routes_only_empty() {
let opts = ResourceOptions::new().with_only(vec![]);
let route = build_resource_routes("operate", "Blog", &opts).unwrap();
assert_eq!(route.len(), 7);
}
#[test]
fn test_build_resource_routes_nested_resource() {
let route =
build_resource_routes("operate", "blog.comment", &ResourceOptions::new()).unwrap();
assert_eq!(route.base_url, "/addons/operate/blog/<blog_id>/comment");
assert_eq!(route.len(), 7);
let index = route.get(ResourceAction::Index).unwrap();
assert_eq!(index.url, "/addons/operate/blog/<blog_id>/comment");
let read = route.get(ResourceAction::Read).unwrap();
assert_eq!(read.url, "/addons/operate/blog/<blog_id>/comment/<id>");
let create = route.get(ResourceAction::Create).unwrap();
assert_eq!(create.url, "/addons/operate/blog/<blog_id>/comment/create");
let edit = route.get(ResourceAction::Edit).unwrap();
assert_eq!(edit.url, "/addons/operate/blog/<blog_id>/comment/<id>/edit");
}
#[test]
fn test_build_resource_routes_nested_three_levels() {
let route =
build_resource_routes("operate", "blog.post.comment", &ResourceOptions::new()).unwrap();
assert_eq!(
route.base_url,
"/addons/operate/blog/<blog_id>/post/<post_id>/comment"
);
let read = route.get(ResourceAction::Read).unwrap();
assert_eq!(
read.url,
"/addons/operate/blog/<blog_id>/post/<post_id>/comment/<id>"
);
}
#[test]
fn test_build_resource_routes_different_addon() {
let route = build_resource_routes("cashier", "Order", &ResourceOptions::new()).unwrap();
assert_eq!(route.base_url, "/addons/cashier/Order");
assert_eq!(route.addon, "cashier");
assert_eq!(route.controller, "Order");
}
#[test]
fn test_build_resource_routes_empty_addon() {
let result = build_resource_routes("", "Blog", &ResourceOptions::new());
assert!(result.is_err());
match result {
Err(AddonLoaderError::RouteParse { reason, .. }) => {
assert!(reason.contains("addon cannot be empty"));
}
_ => panic!("expected RouteParse error"),
}
}
#[test]
fn test_build_resource_routes_empty_controller() {
let result = build_resource_routes("operate", "", &ResourceOptions::new());
assert!(result.is_err());
match result {
Err(AddonLoaderError::RouteParse { reason, .. }) => {
assert!(reason.contains("controller cannot be empty"));
}
_ => panic!("expected RouteParse error"),
}
}
#[test]
fn test_build_resource_routes_get_returns_none_for_excluded() {
let opts = ResourceOptions::new().with_only(vec![ResourceAction::Index]);
let route = build_resource_routes("operate", "Blog", &opts).unwrap();
assert!(route.get(ResourceAction::Index).is_some());
assert!(route.get(ResourceAction::Read).is_none());
}
#[test]
fn test_build_resource_routes_entries_order() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let actions: Vec<ResourceAction> = route.entries.iter().map(|e| e.action).collect();
assert_eq!(
actions,
vec![
ResourceAction::Index,
ResourceAction::Create,
ResourceAction::Edit,
ResourceAction::Read,
ResourceAction::Save,
ResourceAction::Update,
ResourceAction::Delete,
]
);
}
#[test]
fn test_resource_route_len() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
assert_eq!(route.len(), 7);
}
#[test]
fn test_resource_route_is_empty_false() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
assert!(!route.is_empty());
}
#[test]
fn test_resource_route_is_empty_true() {
let opts = ResourceOptions::new().with_except(ResourceAction::all().to_vec());
let route = build_resource_routes("operate", "Blog", &opts).unwrap();
assert!(route.is_empty());
}
#[test]
fn test_resource_route_get_existing() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let entry = route.get(ResourceAction::Save).unwrap();
assert_eq!(entry.method, HttpMethod::Post);
}
#[test]
fn test_resource_route_get_nonexistent() {
let opts = ResourceOptions::new().with_only(vec![ResourceAction::Index]);
let route = build_resource_routes("operate", "Blog", &opts).unwrap();
assert!(route.get(ResourceAction::Save).is_none());
}
#[test]
fn test_r5_thinkphp_rest_default_mapping() {
let expected: &[(ResourceAction, HttpMethod, &str, &str)] = &[
(ResourceAction::Index, HttpMethod::Get, "", "index"),
(ResourceAction::Create, HttpMethod::Get, "/create", "create"),
(ResourceAction::Edit, HttpMethod::Get, "/<id>/edit", "edit"),
(ResourceAction::Read, HttpMethod::Get, "/<id>", "read"),
(ResourceAction::Save, HttpMethod::Post, "", "save"),
(ResourceAction::Update, HttpMethod::Put, "/<id>", "update"),
(
ResourceAction::Delete,
HttpMethod::Delete,
"/<id>",
"delete",
),
];
for &(action, method, suffix, ctrl_method) in expected {
assert_eq!(
action.http_method(),
method,
"{:?} HTTP method mismatch",
action
);
assert_eq!(
action.url_suffix(),
suffix,
"{:?} URL suffix mismatch",
action
);
assert_eq!(
action.controller_method(),
ctrl_method,
"{:?} controller method mismatch",
action
);
}
}
#[test]
fn test_r5_thinkphp_resource_seven_routes() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
assert_eq!(route.len(), 7, "ThinkPHP 6 resource must generate 7 routes");
}
#[test]
fn test_r5_thinkphp_only_filter() {
let opts =
ResourceOptions::new().with_only(vec![ResourceAction::Index, ResourceAction::Read]);
let route = build_resource_routes("operate", "Blog", &opts).unwrap();
assert_eq!(route.len(), 2);
}
#[test]
fn test_r5_thinkphp_except_filter() {
let opts = ResourceOptions::new().with_except(vec![ResourceAction::Delete]);
let route = build_resource_routes("operate", "Blog", &opts).unwrap();
assert_eq!(route.len(), 6);
}
#[test]
fn test_r5_thinkphp_nested_resource() {
let route =
build_resource_routes("operate", "blog.comment", &ResourceOptions::new()).unwrap();
assert_eq!(route.base_url, "/addons/operate/blog/<blog_id>/comment");
}
#[test]
fn test_r5_thinkphp_no_add_action() {
assert!(ResourceAction::parse_name("add").is_none());
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
assert!(route.get(ResourceAction::Save).is_some());
assert_eq!(
route.get(ResourceAction::Save).unwrap().method,
HttpMethod::Post
);
}
#[test]
fn test_r5_thinkphp_update_uses_put_not_patch() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let update = route.get(ResourceAction::Update).unwrap();
assert_eq!(update.method, HttpMethod::Put);
}
#[test]
fn test_r5_thinkphp_index_and_save_share_url() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let index = route.get(ResourceAction::Index).unwrap();
let save = route.get(ResourceAction::Save).unwrap();
assert_eq!(index.url, save.url);
assert_ne!(index.method, save.method);
}
#[test]
fn test_r5_thinkphp_read_update_delete_share_url() {
let route = build_resource_routes("operate", "Blog", &ResourceOptions::new()).unwrap();
let read = route.get(ResourceAction::Read).unwrap();
let update = route.get(ResourceAction::Update).unwrap();
let delete = route.get(ResourceAction::Delete).unwrap();
assert_eq!(read.url, update.url);
assert_eq!(read.url, delete.url);
assert_ne!(read.method, update.method);
assert_ne!(read.method, delete.method);
assert_ne!(update.method, delete.method);
}
#[test]
fn test_r5_addon_prefix_injection() {
let route = build_resource_routes("cashier", "Order", &ResourceOptions::new()).unwrap();
assert!(route.base_url.starts_with("/addons/cashier/"));
for entry in &route.entries {
assert!(entry.url.starts_with("/addons/cashier/Order"));
}
}
#[test]
fn test_r5_addon_prefix_injection_nested() {
let route =
build_resource_routes("operate", "blog.comment", &ResourceOptions::new()).unwrap();
assert!(route.base_url.starts_with("/addons/operate/"));
for entry in &route.entries {
assert!(entry.url.starts_with("/addons/operate/blog/"));
}
}
}