framework_web/
from_web.rs1use 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}