use std::any::type_name;
use br_fields::Field;
use json::{array, object, JsonValue};
use br_web_server::request::{ContentType, Method, Request};
use crate::{ApiResponse, Tools};
use crate::PLUGIN_TOOLS;
pub trait Action {
fn name(&self) -> String {
type_name::<Self>().rsplit("::").next().unwrap().to_lowercase()
}
fn api(&self) -> String {
let t = type_name::<Self>().split("::").collect::<Vec<&str>>();
let plugin = t[2];
let module = t[3];
let action = t[4];
format!("{plugin}.{module}.{action}")
}
fn token(&self) -> bool { false }
fn version(&self) -> String { "v1".to_string() }
fn title(&self) -> String;
fn description(&self) -> String { String::new() }
fn public(&mut self) -> bool { true }
fn auth(&mut self) -> bool { false }
fn interface_type(&mut self) -> InterfaceType {
InterfaceType::API
}
fn method(&mut self) -> Method {
Method::GET
}
fn content_type(&mut self) -> ContentType {
ContentType::Json
}
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();
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();
ApiResponse::error(data, "请求失败")
}
fn run(&mut self, mut request: Request) -> Result<ApiResponse, ApiResponse> {
if 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()));
}
self.check(&mut request.body.content)?;
let res = self.index(request.clone());
match res.success {
true => Ok(res),
false => Err(res)
}
}
fn check(&mut self, request: &mut JsonValue) -> Result<(), ApiResponse> {
let params = self.params();
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);
if request.has_key(name) {
match field["mode"].as_str().unwrap() {
"int" => {
if !request[name].is_number() {
return Err(ApiResponse::fail(-1, format!("请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]", name, field["mode"]).as_str()));
}
}
"string" => {
if !request[name].is_string() {
return Err(ApiResponse::fail(-1, format!("请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]", name, field["mode"]).as_str()));
}
}
"switch" => {
if !request[name].is_boolean() {
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 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, option).as_str()));
}
if request[name].is_array() {
let res1 = request[name].members().map(|m| m.as_str().unwrap_or("")).collect::<Vec<&str>>();
let diff = res1
.iter()
.filter(|&item| !field["option"].members().map(|m| m.as_str().unwrap_or("")).collect::<Vec<&str>>().contains(item))
.collect::<Vec<&&str>>();
if !diff.is_empty() {
return Err(ApiResponse::fail(-1, format!("请求参数选项错误: 参数 [{}] 数据 {:?} 应为[{:?}]范围之内", name, diff, option).as_str()));
}
}
}
"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()));
}
}
_ => {
}
}
} else {
if require {
return Err(ApiResponse::fail(-1, format!("请求参数错误: 参数[{}]必填", name).as_str()));
}
request[name] = field["def"].clone();
}
}
Ok(())
}
fn index(&mut self, request: Request) -> ApiResponse;
fn params_table_list(&mut self) -> JsonValue {
let mut params = object! {};
params["page"] = br_fields::int::Int::new(false, "page", "页数", 10, 1).field();
params["limit"] = br_fields::int::Int::new(false, "limit", "每页行数", 10, 10).field();
params["first"] = br_fields::int::Switch::new(false, "first", "首次", true).field();
params["sorts"] = br_fields::text::Object::new(false, "sorts", "排序", object! {}).field();
params["where_or"] = br_fields::text::Object::new(false, "where_or", "查询条件", object! {}).field();
params["where_and"] = br_fields::text::Object::new(false, "where_and", "查询条件", array![]).field();
params
}
#[cfg(any(feature = "sqlite", feature = "mssql", feature = "mysql"))]
fn table_list(&mut self, request: JsonValue, table_name: &str, fields: JsonValue) -> (i64, JsonValue) {
let page = request["page"].as_i32().unwrap_or(1);
let limit = request["limit"].as_i32().unwrap_or(10);
let _first = request["first"].as_bool().unwrap_or(false);
let sorts = request["sorts"].clone();
let where_or = request["where_or"].clone();
let where_and = request["where_and"].clone();
let fields_key = fields.entries().map(|(field, _)| field).collect::<Vec<&str>>().join(",");
let mut tools = self.tools();
let db = tools.db.table(table_name);
for (key, value) in where_or.entries() {
if value.is_empty() {
continue;
}
if value.is_array() {
db.where_or(key, "between", value.clone());
db.where_or(key, "in", value.clone());
} else if value.is_boolean() {
db.where_or(key, "=", value.clone());
} else {
db.where_or(key, "like", format!("%{}%", value).into());
}
}
for value in where_and.members() {
db.where_and(value[0].as_str().unwrap(), value[1].as_str().unwrap(), value[2].clone());
}
let total = (db.clone().count().as_f64().unwrap() / limit as f64).ceil() as i64;
let mut db_list = db.clone();
for (key, sort) in sorts.entries() {
db_list.order(key, sort.as_bool().unwrap());
}
let data = db_list.field(fields_key.as_str()).page(page, limit).select();
(total, data)
}
fn tools(&mut self) -> Tools {
let tools = PLUGIN_TOOLS.lock().unwrap();
let tools = tools.get("tools").unwrap().clone();
tools
}
fn btn_info(&mut self, btn_type: BtnType, btn_color: BtnColor, title: &str, desc: &str, url: &str, cnd: Vec<JsonValue>) -> JsonValue {
object! {
api:self.api().clone(),
title:if title.is_empty() {self.title()}else{title.into()},
desc:desc,
btn_type:btn_type.str(),
btn_color:btn_color.str(),
cnd:cnd,
url:url,
path:self.api().replace(".","/")
}
}
}
#[derive(Debug, Clone)]
pub enum InterfaceType {
API,
BTN,
MENU,
}
impl InterfaceType {
pub fn str(self) -> String {
match self {
InterfaceType::API => "api",
InterfaceType::BTN => "btn",
InterfaceType::MENU => "menu"
}.to_string()
}
}
#[derive(Debug, Clone)]
pub enum BtnType {
Form,
FormDownload,
Url,
Api,
Download,
Path,
}
impl BtnType {
fn str(self) -> String {
match self {
BtnType::Form => "form",
BtnType::FormDownload => "form_download",
BtnType::Api => "api",
BtnType::Download => "download",
BtnType::Url => "url",
BtnType::Path => "path",
}.to_string()
}
}
#[derive(Debug, Clone)]
pub enum BtnColor {
Primary,
Red,
Blue,
Yellow,
Green,
}
impl BtnColor {
fn str(self) -> String {
match self {
BtnColor::Primary => "primary",
BtnColor::Red => "negative",
BtnColor::Blue => "info",
BtnColor::Yellow => "warning",
BtnColor::Green => "positive",
}.to_string()
}
}