tina-core 0.0.2

Tina platform
Documentation
//!  中间件处理逻辑

use crate::tina::core::service::token::TokenService;
use crate::tina::data::app_error::AppError;
use crate::tina::data::http::request_log_param::RequestLogParam;
use crate::tina::data::http::response_data::HttpResData;
use crate::tina::data::http_status::HttpStatus;
use crate::tina::data::i18n_string::I18nString;
use crate::tina::data::json::ToJson;
use crate::tina::data::no_data::NoData;
use crate::tina::data::throttle::ThrottleParam;
use crate::tina::data::AppResult;
use crate::tina::i18n::message::system_message::SystemMessage;
use crate::tina::server::application::Application;
use crate::tina::server::http::request_ext::RequestExt;
use crate::tina::server::http::response::HttpResponseExt;
use crate::tina::server::http::route::{HttpMethod, RouteBaseConfig};
use crate::tina::server::http::route_ext::ApiResponder;
use crate::tina::server::session::Session;
use crate::tina::util::not_empty::INotEmpty;
use crate::tina::util::string::AsStr;
use crate::{app_error, app_error_from, app_system_error, app_system_error_with_msg, i18n_string};
use chrono::Local;
use either::Either;
use futures_util::FutureExt;
use serde_json::Value;
use std::error::Error;
use std::future::Future;
use std::panic::AssertUnwindSafe;
use std::sync::Arc;
use tracing::Level;

pub async fn before_demo<Req: RequestExt>(req: &Req) -> Result<(), AppError> {
    let application = match req.get_application() {
        Ok(v) => v,
        Err(err) => return Err(err),
    };
    let route = match req.get_route_config() {
        Ok(v) => v,
        Err(err) => return Err(err),
    };
    if application.get_server_config()?.demo_enabled {
        match route.method {
            HttpMethod::POST | HttpMethod::PUT | HttpMethod::DELETE | HttpMethod::PATCH => {
                return Err(app_system_error_with_msg!(i18n_string!(SystemMessage::ERROR_CAN_NOT_OPERATE_WITHIN_DEMO_MODEL)));
            }
            _ => {}
        }
    }
    Ok(())
}

pub async fn before_panic<Req: RequestExt>(req: &Req) -> Result<Option<(String, Arc<RequestLogParam>)>, AppError> {
    match req.get_request_log_param() {
        Ok(v) => match v {
            None => Ok(None),
            Some(v) => Ok(Some((req.get_locale(), v))),
        },
        Err(_) => Ok(None),
    }
}

pub async fn after_panic<Res: HttpResponseExt, Err: Error + Send + Sync + 'static, Fut: Future<Output = Result<Res, Err>>>(
    fut: Fut,
    obj: Option<(String, Arc<RequestLogParam>)>,
) -> Result<Res, AppError> {
    let c = AssertUnwindSafe(fut).catch_unwind();
    let r = c.await;
    match r {
        Ok(res) => match res {
            Ok(res) => Ok(res),
            Err(err) => Err(app_error_from!(err)),
        },
        Err(err) => {
            let err = match AppError::from_any_send(err) {
                Either::Left(err) => err,
                Either::Right(_) => {
                    let app_err = app_system_error!("panic error");
                    let err_msg = app_err.to_string();
                    tracing::error!("{:?}", err_msg);
                    app_err
                }
            };
            if let Some((locale, param)) = obj {
                let err2 =
                    AppError::new_error(err.get_return_code().clone(), err.get_return_msg().clone(), err.get_location(), err.get_line());
                let vo: HttpResData<NoData> = HttpResData {
                    code: err2.get_return_code(),
                    msg: Some(err2.get_return_msg().get_string(&locale)),
                    data: None,
                    extension: Default::default(),
                    session: Session::default(),
                };
                param.set_result(Arc::new(vo.to_json_value()))
            }
            Err(err)
        }
    }
}

pub async fn before_permission<Req: RequestExt>(req: &Req) -> Result<(), AppError> {
    let route_config = req.get_route_config()?;
    if !route_config.enable_token {
        return Ok(());
    }
    if let Some(permission) = route_config.require_permission.as_ref() {
        let permission = permission.as_str().trim();
        if permission.not_empty() {
            let uri = req.get_request_path();
            let uri = uri.as_ref();
            let application2 = req.get_application()?;
            let permission_service = application2.get_permission_service()?;
            let session = Session::from_http_request(req).await?;
            return match permission_service.has_permi(&session, permission) {
                true => Ok(()),
                false => Err(app_error!(HttpStatus::Unauthorized, i18n_string!(SystemMessage::ERROR_NO_PERM_ACCESS_API, ("name", uri)))),
            };
        }
    }
    Ok(())
}

pub async fn before_request_log_param<Req: RequestExt>(
    req: &Req,
) -> Result<(Session, Application, Arc<RequestLogParam>, Arc<RouteBaseConfig>), AppError> {
    let application = req.get_application()?;
    let session = Session::from_http_request(req).await?;
    let req_log_param = req.get_request_log_param()?.ok_or_else(|| app_system_error!("no RequestLogParam found in request"))?;
    let route = req.get_route_config()?;
    req_log_param.set_log_level(route.log_param_level);
    req_log_param.set_handler_name(route.handler_name.to_string());
    req_log_param.set_contains_sensitive_param(route.contains_sensitive_param);
    req_log_param.set_http_method(req.get_method().to_string());
    req_log_param.set_remote_ip_address(req.get_remote_ip_address().to_string());
    req_log_param.set_request_uri(req.get_request_path().into_owned());
    req_log_param.set_max_param_len(application.get_log_config()?.log_parameter_json_length as usize);
    req_log_param.set_start_time_millis(Local::now().naive_local().timestamp_millis());
    Ok((session, application, req_log_param, route))
}

pub async fn after_request_log_param<Res: HttpResponseExt, Err: Error + Send + Sync + 'static>(
    r: Result<Res, Err>,
    obj: (Session, Application, Arc<RequestLogParam>, Arc<RouteBaseConfig>),
) -> Result<Res, AppError> {
    fn log_param(req_log_param: Arc<RequestLogParam>, _application: Option<Application>, err: Option<impl std::error::Error>) {
        req_log_param.set_end_time_millis(Local::now().naive_local().timestamp_millis());
        if let Some(err) = err {
            req_log_param.set_err(err);
        }
        let level = req_log_param.get_log_level();
        match level {
            Level::TRACE => {
                if tracing::enabled!(Level::TRACE) {
                    tracing::trace!("{}", req_log_param);
                }
            }
            Level::DEBUG => {
                if tracing::enabled!(Level::DEBUG) {
                    tracing::debug!("{}", req_log_param);
                }
            }
            Level::INFO => {
                if tracing::enabled!(Level::INFO) {
                    tracing::info!("{}", req_log_param);
                }
            }
            Level::WARN => {
                if tracing::enabled!(Level::WARN) {
                    tracing::warn!("{}", req_log_param);
                }
            }
            Level::ERROR => {
                if tracing::enabled!(Level::ERROR) {
                    tracing::error!("{}", req_log_param);
                }
            }
        }
    }
    let (session, application, req_log_param, route) = obj;
    if Ok(true) == session.is_disable_request_log_param() {
        return r.map_err(app_error_from!());
    }
    req_log_param.set_user_id(session.get_user().map(|v| v.get_username().to_string()).unwrap_or_default());
    let enable_log_param = match application.get_log_config()?.enable_request_parameter_log {
        true => match route.enable_log_param {
            None => matches!(route.method, HttpMethod::POST | HttpMethod::PUT | HttpMethod::DELETE),
            Some(v) => v,
        },
        false => false,
    };
    match r {
        Ok(res) => {
            if enable_log_param {
                let err = match res.success() {
                    true => None,
                    false => Some(AppError::new_error(
                        HttpStatus::Error,
                        I18nString::direct_from(res.get_error_message().as_str()),
                        file!(),
                        line!(),
                    )),
                };
                log_param(req_log_param, Some(application), err);
            }
            Ok(res)
        }
        Err(err) => {
            if enable_log_param {
                log_param(req_log_param, Some(application), Some(&err));
            }
            Err(app_error_from!(err))
        }
    }
}

pub async fn before_token<Req: RequestExt>(req: &Req) -> Result<(), AppError> {
    let session = Session::from_http_request(req).await?;
    // 如果已经通过其他认证方式获得用户信息, 则跳过Token认证
    if session.get_user().is_some() {
        return Ok(());
    }
    let route_config = req.get_route_config()?;
    // 不管接口是否启用token, 只要包含token header, 都会获取用户信息
    let token_user_result = TokenService::get_login_user(req).await;
    let enable_token = route_config.enable_token;
    match token_user_result {
        Ok(token_user) => match token_user {
            // 找不到用户信息时, 只在启用token的情况下返回错误
            None => match enable_token {
                true => Err(app_error!(HttpStatus::Unauthorized, i18n_string!(SystemMessage::ERROR_USER_NOTFOUND))),
                false => Ok(()),
            },
            Some(mut token_user) => {
                match TokenService::verify_token(req, &mut token_user).await {
                    Ok(_) => {
                        let token_user = Arc::new(token_user);
                        session.set_user(token_user)?;
                        Ok(())
                    }
                    // token 校验失败时, 只在启用token的情况下返回错误
                    Err(err) => match enable_token {
                        true => {
                            tracing::error!("验证用户Token异常: {:?}", err);
                            Err(err)
                        }
                        false => Ok(()),
                    },
                }
            }
        },
        // 获取用户信息失败时, 只在启用token的情况下返回错误
        Err(err) => match enable_token {
            true => {
                tracing::error!("获取Token用户信息异常: {:?}", err);
                Err(err)
            }
            false => Ok(()),
        },
    }
}

pub async fn before_transaction<Req: RequestExt>(req: &Req) -> Result<Option<(Session, Arc<RouteBaseConfig>)>, AppError> {
    let route_config = match req.get_route_config() {
        Ok(v) => v,
        Err(err) => return Err(err),
    };
    // 自动开启事务的 HttpMethod
    static AUTO_TRANSACTION_METHOD: [HttpMethod; 3] = [HttpMethod::POST, HttpMethod::PUT, HttpMethod::DELETE];
    match route_config.enable_auto_transaction {
        true => {
            let session = Session::from_http_request(req).await?;
            if AUTO_TRANSACTION_METHOD.contains(&route_config.method) {
                let service = session.get_transaction_service()?;
                service.require_transaction().await?;
            }
            Ok(Some((session, route_config)))
        }
        false => Ok(None),
    }
}

pub async fn after_transaction<Res: HttpResponseExt, Err: Error + Send + Sync + 'static>(
    r: Result<Res, Err>,
    obj: Option<(Session, Arc<RouteBaseConfig>)>,
) -> Result<Res, AppError> {
    match obj {
        Some((session, route_config)) => match route_config.enable_auto_transaction {
            true => match r {
                Ok(res) => {
                    let service = session.get_transaction_service()?;
                    let success = { res.success() };
                    match success {
                        true => {
                            if service.is_in_transaction().await? {
                                service.commit_transaction().await?;
                            }
                        }
                        false => {
                            if service.is_in_transaction().await? {
                                service.rollback_transaction().await?;
                            }
                        }
                    }
                    Ok(res)
                }
                Err(err) => {
                    let service = session.get_transaction_service()?;
                    if service.is_in_transaction().await? {
                        service.rollback_transaction().await?;
                    }
                    Err(app_error_from!(err))
                }
            },
            false => r.map_err(app_error_from!()),
        },
        None => r.map_err(app_error_from!()),
    }
}

pub async fn before_throttle(session: &Session, throttle_params: &[ThrottleParam]) -> AppResult<()> {
    let application = session.get_application();
    let service = application.get_throttle_service()?;
    service.handle_throttle(session, throttle_params).await?;
    Ok(())
}

pub async fn after_throttle<Res: ApiResponder<Inner>, Inner>(
    session: &Session,
    throttle_params: &[ThrottleParam],
    res: &Res,
) -> AppResult<()> {
    let application = session.get_application();
    let service = application.get_throttle_service()?;
    match res.is_success() {
        true => {
            service.after_handle_throttle_success(session, throttle_params).await?;
        }
        false => {
            service.after_handle_throttle_error(session, throttle_params).await?;
        }
    }
    Ok(())
}

#[allow(unused_variables)]
pub async fn before_delegate(
    route_config: Arc<RouteBaseConfig>,
    remote_ip_address: &str,
    session: &Session,
    param_value: &Value,
) -> AppResult<Vec<ThrottleParam>> {
    let mut throttle_params = Vec::new();
    if route_config.throttles.not_empty() {
        for throttle in route_config.throttles.iter() {
            let mut param = throttle.to_throttle_param(remote_ip_address, session, param_value)?;
            if !param.is_empty() {
                throttle_params.append(&mut param);
            }
        }
    }
    if throttle_params.not_empty() {
        before_throttle(session, throttle_params.as_slice()).await?;
    }
    Ok(throttle_params)
}

#[allow(unused_variables)]
pub async fn after_delegate<Res: ApiResponder<Inner>, Inner>(
    session: &Session,
    param_value: &Value,
    res: &Res,
    r1: Vec<ThrottleParam>,
) -> AppResult<()> {
    if r1.not_empty() {
        after_throttle(session, r1.as_slice(), res).await?;
    }
    Ok(())
}