Skip to content

Commit 11fab7d

Browse files
committed
Polishing
See gh-449
1 parent d38ffea commit 11fab7d

File tree

1 file changed

+82
-89
lines changed

1 file changed

+82
-89
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/GraphQlArgumentBinder.java

Lines changed: 82 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
import org.springframework.core.convert.ConversionService;
3939
import org.springframework.core.convert.TypeDescriptor;
4040
import org.springframework.lang.Nullable;
41-
import org.springframework.util.Assert;
4241
import org.springframework.validation.BindException;
4342
import org.springframework.validation.BindingErrorProcessor;
4443
import org.springframework.validation.BindingResult;
@@ -65,6 +64,7 @@ public class GraphQlArgumentBinder {
6564
*/
6665
private static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 1024;
6766

67+
6868
@Nullable
6969
private final SimpleTypeConverter typeConverter;
7070

@@ -139,10 +139,8 @@ public Object bind(
139139
segments.push(name);
140140
}
141141

142-
Class<?> targetClass = targetType.resolve();
143-
Assert.notNull(targetClass, "Could not determine target type from " + targetType);
144-
145-
Object targetValue = bindRawValue(rawValue, targetType, targetClass, bindingResult, segments);
142+
Object targetValue = bindRawValue(
143+
rawValue, targetType, targetType.resolve(Object.class), bindingResult, segments);
146144

147145
if (bindingResult.hasErrors()) {
148146
throw new BindException(bindingResult);
@@ -159,51 +157,43 @@ private void initDataBinder(DataBinder binder) {
159157
@SuppressWarnings({"ConstantConditions", "unchecked"})
160158
@Nullable
161159
private Object bindRawValue(
162-
Object rawValue, ResolvableType targetValueType, Class<?> targetValueClass,
160+
Object rawValue, ResolvableType targetType, Class<?> targetClass,
163161
BindingResult bindingResult, Stack<String> segments) {
164162

165-
boolean isOptional = targetValueClass == Optional.class;
163+
boolean isOptional = (targetClass == Optional.class);
166164

167165
if (isOptional) {
168-
targetValueType = targetValueType.getNested(2);
169-
targetValueClass = targetValueType.resolve();
166+
targetType = targetType.getNested(2);
167+
targetClass = targetType.resolve();
170168
}
171169

172-
Object targetValue;
173-
if (rawValue == null || targetValueClass == Object.class) {
174-
targetValue = rawValue;
170+
Object value;
171+
if (rawValue == null || targetClass == Object.class) {
172+
value = rawValue;
175173
}
176174
else if (rawValue instanceof Collection) {
177-
targetValue = bindCollection((Collection<Object>) rawValue, targetValueType, bindingResult, segments);
175+
value = bindCollection((Collection<Object>) rawValue, targetType, targetClass, bindingResult, segments);
178176
}
179177
else if (rawValue instanceof Map) {
180-
targetValue = bindMap((Map<String, Object>) rawValue, targetValueType, bindingResult, segments);
178+
value = bindMap((Map<String, Object>) rawValue, targetType, targetClass, bindingResult, segments);
181179
}
182180
else {
183-
targetValue = (targetValueClass.isAssignableFrom(rawValue.getClass()) ?
184-
rawValue : convertValue(rawValue, targetValueClass, bindingResult, segments));
181+
value = (targetClass.isAssignableFrom(rawValue.getClass()) ?
182+
rawValue : convertValue(rawValue, targetClass, bindingResult, segments));
185183
}
186184

187-
return (isOptional ? Optional.ofNullable(targetValue) : targetValue);
185+
return (isOptional ? Optional.ofNullable(value) : value);
188186
}
189187

190188
private Collection<?> bindCollection(
191-
Collection<Object> rawCollection, ResolvableType collectionType,
189+
Collection<Object> rawCollection, ResolvableType collectionType, Class<?> collectionClass,
192190
BindingResult bindingResult, Stack<String> segments) {
193191

194-
Class<?> collectionClass = collectionType.resolve();
195-
Assert.notNull(collectionClass, "Unknown Collection class");
196-
197-
if (!Collection.class.isAssignableFrom(collectionClass)) {
198-
bindingResult.rejectValue(toArgumentPath(segments), "typeMismatch", "Expected collection: " + collectionType);
199-
return Collections.emptyList();
200-
}
201-
202192
ResolvableType elementType = collectionType.asCollection().getGeneric(0);
203193
Class<?> elementClass = collectionType.asCollection().getGeneric(0).resolve();
204194
if (elementClass == null) {
205-
bindingResult.rejectValue(toArgumentPath(segments), "unknownElementType", "Unknown element type");
206-
return Collections.emptyList();
195+
bindingResult.rejectValue(toArgumentPath(segments), "unknownTargetType", "Unknown target type");
196+
return Collections.emptyList(); // Keep going, report as many errors as we can
207197
}
208198

209199
Collection<Object> collection =
@@ -215,58 +205,80 @@ private Collection<?> bindCollection(
215205
collection.add(bindRawValue(rawValue, elementType, elementClass, bindingResult, segments));
216206
segments.pop();
217207
}
208+
218209
return collection;
219210
}
220211

212+
private static String toArgumentPath(Stack<String> path) {
213+
StringBuilder sb = new StringBuilder();
214+
path.forEach(sb::append);
215+
return sb.toString();
216+
}
217+
221218
@Nullable
222219
private Object bindMap(
223-
Map<String, Object> rawMap, ResolvableType targetType, BindingResult bindingResult,
224-
Stack<String> segments) {
225-
226-
Class<?> targetClass = targetType.resolve();
227-
Assert.notNull(targetClass, "Unknown target class");
220+
Map<String, Object> rawMap, ResolvableType targetType, Class<?> targetClass,
221+
BindingResult bindingResult, Stack<String> segments) {
228222

229223
if (Map.class.isAssignableFrom(targetClass)) {
230-
ResolvableType valueType = targetType.asMap().getGeneric(1);
231-
Class<?> valueClass = valueType.resolve();
232-
if (valueClass == null) {
233-
bindingResult.rejectValue(toArgumentPath(segments), "unknownMapValueType", "Unknown Map value type");
234-
return Collections.emptyMap();
235-
}
236-
Map<String, Object> map = CollectionFactory.createMap(targetClass, rawMap.size());
237-
for (Map.Entry<String, Object> entry : rawMap.entrySet()) {
238-
String key = entry.getKey();
239-
segments.push("[" + key + "]");
240-
map.put(key, bindRawValue(entry.getValue(), valueType, valueClass, bindingResult, segments));
241-
segments.pop();
242-
}
243-
return map;
224+
return bindMapToMap(rawMap, targetType, bindingResult, segments, targetClass);
244225
}
245226

246-
Object target;
247227
Constructor<?> constructor = BeanUtils.getResolvableConstructor(targetClass);
228+
if (constructor.getParameterCount() > 0) {
229+
return bindMapToObjectViaConstructor(rawMap, constructor, bindingResult, segments);
230+
}
231+
232+
Object target = BeanUtils.instantiateClass(constructor);
233+
DataBinder dataBinder = new DataBinder(target);
234+
initDataBinder(dataBinder);
235+
dataBinder.getBindingResult().setNestedPath(toArgumentPath(segments));
236+
dataBinder.setConversionService(getConversionService());
237+
dataBinder.bind(createPropertyValues(rawMap));
238+
239+
if (dataBinder.getBindingResult().hasErrors()) {
240+
String nestedPath = dataBinder.getBindingResult().getNestedPath();
241+
for (FieldError error : dataBinder.getBindingResult().getFieldErrors()) {
242+
bindingResult.addError(
243+
new FieldError(bindingResult.getObjectName(), nestedPath + error.getField(),
244+
error.getRejectedValue(), error.isBindingFailure(), error.getCodes(),
245+
error.getArguments(), error.getDefaultMessage()));
246+
}
247+
return null;
248+
}
248249

249-
// Default constructor + data binding via properties
250+
return target;
251+
}
250252

251-
if (constructor.getParameterCount() == 0) {
252-
target = BeanUtils.instantiateClass(constructor);
253-
DataBinder dataBinder = new DataBinder(target);
254-
initDataBinder(dataBinder);
255-
dataBinder.getBindingResult().setNestedPath(toArgumentPath(segments));
256-
dataBinder.setConversionService(getConversionService());
257-
dataBinder.bind(toPropertyValues(rawMap));
253+
private Map<?, Object> bindMapToMap(
254+
Map<String, Object> rawMap, ResolvableType targetType, BindingResult bindingResult,
255+
Stack<String> segments, Class<?> targetClass) {
258256

259-
if (dataBinder.getBindingResult().hasErrors()) {
260-
copyBindingErrors(dataBinder, bindingResult, segments);
261-
return null;
262-
}
257+
ResolvableType valueType = targetType.asMap().getGeneric(1);
258+
Class<?> valueClass = valueType.resolve();
259+
if (valueClass == null) {
260+
bindingResult.rejectValue(toArgumentPath(segments), "unknownTargetType", "Unknown target type");
261+
return Collections.emptyMap(); // Keep going, report as many errors as we can
262+
}
263+
264+
Map<String, Object> map = CollectionFactory.createMap(targetClass, rawMap.size());
263265

264-
return target;
266+
for (Map.Entry<String, Object> entry : rawMap.entrySet()) {
267+
String key = entry.getKey();
268+
segments.push("[" + key + "]");
269+
map.put(key, bindRawValue(entry.getValue(), valueType, valueClass, bindingResult, segments));
270+
segments.pop();
265271
}
266272

267-
// Data class constructor
273+
return map;
274+
}
275+
276+
@Nullable
277+
private Object bindMapToObjectViaConstructor(
278+
Map<String, Object> rawMap, Constructor<?> constructor, BindingResult bindingResult,
279+
Stack<String> segments) {
268280

269-
if (!segments.isEmpty()) {
281+
if (segments.size() > 0) {
270282
segments.push(".");
271283
}
272284

@@ -290,15 +302,15 @@ private Object bindMap(
290302
return BeanUtils.instantiateClass(constructor, args);
291303
}
292304
catch (BeanInstantiationException ex) {
293-
// Ignore if we had binding errors already
305+
// Ignore: we had binding errors to begin with
294306
if (bindingResult.hasErrors()) {
295307
return null;
296308
}
297309
throw ex;
298310
}
299311
}
300312

301-
private static MutablePropertyValues toPropertyValues(Map<String, Object> rawMap) {
313+
private static MutablePropertyValues createPropertyValues(Map<String, Object> rawMap) {
302314
MutablePropertyValues mpvs = new MutablePropertyValues();
303315
Stack<String> segments = new Stack<>();
304316
for (String key : rawMap.keySet()) {
@@ -337,41 +349,22 @@ else if (value instanceof Map) {
337349
}
338350
}
339351

340-
private static String toArgumentPath(Stack<String> path) {
341-
StringBuilder sb = new StringBuilder();
342-
path.forEach(sb::append);
343-
return sb.toString();
344-
}
345-
346352
@SuppressWarnings("unchecked")
347353
@Nullable
348-
private <T> T convertValue(@Nullable Object rawValue, Class<T> type, BindingResult result, Stack<String> segments) {
349-
return (T) convertValue(rawValue, type, TypeDescriptor.valueOf(type), result, segments);
350-
}
351-
352-
@Nullable
353-
private Object convertValue(
354-
@Nullable Object rawValue, Class<?> type, TypeDescriptor descriptor,
355-
BindingResult bindingResult, Stack<String> segments) {
354+
private <T> T convertValue(
355+
@Nullable Object rawValue, Class<T> type, BindingResult bindingResult, Stack<String> segments) {
356356

357+
Object value = null;
357358
try {
358-
return getTypeConverter().convertIfNecessary(rawValue, type, descriptor);
359+
value = getTypeConverter().convertIfNecessary(rawValue, (Class<?>) type, TypeDescriptor.valueOf(type));
359360
}
360361
catch (TypeMismatchException ex) {
361362
String name = toArgumentPath(segments);
362363
ex.initPropertyName(name);
363364
bindingResult.recordFieldValue(name, type, rawValue);
364365
this.bindingErrorProcessor.processPropertyAccessException(ex, bindingResult);
365366
}
366-
return null;
367-
}
368-
369-
private static void copyBindingErrors(DataBinder binder, BindingResult bindingResult, Stack<String> segments) {
370-
String path = (!segments.isEmpty() ? toArgumentPath(segments) + "." : "");
371-
binder.getBindingResult().getFieldErrors().forEach(error -> bindingResult.addError(
372-
new FieldError(bindingResult.getObjectName(), path + error.getField(),
373-
error.getRejectedValue(), error.isBindingFailure(), error.getCodes(),
374-
error.getArguments(), error.getDefaultMessage())));
367+
return (T) value;
375368
}
376369

377370
}

0 commit comments

Comments
 (0)