Skip to content

Latest commit

 

History

History

spring-web-tools

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

Spring Web Tools

This is a library that provides some convenience code to make implementing Spring MVC and WebFlux controllers easier.

A monadic MappedPayload type

Controller implementation that receive a request payload usually follow a pretty predictable set of steps handling the payload:

  1. Validate the incoming payload.

  2. Map it onto some domain object.

  3. Invoke some business logic.

  4. Produces some response based on the outcome of the business activity.

MappedPayloads is a monadic type that allows to set up processing pipelines in a fluent way, conclude

@RestController
@RequiredArgsController
class MyController {

  private final SomeRepository repository;
  private final YaviValidator<Something> validator;

  @PutMapping("/payload/{id}")
  HttpEntity<?> putPayload(
    @PathVariable Long id,
    @RequestBody Something something,
    Errors errors) {

    return MappedPayload.of(something, errors)
      .notFoundIf(!repository.exists(id)) // End up with a 404 if true (see graphic below)
      .validate(validator) // Invoke validation
      .rejectField(…) // reject fields based on conditions
      .map(it -> …) // *always* invoke business method
      .mapIfValid(it -> …) // invoke business method if no errors have occured yet
      .concludeIfValid(it -> ); // Create an HTTP response using the current payload
  }
}

The example above will do the following:

  • It will immediately flip into absent mode and will eventually produce a 404 response if the repository lookup returns false. The 404 response can be customized by registering an absence handler using ….onAbsence(…).

  • Invoke validation explicitly, in this case using the YAVI validation library.

  • There is API to reject fields unconditionally or based on boolean expressions and Predicates.

  • Unless we deal with absence, the handler in ….map(…) will be invoked.

  • The handler passed to the ….mapIfValid(…) method, will only be invoked, if no errors have been accumulated yet. Additional overloads exist that also receive the Errors instance so that additional errors can be registered manually.

  • ….concludeIfValid(…) is the terminating method to produce the success response if no errors have been accumulated until the call. If the instance is in absent mode, a 404 Not found will be rendered, customizable via ….onAbsence(…). If errors have been accumulated, a default 400 Bad Request will be produced to contain the Errors instance as payload (see Serializing Error instances for how that is handled). ….onErrors(…) can be used to customize the error response.

mapped payload

Serializing Error instances

Error instances contain binding and validation errors related to the request body. This library provides a bit of infrastructure integration to create JSON-based responses for those. In general, an Errors instance is comprised of key-value pairs. The key is a property path expression (e.g. username or address.zipCode) of the erronerous field received. The value is a MessageSourceResolvable, i.e. an abstraction that can be resolved via a MessageSource. That in turn usually is a key plus optional arguments resolvable via a resource bundle.

ErrorsModule is a Jackson module that will turn the following arrangement:

class UserAccount {
  String password;
}

Errors errors = new BeanPropertyBindingResult(new UserAccount(), "account");
errors.rejectValue("password", "password.notNull";

// Resource bundle
password.notNull = Password must not be null!

into this JSON response:

{
  "password" : "Password must not be null!"
}

I18nable fields in responses

We provide a I18nedMessage object and a corresponding Jackson serializer that will automatically resolve the key and argument contained in the I18nedMessage against a resource bundle.

@Data
class ResponsePayload {
  private I18nedMessage message;
}

var payload = new ResponsePayload()
  .setMessage(I18nedMessage.of("password.notNull"));

Using the above mentioned resource bundle, rendering this would result in:

{
  "message" : "Password must not be null!"
}

Miscellaneous

There’s ErrorsWithDetails that allows to use the mechanism described in Serializing Error instances but override or add additional fields containing complex objects.

Therer’s MessageSourceResolvableHttpMessageConverter that will resolve and render MessageSourceResolvable instances returned from controller methods as text/html.