Tom Homberg provided a nice guide for implementing user-feedback validation within Spring applications, quite helpful for me in improving what I had in TightBlog. He creates a field - message Violation object (e.g., {"Name" "Name is required"}), a list of which is wrapped by a ValidationErrorResponse, the latter of which gets serialized to JSON and sent to the client to display validation errors. For my own implementation, I left the field value blank to display errors not specific to a particular field, and used it for both sending 400-type for user-input problems and generic 500-type messages for system errors.
Implementing this validation for TightBlog's blogger UI, I soon found it helpful to have convenience methods for quick creation of the Violations, ValidationErrorResponses and Spring ResponseEntities for providing feedback to the client:
public static ResponseEntity<ValidationErrorResponse> badRequest(String errorMessage) { return badRequest(new Violation(errorMessage)); } public static ResponseEntity<ValidationErrorResponse> badRequest(Violation error) { return badRequest(Collections.singletonList(error)); } public static ResponseEntity<ValidationErrorResponse> badRequest(Listerrors) { return ResponseEntity.badRequest().body(new ValidationErrorResponse(errors)); }
i18n can be handled via the Locale method argument, one of the parameters automatically provided by Spring:
@Autowired private MessageSource messages; @PostMapping(...) public ResponseEntity doFoo(Locale locale) { ... if (error) { return ValidationErrorResponse.badRequest(messages.getMessage("mediaFile.error.duplicateName", null, locale)); } }
On the front-end, I have Angular.js trap the code and then output the error messages (am not presently not using the field names). Below truncated for brevity (full source: JavaScript and JSP):
this.commonErrorResponse = function(response) { self.errorObj = response.data; } <div id="errorMessageDiv" class="alert alert-danger" role="alert" ng-show="ctrl.errorObj.errors" ng-cloak> <button type="button" class="close" data-ng-click="ctrl.errorObj.errors = null" aria-label="Close"> <span aria-hidden="true">×</span> </button> <ul class="list-unstyled"> <li ng-repeat="item in ctrl.errorObj.errors">{{item.message}}</li> </ul> </div>
Appearance:
Additionally, I was able to remove a fair amount of per-endpoint boilerplate by creating a single ExceptionHandler for unexpected 500 response code system errors and attaching it to my ControllerAdvice class so it would be used by all REST endpoints. For these types of exceptions usually a generic "System error occurred, please contact Administrator" message is sent to the user. However, I added a UUID that both appears on the client and goes into the logs along with the exception details, making it easy to search the logs for the specific problem. The exception handler (from the TightBlog source):
@ExceptionHandler(value = Exception.class) // avoiding use of ResponseStatus as it activates Tomcat HTML page (see ResponseStatus JavaDoc) public ResponseEntity<ValidationErrorResponse> handleException(Exception ex, Locale locale) { UUID errorUUID = UUID.randomUUID(); log.error("Internal Server Error (ID: {}) processing REST call", errorUUID, ex); ValidationErrorResponse error = new ValidationErrorResponse(); error.getErrors().add(new Violation(messages.getMessage( "generic.error.check.logs", new Object[] {errorUUID}, locale))); return ResponseEntity.status(500).body(error); }
Screen output:
Log messaging containing the same UUID:
Additional Resources
Posted by Glen Mazza in Programming at 02:00AM Nov 06, 2019 | Comments[0]