What You'll Learn

We've coded the "happy path" for form submission, but users get things wrong and we want to protect our domain from invalid data. Our service layer will do what it requires to maintain consistency (or as much consistency as it can if it's part of a highly distributed system), but we still want to validate user input as soon as we can.

We're going to validate the request. This can be augmented on the client-side via JavaScript which may provide a better user experience, but does then mean that the validation rules on client and server have to be kept consistent with each other.

If not already added, make sure that the validation starter is added to build.gradle.

implementation 'org.springframework.boot:spring-boot-starter-validation'

We want to ensure that the fields of the charity form contain reasonable data so we annotate the fields.

Most of these annotations should be self-explanatory. The message attribute is what should be played if the validation fails. The @Pattern annotation validates the input against a Regular Expression (for an excellent learning resource see Regexr.

There are many other validations including numerical and date ranges, but to use these the form object fields must be have the relevant type. That then begs the question, what happens if the user provides an input such as "ABC" for an integer field?

The easiest way to deal with these types of issues is to add a file called messages.properties (in main/resources) and in that file add properties that start with typeMismatch. (e.g. "`typeMismatch.java.lang.Integer) and set the value of the property to be the message to be played. This can be repeated for different types and can also be customised to specific objects.

@NotEmpty(message = "The name of the charity must be between 1 and 100 characters.")
 @Size(min = 1, max = 100, message = "The name of the charity must be between 1 and 100 characters.")
 private String name;

 @NotEmpty(message = "The registration of the charity must be all numbers or, if Scottish, start with an 'S'")
 @Pattern(regexp = "([S]?[0-9]*)", message = "The registration of the charity must be all numbers or, if Scottish, start with an 'S'")
 private String registration;

 @NotEmpty(message = "The acronym of the charity acts as its web identifier and must be unique")
 private String acronym;

 @NotEmpty(message = "The mission statement must contain at least 10 characters and be less than 500 characters")
 @Size(message = "The mission statement must contain at least 10 characters and be less than 500 characters")
 private String missionStatement;

## Tell Spring to validate the submission

We need to change the method that handles to post.

 @PostMapping("/charity")
    public String charityFormPost(
      @Valid CharityForm charityForm,
      BindingResult bindingResult,
      Model model) {
        CharityDTO charityDTO = new CharityDTO(charityForm.getName(),
                                               charityForm.getRegistration(),
                                               charityForm.getAcronym(),
                                               charityForm.getAcronym(),
                                               charityForm.getMissionStatement(),
                                               Boolean.FALSE);

        if (bindingResult.hasErrors()) {
            return "admin/charity-form";
        }

        charitySearch.saveCharity(charityDTO);
        return "redirect:/charities";
    }

In this version, we will do the simplest check possible. We add the @Valid annotation to the CharityForm parameter. This tells Spring to validate it on submission. We can then ask Spring to inject the results of those "bindings" (i.e. we're binding web requests to objects) into a BindingResult object. We can they query this object to see if there were any errors. If there are errors, we take the user back to the form, otherwise we carry on as before.

Previously, we'd added span elements to the charity-form.html template to show errors.

These should now be enabled...

<span th:errors="*{name}" th:if="${#fields.hasErrors('name')}">Name Error</span>

The span will show any errors associated with the ‘name' field (this comes from the th:errors attribute) if there are errors associated with the ‘name' field (the th:if attribute brings the conditionality).

(In the approved version in tag 20, we replace the span with a div and update some of the messages.)

Test the form by trying invalid values and asserting that the correct error message is shown.

We've added validation to the form. At this point, we can only validate one field at a time on one object at a time. Sometimes validations need to be more complicated and broad-ranging.