nrich-form-configuration
is a module intended to provide a central place for constraint definitions.
It resolves jakarta-validation-api
constraints defined on classes in a form that can be interpreted by the client-side. On the server-side user registers a form id (a string) to a class that
defines constraints and can then retrieve the resolved constraint list via REST API. Messages for constraints are resolved through Spring's MessageSource
.
Both manual form registration or automatic by using @FormValidationConfiguration
annotation on classes are supported.
To be able to use this module with manual registration following configuration is required:
@Configuration(proxyBeanMethods = false)
public class ApplicationConfiguration {
@Bean
public FieldErrorMessageResolverService fieldErrorMessageResolverService(MessageSource messageSource) {
return new MessageSourceFieldErrorMessageResolverService(messageSource);
}
@Bean
public ConstrainedPropertyValidatorConverterService constrainedPropertyValidatorConverterService(FieldErrorMessageResolverService fieldErrorMessageResolverService) {
return new DefaultConstrainedPropertyValidatorConverterService(fieldErrorMessageResolverService);
}
@Bean
public FormConfigurationService formConfigurationService(LocalValidatorFactoryBean validator, List<ConstrainedPropertyValidatorConverterService> constrainedPropertyValidatorConverterServiceList) {
Map<String, Class<?>> formIdConstraintHolderMap = new LinkedHashMap<>();
formIdConstraintHolderMap.put("testRequest.formId", FormConfigurationServiceTestRequest.class);
return new DefaultFormConfigurationService(validator.getValidator(), formIdConstraintHolderMap, constrainedPropertyValidatorConverterServiceList);
}
@Bean
public FormConfigurationController formConfigurationController(FormConfigurationService formConfigurationService) {
return new FormConfigurationController(formConfigurationService);
}
}
To be able to use this module with automatic registration following configuration is required:
@Configuration(proxyBeanMethods = false)
public class ApplicationConfiguration {
@Bean
public FieldErrorMessageResolverService fieldErrorMessageResolverService(MessageSource messageSource) {
return new MessageSourceFieldErrorMessageResolverService(messageSource);
}
@Bean
public ConstrainedPropertyValidatorConverterService constrainedPropertyValidatorConverterService(FieldErrorMessageResolverService fieldErrorMessageResolverService) {
return new DefaultConstrainedPropertyValidatorConverterService(fieldErrorMessageResolverService);
}
@Bean
public FormConfigurationAnnotationResolvingService formConfigurationAnnotationResolvingService() {
return new DefaultFormConfigurationAnnotationResolvingService();
}
@Bean
public FormConfigurationService formConfigurationService(LocalValidatorFactoryBean validator, FormConfigurationAnnotationResolvingService formConfigurationAnnotationResolvingService,
List<ConstrainedPropertyValidatorConverterService> constrainedPropertyValidatorConverterServiceList) {
Map<String, Class<?>> formIdConstraintHolderMap = new LinkedHashMap<>(formConfigurationAnnotationResolvingService.resolveFormConfigurations(Collections.singletonList("net.croz")));
return new DefaultFormConfigurationService(validator.getValidator(), formIdConstraintHolderMap, constrainedPropertyValidatorConverterServiceList);
}
@Bean
public FormConfigurationController formConfigurationController(FormConfigurationService formConfigurationService) {
return new FormConfigurationController(formConfigurationService);
}
}
FieldErrorMessageResolverService
is responsible for resolving messages for constraints (i.e. 'Value cannot be null' for @NotNull constraint).
Default implementation is MessageSourceFieldErrorMessageResolverService
that resolves messages from MessageSource
.
For example, for class:
package net.croz.nrich.formconfiguration.stub;
public class FormConfigurationServiceNestedTestRequest {
@NotNull
private String propertyName;
}
the following message codes will be searched:
net.croz.nrich.formconfiguration.stub.FormConfigurationServiceNestedTestRequest.propertyName.client.NotNull.invalid
formConfigurationServiceNestedTestRequest.propertyName.client.NotNull.invalid
net.croz.nrich.formconfiguration.stub.FormConfigurationServiceNestedTestRequest.propertyName.NotNull.invalid
formConfigurationServiceNestedTestRequest.propertyName.NotNull.invalid
client.propertyName.NotNull.invalid
propertyName.NotNull.invalid
client.NotNull.invalid
NotNull.invalid
ConstrainedPropertyValidatorConverterService
is responsible for converting constraints in a format the client can interpret.
Default implementation is DefaultConstrainedPropertyValidatorConverterService
but users can register their own by implementing
ConstrainedPropertyValidatorConverterService
interface.
ConstrainedPropertyValidatorConverterService
returns a list of
ConstrainedPropertyClientValidatorConfiguration
for each constraint (a list since some constraints on server may translate to multiple
constraints on the client).
FormConfigurationService
processes constraints defined on a class for form id list and returns a list of FormConfiguration
instances
holding client-side constrained property configuration.
FormConfigurationAnnotationResolvingService
scans provided packages for classes annotated with
@FormValidationConfiguration
and constructs a map where keys are form ids and values are annotated classes that contain constraints which can then be used by
FormConfigurationService
.
FormConfigurationController
is REST API exposed to the client.
It has two POST methods:
fetch
mapped tonrich/form/configuration/fetch
that accepts a request whose body has a form id list in propertyformIdList
and it returns a list ofFormConfiguration
instancesfetch-all
mapped tonrich/form/configuration/fetch-all
that returns a list ofFormConfiguration
instances for every registered form.
REST API base path of the URL can be changed by setting a custom nrich.form-configuration.endpoint-path
property value.
On the server-side users should register form id with class that is used to bind values submitted from that form. On the client-side the REST API endpoint for fetching form configuration should be
called (nrich/form/configuration/fetch
) and received response should be converted to client-side constraints and applied to fields defined on the form.
For the following request:
package example;
@Setter
@Getter
public class EmployeeRequest {
@NotBlank
@Email
private String email;
@NotNull
private Date startDate;
@NotNull
private Date endDate;
@Size(min = 3, max = 3)
private String phone1;
@Max(23)
@Min(0)
private Integer hours;
private String title;
@NotBlank
private String firstName;
@DecimalMin("0.0")
@Digits(integer = 10, fraction = 2)
private BigDecimal income;
}
The above request can be registered manually by adding it to the map and passing it to FormConfigurationService
where map key is for example "example.form" and value is request class.
It can alternatively be annotated with @FormValidationConfiguration
annotation i.e.
@Setter
@Getter
@FormValidationConfiguration("example.form")
public class EmployeeRequest {
// properties omitted for brevity
}
For request:
{
"formIdList": [
"example.form"
]
}
the response sent from server is in the following form:
[
{
"formId": "example.form",
"constrainedPropertyConfigurationList": [
{
"path": "firstName",
"propertyType": "java.lang.String",
"javascriptType": "string",
"validatorList": [
{
"name": "NotBlank",
"argumentMap": {},
"errorMessage": "Cannot be blank"
}
]
},
{
"path": "email",
"propertyType": "java.lang.String",
"javascriptType": "string",
"validatorList": [
{
"name": "Email",
"argumentMap": {
"regexp": ".*",
"flags": []
},
"errorMessage": "Email is not in the correct format"
},
{
"name": "NotBlank",
"argumentMap": {},
"errorMessage": "Cannot be blank"
}
]
},
{
"path": "hours",
"propertyType": "java.lang.Integer",
"javascriptType": "number",
"validatorList": [
{
"name": "Min",
"argumentMap": {
"value": 0
},
"errorMessage": "Minimum value is: 0"
},
{
"name": "Max",
"argumentMap": {
"value": 23
},
"errorMessage": "Maximum value is: 23"
}
]
},
{
"path": "income",
"propertyType": "java.math.BigDecimal",
"javascriptType": "number",
"validatorList": [
{
"name": "Digits",
"argumentMap": {
"integer": 10,
"fraction": 2
},
"errorMessage": "Maximum number of digits is: 10 and scale is: 2"
},
{
"name": "DecimalMin",
"argumentMap": {
"inclusive": true,
"value": "0.0"
},
"errorMessage": "Minimum value is: 0.0"
}
]
},
{
"path": "endDate",
"propertyType": "java.util.Date",
"javascriptType": "date",
"validatorList": [
{
"name": "NotNull",
"argumentMap": {},
"errorMessage": "Cannot be null"
}
]
},
{
"path": "phone1",
"propertyType": "java.lang.String",
"javascriptType": "string",
"validatorList": [
{
"name": "Size",
"argumentMap": {
"min": 3,
"max": 3
},
"errorMessage": "Size must be between: 3 and 3"
}
]
},
{
"path": "startDate",
"propertyType": "java.util.Date",
"javascriptType": "date",
"validatorList": [
{
"name": "NotNull",
"argumentMap": {},
"errorMessage": "Cannot be null"
}
]
}
]
}
]
If user needs to fetch constraint descriptions for all registered forms, nrich/form/configuration/fetch-all
endpoint should be queried.