Add request binding to functional endpoints
This commit introduces form binding to ServerRequest in both WebMVC.fn and WebFlux.fn's, in the form of a bind(Class) method. Closes gh-25943
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -36,6 +36,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
@@ -47,6 +48,7 @@ import jakarta.servlet.http.HttpSession;
|
||||
import jakarta.servlet.http.Part;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRange;
|
||||
@@ -56,12 +58,17 @@ import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.RequestPath;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
@@ -218,6 +225,35 @@ class DefaultServerRequest implements ServerRequest {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) throws BindException {
|
||||
Assert.notNull(bindType, "BindType must not be null");
|
||||
Assert.notNull(dataBinderCustomizer, "DataBinderCustomizer must not be null");
|
||||
|
||||
ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(null);
|
||||
dataBinder.setTargetType(ResolvableType.forClass(bindType));
|
||||
dataBinderCustomizer.accept(dataBinder);
|
||||
|
||||
HttpServletRequest servletRequest = servletRequest();
|
||||
dataBinder.construct(servletRequest);
|
||||
dataBinder.bind(servletRequest);
|
||||
|
||||
BindingResult bindingResult = dataBinder.getBindingResult();
|
||||
if (bindingResult.hasErrors()) {
|
||||
throw new BindException(bindingResult);
|
||||
}
|
||||
else {
|
||||
T result = (T) bindingResult.getTarget();
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Binding result has neither target nor errors");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Object> attribute(String name) {
|
||||
return Optional.ofNullable(servletRequest().getAttribute(name));
|
||||
|
||||
@@ -42,6 +42,7 @@ import jakarta.servlet.http.HttpSession;
|
||||
import jakarta.servlet.http.Part;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -52,7 +53,11 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.util.UriBuilder;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@@ -323,6 +328,35 @@ class DefaultServerRequestBuilder implements ServerRequest.Builder {
|
||||
throw new HttpMediaTypeNotSupportedException(contentType, Collections.emptyList(), method());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) throws BindException {
|
||||
Assert.notNull(bindType, "BindType must not be null");
|
||||
Assert.notNull(dataBinderCustomizer, "DataBinderCustomizer must not be null");
|
||||
|
||||
ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(null);
|
||||
dataBinder.setTargetType(ResolvableType.forClass(bindType));
|
||||
dataBinderCustomizer.accept(dataBinder);
|
||||
|
||||
HttpServletRequest servletRequest = servletRequest();
|
||||
dataBinder.construct(servletRequest);
|
||||
dataBinder.bind(servletRequest);
|
||||
|
||||
BindingResult bindingResult = dataBinder.getBindingResult();
|
||||
if (bindingResult.hasErrors()) {
|
||||
throw new BindException(bindingResult);
|
||||
}
|
||||
else {
|
||||
T result = (T) bindingResult.getTarget();
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Binding result has neither target nor errors");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> attributes() {
|
||||
return this.attributes;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -54,6 +55,8 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.cors.CorsUtils;
|
||||
import org.springframework.web.util.UriBuilder;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
@@ -1018,6 +1021,16 @@ public abstract class RequestPredicates {
|
||||
return this.request.body(bodyType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T bind(Class<T> bindType) throws BindException {
|
||||
return this.request.bind(bindType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) throws BindException {
|
||||
return this.request.bind(bindType, dataBinderCustomizer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Object> attribute(String name) {
|
||||
return this.request.attribute(name);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -48,6 +48,8 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.util.ServletRequestPathUtils;
|
||||
import org.springframework.web.util.UriBuilder;
|
||||
|
||||
@@ -148,6 +150,30 @@ public interface ServerRequest {
|
||||
*/
|
||||
<T> T body(ParameterizedTypeReference<T> bodyType) throws ServletException, IOException;
|
||||
|
||||
/**
|
||||
* Bind to this request and return an instance of the given type.
|
||||
* @param bindType the type of class to bind this request to
|
||||
* @param <T> the type to bind to
|
||||
* @return a constructed and bound instance of {@code bindType}
|
||||
* @throws BindException in case of binding errors
|
||||
* @since 6.1
|
||||
*/
|
||||
default <T> T bind(Class<T> bindType) throws BindException {
|
||||
return bind(bindType, dataBinder -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to this request and return an instance of the given type.
|
||||
* @param bindType the type of class to bind this request to
|
||||
* @param dataBinderCustomizer used to customize the data binder, e.g. set
|
||||
* (dis)allowed fields
|
||||
* @param <T> the type to bind to
|
||||
* @return a constructed and bound instance of {@code bindType}
|
||||
* @throws BindException in case of binding errors
|
||||
* @since 6.1
|
||||
*/
|
||||
<T> T bind(Class<T> bindType, Consumer<WebDataBinder> dataBinderCustomizer) throws BindException;
|
||||
|
||||
/**
|
||||
* Get the request attribute value if present.
|
||||
* @param name the attribute name
|
||||
|
||||
Reference in New Issue
Block a user