168 lines
4.4 KiB
Rust
168 lines
4.4 KiB
Rust
// 对比实际运行库、Axum Query 和其底层 serde_urlencoded;不包含网络或路由耗时。
|
|
// 默认库的错误输入行为另行检查,合法输入计时不能证明完整边界语义等价。
|
|
use axum::{extract::Query, http::Uri};
|
|
use dever_runtime::api::{self, Request};
|
|
use serde::{Deserialize, de::DeserializeOwned};
|
|
|
|
#[derive(Debug, Deserialize, PartialEq)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct QueryArguments {
|
|
name: String,
|
|
count: i64,
|
|
enabled: bool,
|
|
ratio: f64,
|
|
tag: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, PartialEq)]
|
|
#[serde(deny_unknown_fields)]
|
|
struct WideArguments {
|
|
field0: String,
|
|
field1: String,
|
|
field2: String,
|
|
field3: String,
|
|
field4: String,
|
|
field5: String,
|
|
field6: String,
|
|
field7: String,
|
|
field8: String,
|
|
field9: String,
|
|
field10: String,
|
|
field11: String,
|
|
field12: String,
|
|
field13: String,
|
|
field14: String,
|
|
field15: String,
|
|
field16: String,
|
|
field17: String,
|
|
field18: String,
|
|
field19: String,
|
|
field20: String,
|
|
field21: String,
|
|
field22: String,
|
|
field23: String,
|
|
field24: String,
|
|
field25: String,
|
|
field26: String,
|
|
field27: String,
|
|
field28: String,
|
|
field29: String,
|
|
field30: String,
|
|
field31: String,
|
|
}
|
|
|
|
struct QueryInput {
|
|
request: Request,
|
|
uri: Uri,
|
|
}
|
|
|
|
fn query_fault(_: impl std::fmt::Display) -> Fault {
|
|
&FaultInfo {
|
|
message: "invalid query",
|
|
source: "query-comparison",
|
|
line: 1,
|
|
column: 1,
|
|
}
|
|
}
|
|
|
|
fn runtime_query<Arguments: DeserializeOwned>(
|
|
values: &[QueryInput],
|
|
) -> Result<Option<Arguments>, Fault> {
|
|
let Some(input) = values.first() else {
|
|
return Ok(None);
|
|
};
|
|
api::input(&input.request)
|
|
.map_err(query_fault)?
|
|
.decode(dever_runtime::wire::decode)
|
|
.map(Some)
|
|
.map_err(query_fault)
|
|
}
|
|
|
|
fn axum_query<Arguments: DeserializeOwned>(
|
|
values: &[QueryInput],
|
|
) -> Result<Option<Arguments>, Fault> {
|
|
let Some(input) = values.first() else {
|
|
return Ok(None);
|
|
};
|
|
Query::<Arguments>::try_from_uri(&input.uri)
|
|
.map(|Query(arguments)| Some(arguments))
|
|
.map_err(query_fault)
|
|
}
|
|
|
|
fn serde_query<Arguments: DeserializeOwned>(
|
|
values: &[QueryInput],
|
|
) -> Result<Option<Arguments>, Fault> {
|
|
let Some(input) = values.first() else {
|
|
return Ok(None);
|
|
};
|
|
serde_urlencoded::from_str(input.uri.query().unwrap_or_default())
|
|
.map(Some)
|
|
.map_err(query_fault)
|
|
}
|
|
|
|
fn query_input(query: &str) -> QueryInput {
|
|
let target = format!("/test?{query}");
|
|
QueryInput {
|
|
uri: target.parse().unwrap(),
|
|
request: axum::http::Request::builder()
|
|
.uri(target)
|
|
.body(Default::default())
|
|
.unwrap(),
|
|
}
|
|
}
|
|
|
|
fn query_comparison<Arguments: DeserializeOwned + PartialEq + std::fmt::Debug>(
|
|
name: &str,
|
|
query: &str,
|
|
count: usize,
|
|
) {
|
|
let input = [query_input(query)];
|
|
for (label, decoder) in [
|
|
("axum", axum_query::<Arguments> as fn(&[QueryInput]) -> _),
|
|
(
|
|
"serde_urlencoded",
|
|
serde_query::<Arguments> as fn(&[QueryInput]) -> _,
|
|
),
|
|
] {
|
|
compare_named(
|
|
&format!("query_{name}_{label}"),
|
|
&input,
|
|
count,
|
|
runtime_query::<Arguments>,
|
|
decoder,
|
|
|output| u64::from(output.is_some()),
|
|
label,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
if std::env::args().any(|argument| argument == "--check") {
|
|
query_checks();
|
|
return;
|
|
}
|
|
println!("case,sample,implementation,elapsed_ns,checksum");
|
|
for (name, value, count) in [
|
|
("plain", "Alice".to_owned(), 20_000),
|
|
("unicode", "%E9%9B%AA+test".to_owned(), 20_000),
|
|
("escaped", "%22%5C%00%26%3D%25".repeat(8), 10_000),
|
|
("long", "value%2B%E9%9B%AA".repeat(256), 1_000),
|
|
] {
|
|
query_comparison::<QueryArguments>(
|
|
name,
|
|
&format!("name={value}&count=-7&enabled=true&ratio=1.25&tag=demo"),
|
|
count,
|
|
);
|
|
}
|
|
for (name, value, count) in [
|
|
("wide_plain", "testvalue".to_owned(), 2_000),
|
|
("wide_escaped", "value%2B%E9%9B%AA".repeat(8), 500),
|
|
] {
|
|
let query = (0..32)
|
|
.map(|index| format!("field{index}={value}"))
|
|
.collect::<Vec<_>>()
|
|
.join("&");
|
|
query_comparison::<WideArguments>(name, &query, count);
|
|
}
|
|
}
|