This is a library that provides some convenience code to make implementing Spring MVC and WebFlux controllers easier.
Controller implementation that receive a request payload usually follow a pretty predictable set of steps handling the payload:
-
Validate the incoming payload.
-
Map it onto some domain object.
-
Invoke some business logic.
-
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 andPredicate
s. -
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 theErrors
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, a404 Not found
will be rendered, customizable via….onAbsence(…)
. If errors have been accumulated, a default400 Bad Request
will be produced to contain theErrors
instance as payload (see Serializing Error instances for how that is handled).….onErrors(…)
can be used to customize the error response.
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!"
}
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!"
}
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
.