use crate::tina::data::app_error::AppError;
use crate::tina::data::dynamic_value::DynamicValue;
use crate::tina::data::AppResult;
use crate::tina::log::operation::{BusinessType, OperationLog, OperatorType};
use crate::tina::server::application::Application;
use crate::{app_system_error, tina::data::api_schema::ApiSchema};
use crate::tina::data::throttle::ThrottleConfig;
use crate::tina::server::http::route_ext::RouteConfig;
use crate::tina::util::not_empty::INotEmpty;
use http::Method;
use indexmap::{IndexMap, IndexSet};
use once_cell::sync::Lazy;
use std::fmt::{Debug, Display, Formatter};
use std::sync::{Arc, RwLock};
use std::{any::Any, str::FromStr};
use tracing::Level;
use utoipa::openapi::path::Parameter;
use utoipa::openapi::{request_body::RequestBody, Responses};
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct RouteConfigKey {
pub method: HttpMethod,
pub path: String,
}
#[derive(Clone)]
pub struct RouteBaseConfig {
pub api_group: IndexSet<String>,
pub path: String,
pub method: HttpMethod,
pub handler_name: String,
pub request_parameter: Option<Vec<Parameter>>,
pub request_body: Option<RequestBody>,
pub response_body: Option<Responses>,
pub tags: Vec<String>,
pub summary: String,
pub description: Vec<String>,
pub show_doc: bool,
pub order: Option<i32>,
pub author: Vec<String>,
pub validate_group: Option<Vec<&'static str>>,
pub show_security: bool,
pub enable_default_prefix: bool,
pub enable_token: bool,
pub enable_auto_transaction: bool,
pub enable_log_param: Option<bool>,
pub log_param_level: Level,
pub operation_log: Option<OperationLog>,
pub require_permission: Option<String>,
pub contains_sensitive_param: bool,
pub throttles: Vec<ThrottleConfig>,
pub extensions: IndexMap<String, Arc<dyn Any + Send + Sync>>,
pub openapi_extensions: IndexMap<String, DynamicValue>,
pub openapi_request_headers: IndexMap<String, (String, bool)>,
pub openapi_response_headers: IndexMap<String, (String, bool)>,
}
impl Default for RouteBaseConfig {
fn default() -> Self {
Self {
api_group: Default::default(),
path: "".to_string(),
method: Default::default(),
handler_name: "".to_string(),
request_parameter: None,
request_body: None,
response_body: None,
tags: vec![],
summary: "".to_string(),
description: vec![],
show_doc: true,
order: None,
author: vec![],
validate_group: None,
show_security: false,
enable_default_prefix: false,
enable_token: false,
enable_auto_transaction: true,
enable_log_param: None,
log_param_level: Level::DEBUG,
operation_log: None,
require_permission: None,
contains_sensitive_param: false,
throttles: vec![],
extensions: Default::default(),
openapi_extensions: Default::default(),
openapi_request_headers: Default::default(),
openapi_response_headers: Default::default(),
}
}
}
impl RouteBaseConfig {
pub fn get_route_path(application: &Application, config: &RouteBaseConfig) -> String {
let mut path = if config.enable_default_prefix {
format!("{}{}", application.get_server_config().expect("No Server Config found").default_api_prefix, config.path)
} else {
config.path.to_string()
};
while path.contains("//") {
path = path.replace("//", "/")
}
path
}
pub(crate) fn should_delegate_handler(&self) -> bool {
if self.throttles.not_empty() {
return true;
}
false
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum HttpMethod {
OPTIONS,
GET,
POST,
PUT,
DELETE,
HEAD,
TRACE,
CONNECT,
PATCH,
}
impl FromStr for HttpMethod {
type Err = AppError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
impl HttpMethod {
pub fn from_method(method: Method) -> Self {
match method {
Method::OPTIONS => Self::OPTIONS,
Method::GET => Self::GET,
Method::POST => Self::POST,
Method::PUT => Self::PUT,
Method::DELETE => Self::DELETE,
Method::HEAD => Self::HEAD,
Method::TRACE => Self::TRACE,
Method::CONNECT => Self::CONNECT,
Method::PATCH => Self::PATCH,
_ => panic!("unsupported method: {}", method),
}
}
pub fn to_method(&self) -> Method {
match self {
HttpMethod::OPTIONS => Method::OPTIONS,
HttpMethod::GET => Method::GET,
HttpMethod::POST => Method::POST,
HttpMethod::PUT => Method::PUT,
HttpMethod::DELETE => Method::DELETE,
HttpMethod::HEAD => Method::HEAD,
HttpMethod::TRACE => Method::TRACE,
HttpMethod::CONNECT => Method::CONNECT,
HttpMethod::PATCH => Method::PATCH,
}
}
}
impl Display for HttpMethod {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let s = match self {
HttpMethod::OPTIONS => "OPTIONS",
HttpMethod::GET => "GET",
HttpMethod::POST => "POST",
HttpMethod::PUT => "PUT",
HttpMethod::DELETE => "DELETE",
HttpMethod::HEAD => "HEAD",
HttpMethod::TRACE => "TRACE",
HttpMethod::CONNECT => "CONNECT",
HttpMethod::PATCH => "PATCH",
};
f.write_str(s)
}
}
impl Default for HttpMethod {
fn default() -> Self {
Self::GET
}
}
impl TryFrom<&str> for HttpMethod {
type Error = AppError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
from_str(value)
}
}
impl TryFrom<String> for HttpMethod {
type Error = AppError;
fn try_from(value: String) -> Result<Self, Self::Error> {
from_str(value.as_str())
}
}
impl TryFrom<&String> for HttpMethod {
type Error = AppError;
fn try_from(value: &String) -> Result<Self, Self::Error> {
from_str(value.as_str())
}
}
impl TryFrom<&mut String> for HttpMethod {
type Error = AppError;
fn try_from(value: &mut String) -> Result<Self, Self::Error> {
from_str(value.as_str())
}
}
fn from_str(value: &str) -> AppResult<HttpMethod> {
match value {
"OPTIONS" => Ok(HttpMethod::OPTIONS),
"GET" => Ok(HttpMethod::GET),
"POST" => Ok(HttpMethod::POST),
"PUT" => Ok(HttpMethod::PUT),
"DELETE" => Ok(HttpMethod::DELETE),
"HEAD" => Ok(HttpMethod::HEAD),
"TRACE" => Ok(HttpMethod::TRACE),
"CONNECT" => Ok(HttpMethod::CONNECT),
"PATCH" => Ok(HttpMethod::PATCH),
_ => Err(app_system_error!("unkown http method string: {}", value)),
}
}
type GlobalRoutes = Arc<RwLock<Vec<Box<dyn Fn() -> Vec<RouteConfig> + Send + Sync + 'static>>>>;
static GLOBAL_ROUTES: Lazy<GlobalRoutes> = Lazy::new(|| Arc::new(RwLock::new(Vec::new())));
pub struct RouteRegister;
impl RouteRegister {
pub fn regist_route<F: Fn() -> RouteConfig + Send + Sync + 'static>(f: F) {
let mut lock = GLOBAL_ROUTES.write().expect("GLOBAL_ROUTES write lock failed.");
lock.push(Box::new(move || vec![f()]));
}
pub fn regist_routes<F: Fn() -> Vec<RouteConfig> + Send + Sync + 'static>(f: F) {
let mut lock = GLOBAL_ROUTES.write().expect("GLOBAL_ROUTES write lock failed.");
lock.push(Box::new(f));
}
pub fn get_registed_routes_count() -> usize {
let lock = GLOBAL_ROUTES.read().expect("GLOBAL_ROUTES read lock failed.");
lock.iter().map(|f| f().len()).sum()
}
pub fn get_registed_routes() -> Vec<RouteConfig> {
let lock = GLOBAL_ROUTES.read().expect("GLOBAL_ROUTES read lock failed.");
let mut routes = Vec::with_capacity(lock.len());
for f in lock.iter() {
let mut route = f();
routes.append(&mut route);
}
routes
}
pub fn get_registed_routes_config() -> Vec<RouteBaseConfig> {
let lock = GLOBAL_ROUTES.read().expect("GLOBAL_ROUTES read lock failed.");
let mut routes = Vec::with_capacity(lock.len());
for f in lock.iter() {
let mut route = f().into_iter().map(|v| v.config).collect::<Vec<RouteBaseConfig>>();
routes.append(&mut route);
}
routes
}
}
pub struct RouteBuilder {
api_group: IndexSet<String>,
path: String,
method: Method,
pub(crate) handler_name: String,
pub(crate) request_parameter: Option<Vec<Parameter>>,
pub(crate) request_body: Option<RequestBody>,
pub(crate) response_body: Option<Responses>,
tags: Vec<String>,
summary: String,
description: Vec<String>,
show_doc: bool,
order: Option<i32>,
author: Vec<String>,
validate_group: Option<Vec<&'static str>>,
show_security: bool,
enable_default_prefix: bool,
enable_token: bool,
enable_auto_transaction: bool,
enable_log_param: Option<bool>,
log_param_level: Level,
operation_log: Option<OperationLog>,
require_permission: Option<String>,
contains_sensitive_param: bool,
throttles: Vec<ThrottleConfig>,
extensions: IndexMap<String, Arc<dyn Any + Send + Sync>>,
openapi_extensions: IndexMap<String, DynamicValue>,
openapi_request_headers: IndexMap<String, (String, bool)>,
openapi_response_headers: IndexMap<String, (String, bool)>,
}
impl RouteBuilder {
pub fn new(method: Method, path: &str) -> RouteBuilder {
RouteBuilder {
api_group: IndexSet::new(),
path: path.to_owned(),
method,
handler_name: "".to_string(),
request_parameter: None,
request_body: None,
response_body: None,
tags: vec![],
summary: "".to_owned(),
description: vec![],
show_doc: false,
order: None,
author: vec![],
validate_group: None,
show_security: true,
enable_default_prefix: true,
enable_token: true,
enable_auto_transaction: true,
enable_log_param: None,
log_param_level: Level::DEBUG,
operation_log: None,
require_permission: None,
contains_sensitive_param: false,
throttles: vec![],
extensions: Default::default(),
openapi_extensions: Default::default(),
openapi_request_headers: Default::default(),
openapi_response_headers: Default::default(),
}
}
pub fn get(path: &str) -> RouteBuilder {
RouteBuilder::new(Method::GET, path)
}
pub fn post(path: &str) -> RouteBuilder {
RouteBuilder::new(Method::POST, path)
}
pub fn put(path: &str) -> RouteBuilder {
RouteBuilder::new(Method::PUT, path)
}
pub fn patch(path: &str) -> RouteBuilder {
RouteBuilder::new(Method::PATCH, path)
}
pub fn delete(path: &str) -> RouteBuilder {
RouteBuilder::new(Method::DELETE, path)
}
pub fn api_group(&mut self, group: &str) -> &mut Self {
self.api_group.insert(group.to_owned());
self
}
pub fn api_groups(&mut self, group: IndexSet<String>) -> &mut Self {
self.api_group.extend(group.into_iter());
self
}
pub fn tag(&mut self, tag: &str) -> &mut Self {
self.tags.push(tag.to_owned());
self
}
pub fn summary(&mut self, summary: &str) -> &mut Self {
self.summary = summary.to_owned();
self
}
pub fn description(&mut self, description: &str) -> &mut Self {
self.description.push(description.to_owned());
self
}
pub fn show_doc(&mut self, show_doc: bool) -> &mut Self {
self.show_doc = show_doc;
self
}
pub fn order(&mut self, order: i32) -> &mut Self {
self.order = Some(order);
self
}
pub fn author(&mut self, author: &str) -> &mut Self {
self.author.push(author.to_owned());
self
}
pub fn validate_group(&mut self, group: &'static str) -> &mut Self {
if self.validate_group.is_none() {
self.validate_group = Some(Vec::new());
}
if let Some(validate_group) = self.validate_group.as_mut() {
validate_group.push(group);
}
self
}
pub fn show_security(&mut self, show_security: bool) -> &mut Self {
self.show_security = show_security;
self
}
pub fn enable_default_prefix(&mut self, enable_default_prefix: bool) -> &mut Self {
self.enable_default_prefix = enable_default_prefix;
self
}
pub fn enable_token(&mut self, enable_token: bool) -> &mut Self {
self.enable_token = enable_token;
self
}
pub fn enable_auto_transaction(&mut self, enable_auto_transaction: bool) -> &mut Self {
self.enable_auto_transaction = enable_auto_transaction;
self
}
pub fn enable_log_param(&mut self, enable_log_param: bool) -> &mut Self {
self.enable_log_param = Some(enable_log_param);
self
}
pub fn log_param_level(&mut self, log_param_level: Level) -> &mut Self {
self.log_param_level = log_param_level;
self
}
pub fn operation_log(&mut self, title: &str, business_type: BusinessType) -> &mut Self {
self.operation_log = Some(
OperationLog::new()
.title(title)
.business_type(business_type)
.operator_type(OperatorType::MANAGE)
.save_request_data(true)
.save_response_data(true),
);
self
}
pub fn operation_log_full(
&mut self,
title: &str,
business_type: BusinessType,
operator_type: OperatorType,
save_request_data: bool,
save_response_data: bool,
) -> &mut Self {
self.operation_log = Some(
OperationLog::new()
.title(title)
.business_type(business_type)
.operator_type(operator_type)
.save_request_data(save_request_data)
.save_response_data(save_response_data),
);
self
}
pub fn require_permission(&mut self, permission: &str) -> &mut Self {
self.require_permission = Some(permission.to_string());
self
}
pub fn contains_sensitive_param(&mut self, contains_sensitive_param: bool) -> &mut Self {
self.contains_sensitive_param = contains_sensitive_param;
self
}
pub fn add_throttle(&mut self, throttle_config: ThrottleConfig) -> &mut Self {
self.throttles.push(throttle_config);
self
}
pub fn request_body<T: ApiSchema>(&mut self) -> &mut Self {
self.request_body = T::get_request_body();
self
}
pub fn request_parameter<T: ApiSchema>(&mut self) -> &mut Self {
self.request_parameter = T::get_request_params();
self
}
pub fn response_body<T: ApiSchema>(&mut self) -> &mut Self {
self.response_body = Some(T::get_responses());
self
}
pub fn add_extension<T: Send + Sync + 'static>(&mut self, key: &str, value: T) -> &mut Self {
self.extensions.insert(key.to_owned(), Arc::new(value));
self
}
pub fn add_openapi_extension(&mut self, key: &str, value: DynamicValue) -> &mut Self {
self.openapi_extensions.insert(key.to_owned(), value);
self
}
pub fn add_openapi_request_header(&mut self, name: &str, description: &str, required: bool) -> &mut Self {
self.openapi_request_headers.insert(name.to_owned(), (description.to_owned(), required));
self
}
pub fn add_openapi_response_header(&mut self, name: &str, description: &str, required: bool) -> &mut Self {
self.openapi_response_headers.insert(name.to_owned(), (description.to_owned(), required));
self
}
pub(crate) fn to_route(&self) -> RouteConfig {
RouteConfig {
route: None,
config: RouteBaseConfig {
api_group: self.api_group.clone(),
path: self.path.clone(),
method: HttpMethod::from_method(self.method.clone()),
handler_name: self.handler_name.clone(),
request_parameter: self.request_parameter.clone(),
request_body: self.request_body.clone(),
response_body: self.response_body.clone(),
tags: self.tags.clone(),
summary: self.summary.clone(),
description: self.description.clone(),
show_doc: self.show_doc,
order: self.order,
author: self.author.clone(),
enable_token: self.enable_token,
enable_auto_transaction: self.enable_auto_transaction,
enable_log_param: self.enable_log_param,
enable_default_prefix: self.enable_default_prefix,
contains_sensitive_param: self.contains_sensitive_param,
throttles: self.throttles.clone(),
extensions: self.extensions.clone(),
openapi_extensions: self.openapi_extensions.clone(),
openapi_request_headers: self.openapi_request_headers.clone(),
openapi_response_headers: self.openapi_response_headers.clone(),
show_security: self.show_security,
operation_log: self.operation_log.clone(),
require_permission: self.require_permission.clone(),
validate_group: self.validate_group.clone(),
log_param_level: self.log_param_level,
},
}
}
}
pub struct RouteGroup {
group: IndexSet<String>,
path: String,
tag: String,
author: String,
order: i32,
}
impl Default for RouteGroup {
fn default() -> Self {
Self::new()
}
}
impl RouteGroup {
pub fn new() -> RouteGroup {
RouteGroup {
group: IndexSet::new(),
path: "".to_string(),
tag: "".to_string(),
author: "".to_string(),
order: 100,
}
}
pub fn group(&mut self, group: &str) -> &mut Self {
self.group.insert(group.to_owned());
self
}
pub fn groups(&mut self, group: IndexSet<String>) -> &mut Self {
self.group.extend(group.into_iter());
self
}
pub fn path(&mut self, path: &str) -> &mut Self {
self.path = path.to_owned();
self
}
pub fn tag(&mut self, tag: &str) -> &mut Self {
self.tag = tag.to_owned();
self
}
pub fn author(&mut self, author: &str) -> &mut Self {
self.author = author.to_owned();
self
}
pub fn order(&mut self, order: i32) -> &mut Self {
self.order = order;
self
}
pub fn routes(&self, mut routes: Vec<RouteConfig>) -> Vec<RouteConfig> {
let mut order = 0;
for route in routes.iter_mut() {
order += 1;
route.config.api_group.extend(self.group.iter().cloned());
route.config.path = self.path.to_owned() + route.config.path.as_str();
if route.config.tags.is_empty() && !self.tag.as_str().is_empty() {
route.config.tags.push(self.tag.to_owned());
}
if route.config.author.is_empty() && !self.author.as_str().is_empty() {
route.config.author.push(self.author.to_owned());
}
route.config.order = match route.config.order {
None => Some(self.order * 100 + order),
Some(d) => Some(self.order * 100 + d),
};
}
routes
}
}