Skip to content

Latest commit

 

History

History
 
 

nrich-form-configuration

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

nrich-form-configuration

Maven Central

Overview

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.

Setting up Spring beans

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

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

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

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

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

FormConfigurationController is REST API exposed to the client.

It has two POST methods:

  • fetch mapped to nrich/form/configuration/fetch that accepts a request whose body has a form id list in property formIdList and it returns a list of FormConfiguration instances
  • fetch-all mapped to nrich/form/configuration/fetch-all that returns a list of FormConfiguration 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.

Usage

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.