@@ -62,41 +62,40 @@ static void apply(
6262 BiConsumer <String , String > headerSink ) {
6363 WireHeaderReader r = new WireHeaderReader (buf , off , len );
6464 int status = 500 ;
65- if (r .peek () == '{' ) {
66- r .beginObject ();
67- int seen = 0 ;
68- int key ;
69- while ((key = r .nextRootKey ()) != KEY_END ) {
70- seen = r .rejectDuplicateRootKey (seen , key );
71- switch (key ) {
72- case KEY_STATUS -> status = r .readInt ();
73- case KEY_HEADERS -> {
74- if (r .isObjectStart ()) {
75- r .beginObject ();
76- String k ;
77- // Canonical keys reuse one shared String per common
78- // header name (content-type, content-length, …) —
79- // the same allocation-free path decode() uses, so
80- // the per-request DIRECT/streaming apply() no longer
81- // allocates a fresh key String for each header.
82- while ((k = r .nextKeyCanonical ()) != null ) {
83- if (r .isArrayStart ()) {
84- r .beginArray ();
85- while (r .hasNextElement ()) {
86- headerSink .accept (k , r .readString ());
87- }
88- } else {
65+ r .requireObjectStart ();
66+ r .beginObject ();
67+ int seen = 0 ;
68+ int key ;
69+ while ((key = r .nextRootKey ()) != KEY_END ) {
70+ seen = r .rejectDuplicateRootKey (seen , key );
71+ switch (key ) {
72+ case KEY_STATUS -> status = r .readInt ();
73+ case KEY_HEADERS -> {
74+ if (r .isObjectStart ()) {
75+ r .beginObject ();
76+ String k ;
77+ // Canonical keys reuse one shared String per common
78+ // header name (content-type, content-length, …) —
79+ // the same allocation-free path decode() uses, so
80+ // the per-request DIRECT/streaming apply() no longer
81+ // allocates a fresh key String for each header.
82+ while ((k = r .nextKeyCanonical ()) != null ) {
83+ if (r .isArrayStart ()) {
84+ r .beginArray ();
85+ while (r .hasNextElement ()) {
8986 headerSink .accept (k , r .readString ());
9087 }
88+ } else {
89+ headerSink .accept (k , r .readString ());
9190 }
92- } else {
93- r .skipValue ();
9491 }
92+ } else {
93+ r .skipValue ();
9594 }
96- // KEY_OTHER: "v", "metadata", "validation_errors", … —
97- // matched by bytes, value skipped, never materialised.
98- default -> r .skipValue ();
9995 }
96+ // KEY_OTHER: "v", "metadata", "validation_errors", … —
97+ // matched by bytes, value skipped, never materialised.
98+ default -> r .skipValue ();
10099 }
101100 }
102101 statusSink .accept (status );
@@ -133,76 +132,75 @@ static final class Decoded {
133132 static Decoded decode (ByteBuffer buf , int off , int len ) {
134133 WireHeaderReader r = new WireHeaderReader (buf , off , len );
135134 Decoded out = new Decoded ();
136- if (r .peek () == '{' ) {
137- r .beginObject ();
138- int seen = 0 ;
139- int key ;
140- while ((key = r .nextRootKey ()) != KEY_END ) {
141- seen = r .rejectDuplicateRootKey (seen , key );
142- switch (key ) {
143- case KEY_STATUS -> out .status = r .readInt ();
144- case KEY_HEADERS -> {
145- if (r .isObjectStart ()) {
146- r .beginObject ();
147- String k ;
148- while ((k = r .nextKeyCanonical ()) != null ) {
149- if (out .headers == null ) {
150- // Pre-size for a typical response header
151- // count (content-type, content-length, …).
152- out .headers = new LinkedHashMap <>(8 );
153- }
154- if (r .isArrayStart ()) {
155- r .beginArray ();
156- List <String > list = new ArrayList <>();
157- while (r .hasNextElement ()) {
158- list .add (r .readString ());
159- }
160- out .headers .put (k , list );
161- } else {
162- out .headers .put (k , r .readString ());
135+ r .requireObjectStart ();
136+ r .beginObject ();
137+ int seen = 0 ;
138+ int key ;
139+ while ((key = r .nextRootKey ()) != KEY_END ) {
140+ seen = r .rejectDuplicateRootKey (seen , key );
141+ switch (key ) {
142+ case KEY_STATUS -> out .status = r .readInt ();
143+ case KEY_HEADERS -> {
144+ if (r .isObjectStart ()) {
145+ r .beginObject ();
146+ String k ;
147+ while ((k = r .nextKeyCanonical ()) != null ) {
148+ if (out .headers == null ) {
149+ // Pre-size for a typical response header
150+ // count (content-type, content-length, …).
151+ out .headers = new LinkedHashMap <>(8 );
152+ }
153+ if (r .isArrayStart ()) {
154+ r .beginArray ();
155+ List <String > list = new ArrayList <>();
156+ while (r .hasNextElement ()) {
157+ list .add (r .readString ());
163158 }
159+ out .headers .put (k , list );
160+ } else {
161+ out .headers .put (k , r .readString ());
164162 }
165- } else {
166- r .skipValue ();
167163 }
164+ } else {
165+ r .skipValue ();
168166 }
169- case KEY_METADATA -> {
170- if ( r . isObjectStart ()) {
171- r . beginObject ();
172- out . metadata = r . readStringMap ();
173- } else {
174- r . skipValue ();
175- }
167+ }
168+ case KEY_METADATA -> {
169+ if ( r . isObjectStart ()) {
170+ r . beginObject ();
171+ out . metadata = r . readStringMap ();
172+ } else {
173+ r . skipValue ();
176174 }
177- case KEY_VALIDATION -> {
178- if (r .isArrayStart ()) {
179- r .beginArray ();
180- out .validationErrors = new ArrayList <>();
181- while (r .hasNextElement ()) {
182- if (!r .isObjectStart ()) {
183- // Fixed schema is an array of objects; a
184- // non-object element (only on malformed
185- // input) is skipped so the cursor still
186- // reaches the array end cleanly.
187- r .skipValue ();
188- continue ;
189- }
190- r .beginObject ();
191- Map <String , Object > entry = new LinkedHashMap <>(4 );
192- String k ;
193- while ((k = r .nextKeyCanonical ()) != null ) {
194- entry .put (k , r .readPrimitiveValue ());
195- }
196- out .validationErrors .add (entry );
175+ }
176+ case KEY_VALIDATION -> {
177+ if (r .isArrayStart ()) {
178+ r .beginArray ();
179+ out .validationErrors = new ArrayList <>();
180+ while (r .hasNextElement ()) {
181+ if (!r .isObjectStart ()) {
182+ // Fixed schema is an array of objects; a
183+ // non-object element (only on malformed
184+ // input) is skipped so the cursor still
185+ // reaches the array end cleanly.
186+ r .skipValue ();
187+ continue ;
188+ }
189+ r .beginObject ();
190+ Map <String , Object > entry = new LinkedHashMap <>(4 );
191+ String k ;
192+ while ((k = r .nextKeyCanonical ()) != null ) {
193+ entry .put (k , r .readPrimitiveValue ());
197194 }
198- } else {
199- r .skipValue ();
195+ out .validationErrors .add (entry );
200196 }
197+ } else {
198+ r .skipValue ();
201199 }
202- // KEY_OTHER: "v" and any unknown field — value skipped,
203- // never materialised.
204- default -> r .skipValue ();
205200 }
201+ // KEY_OTHER: "v" and any unknown field — value skipped,
202+ // never materialised.
203+ default -> r .skipValue ();
206204 }
207205 }
208206 return out ;
@@ -261,6 +259,12 @@ private IllegalArgumentException err(String what) {
261259 return new IllegalArgumentException ("wire header JSON: " + what + " at offset " + pos );
262260 }
263261
262+ private void requireObjectStart () {
263+ if (peek () != '{' ) {
264+ throw err ("expected object" );
265+ }
266+ }
267+
264268 private int rejectDuplicateRootKey (int seen , int key ) {
265269 if (key < 0 ) {
266270 return seen ;
@@ -310,8 +314,8 @@ String nextKey() {
310314 * construction (HTTP field names + the fixed metadata / validation keys).
311315 */
312316 /**
313- * If the upcoming quoted member key is a plain-ASCII {@link #CANONICAL_KEYS}
314- * entry, consume it (key + closing quote) and return the shared instance;
317+ * If the upcoming quoted member key is a plain-ASCII canonical-key entry,
318+ * consume it (key + closing quote) and return the shared instance;
315319 * otherwise leave {@code pos} untouched and return {@code null} so the
316320 * caller falls back to {@link #readString()} — escaped / non-ASCII /
317321 * unknown keys still allocate exactly as before.
0 commit comments