@@ -76,13 +76,33 @@ async fn echo(body: Bytes) -> Bytes {
7676 body
7777}
7878
79+ /// Returns a `422` with a JSON `{"errors":[...]}` body so the wire path
80+ /// exercises the `to_wire_bytes` 422 `validation_errors` hoist plus the
81+ /// header-capacity estimate. Two realistic errors push the hoisted header
82+ /// JSON past the `WIRE_HEADER_RESERVE` floor, so a build whose capacity
83+ /// estimate ignores the hoisted errors reallocates once mid-serialize; the
84+ /// validation-errors capacity estimate removes that realloc.
85+ async fn validate_fail ( ) -> axum:: response:: Response {
86+ use axum:: response:: IntoResponse ;
87+ (
88+ axum:: http:: StatusCode :: UNPROCESSABLE_ENTITY ,
89+ [ (
90+ axum:: http:: header:: CONTENT_TYPE ,
91+ axum:: http:: HeaderValue :: from_static ( "application/json" ) ,
92+ ) ] ,
93+ r#"{"errors":[{"path":"username","message":"length is lower than 3"},{"path":"email","message":"not a valid email"}]}"# ,
94+ )
95+ . into_response ( )
96+ }
97+
7998fn install ( ) {
8099 static INIT : Once = Once :: new ( ) ;
81100 INIT . call_once ( || {
82101 register_app ( || {
83102 Router :: new ( )
84103 . route ( "/ping" , get ( ping) )
85104 . route ( "/echo" , post ( echo) )
105+ . route ( "/validate" , post ( validate_fail) )
86106 } ) ;
87107 } ) ;
88108}
@@ -209,6 +229,21 @@ fn allocation_budgets() {
209229 let _ = dispatch_into ( wire_get. clone ( ) , & mut out, & rt) ;
210230 } ) ;
211231
232+ // ── Case F: 422 JSON response via the buffered materialise path. The
233+ // `to_wire_bytes` 422 path hoists `{"errors":[...]}` into the wire
234+ // header's `validation_errors`. With the hoisted-errors length folded
235+ // into the capacity estimate the response `Vec` is sized to serialise the
236+ // header in one shot — the realloc a hoist-blind estimate paid is gone.
237+ let wire_validate = encode (
238+ "POST" ,
239+ "/validate" ,
240+ & [ ( "content-type" , "application/json" ) ] ,
241+ br#"{"x":1}"# ,
242+ ) ;
243+ let validate_422 = measure ( 200 , 2000 , || {
244+ let _ = dispatch_from_bytes ( wire_validate. clone ( ) , & rt) ;
245+ } ) ;
246+
212247 // (label, sample, budget). The gate metric is total per-op allocation
213248 // OPS (`alloc` + `realloc` calls) — the deterministic, noise-free
214249 // figure; bytes/op is informational only.
@@ -234,6 +269,7 @@ fn allocation_budgets() {
234269 & direct_into,
235270 BUDGET_DISPATCH_INTO ,
236271 ) ,
272+ ( "F 422-validate materialise" , & validate_422, BUDGET_VALIDATE_422 ) ,
237273 ] ;
238274
239275 // Print every case first so a regression failure still shows the full
@@ -282,3 +318,8 @@ const BUDGET_HEADERS_POST: usize = 40; // borrowed: 40 alloc + 0 realloc (header
282318// path copy (or any other owned-path allocation) trips these tightened budgets.
283319const BUDGET_MATERIALISE : usize = 16 ; // dispatch_from_bytes: +input clone +response Vec, URI shared
284320const BUDGET_DISPATCH_INTO : usize = 15 ; // dispatch_into: +input clone, reused out, URI shared
321+ // 422 materialise path: the hoisted `validation_errors` JSON is now folded
322+ // into the response-`Vec` capacity estimate, so the wire header serialises
323+ // without the mid-write realloc a hoist-blind estimate paid (26 alloc + 0
324+ // realloc; was 26 alloc + 1 realloc). A re-introduced realloc trips this.
325+ const BUDGET_VALIDATE_422 : usize = 26 ; // realloc-free 422 hoist (was 27 w/ realloc)
0 commit comments