Skip to content

Commit b361fd8

Browse files
committed
Preserve \Accept\ header order in Quarkus REST
Fixes: #51078
1 parent 7e5adfb commit b361fd8

File tree

3 files changed

+67
-20
lines changed

3 files changed

+67
-20
lines changed

independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/headers/HeaderUtil.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ public static List<MediaType> getAcceptableMediaTypes(MultivaluedMap<String, ? e
287287
list.add(MediaTypeHelper.valueOf(accept.trim()));
288288
}
289289
}
290+
// Sort by weight (q) while preserving header-provided order on total ties
290291
MediaTypeHelper.sortByWeight(list);
291292
return list;
292293
}

independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/MediaTypeHelper.java

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -127,25 +127,6 @@ public int compare(MediaType mediaType2, MediaType mediaType) {
127127
if (!isWildcardCompositeSubtype(mediaType.getSubtype()) && isWildcardCompositeSubtype(mediaType2.getSubtype()))
128128
return 1;
129129

130-
int numNonQ = 0;
131-
if (mediaType.getParameters() != null) {
132-
numNonQ = mediaType.getParameters().size();
133-
if (wasQ)
134-
numNonQ--;
135-
}
136-
137-
int numNonQ2 = 0;
138-
if (mediaType2.getParameters() != null) {
139-
numNonQ2 = mediaType2.getParameters().size();
140-
if (wasQ2)
141-
numNonQ2--;
142-
}
143-
144-
if (numNonQ < numNonQ2)
145-
return -1;
146-
if (numNonQ > numNonQ2)
147-
return 1;
148-
149130
return 0;
150131
}
151132
}
@@ -165,7 +146,22 @@ public static void sortByWeight(List<MediaType> types) {
165146
if (hasAtMostOneItem(types)) {
166147
return;
167148
}
168-
types.sort(Q_COMPARATOR);
149+
// Stable sort: preserve original order when comparator returns 0
150+
List<IndexedMediaType> indexed = new ArrayList<>(types.size());
151+
for (int i = 0; i < types.size(); i++) {
152+
indexed.add(new IndexedMediaType(types.get(i), i));
153+
}
154+
indexed.sort(new Comparator<IndexedMediaType>() {
155+
@Override
156+
public int compare(IndexedMediaType a, IndexedMediaType b) {
157+
int cmp = Q_COMPARATOR.compare(a.mediaType, b.mediaType);
158+
return cmp != 0 ? cmp : Integer.compare(a.originalIndex, b.originalIndex);
159+
}
160+
});
161+
types.clear();
162+
for (IndexedMediaType imt : indexed) {
163+
types.add(imt.mediaType);
164+
}
169165
}
170166

171167
public static void sortByQSWeight(List<MediaType> types) {
@@ -365,4 +361,7 @@ private static int countMatchingMediaTypes(List<MediaType> produces, List<MediaT
365361

366362
return count;
367363
}
364+
365+
private record IndexedMediaType(MediaType mediaType, int originalIndex) {
366+
}
368367
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.jboss.resteasy.reactive.server.vertx.test.resource.basic;
2+
3+
import static io.restassured.RestAssured.given;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
6+
import java.util.stream.Collectors;
7+
8+
import jakarta.ws.rs.GET;
9+
import jakarta.ws.rs.Path;
10+
import jakarta.ws.rs.core.Context;
11+
import jakarta.ws.rs.core.HttpHeaders;
12+
import jakarta.ws.rs.core.MediaType;
13+
14+
import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest;
15+
import org.jboss.shrinkwrap.api.ShrinkWrap;
16+
import org.jboss.shrinkwrap.api.spec.JavaArchive;
17+
import org.junit.jupiter.api.Test;
18+
import org.junit.jupiter.api.extension.RegisterExtension;
19+
20+
public class AcceptHeaderOrderTest {
21+
22+
@RegisterExtension
23+
static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest()
24+
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(Resource.class));
25+
26+
@Path("/headers")
27+
public static class Resource {
28+
@GET
29+
@Path("/amt")
30+
public String amt(@Context HttpHeaders hs) {
31+
return hs.getAcceptableMediaTypes().stream().map(MediaType::toString).collect(Collectors.joining(","));
32+
}
33+
}
34+
35+
@Test
36+
public void preservesOrderOnTies() {
37+
String response = given()
38+
.header("Accept",
39+
"application/json,text/html; charset=UTF-8,text/plain; charset=UTF-8,*/*;q=0.8")
40+
.get("/headers/amt")
41+
.then()
42+
.statusCode(200)
43+
.extract()
44+
.asString();
45+
assertEquals("application/json,text/html;charset=UTF-8,text/plain;charset=UTF-8,*/*;q=0.8", response);
46+
}
47+
}

0 commit comments

Comments
 (0)