@@ -14,19 +14,20 @@ use super::fetch::{
1414use super :: http:: get_client;
1515use crate :: model:: manifest:: { CoreVersionManifest , FullManifest } ;
1616
17- /// Parse JSON bytes on rayon's CPU thread pool (native) or inline
18- /// (wasm32). Keeps the tokio runtime free of `simd_json` work so other
19- /// in-flight manifest fetches keep driving network IO while this one
20- /// parses.
21- pub ( crate ) async fn parse_json_off_runtime < T > ( bytes : Bytes ) -> Result < T , anyhow:: Error >
17+ /// Parse a JSON buffer on rayon's CPU thread pool (native) or inline
18+ /// (wasm32). The buffer is consumed because `simd_json` mutates it in-place.
19+ /// Keeps the tokio runtime free of `simd_json` work so other in-flight
20+ /// manifest fetches keep driving network IO while this one parses.
21+ pub ( crate ) async fn parse_json_vec_off_runtime < T > (
22+ mut parse_buf : Vec < u8 > ,
23+ ) -> Result < T , anyhow:: Error >
2224where
2325 T : serde:: de:: DeserializeOwned + Send + ' static ,
2426{
2527 #[ cfg( not( target_arch = "wasm32" ) ) ]
2628 {
2729 let ( tx, rx) = tokio:: sync:: oneshot:: channel ( ) ;
2830 rayon:: spawn ( move || {
29- let mut parse_buf = bytes. to_vec ( ) ;
3031 let result = simd_json:: serde:: from_slice :: < T > ( & mut parse_buf)
3132 . map_err ( |e| anyhow ! ( "JSON parse error: {e}" ) ) ;
3233 let _ = tx. send ( result) ;
3637 }
3738 #[ cfg( target_arch = "wasm32" ) ]
3839 {
39- let mut parse_buf = bytes. to_vec ( ) ;
4040 simd_json:: serde:: from_slice :: < T > ( & mut parse_buf)
4141 . map_err ( |e| anyhow ! ( "JSON parse error: {e}" ) )
4242 }
@@ -71,7 +71,8 @@ pub(crate) async fn parse_full_manifest_off_runtime(
7171 }
7272 #[ cfg( target_arch = "wasm32" ) ]
7373 {
74- let mut manifest: FullManifest = parse_json_off_runtime ( raw_bytes. clone ( ) ) . await ?;
74+ let mut manifest: FullManifest =
75+ parse_json_vec_off_runtime ( raw_bytes. clone ( ) . to_vec ( ) ) . await ?;
7576 manifest. raw = raw_bytes;
7677 Ok ( manifest)
7778 }
@@ -220,6 +221,28 @@ pub async fn fetch_full_manifest_fresh(
220221 }
221222}
222223
224+ #[ cfg( not( target_arch = "wasm32" ) ) ]
225+ async fn read_body_vec ( mut response : reqwest:: Response ) -> Result < Vec < u8 > , FetchError > {
226+ let capacity = response
227+ . content_length ( )
228+ . and_then ( |len| usize:: try_from ( len) . ok ( ) )
229+ . unwrap_or ( 0 ) ;
230+ let mut body = Vec :: with_capacity ( capacity) ;
231+ while let Some ( chunk) = response. chunk ( ) . await . map_err ( classify_reqwest_error) ? {
232+ body. extend_from_slice ( & chunk) ;
233+ }
234+ Ok ( body)
235+ }
236+
237+ #[ cfg( target_arch = "wasm32" ) ]
238+ async fn read_body_vec ( response : reqwest:: Response ) -> Result < Vec < u8 > , FetchError > {
239+ response
240+ . bytes ( )
241+ . await
242+ . map ( |bytes| bytes. to_vec ( ) )
243+ . map_err ( classify_reqwest_error)
244+ }
245+
223246/// Options for fetching a version manifest.
224247pub struct FetchVersionManifestOptions < ' a > {
225248 pub registry_url : & ' a str ,
@@ -230,6 +253,17 @@ pub struct FetchVersionManifestOptions<'a> {
230253
231254/// Fetch version manifest bytes with retry, without parsing.
232255pub async fn fetch_version_manifest_bytes ( opts : FetchVersionManifestOptions < ' _ > ) -> Result < Bytes > {
256+ fetch_version_manifest_vec ( opts) . await . map ( Bytes :: from)
257+ }
258+
259+ /// Fetch version manifest into a mutable parse buffer with retry.
260+ ///
261+ /// Unlike full manifests, exact-version manifests do not need to keep raw
262+ /// response bytes for later extraction. Reading directly into `Vec<u8>` avoids
263+ /// the hot-path `Bytes -> Vec` copy before `simd_json` parses in place.
264+ pub ( crate ) async fn fetch_version_manifest_vec (
265+ opts : FetchVersionManifestOptions < ' _ > ,
266+ ) -> Result < Vec < u8 > > {
233267 let url = format ! ( "{}/{}/{}" , opts. registry_url, opts. name, opts. spec) ;
234268
235269 let accept = match opts. format {
@@ -251,7 +285,7 @@ pub async fn fetch_version_manifest_bytes(opts: FetchVersionManifestOptions<'_>)
251285 . map_err ( classify_reqwest_error) ?;
252286
253287 if response. status ( ) . is_success ( ) {
254- response . bytes ( ) . await . map_err ( classify_reqwest_error )
288+ read_body_vec ( response ) . await
255289 } else {
256290 Err ( classify_status ( response. status ( ) , & url) )
257291 }
@@ -271,6 +305,36 @@ pub async fn fetch_version_manifest_bytes(opts: FetchVersionManifestOptions<'_>)
271305pub async fn fetch_version_manifest (
272306 opts : FetchVersionManifestOptions < ' _ > ,
273307) -> Result < CoreVersionManifest > {
274- let bytes = fetch_version_manifest_bytes ( opts) . await ?;
275- parse_json_off_runtime :: < CoreVersionManifest > ( bytes) . await
308+ let bytes = fetch_version_manifest_vec ( opts) . await ?;
309+ parse_json_vec_off_runtime :: < CoreVersionManifest > ( bytes) . await
310+ }
311+
312+ #[ cfg( test) ]
313+ mod tests {
314+ use serde:: Deserialize ;
315+
316+ use super :: * ;
317+
318+ #[ derive( Debug , Deserialize , PartialEq ) ]
319+ struct TinyManifest {
320+ name : String ,
321+ version : String ,
322+ }
323+
324+ #[ tokio:: test]
325+ async fn parse_json_vec_off_runtime_consumes_mutable_buffer ( ) {
326+ let parsed = parse_json_vec_off_runtime :: < TinyManifest > (
327+ br#"{"name":"demo","version":"1.0.0"}"# . to_vec ( ) ,
328+ )
329+ . await
330+ . unwrap ( ) ;
331+
332+ assert_eq ! (
333+ parsed,
334+ TinyManifest {
335+ name: "demo" . to_string( ) ,
336+ version: "1.0.0" . to_string( ) ,
337+ }
338+ ) ;
339+ }
276340}
0 commit comments