templates.plugins.spincast-validation.spincast-validation.html Maven / Gradle / Ivy
Show all versions of spincast-website Show documentation
{#==========================================
Spincast Properties File Config
==========================================#}
{% extends "../../layout.html" %}
{% block sectionClasses %}plugins plugins-spincast-validation{% endblock %}
{% block meta_title %}Plugins - Spincast Validation{% endblock %}
{% block meta_description %}Validation for your beans/models.{% endblock %}
{% block scripts %}
{% endblock %}
{% block body %}
Overview
This plugin provides classes and patterns to help validate your beans/models.
The validation pattern this plugin promotes is not one using annotations
(like Hibernate's validator, for example).
It targets developers who don't like to pollute their
models with too many annotations.
By using this plugin and the mix-ins
provided by the Spincast Jackson Json plugin and
the Spincast Jackson XML plugin, you can have models
which are not bloated with annotations, but that can still be (de)serialized (from/to Json
and XML) and can still be validated properly.
Installation
Add this artifact to your project:
<dependency>
<groupId>org.spincast</groupId>
<artifactId>spincast-plugins-validation</artifactId>
<version>{{spincastCurrrentVersion}}</version>
</dependency>
Then, install the plugin's Guice module, by passing it to the Guice.createInjector(...) method:
Injector guice = Guice.createInjector(
new AppModule(args),
new SpincastValidationPluginGuiceModule(IAppRequestContext.class,
IAppWebsocketContext.class)
// other modules...
);
... or by using the install(...) method from your custom Guice module:
public class AppModule extends SpincastDefaultGuiceModule {
//...
@Override
protected void configure() {
super.configure();
install(new SpincastValidationPluginGuiceModule(getRequestContextType(),
getWebsocketContextType()));
// other modules...
}
// ...
}
When this is in place, you can create validator classes.
For example, a UserValidator, to validate a user:
public class UserValidator extends SpincastValidatorBase<IUser> {
//...
@Override
protected void validate() {
// The validation rules go here.
}
}
A validator extends the SpincastValidatorBase base class, and parameterizes it
with the type of the objects to validate. It overrides the validate()
method to define the validation rules. We'll see an example of this in the
following Usage section.
You also have to bind a assisted factory for your new validator.
A factory is required because
a validator is stateful, it keeps informations about the current object being
validated. Therefore a validator can't be reused and a new instance must
be created for each object to validate, using the factory.
public class AppModule extends SpincastDefaultGuiceModule {
public AppModule(String[] mainArgs) {
super(mainArgs);
}
@Override
protected void configure() {
super.configure();
install(new SpincastValidationPluginGuiceModule(getRequestContextType(),
getWebsocketContextType()));
install(new FactoryModuleBuilder().implement(IValidator.class, UserValidator.class)
.build(new TypeLiteral<IValidatorFactory<IUser>>(){}));
//...
}
//...
}
You can then inject the IValidatorFactory<IUser> where you need to
create validators for users. Let's see an example...
Usage
In this example, we have a UserController controller containing a addUser(...)
route handler that receives POST requests to add new users:
public class UserController implements IUserController {
private final IUserService userService;
@Inject
public UserController(IUserService userService) {
this.userService = userService;
}
protected IUserService getUserService() {
return this.userService;
}
@Override
public void addUser(IAppRequestContext context) {
IUser user = context.request().getJsonBody(User.class);
user = getUserService().addUser(user);
context.response().sendJsonObj(user);
}
}
Explanation :
-
17 : We deserialize the body of a request we receive
to a
IUser object. If the body is not valid Json or if the Json
can't be properly deserialized to a User object, an exception is thrown.
-
18 : We pass the new user to add to a
IUserService service. As we'll see, the validation of the user
object will be done there!
-
20 : If the user is valid, and therefore saved in our system,
we can return a new serialized version of it. This new version now contains
an unique
id that our system attributed to the new user.
Let's now have a look at the IUserService service, where the user object is validated:
public class UserService implements IUserService {
private final IUserRepository userRepository;
private final IValidatorFactory<IUser> userValidatorFactory;
@Inject
public UserService(IUserRepository userRepository,
IValidatorFactory<IUser> userValidatorFactory) {
this.userRepository = userRepository;
this.userValidatorFactory = userValidatorFactory;
}
protected IUserRepository getUserRepository() {
return this.userRepository;
}
protected IValidatorFactory<IUser> getUserValidatorFactory() {
return this.userValidatorFactory;
}
@Override
public IUser addUser(IUser user) {
IValidator userValidator = getUserValidatorFactory().create(user);
if(!userValidator.isValid()) {
String errorMessage = "The user contains errors:\n\n";
errorMessage += userValidator.getErrorsFormatted(FormatType.PLAIN_TEXT);
throw new PublicException(errorMessage);
}
return getUserRepository().addUser(user);
}
}
Explanation :
-
8 : We inject the
IValidatorFactory<IUser>
factory, the object that creates user validators.
-
24 : We create a validator instance, by passing as a parameter
the
user object that we want to validate.
-
25 : By calling
userValidator.isValid(), we
can know if the validation was successful or not.
-
27-28 : If some errors occured, we can access each error
separately, but we can also ask the validator to give us a formatted listing of all the errors.
Here, we ask for a plain text listing, and we use it to build the message we're going to
send in an exception.
-
30 : By throwing a
PublicException exception,
the error message will be available to the end user. Which is desired, since we want to inform
him of what went wrong.
-
33 : In the other hand, if the validation is successful,
we add the user to our system! In general, a repository will add the new object to the
appropriated data source, will set the generated
id on the saved object and
will return this updated object.
Here is what the validator itself would look like:
public class UserValidator extends SpincastValidatorBase<IUser> {
@AssistedInject
public UserValidator(@Assisted IUser user,
SpincastValidatorBaseDeps spincastValidatorBaseDeps) {
super(user, spincastValidatorBaseDeps);
}
@Override
protected void validate() {
// The name of the user can't be null.
validateNotNull("name", getObjToValidate().getName());
// The password of the user should be at least 12 characters long.
validateMinLength("password", getObjToValidate().getPassword(), 12);
// A custom validation
if("Stromgol".equalsIgnoreCase(getObjToValidate().getName())) {
addError("name",
"APP_USER_VALIDATION_ERROR_STROMGOL",
"I know Stromgol very well and you are not him!");
}
}
}
Explanation :
-
9-10 : We override the abstract
validate()
method, which is where we declare the validation rules.
-
13 : We validate that the user's
name is
not null. Validation rules may use methods provided by the SpincastValidatorBase
base class, like validateNotNull(...) in this case. Each of those validation rule
at least takes a name representing the field that
is validated on the target object, and the value of the field. Also note that the
getObjToValidate() method returns the object to validate (the user in
this case).
-
16 : We validate that the user's
password contains
at least 12 characters.
-
19-23 : We add a custom validation rule.
Here, the user can't be named "Stromgol". If the custom rule fails, we register an error using the
addError(...) method. A validation error includes the name of the invalid field,
a type representing the error, and an error message.
Finally, don't forget to bind the assisted factory
for your validator, in your custom Guice module. This is what makes possible its injection in the
IUserService:
public class AppModule extends SpincastDefaultGuiceModule {
public AppModule(String[] mainArgs) {
super(mainArgs);
}
@Override
protected void configure() {
super.configure();
install(new SpincastValidationPluginGuiceModule(getRequestContextType(),
getWebsocketContextType()));
install(new FactoryModuleBuilder().implement(IValidator.class, UserValidator.class)
.build(new TypeLiteral<IValidatorFactory<IUser>>(){}));
//...
}
//...
}
The base methods
Every validator implements the IValidator interface which provides
those methods:
-
boolean isValid()
Is the object valid? Was the validation successful?
-
void revalidate()
Revalidates the object (it may have been modified!). The result of the validation is cached, so it will always
be the same except if this method is called to clear it.
-
Map<String, List<IValidationError>> getErrors()
Gets the validation errors. The keys are the names of invalid fields.
-
List<IValidationError> getErrors(String fieldName)
Gets the validation errors for the specified field.
-
String getErrorsFormatted(FormatType formatType)
Gets the formatted errors, if there are any.
-
String getErrorsFormatted(String fieldName, FormatType formatType)
Gets the formatted errors for the specified field, if there are any.
The default validation methods
By extending SpincastValidatorBase, your validators get access to some default validation
methods. As we will see in the
next section, we can also define custom validation methods.
The default validation methods all return false if a validation error occures,
or true if the validation is successful:
-
boolean validateNotNull(String fieldName, Object fieldValue)
Validates that a field is not null.
-
boolean validateNotNull(String fieldName, Object fieldValue, String errorMessage)
Validates that a field is not null, and allows a custom error message.
-
boolean validateNotBlank(String fieldName, String fieldValue)
Validates that a field is not blank (null, empty or contain only spaces).
-
boolean validateNotBlank(String fieldName, String fieldValue, String errorMessage)
Validates that a field is not blank (null, empty or contain only spaces), and allows a custom error message.
-
boolean validateMinLength(String fieldName, String fieldValue, int minLength)
Validates that a field has a minimum length.
-
boolean validateMinLength(String fieldName, String fieldValue, int minLength, String errorMessage)
Validates that a field has a minimum length, and allows a custom error message.
-
boolean validateMaxLength(String fieldName, String fieldValue, int maxLength)
Validates that a field has a maximum length.
-
boolean validateMaxLength(String fieldName, String fieldValue, int maxLength, String errorMessage)
Validates that a field has a maximum length, and allows a custom error message.
-
boolean validateMinSize(String fieldName, Integer fieldValue, int minSize)
Validates that a field has a minimum size.
-
boolean validateMinSize(String fieldName, Integer fieldValue, int minSize, String errorMessage)
Validates that a field has a minimum size, and allows a custom error message.
-
boolean validateMaxSize(String fieldName, Integer fieldValue, int maxSize)
Validates that a field has a maximum size.
-
boolean validateMaxSize(String fieldName, Integer fieldValue, int maxSize, String errorMessage)
Validates that a field has a maximum size, and allows a custom error message.
-
boolean validatePattern(String fieldName, String fieldValue, String pattern)
Validates that a field matches a pattern.
-
boolean validatePattern(String fieldName, String fieldValue, String pattern, String errorMessage)
Validates that a field matches a pattern, and allows a custom error message.
-
boolean validatePattern(String fieldName, String fieldValue, String pattern, boolean mustMatch)
Validates that a field matches a pattern or not.
-
boolean validatePattern(String fieldName, String fieldValue, String pattern, boolean mustMatch)
Validates that a field matches a pattern or not, and allows a custom error message.
-
boolean validateEmail(String fieldName, String fieldValue)
Validates that a field is a valid email address.
-
boolean validateEmail(String fieldName, String fieldValue, String errorMessage)
Validates that a field is a valid email address, and allows a custom error message.
In addition to those validation methods, SpincastValidatorBase
also provides methods to add your own errors when you define custom validation rules:
-
void addError(IValidationError error)
Adds a validation error object directly.
-
void addError(String fieldName, String errorType, String errorMessage)
Adds a validation error by specifying the name of the validated field, the type
of the error and its message.
-
void addErrors(List<IValidationError> errors)
Adds validation errors directly.
Custom validation
In addition to the default validation methods provided by the SpincastValidatorBase
base class, you can define your own validation rules.
A custom rules is simply a validation followed by the registration of an error,
if the validation fails.
For example, let's say we want to validate that a password contains at least one number. But let's
also say we only want this
error to be registered if the password is not empty in the first place (which is also invalid)!
In other words, we want the resulting validation of an empty password to contain
only one error, "Can't be null, empty or only contain spaces.".
We don't want two errors: "Can't be null, empty or only contain spaces."
and "Must contain at least one number!":
public class UserValidator extends SpincastValidatorBase<IUser> {
@AssistedInject
public UserValidator(@Assisted IUser user,
SpincastValidatorBaseDeps spincastValidatorBaseDeps) {
super(user, spincastValidatorBaseDeps);
}
@Override
protected void validate() {
// Validates that the password is not blank.
// False is returned if the validation fails.
boolean passwordValid = validateNotBlank("password", getObjToValidate().getPassword());
// We only validate that the password contains at least one number
// if it is not blank (if the previous validation succeeded)!
if(passwordValid) {
if(getObjToValidate().getPassword().matches(".*\\d+.*")) {
passwordValid = addError("password",
"APP_USER_VALIDATION_ERROR_PASSWORD_AT_LEAST_ONE_NUMBER",
"Must contain at least one number!");
}
}
}
}
Explanation :
-
14 : We first validate that the password
is not blank. If it is blank, the validation fails and
false
is returned.
-
18 : We only continue validating the password
if it is not blank in the first place. Of course, we could still run the other validations and have more
errors displayed to the end user: this is up to you.
-
19 : Is the password is not empty, we validate
that it contains at least one number.
-
20-22 : If our custom validation fails, we
register an validation error. A validation error is composed of a
field name,
an error type and an error message.
{% endblock %} 
Spincast Validation