use super::*; use crate::wire::document::{MAX_BYTES, MAX_VALUES}; #[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq)] #[serde(deny_unknown_fields)] struct Arguments { name: String, count: i64, enabled: bool, } #[test] #[ignore = "explicit release performance probe"] fn form_decode_cost() { use std::{hint::black_box, time::Instant}; println!("\ncase,sample,implementation,iterations,elapsed_ns,checksum"); for bytes in [64, 4096, 65536] { let text = "a".repeat(bytes); for sample in 0..15 { for offset in 0..2 { let native = (sample + offset) % 2 == 1; let started = Instant::now(); let mut checksum = 0; for _ in 0..1000 { let fields = [ ("name".into(), black_box(&text).clone()), ("count".into(), "-7".into()), ("enabled".into(), "true".into()), ] .into_iter() .collect(); let result: Arguments = if native { native_form(fields).unwrap() } else { Input::fields(fields).decode(crate::wire::decode).unwrap() }; assert_eq!(result.count, -7); assert!(result.enabled); checksum += black_box(result.name.len()); } let elapsed = started.elapsed().as_nanos(); let implementation = if native { "rust" } else { "dever" }; println!("form_{bytes},{sample},{implementation},1000,{elapsed},{checksum}"); } } } } fn native_form(mut fields: BTreeMap) -> Option { if fields.len() != 3 { return None; } let result = Arguments { name: fields.remove("name")?, count: fields.remove("count")?.parse().ok()?, enabled: fields.remove("enabled")?.parse().ok()?, }; // 字段名固定;Int、Bool 和标点合计不超过 60 字节。 if result.name.len().saturating_mul(6).saturating_add(60) > MAX_BYTES && serde_json::to_vec(&result).ok()?.len() > MAX_BYTES { return None; } Some(result) } #[test] fn form_fields_use_target_types_without_url_decoding() { let fields = BTreeMap::from([ ("name".into(), "%FF+雪".into()), ("count".into(), "-7".into()), ("enabled".into(), "true".into()), ]); let parsed: Arguments = Input::fields(fields.clone()) .decode(crate::wire::decode) .unwrap(); assert_eq!(parsed, native_form(fields.clone()).unwrap()); assert_eq!(parsed.name, "%FF+雪"); for (name, value) in [("count", "1.5"), ("enabled", "1"), ("extra", "x")] { let mut invalid = fields.clone(); invalid.insert(name.into(), value.into()); assert!( Input::fields(invalid.clone()) .decode::(crate::wire::decode) .is_err() ); assert!(native_form(invalid).is_none()); } let mut missing = fields; missing.remove("enabled"); assert!( Input::fields(missing) .decode::(crate::wire::decode) .is_err() ); } fn query_request(query: &str) -> Request { hyper::Request::builder() .uri(format!("/test?{query}")) .body(Default::default()) .unwrap() } #[test] fn scalar_fields_decode_without_changing_argument_rules() { let request = query_request("name=%E9%9B%AA+test&count=-7&enabled=true"); assert_eq!( input(&request) .unwrap() .decode::(crate::wire::decode) .unwrap(), Arguments { name: "雪 test".into(), count: -7, enabled: true } ); for query in [ "name=x&name=y", "unknown=x", "count=1.5", "enabled=1", "name=%FF", "name=%", ] { assert!( input(&query_request(query)) .unwrap() .decode::(crate::wire::decode) .is_err(), "{query}" ); } assert!( input(&query_request("name=x")) .unwrap() .decode::(crate::wire::decode) .is_err() ); } #[test] fn form_budgets_count_json_escaping_and_root_value() { let mut fields = BTreeMap::new(); fields.insert("text".into(), "\0".repeat(MAX_BYTES / 6)); assert!( Input::fields(fields) .decode::>(crate::wire::decode) .is_err() ); let fields = (0..MAX_VALUES - 1) .map(|index| (index.to_string(), "false".to_owned())) .collect::>(); assert!( Input::fields(fields.clone()) .decode::>(crate::wire::decode) .is_ok() ); let mut fields = fields; fields.insert("extra".into(), "true".to_owned()); assert!( Input::fields(fields) .decode::>(crate::wire::decode) .is_err() ); } #[test] fn query_rejects_bad_trailing_optional_fields_and_non_finite_numbers() { #[derive(Debug, serde::Deserialize)] #[serde(deny_unknown_fields)] struct Scalars { ratio: f64, tag: Option, } for query in [ "ratio=1&tag=%", "ratio=1&tag=%FF", "ratio=1&tag", "ratio=1&tag=a&tag=b", "ratio=1&extra=x", "ratio=NaN", "ratio=inf", "ratio=1e999", ] { assert!( input(&query_request(query)) .unwrap() .decode::(crate::wire::decode) .is_err(), "{query}" ); } let result: Scalars = input(&query_request("ratio=-0&tag=%E9%9B%AA+test")) .unwrap() .decode(crate::wire::decode) .unwrap(); assert_eq!(result.ratio.to_bits(), (-0.0f64).to_bits()); assert_eq!(result.tag.as_deref(), Some("雪 test")); } #[test] fn large_query_uses_exact_existing_json_byte_budget() { #[derive(Debug, serde::Deserialize)] #[serde(deny_unknown_fields)] struct TextArgument { text: String, } let overhead = serde_json::to_vec(&serde_json::json!({"text":""})) .unwrap() .len(); for (plain, count) in [ (true, MAX_BYTES / 6 + 1), (true, MAX_BYTES - overhead), (true, MAX_BYTES - overhead + 1), (false, (MAX_BYTES - overhead) / 6), (false, (MAX_BYTES - overhead) / 6 + 1), ] { let text = if plain { "a" } else { "\0" }.repeat(count); let encoded = if plain { "a" } else { "%00" }.repeat(count); let source = serde_json::to_string(&serde_json::json!({"text": text})).unwrap(); let expected = crate::wire::decode::(&source); let fields = BTreeMap::from([("text".into(), text)]); let form = Input::fields(fields).decode::(crate::wire::decode); assert_eq!(form.is_ok(), expected.is_ok()); if let (Ok(form), Ok(expected)) = (&form, &expected) { assert_eq!(form.text, expected.text); } // 此处独立验证字段 codec 的 JSON 预算;超长 URI 会先被 HTTP 库拒绝。 let query = format!("text={encoded}"); let actual = Input(Data::Query(&query)).decode::(crate::wire::decode); assert_eq!( actual.is_ok(), expected.is_ok(), "plain={plain}, count={count}" ); if let (Ok(actual), Ok(expected)) = (actual, expected) { assert_eq!(actual.text, expected.text); } } } #[cfg(all(feature = "calendar", feature = "uuid"))] #[test] fn query_uses_existing_scalar_codecs_and_optional_types() { use crate::{ time::calendar::{Date, DateTime, Duration, Time}, value::{decimal::Decimal, id::Id, uuid::Uuid}, }; #[derive(Debug, PartialEq, serde::Deserialize)] #[serde(deny_unknown_fields)] struct Scalars { decimal: Decimal, id: Id, uuid: Uuid, date: Date, time: Time, datetime: DateTime, duration: Duration, count: Option, enabled: Option, ratio: Option, } let fields = serde_json::json!({ "decimal": "123456789.0123456789", "id": "雪", "uuid": "550e8400-e29b-41d4-a716-446655440000", "date": "2026-10-26", "time": "12:34:56.789", "datetime": "2026-10-26T12:34:56Z", "duration": -1500, "count": 7, "enabled": true, "ratio": 1.25 }); let expected: Scalars = crate::wire::decode(&fields.to_string()).unwrap(); let query = "decimal=123456789.0123456789&id=%E9%9B%AA&uuid=550e8400-e29b-41d4-a716-446655440000&date=2026-10-26&time=12:34:56.789&datetime=2026-10-26T12:34:56Z&duration=-1500&count=7&enabled=true&ratio=1.25"; let actual: Scalars = input(&query_request(query)) .unwrap() .decode(crate::wire::decode) .unwrap(); assert_eq!(actual, expected); for (valid, invalid) in [ ("123456789.0123456789", "NaN"), ("550e8400-e29b-41d4-a716-446655440000", "bad-uuid"), ("date=2026-10-26", "date=2026-02-30"), ("time=12:34:56.789", "time=25:34:56.789"), ("duration=-1500", "duration=1.5"), ("count=7", "count=9223372036854775808"), ("enabled=true", "enabled=yes"), ("ratio=1.25", "ratio=NaN"), ] { assert!( input(&query_request(&query.replace(valid, invalid))) .unwrap() .decode::(crate::wire::decode) .is_err(), "{invalid}" ); } }