-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New validations implemented #199
Conversation
Does this division play nicely with service loader? https://github.com/serverlessworkflow/sdk-java/blob/main/spi/src/test/resources/META-INF/services/io.serverlessworkflow.api.interfaces.WorkflowValidator I am honestly not a big fan of splitting up each validation into own class, think long term it will just create more maintenance. Also, if you need extra validation ontop of what SDK provides another option is to define your own validator and set it via service loader. |
I agree with @tsurdilo |
This is ready to review, @tsurdilo. |
validation/src/main/java/io/serverlessworkflow/validation/StatesValidator.java
Outdated
Show resolved
Hide resolved
@hbelmiro thanks for the updates. will review as soon as possible. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So are we proceeding with the validations of the other sections such as functions, events, and so on in other PRs?
@hbelmiro so this is just validating switch state at this time? from @ricardozanini question, if you want to do it in separate prs let's maybe create a branch for this? think it would maybe help until everything is done so we could move to main branch? So far I think this looks ok, ty. Just really wanna make sure it plays well with service loader in the end. |
@tsurdilo @ricardozanini |
@tsurdilo currently only the
WorkflowValidator setWorkflow(Workflow workflow);
WorkflowValidator setSchemaValidationEnabled(boolean schemaValidationEnabled); |
@hbelmiro thanks for the info! what do you think would be better? In my mind having users extend our WorkflowValidatorImpl and be able to override a particular validation method would work too. wdyt? |
@tsurdilo I think we can go on the way we are (users can set their own WorkflowValidator only). In the future we can make it more flexible if needed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some things that need to be sorted out specifically commented in the code.
Although I do not mention that specifically, in general, I think we are using Optional too much.
I understood the reason, to not pass the list of error to all the validation methods, and do not have this kind of idiom
if (validateXXX) {errors.add(xxx_ERROR)}
Having
validateXXX().ifPresent(errors:add);
you have the error condidition and the error message in the validateXXX(), but since at the end the error message is a constant, I found easier to read the first one.
validation/src/main/java/io/serverlessworkflow/validation/CommonStateValidator.java
Outdated
Show resolved
Hide resolved
return error; | ||
} | ||
|
||
protected abstract List<ValidationError> runSpecificValidations(Workflow workflow, T state); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than declaring this method as abstract (you can still declare the class as abstract event if you do not have any abstrace method), you can return new ArrayList by default and save a lot of code in childred classes that are just returning new ArrayList
|
||
interface StateValidator { | ||
|
||
List<ValidationError> validate(Workflow workflow, State state); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless there is need to random access, I will declare this as collection rather than list (you might eventually implement equals in ValidationError and use a set without changing the interface)
validation/src/main/java/io/serverlessworkflow/validation/StatesValidator.java
Outdated
Show resolved
Hide resolved
validation/src/main/java/io/serverlessworkflow/validation/StatesValidator.java
Outdated
Show resolved
Hide resolved
validation/src/main/java/io/serverlessworkflow/validation/StatesValidator.java
Outdated
Show resolved
Hide resolved
if (stateValidator != null) { | ||
errors.addAll(stateValidator.validate(workflow, state)); | ||
} else { | ||
throw new IllegalStateException("No validator found for state type: " + state.getType()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are not providing the complete set of validator. hence this will eventually throw IlleglStateException even for valid states.
I would rather do nothing if there is not validator for that state and print a log.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also by doing that, you wont need dummy implementations of validator that just return an empty list of errors (they can be added lated when the validator is implemented)
List<ValidationError> errors = new ArrayList<>(); | ||
|
||
if (!switchState.getDataConditions().isEmpty() && !switchState.getEventConditions().isEmpty()) { | ||
ValidationError error = new ValidationError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You already have a protected final method in the parent class to create the validation error
defaultCondition.getTransition() != null | ||
? defaultCondition.getTransition().getNextState() | ||
: null; | ||
if (nextState != null && !nextState.isEmpty()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it will be nice to have an utility static method that check if a string is not null and is not empty, so you save a lot of code and dummy erros (in this case you forget the trim, in other case you miss the !)
Thanks for the review @fjtirado. Regarding the use of Optional, I can remove it. In general, I like using Optional (always trying to not overuse it) to let things obvious and more intuitive. |
could we please not mention KOGITO in commit names? I would like to request to update the commits if possible to just include what the changes are. Thanks!! |
Sure. I'll fix them. |
Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>
Signed-off-by: Helber Belmiro <helber.belmiro@gmail.com>
|
||
if (stateValidator != null) { | ||
finalValidationResult = | ||
finalValidationResult && stateValidator.isValidState(workflow, state); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so, at the moment a validator fail, we cancel the whole validation process?
I guess this is intentional, but just double checking.
Cancelling the validation process when the first state that has a validation error is found will make the validationErrors list not complete.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. The idea is to return false, which means "doesn't have valid states", but continue with the validation.
Currently the return value is being ignored, but that's just because it's not fully implemented yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, then, you need to change this code because stateValidator.isValidState(workflow, state) is not going to be invoked if previous state returned false.
you should write finalValidationResult&=stateValidator.isValidState(workflow, state);
public StatesValidator(Collection<ValidationError> validationErrors) { | ||
this.validationErrors = validationErrors; | ||
|
||
stateValidators.put(Type.EVENT, new EventStateValidator(validationErrors)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have to create the instance of validator for event type every we want to validate states?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it will create only one instance of StatesValidator
per WorkflowValidatorImpl
instance.
See: https://github.com/hbelmiro/sdk-java/blob/cdf134a2d324310e7fa37f6e63823019a104d4d4/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java#L54
That instance of StatesValidator
has several instances of StateValidator
(one per type) - I know I need to think about better names :)
In the end, only one instance of each class will be created.
Before proceeding with this, I opened a new PR to use Bean Validations. With that PR we'll significantly reduce the amount of manually implemented validations. |
@ricardozanini closing as stale. |
This is a work in progress...
Validations Implemented
Final proposal
Validations are grouped by workflow definitions (state, even, function...).
Initial proposal (not approved)
I'm implementing new workflow validations and wanted to check what do you guys think of my approach?
Currently, we’re performing all the validations in the
io.serverlessworkflow.validation.WorkflowValidatorImpl#validate
method. That method already is quite long, and my new validations would make it even longer. That would increase the code complexity and it would become harder to test.So I decided to break the new validations into new classes.
I created this
io.serverlessworkflow.validation.WorkflowValidation
interface:Each validation will implement that interface and be registered as a service on
resources/META-INF/services/io.serverlessworkflow.validation.WorkflowValidation
, so the originalWorkflowValidatorImpl#validate
method can load those services and perform the validations.The current validations are still in the original method. I only implemented a couple of new validations in this new structure. If you like it, we can move all the validations to their respective classes in the future.
Advantages of this approach:
For instance: If we have a new required attribute in the spec, we just have to create a new validation and test it. We don't need to change all the other tests to include this new required attribute, because they don't care about this new attribute.
Disadvantage of this approach:
Please let me know what do you think and if I can continue implementing the validations this way. Feel free to suggest improvements in this solution as well.