Pretty print bean validation errors

Fixes #137
This commit is contained in:
Eric Bottard
2017-08-30 21:59:53 +02:00
parent 73bf00bb44
commit 3b0901af17
6 changed files with 151 additions and 7 deletions

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell;
import javax.validation.ConstraintViolation;
import java.util.Set;
/**
* Thrown when one or more parameters fail bean validation constraints.
*
* @author Eric Bottard
*/
public class ParameterValidationException extends RuntimeException {
private final Set<ConstraintViolation<Object>> constraintViolations;
private final MethodTarget methodTarget;
public ParameterValidationException(Set<ConstraintViolation<Object>> constraintViolations, MethodTarget methodTarget) {
this.constraintViolations = constraintViolations;
this.methodTarget = methodTarget;
}
public Set<ConstraintViolation<Object>> getConstraintViolations() {
return constraintViolations;
}
public MethodTarget getMethodTarget() {
return methodTarget;
}
}

View File

@@ -63,6 +63,9 @@ public class Shell implements CommandRegistry {
*/
protected static final Object UNRESOLVED = new Object();
private final ExecutableValidator executableValidator = Validation
.buildDefaultValidatorFactory().getValidator().forExecutables();
public Shell(ResultHandler resultHandler) {
this.resultHandler = resultHandler;
}
@@ -229,13 +232,13 @@ public class Shell implements CommandRegistry {
throw new IllegalStateException("Could not resolve " + methodParameter);
}
}
ExecutableValidator executableValidator = Validation
.buildDefaultValidatorFactory().getValidator().forExecutables();
Set<ConstraintViolation<Object>> constraintViolations = executableValidator.validateParameters(methodTarget.getBean(),
Set<ConstraintViolation<Object>> constraintViolations = executableValidator.validateParameters(
methodTarget.getBean(),
methodTarget.getMethod(),
args);
args
);
if (constraintViolations.size() > 0) {
System.out.println(constraintViolations);
throw new ParameterValidationException(constraintViolations, methodTarget);
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.result;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.shell.ParameterDescription;
import org.springframework.shell.ParameterResolver;
import org.springframework.shell.ParameterValidationException;
import org.springframework.shell.Utils;
import org.springframework.stereotype.Component;
import javax.validation.ElementKind;
import javax.validation.Path;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Displays validation errors on the terminal.
*
* @author Eric Bottard
*/
@Component
public class ParameterValidationExceptionResultHandler
extends TerminalAwareResultHandler<ParameterValidationException> {
@Autowired
private List<ParameterResolver> parameterResolvers;
@Override
protected void doHandleResult(ParameterValidationException result) {
terminal.writer().println(new AttributedString("The following constraints were not met:",
AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)).toAnsi());
result.getConstraintViolations().stream()
.forEach(v -> {
Optional<Integer> parameterIndex = StreamSupport.stream(v.getPropertyPath().spliterator(), false)
.filter(n -> n.getKind() == ElementKind.PARAMETER)
.map(n -> ((Path.ParameterNode) n).getParameterIndex())
.findFirst();
MethodParameter methodParameter = Utils.createMethodParameter(result.getMethodTarget().getMethod(),
parameterIndex.get());
List<ParameterDescription> descriptions = findParameterResolver(methodParameter)
.describe(methodParameter).collect(Collectors.toList());
if (descriptions.size() == 1) {
ParameterDescription description = descriptions.get(0);
AttributedStringBuilder ansi = new AttributedStringBuilder(100);
ansi.append("\t").append(description.keys().get(0), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED).bold());
ansi.append(" ").append(description.formal(), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED).underline());
String msg = String.format(" : %s (You passed '%s')",
v.getMessage(),
String.valueOf(v.getInvalidValue())
);
ansi.append(msg, AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
terminal.writer().println(ansi.toAnsi(terminal));
}
// Several formals for one method param, must be framework like JCommander, etc
else {
// Output toString() for now...
terminal.writer().println(new AttributedString(v.toString(),
AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)).toAnsi(terminal));
}
});
}
private ParameterResolver findParameterResolver(MethodParameter methodParameter) {
return parameterResolvers.stream().filter(pr -> pr.supports(methodParameter)).findFirst().get();
}
}

View File

@@ -21,6 +21,8 @@ import java.util.List;
import com.beust.jcommander.Parameter;
import javax.validation.constraints.Min;
/**
* An example straight from the JCommander documentation.
*
@@ -31,6 +33,7 @@ public class Args {
@Parameter
private List<String> parameters = new ArrayList<>();
@Min(3)
@Parameter(names = { "-log", "-verbose" }, description = "Level of verbosity")
private Integer verbose = 1;

View File

@@ -20,6 +20,8 @@ import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellOption;
import javax.validation.Valid;
/**
* A class with JCommander commands.
*
@@ -29,7 +31,7 @@ import org.springframework.shell.standard.ShellOption;
public class JCommanderCommands {
@ShellMethod("Bind parameters to JCommander POJO.")
public String jcommander(@ShellOption(optOut = true) Args args) {
public String jcommander(@ShellOption(optOut = true) @Valid Args args) {
return "You said " + args;
}
}

View File

@@ -30,6 +30,8 @@ import org.springframework.shell.standard.ShellOption;
import org.springframework.shell.standard.ValueProviderSupport;
import org.springframework.stereotype.Component;
import javax.validation.constraints.Size;
/**
* Example commands for the Shell 2 Standard resolver.
*
@@ -44,7 +46,7 @@ public class Commands {
}
@ShellMethod("It's cool.")
public String foo(String bar) {
public String foo(@Size(min = 2) String bar) {
return bar;
}