Skip to main content

framework_web/
from_web.rs

1use crate::{WebError, WebMethod, use_web};
2use anyhow::{Result, anyhow};
3use async_trait::async_trait;
4use framework_core::types::{PaginationParams, PaginationSort};
5use serde::Deserialize;
6use serde::de::DeserializeOwned;
7
8const DEFAULT_PAGE_CURRENT: i64 = 1;
9const DEFAULT_PAGE_SIZE: i64 = 10;
10const MAX_PAGE_SIZE: i64 = 100;
11pub struct Json<T>(pub T);
12
13pub struct Query<T>(pub T);
14
15#[async_trait]
16pub trait FromWeb: Sized + Send {
17    async fn from_web() -> Result<Self>;
18}
19
20#[async_trait]
21impl<T> FromWeb for Json<T>
22where
23    T: DeserializeOwned + Send,
24{
25    async fn from_web() -> Result<Self> {
26        let context = use_web()?;
27        let source = if context.request().method == WebMethod::Get {
28            context.query_json()?
29        } else {
30            context.body_json()?
31        };
32        T::deserialize(source)
33            .map(Self)
34            .map_err(|error| WebError::parameter("请求参数转换失败", error).into())
35    }
36}
37
38#[async_trait]
39impl<T> FromWeb for Query<T>
40where
41    T: DeserializeOwned + Send,
42{
43    async fn from_web() -> Result<Self> {
44        let context = use_web()?;
45        T::deserialize(context.query_json()?)
46            .map(Self)
47            .map_err(|error| WebError::parameter("查询参数转换失败", error).into())
48    }
49}
50
51#[derive(Deserialize)]
52#[serde(rename_all = "camelCase")]
53struct PaginationInput {
54    current: Option<i64>,
55    size: Option<i64>,
56    #[serde(default)]
57    sorts: Vec<PaginationSort>,
58}
59
60#[async_trait]
61impl FromWeb for PaginationParams {
62    async fn from_web() -> Result<Self> {
63        let context = use_web()?;
64        let source = if context.request().method == WebMethod::Get {
65            context.query_json()?
66        } else {
67            context.body_json()?
68        };
69        let pagination = PaginationInput::deserialize(source)
70            .map_err(|error| WebError::parameter("分页参数转换失败", error))?;
71        let size = pagination
72            .size
73            .filter(|size| *size > 0)
74            .unwrap_or(DEFAULT_PAGE_SIZE);
75        if size > MAX_PAGE_SIZE {
76            return Err(WebError::parameter(
77                format!("分页大小不能超过 {MAX_PAGE_SIZE}"),
78                anyhow!("分页大小超出上限:{size}"),
79            )
80            .into());
81        }
82        if let Some(sort) = pagination
83            .sorts
84            .iter()
85            .find(|sort| !is_safe_sort_field(&sort.field))
86        {
87            return Err(WebError::parameter(
88                "分页排序字段包含非法字符",
89                anyhow!("可疑分页排序字段:{}", sort.field),
90            )
91            .into());
92        }
93        Ok(Self {
94            current: pagination
95                .current
96                .filter(|current| *current > 0)
97                .unwrap_or(DEFAULT_PAGE_CURRENT),
98            size,
99            sorts: pagination.sorts,
100        })
101    }
102}
103
104fn is_safe_sort_field(field: &str) -> bool {
105    let mut chars = field.chars();
106    matches!(chars.next(), Some(first) if first.is_ascii_alphabetic() || first == '_')
107        && chars.all(|character| character.is_ascii_alphanumeric() || character == '_')
108}