Skip to content

Refactor JavaValidationRenderer #185

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

Merged
merged 26 commits into from
Jan 21, 2025
Merged

Conversation

yevhenii-nadtochii
Copy link
Contributor

@yevhenii-nadtochii yevhenii-nadtochii commented Jan 16, 2025

This PR refactors JavaValidationRenderer and suggests renaming of MessageValidationCompilationMessage. I need this change to proceed with the migration to PSI and simultaneous support of rules and standalone generators (as we complete the migration to the latter).

Changes to JavaValidationRenderer:

  1. Revises the class top-level documentation. It was outdated.
  2. Decreases the number of intermediate and file-level methods in the file.
  3. The top-level documentation is directly mapped to the body of render() method now. A significant part of this method is local comments, describing why all modifications of a message file cannot be done under a single forEach. I describe this inconsistency, but leave it "as is" because I'm going to migrate to PSI in the upcoming PRs.
  4. I removed the protected modifier from render() because it is unnecessary with override, and makes the entry point a bit harder to locate.

Other changes:

  1. I have removed Validations collection, we can easily go without this class. We already have many classes with "Validation" in their names. Takes time to differentiate their purposes.
  2. Moved declaration of FieldId up in field_views.proto file.
  3. Renamed MessageValidation to CompilationMessage.
    3.1. This projection stores views for each message being compiled.
    3.2. Having one or more constraints is optional. We always add validate() to all messages (and related stuff), which go through the library no matter if they have constraints or not.
    3.3. When we complete migration from Rule, this class will disappear completely. Each generator will query only a view of interest, like RequiredFieldView or DistinctFieldView.
    3.4. Its usages talked more about a compilationMessage, and less as of a set of constraints.
    3.5. This eliminates another class with "Validation" in its name.

Unrelated changes:

  1. Renamed a local variable in SetOnceValidationRenderer. Both options are good enough, but the new one looks a bit better to me. And JavaValidationRenderer also uses allCompilationMessages.
  2. Took the latest ProtoData.

@yevhenii-nadtochii yevhenii-nadtochii self-assigned this Jan 16, 2025
@yevhenii-nadtochii yevhenii-nadtochii marked this pull request as ready for review January 16, 2025 16:29
@yevhenii-nadtochii
Copy link
Contributor Author

@armiol @alexander-yevsyukov PTAL

Copy link
Collaborator

@armiol armiol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yevhenii-nadtochii very dense PR, good job! However, in some places I think you went too far. Please see my comments.

// All the validation rules of a single message type.
message MessageValidation {
// A message compiled by the validation library with its constraints, if any.
message CompiledMessage {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as in Spine there is a "Model Compiler" (which in turn heavily uses the Validation codegen capabilities), we cannot use CompiledMessage, because it clashes with that domain. Until I saw the before-after in this changeset, I had no idea what a CompiledMessage was. My only guess was that it was something describing the work of the Model Compiler routines completed for some particular Message instance, more of an item in some audit log.

I agree that MessageValidation isn't the best name. But still, what this declaration represents is by no means a result of a compilation process in that sense which we assume in Model Compiler. That's why it cannot be "compiled" (a participle, meaning "it's done"), It's rather an intermediate view of a "to-do" list for the next stages.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's think of a better name. I will, too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about CompiledMessage as a message that is being compiled right now. It can also be CompilingMessage, CompilationMessage.

messageTypes
.map { m -> m.message.javaFile(m.fileHeader) }
.mapNotNull { path -> find(path) }
.distinct()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure that the newly introduced distinct() (line 98) is semantically equal to this whole block? I suspect there might be a subtle difference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, ProtoData combines calls of javaFile(header), find(path) and unchecked cast to SourceFile<Java> in a single SourceFileSet.javaFileOf() method.

This new method throws when find(path) returns null (meaning no Java file). I have added if (!sources.hasJavaRoot) { return } in the beginning of the renderer to avoid this.

@@ -126,7 +126,7 @@ internal class ValidationConstraintsCode private constructor(
/**
* Creates a new instance with the generated validation constraints code.
*/
fun generate(r: JavaValidationRenderer, v: MessageValidation): ValidationConstraintsCode {
fun generate(r: JavaValidationRenderer, v: CompiledMessage): ValidationConstraintsCode {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a general comment: when renaming something, please check the corresponding variable names, too.

string error_message = 2;

// `true` if the field is marked with `(required) = true`, `false` otherwise.
bool required = 3;
bool enabled = 3;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The field was called required because it's written like that in the consumer code.

"Enabled" is about something else. "Whether the validation is enabled or not?"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bit weird both ways.

I suggest not create RequiredField view for non-required fields at all. The same with the rest of such views. We do nothing when such a flag is false because RequiredField with required = false is NonRequiredField.

Having this bool something = false flag in SomethingField means this something field is not something.

@@ -11,17 +11,30 @@ option java_multiple_files = true;

import "spine/protodata/ast.proto";

// Uniquely identifies a message field.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say it uniquely identifies a declaration of a field in some Message.

It doesn't identify the field instance itself, as one might think reading this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't identify the field instance itself, as one might think reading this.

It does because it has both the parent type name and the field name.

// Name of the containing type.
protodata.TypeName type = 1;

// Short name of the field.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's enough that we call the field name "short" in the declaration of the FieldName itself.

string error_message = 2;

// Value of `set_once` boolean option.
// `true` if the field is marked with `(set_once) = true`, `false` otherwise.
bool enabled = 3;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, I would have this named set_once for that same reason as above.

@@ -55,7 +55,7 @@ message MessageValidation {

// Validation rules.
//
// All these rules must be met in order for a message to be regarded as valid.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove? The meaning is somewhat changed, don't you think?

Copy link

codecov bot commented Jan 21, 2025

Codecov Report

Attention: Patch coverage is 31.91489% with 32 lines in your changes missing coverage. Please review.

Project coverage is 32.16%. Comparing base (2e6ec50) to head (071b004).
Report is 27 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master     #185      +/-   ##
============================================
+ Coverage     31.65%   32.16%   +0.50%     
  Complexity      329      329              
============================================
  Files           142      141       -1     
  Lines          2818     2786      -32     
  Branches        229      227       -2     
============================================
+ Hits            892      896       +4     
+ Misses         1860     1824      -36     
  Partials         66       66              

@yevhenii-nadtochii
Copy link
Contributor Author

@armiol PTAL

Copy link
Collaborator

@armiol armiol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yevhenii-nadtochii LGTM with the respect of the comment.

@@ -11,17 +11,30 @@ option java_multiple_files = true;

import "spine/protodata/ast.proto";

// Uniquely identifies a field declaration within the compilation messages.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this type addresses not only "compilation" messages. It's a part of AST, so I say we shouldn't be specific about "compilation" messages.

@yevhenii-nadtochii yevhenii-nadtochii merged commit 9748a15 into master Jan 21, 2025
6 checks passed
@yevhenii-nadtochii yevhenii-nadtochii deleted the refactor-entry-point branch January 21, 2025 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants