use crate::request::{ContentType, Method, Request};
use crate::{CONFIG, GLOBAL_DATA, PLUGIN_TOOLS};
use crate::{ApiResponse, Tools};
use br_fields::Field;
use json::{object, JsonValue};
use std::any::type_name;
use crate::tables::Tables;
pub trait Action {
fn _name(&self) -> String {
type_name::<Self>().rsplit("::").next().unwrap().to_lowercase()
}
fn module_name(&self) -> String {
let t = type_name::<Self>().split("::").collect::<Vec<&str>>();
let plugin = t[2].to_lowercase();
let module = t[3].to_lowercase();
format!("{plugin}.{module}")
}
fn addon_name(&self) -> String {
let t = type_name::<Self>().split("::").collect::<Vec<&str>>();
t[2].to_lowercase()
}
fn api(&self) -> String {
let t = type_name::<Self>().split("::").collect::<Vec<&str>>();
let plugin = t[2].to_lowercase();
let module = t[3].to_lowercase();
let action = t[4].to_lowercase();
format!("{plugin}.{module}.{action}")
}
fn token(&self) -> bool {
true
}
fn sort(&self) -> usize {
99
}
fn title(&self) -> &'static str;
fn description(&self) -> &'static str {
""
}
fn path(&self) -> &'static str {
""
}
fn query(&self) -> JsonValue {
object! {}
}
fn tags(&self) -> &'static [&'static str] {
&[]
}
fn icon(&self) -> &'static str {
""
}
fn public(&self) -> bool {
true
}
fn auth(&self) -> bool {
true
}
fn interface_type(&self) -> InterfaceType {
InterfaceType::API
}
fn method(&mut self) -> Method {
Method::Post
}
fn content_type(&mut self) -> ContentType {
ContentType::Json
}
fn params_check(&mut self) -> bool {
true
}
fn params(&mut self) -> JsonValue {
object! {}
}
fn success(&mut self) -> ApiResponse {
let mut data = object! {};
data["code"] = br_fields::int::Int::new(true, "code", "编号", 10, 0).example(0.into()).swagger();
data["message"] = br_fields::str::Str::new(true, "message", "成功消息", 256, "").example("成功".into()).swagger();
data["data"] = br_fields::text::Json::new(true, "data", "返回数据", object! {}).swagger();
data["success"] = br_fields::int::Switch::new(true, "success", "成功状态", true).example(true.into()).swagger();
data["timestamp"] = br_fields::datetime::Timestamp::new(true, "timestamp", "时间戳", 0, 0.0).swagger();
ApiResponse::success(data, "请求成功")
}
fn error(&mut self) -> ApiResponse {
let mut data = object! {};
data["code"] = br_fields::int::Int::new(true, "code", "编号", 10, 1000).example(1000.into()).swagger();
data["message"] = br_fields::str::Str::new(true, "message", "错误消息", 256, "").example("失败".into()).swagger();
data["data"] = br_fields::text::Json::new(true, "data", "返回数据", object! {}).swagger();
data["success"] = br_fields::int::Switch::new(true, "success", "成功状态", false).example(false.into()).swagger();
data["timestamp"] = br_fields::datetime::Timestamp::new(true, "timestamp", "时间戳", 0, 0.0).swagger();
ApiResponse::error(data, "请求失败")
}
fn run(&mut self, mut request: Request) -> Result<ApiResponse, ApiResponse> {
if self.public() && !self.method().str().is_empty() && self.method().str().to_lowercase() != request.method.str().to_lowercase() {
return Err(ApiResponse::fail(
-1,
format!(
"Request type error: Actual [{}] Expected [{}]",
request.method.str(),
self.method().str()
).as_str(),
));
}
let params = self.params().clone();
if self.params_check() {
self.check(&mut request.query.clone(), self.query().clone())?;
self.check(&mut request.body, params)?;
}
let res = self.index(request.clone());
match res.success {
true => Ok(res),
false => Err(res),
}
}
fn check(&mut self, request: &mut JsonValue, params: JsonValue) -> Result<(), ApiResponse> {
let req = request.clone();
for (name, _) in req.entries() {
if !params.has_key(name) {
request.remove(name);
}
}
for (name, field) in params.entries() {
let require = field["require"].as_bool().unwrap_or(false);
let title = field["title"].as_str().unwrap_or("");
if request.has_key(name) {
match field["mode"].as_str().unwrap() {
"key" => {
if !request[name].is_string() {
return Err(ApiResponse::fail(
900_001,
format!(
"请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
name, field["mode"]
).as_str(),
));
}
if require && request[name].is_empty() {
return Err(ApiResponse::fail(
900014,
format!("请求参数数据类型错误: 参数 [{name}] 不能为空").as_str(),
));
}
}
"text" | "table" | "tree" => {
if !request[name].is_string() {
return Err(ApiResponse::fail(
900002,
format!(
"请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
name, field["mode"]
).as_str(),
));
}
if require && request[name].is_empty() {
return Err(ApiResponse::fail(
900002,
format!("{title} 必填").as_str(),
));
}
}
"file" => {
if !request[name].is_array() && !request[name].is_string() {
return Err(ApiResponse::fail(
900003,
format!("参数 [{}] 数据类型应为[{}]", name, field["mode"]).as_str(),
));
}
if require && request[name].is_empty() {
return Err(ApiResponse::fail(
900002,
format!("{title} 必填").as_str(),
));
}
}
"int" => {
if require && request[name].to_string().is_empty() {
return Err(ApiResponse::fail(
900_002,
format!("{title} 必填").as_str(),
));
}
if !request[name].to_string().is_empty() {
match request[name].to_string().parse::<i64>() {
Ok(_) => {}
Err(_) => {
return Err(ApiResponse::fail(
900013,
format!(
"请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
name, field["mode"]
).as_str(),
));
}
}
}
}
"timestamp" => {
if require && request[name].to_string().is_empty() {
return Err(ApiResponse::fail(
900002,
format!("{title} 必填").as_str(),
));
}
if !request[name].to_string().is_empty() {
if request[name].is_array() && request[name].len() == 2 {
for item in request[name].members() {
match item.to_string().parse::<f64>() {
Ok(_) => {}
Err(_) => {
return Err(ApiResponse::fail(
900_013,
format!(
"请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
name, field["mode"]
).as_str(),
));
}
}
}
} else {
match request[name].to_string().parse::<f64>() {
Ok(_) => {}
Err(_) => {
return Err(ApiResponse::fail(
900013,
format!(
"请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
name, field["mode"]
).as_str(),
));
}
}
}
}
}
"float" => {
if require && request[name].to_string().is_empty() {
return Err(ApiResponse::fail(
900002,
format!("{title} 必填").as_str(),
));
}
if !request[name].to_string().is_empty() {
match request[name].to_string().parse::<f64>() {
Ok(_) => {}
Err(_) => {
return Err(ApiResponse::fail(
900023,
format!(
"请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
name, field["mode"]
).as_str(),
));
}
}
}
}
"string" | "url" | "time" | "code" | "pass" | "email" | "location" | "color" | "date" | "barcode" | "datetime" | "editor" | "tel" => {
if !request[name].is_string() {
return Err(ApiResponse::fail(
-1,
format!(
"请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
name, field["mode"]
).as_str(),
));
}
if require && request[name].is_empty() {
return Err(ApiResponse::fail(
900004,
format!("{title} 必填").as_str(),
));
}
}
"dict" => {
if require && request[name].is_empty() {
return Err(ApiResponse::fail(
900005,
format!("{title} 必填").as_str(),
));
}
}
"switch" => {
match request[name].to_string().parse::<bool>() {
Ok(e) => {
request[name] = e.into();
}
Err(_) => {
return Err(ApiResponse::fail(
-1,
format!("请求参数数据类型错误: 参数 [{name}] 数据类型应为[{}]", field["mode"]).as_str(),
));
}
}
}
"select" => {
if !request[name].is_array() && !request[name].is_string() {
return Err(ApiResponse::fail(
-1,
format!(
"请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
name, field["mode"]
).as_str(),
));
}
let value = if request[name].is_array() {
request[name].members().map(|m| m.to_string()).collect::<Vec<String>>()
} else {
request[name].to_string().split(",").map(|x| x.to_string()).collect::<Vec<String>>()
};
let option = field["option"].members().map(|m| m.as_str().unwrap_or("")).collect::<Vec<&str>>();
let require = field["require"].as_bool().unwrap();
for item in value.clone() {
if !option.contains(&&*item.clone()) && (item.is_empty() && require) {
let option = field["option"].members().map(|m| m.as_str().unwrap_or("")).collect::<Vec<&str>>().join(",");
return Err(ApiResponse::fail(-1, format!("请求参数选项错误: 参数 [{item}] 数据类型应为[{option}]之内").as_str()));
}
}
request[name] = value.into();
}
"radio" => {
if !request[name].is_string() {
return Err(ApiResponse::fail(
-1,
format!(
"请求参数数据类型错误: 参数 [{}] 数据类型应为[{}] 实际为[{}]",
name, field["mode"], request[name]
).as_str(),
));
}
let option = field["option"].members().map(|m| m.as_str().unwrap_or("")).collect::<Vec<&str>>().join(",");
if request[name].is_string() && !field["option"].contains(request[name].as_str().unwrap_or(""))
{
return Err(ApiResponse::fail(
-1,
format!(
"请求参数选项错误: 参数 [{}] 数据 [{}] 应为 [{}] 之一",
name, request[name], option
).as_str(),
));
}
}
"array" | "polygon" => {
if !request[name].is_array() {
return Err(ApiResponse::fail(
900_009,
format!(
"请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
name, field["mode"]
).as_str(),
));
}
if require && request[name].is_empty() {
return Err(ApiResponse::fail(
900010,
format!("请求参数数据类型错误: 参数 [{name}] 不能为空").as_str(),
));
}
}
"object" => {
if !request[name].is_object() {
return Err(ApiResponse::fail(
900009,
format!(
"请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
name, field["mode"]
).as_str(),
));
}
if require && request[name].is_empty() {
return Err(ApiResponse::fail(
900006,
format!("{title} 必填").as_str(),
));
}
}
_ => {
println!("检查未知类型: {}", field["mode"])
}
}
} else {
if require {
return Err(ApiResponse::fail(
900007,
format!("{title} 必填").as_str(),
));
}
request[name] = field["def"].clone();
}
}
Ok(())
}
fn index(&mut self, request: Request) -> ApiResponse;
#[cfg(any(feature = "sqlite", feature = "mssql", feature = "mysql", feature = "pgsql"))]
fn table_select(
&mut self,
request: JsonValue,
table_name: &str,
fields: Vec<&str>,
) -> JsonValue {
self.table().main_select_fields(table_name, fields).params(request.clone()).get_table_select()
}
#[allow(clippy::too_many_arguments)]
#[cfg(any(feature = "sqlite", feature = "mssql", feature = "mysql", feature = "pgsql"))]
fn table_list(
&mut self,
request: JsonValue,
table_name: &str,
fields: JsonValue,
hidd_field: Vec<&str>,
show_field: Vec<&str>,
search_fields: Vec<&str>,
filter_fields: Vec<&str>,
) -> JsonValue {
self.table().main_table_fields(table_name, fields, hidd_field, show_field).search_fields(search_fields).filter_fields(filter_fields).params(request).get_table()
}
#[allow(clippy::too_many_arguments)]
#[cfg(any(feature = "sqlite", feature = "mssql", feature = "mysql", feature = "pgsql"))]
fn table(&mut self) -> Tables {
Tables::new(self.tools().db.clone())
}
fn tools(&mut self) -> Tools {
let tools = PLUGIN_TOOLS.lock().unwrap();
let tools = tools.get("tools").unwrap().clone();
tools
}
fn config(&mut self, name: &str) -> JsonValue {
if CONFIG.lock().unwrap().get(name).is_none() {
object! {}
} else {
CONFIG.lock().unwrap().get(name).unwrap().clone()
}
}
fn btn(&mut self) -> Btn {
let mut btn = Btn::new(self.api().as_str());
btn.fields(self.params().clone());
btn.tags(self.tags());
btn.icon(self.icon());
btn.desc(self.description());
btn.auth(self.auth());
btn.public(self.public());
btn.title(self.title());
btn.btn_type(BtnType::Api);
btn.btn_color(BtnColor::Primary);
btn.path(self.api().clone().replace(".", "/").as_str());
btn.pass(false);
btn.addon();
btn
}
fn set_global_data(&mut self, key: &str, value: JsonValue) {
GLOBAL_DATA.with(|data| {
data.borrow_mut()[key] = value;
});
}
fn get_global_data(&mut self) -> JsonValue {
GLOBAL_DATA.with(|data| {
data.borrow().clone()
})
}
fn get_global_data_key(&mut self, key: &str) -> JsonValue {
GLOBAL_DATA.with(|data| {
data.borrow()[key].clone()
})
}
}
#[derive(Debug, Clone)]
pub struct Btn {
api: String,
title: String,
desc: String,
tags: &'static [&'static str],
auth: bool,
public: bool,
btn_type: BtnType,
color: BtnColor,
icon: String,
cnd: Vec<JsonValue>,
url: String,
path: String,
fields: JsonValue,
addon: String,
version: usize,
pass: bool,
}
impl Btn {
pub fn new(api: &str) -> Self {
Self {
api: api.to_string(),
title: "".to_string(),
desc: "".to_string(),
btn_type: BtnType::Api,
color: BtnColor::Primary,
icon: "".to_string(),
auth: false,
public: false,
cnd: vec![],
url: "".to_string(),
path: "".to_string(),
fields: object! {},
tags: &[],
pass: false,
addon: "".to_string(),
version: 0,
}
}
pub fn addon(&mut self) -> &mut Self {
self.addon = self.api.split('.').nth(0).unwrap().to_string();
self
}
pub fn path(&mut self, path: &str) -> &mut Self {
self.path = path.to_string();
self
}
pub fn cnd(&mut self, cnd: Vec<JsonValue>) -> &mut Self {
self.cnd = cnd;
self
}
pub fn version(&mut self, version: usize) -> &mut Self {
self.version = version;
self
}
pub fn btn_type(&mut self, btn_type: BtnType) -> &mut Self {
self.btn_type = btn_type;
self
}
pub fn btn_color(&mut self, btn_color: BtnColor) -> &mut Self {
self.color = btn_color;
self
}
pub fn fields(&mut self, fields: JsonValue) -> &mut Self {
self.fields = fields;
self
}
pub fn pass(&mut self, pass: bool) -> &mut Self {
self.pass = pass;
self
}
pub fn url(&mut self, url: &str) -> &mut Self {
self.url = url.to_string();
self
}
pub fn title(&mut self, title: &str) -> &mut Self {
self.title = title.to_string();
self
}
pub fn desc(&mut self, desc: &str) -> &mut Self {
self.desc = desc.to_string();
self
}
pub fn tags(&mut self, tags: &'static [&'static str]) -> &mut Self {
self.tags = tags;
self
}
pub fn public(&mut self, public: bool) -> &mut Self {
self.public = public;
self
}
pub fn auth(&mut self, auth: bool) -> &mut Self {
self.auth = auth;
self
}
pub fn icon(&mut self, icon: &str) -> &mut Self {
self.icon = icon.to_string();
self
}
pub fn json(&mut self) -> JsonValue {
let color = match self.version {
0 => self.color.clone().str(),
_ => self.color.clone().str_v_1()
};
object! {
addon:self.addon.to_string() ,
api:self.api.clone(),
title:self.title.clone(),
desc:self.desc.clone(),
auth:self.auth,
public:self.public,
btn_type:self.btn_type.clone().str(),
color:color,
icon:self.icon.clone(),
cnd:self.cnd.clone(),
url:self.url.clone(),
path:self.path.clone(),
fields:self.fields.clone(),
tags:self.tags,
pass:self.pass,
}
}
}
#[derive(Debug, Clone)]
pub enum InterfaceType {
API,
BTN,
MENU,
OPENAPI,
}
impl InterfaceType {
pub fn str(&self) -> &'static str {
match self {
InterfaceType::API => "api",
InterfaceType::BTN => "btn",
InterfaceType::MENU => "menu",
InterfaceType::OPENAPI => "openapi",
}
}
pub fn types() -> Vec<&'static str> {
vec!["api", "btn", "menu", "openapi"]
}
}
#[derive(Debug, Clone)]
pub enum BtnType {
Form,
FormDownload,
FormCustom,
FormData,
Url,
Api,
Download,
Path,
DialogCustom,
FormApiDialogCustom,
Preview,
}
impl BtnType {
fn str(self) -> &'static str {
match self {
BtnType::Form => "form",
BtnType::FormDownload => "form_download",
BtnType::FormCustom => "form_custom",
BtnType::FormData => "form_data",
BtnType::Api => "api",
BtnType::Download => "download",
BtnType::Url => "url",
BtnType::Path => "path",
BtnType::DialogCustom => "dialog_custom",
BtnType::FormApiDialogCustom => "form_api_dialog_custom",
BtnType::Preview => "preview",
}
}
}
#[derive(Debug, Clone)]
pub enum BtnColor {
Primary,
Red,
Yellow,
Green,
}
impl BtnColor {
fn str(self) -> &'static str {
match self {
BtnColor::Primary => "primary",
BtnColor::Red => "negative",
BtnColor::Yellow => "warning",
BtnColor::Green => "positive",
}
}
fn str_v_1(self) -> &'static str {
match self {
BtnColor::Primary => "normal",
BtnColor::Red => "danger",
BtnColor::Yellow => "warning",
BtnColor::Green => "success",
}
}
}
pub struct Dashboard {
title: String,
data: JsonValue,
model: DashboardModel,
class: String,
icon: String,
desc: String,
api: String,
options: JsonValue,
}
impl Dashboard {
pub fn new(title: &str) -> Dashboard {
Dashboard {
title: title.to_string(),
data: JsonValue::Null,
model: DashboardModel::Number,
class: "col-4".to_string(),
icon: "".to_string(),
desc: "".to_string(),
api: "".to_string(),
options: JsonValue::Null,
}
}
pub fn options(&mut self, options: JsonValue) -> &mut Self {
self.options = options;
self
}
pub fn api(&mut self, api: &str) -> &mut Self {
self.api = api.to_string();
self
}
pub fn data(&mut self, data: JsonValue) -> &mut Self {
self.data = data;
self
}
pub fn class(&mut self, name: &str) -> &mut Self {
self.class = name.to_string();
self
}
pub fn icon(&mut self, name: &str) -> &mut Self {
self.icon = name.to_string();
self
}
pub fn model(&mut self, dashboard_model: DashboardModel) -> &mut Self {
self.model = dashboard_model;
self
}
pub fn desc(&mut self, desc: &str) -> &mut Self {
self.desc = desc.to_string();
self
}
pub fn json(&self) -> JsonValue {
object! {
title: self.title.clone(),
data: self.data.clone(),
class: self.class.clone(),
icon: self.icon.clone(),
model: self.model.str(),
desc: self.desc.clone(),
api: self.api.clone(),
options:self.options.clone(),
}
}
}
pub enum DashboardModel {
Number,
EchartsBar,
EchartsStackedBar,
EchartsBarRace,
EchartsPie,
EchartsDoughnut,
EchartsStackedLine,
EchartsStackedLineArea,
EchartsGeoGraph,
}
impl DashboardModel {
pub fn str(&self) -> &'static str {
match self {
Self::Number => "number",
Self::EchartsBar => "echarts-bar",
Self::EchartsStackedBar => "echarts-stacked_bar",
Self::EchartsBarRace => "echarts-bar_race",
Self::EchartsPie => "echarts-pie",
Self::EchartsDoughnut => "echarts-doughnut",
Self::EchartsStackedLine => "echarts-stacked_line",
Self::EchartsStackedLineArea => "echarts-stacked_line_area",
Self::EchartsGeoGraph => "echarts-geo_graph",
}
}
}