Skip to content

Commit f43536d

Browse files
committed
Add Record support in PageableSpringQueryMapEncoder
- Add Record type detection using Class.isRecord() - Delegate Record encoding to FieldQueryMapEncoder - Maintain backward compatibility for Pageable/Sort/Bean - Add test coverage for Record scenarios Fixes gh-1266
1 parent 0bcb831 commit f43536d

File tree

2 files changed

+177
-2
lines changed

2 files changed

+177
-2
lines changed

spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/PageableSpringQueryMapEncoder.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@
1717
package org.springframework.cloud.openfeign.support;
1818

1919
import java.util.ArrayList;
20+
import java.util.Collections;
2021
import java.util.HashMap;
2122
import java.util.List;
2223
import java.util.Map;
2324

25+
import feign.QueryMapEncoder;
2426
import feign.querymap.BeanQueryMapEncoder;
27+
import feign.querymap.FieldQueryMapEncoder;
2528

2629
import org.springframework.data.domain.Pageable;
2730
import org.springframework.data.domain.Sort;
@@ -37,6 +40,12 @@
3740
*/
3841
public class PageableSpringQueryMapEncoder extends BeanQueryMapEncoder {
3942

43+
/**
44+
* QueryMapEncoder for Java Record types. Uses field-based reflection to encode Record
45+
* components as query parameters.
46+
*/
47+
private final QueryMapEncoder recordEncoder = new FieldQueryMapEncoder();
48+
4049
/**
4150
* Page index parameter name.
4251
*/
@@ -71,6 +80,13 @@ public void setSortParameter(String sortParameter) {
7180

7281
@Override
7382
public Map<String, Object> encode(Object object) {
83+
// Null safety: return empty map for null input
84+
if (object == null) {
85+
return Collections.emptyMap();
86+
}
87+
88+
// Priority 1: Pageable handling (existing logic)
89+
// Must be checked before Record to avoid serialVersionUID conflicts
7490
if (supports(object)) {
7591
Map<String, Object> queryMap = new HashMap<>();
7692

@@ -90,9 +106,15 @@ else if (object instanceof Sort sort) {
90106
}
91107
return queryMap;
92108
}
93-
else {
94-
return super.encode(object);
109+
110+
// Priority 2: Java Record handling (new logic)
111+
// Records require field-based reflection instead of getter methods
112+
if (object.getClass().isRecord()) {
113+
return recordEncoder.encode(object);
95114
}
115+
116+
// Priority 3: Bean/POJO handling (existing fallback)
117+
return super.encode(object);
96118
}
97119

98120
private void applySort(Map<String, Object> queryMap, Sort sort) {
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* Copyright 2013-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.openfeign.support;
18+
19+
import java.util.Map;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import org.springframework.data.domain.PageRequest;
24+
import org.springframework.data.domain.Pageable;
25+
import org.springframework.data.domain.Sort;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
29+
/**
30+
* Tests for Java Record support in {@link PageableSpringQueryMapEncoder}.
31+
*
32+
* @author Joo (Seongho)
33+
*/
34+
class RecordQueryMapEncoderTests {
35+
36+
private final PageableSpringQueryMapEncoder encoder = new PageableSpringQueryMapEncoder();
37+
38+
@Test
39+
void testSimpleRecordEncoding() {
40+
// TC1: Record Only
41+
SimpleRecord record = new SimpleRecord("hello", 1);
42+
43+
Map<String, Object> result = encoder.encode(record);
44+
45+
assertThat(result).isNotNull();
46+
assertThat(result).hasSize(2);
47+
assertThat(result.get("keyword")).isEqualTo("hello");
48+
assertThat(result.get("page")).isEqualTo(1);
49+
}
50+
51+
@Test
52+
void testEmptyRecordEncoding() {
53+
// TC4: Empty Record
54+
EmptyRecord record = new EmptyRecord();
55+
56+
Map<String, Object> result = encoder.encode(record);
57+
58+
assertThat(result).isNotNull();
59+
assertThat(result).isEmpty();
60+
}
61+
62+
@Test
63+
void testRecordWithNullField() {
64+
// TC5: Record with null field
65+
RecordWithNull record = new RecordWithNull(null);
66+
67+
Map<String, Object> result = encoder.encode(record);
68+
69+
assertThat(result).isNotNull();
70+
// FieldQueryMapEncoder excludes null fields
71+
assertThat(result).isEmpty();
72+
}
73+
74+
@Test
75+
void testNullInput() {
76+
// TC6: Null input
77+
Map<String, Object> result = encoder.encode(null);
78+
79+
assertThat(result).isNotNull();
80+
assertThat(result).isEmpty();
81+
}
82+
83+
@Test
84+
void testPageableEncodingNoConflict() {
85+
// TC2 & TC7: Pageable encoding - ensure no serialVersionUID conflict
86+
Pageable pageable = PageRequest.of(2, 20, Sort.by("name").ascending());
87+
88+
Map<String, Object> result = encoder.encode(pageable);
89+
90+
assertThat(result).isNotNull();
91+
assertThat(result).containsEntry("page", 2);
92+
assertThat(result).containsEntry("size", 20);
93+
assertThat(result).containsKey("sort");
94+
// Most important: no IllegalStateException with "Duplicate key serialVersionUID"
95+
}
96+
97+
@Test
98+
void testSortEncoding() {
99+
// Sort encoding test
100+
Sort sort = Sort.by("name", "age").ascending();
101+
102+
Map<String, Object> result = encoder.encode(sort);
103+
104+
assertThat(result).isNotNull();
105+
assertThat(result).containsKey("sort");
106+
}
107+
108+
@Test
109+
void testPojoFallback() {
110+
// TC3: Bean/POJO fallback to BeanQueryMapEncoder
111+
SimplePojo pojo = new SimplePojo("John", 30);
112+
113+
Map<String, Object> result = encoder.encode(pojo);
114+
115+
assertThat(result).isNotNull();
116+
assertThat(result).hasSize(2);
117+
assertThat(result.get("name")).isEqualTo("John");
118+
assertThat(result.get("age")).isEqualTo(30);
119+
}
120+
121+
// Test Records (local definitions)
122+
record SimpleRecord(String keyword, int page) {
123+
}
124+
125+
record EmptyRecord() {
126+
}
127+
128+
record RecordWithNull(String value) {
129+
}
130+
131+
// Test POJO for fallback testing
132+
public static class SimplePojo {
133+
134+
private final String name;
135+
136+
private final int age;
137+
138+
SimplePojo(String name, int age) {
139+
this.name = name;
140+
this.age = age;
141+
}
142+
143+
public String getName() {
144+
return name;
145+
}
146+
147+
public int getAge() {
148+
return age;
149+
}
150+
151+
}
152+
153+
}

0 commit comments

Comments
 (0)