-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add grpc-validation-utils module #41
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
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| plugins { | ||
| `java-library` | ||
| } | ||
|
|
||
| dependencies { | ||
| api(project(":grpc-context-utils")) | ||
| api("io.grpc:grpc-api") | ||
| implementation("com.google.protobuf:protobuf-java-util:3.21.7") | ||
| } | ||
58 changes: 58 additions & 0 deletions
58
...tion-utils/src/main/java/org/hypertrace/core/grpcutils/validation/GrpcValidatorUtils.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package org.hypertrace.core.grpcutils.validation; | ||
|
|
||
| import com.google.protobuf.Descriptors.FieldDescriptor; | ||
| import com.google.protobuf.Message; | ||
| import com.google.protobuf.util.JsonFormat; | ||
| import io.grpc.Status; | ||
| import org.hypertrace.core.grpcutils.context.RequestContext; | ||
|
|
||
| public class GrpcValidatorUtils { | ||
| private static final JsonFormat.Printer JSON_PRINTER = JsonFormat.printer(); | ||
|
|
||
| private GrpcValidatorUtils() {} | ||
|
|
||
| public static void validateRequestContextOrThrow(RequestContext requestContext) { | ||
| if (requestContext.getTenantId().isEmpty()) { | ||
| throw Status.INVALID_ARGUMENT | ||
| .withDescription("Missing expected Tenant ID") | ||
| .asRuntimeException(); | ||
| } | ||
| } | ||
|
|
||
| public static <T extends Message> void validateNonDefaultPresenceOrThrow( | ||
| T source, int fieldNumber) { | ||
| FieldDescriptor descriptor = source.getDescriptorForType().findFieldByNumber(fieldNumber); | ||
|
|
||
| if (descriptor.isRepeated()) { | ||
| validateNonDefaultPresenceRepeatedOrThrow(source, descriptor); | ||
| } else if (!source.hasField(descriptor) | ||
| || source.getField(descriptor).equals(descriptor.getDefaultValue())) { | ||
| throw Status.INVALID_ARGUMENT | ||
| .withDescription( | ||
| String.format( | ||
| "Expected field value %s but not present:%n %s", | ||
| descriptor.getFullName(), printMessage(source))) | ||
| .asRuntimeException(); | ||
| } | ||
| } | ||
|
|
||
| private static <T extends Message> void validateNonDefaultPresenceRepeatedOrThrow( | ||
| T source, FieldDescriptor descriptor) { | ||
| if (source.getRepeatedFieldCount(descriptor) == 0) { | ||
| throw Status.INVALID_ARGUMENT | ||
| .withDescription( | ||
| String.format( | ||
| "Expected at least 1 value for repeated field %s but not present:%n %s", | ||
| descriptor.getFullName(), printMessage(source))) | ||
| .asRuntimeException(); | ||
| } | ||
| } | ||
|
|
||
| public static String printMessage(Message message) { | ||
| try { | ||
| return JSON_PRINTER.print(message); | ||
| } catch (Exception exception) { | ||
| return message.toString(); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
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.
@aaron-steinfeld This was defined as an api dependency at the place from which this module is copied - https://github.com/hypertrace/config-service/blob/2a36d97d9d50978cc8ea119d6b8f96bd9e0238e5/validation-utils/build.gradle.kts#L7. We're only using
io.grpc.Statusfrom this dependency and it is used to throwStatusRuntimeException. But, since none of the public methods declare that it throws that exception, shouldn't it be an implementation dependency?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.
Technically... maybe? But I would leave it since it's intended to be a grpc status exception, it's part of its implicit contract. It's only a runtime exception because it makes it easier to work with in config service.
On the broader move to pull it out of config service and publish it - I'm not really a big fan, but could maybe be convinced. At a high level, this just feels like the DRY plague too me - there's about 40 lines of code that we might want to share, is that worth having to work across multiple repos, maintain library versioning etc? And more over, this generic proto stuff shouldn't really be applying to other repos - config is unique in hosting many unrelated services.
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.
Got it.
I agree that there's only a few lines of code but my thinking was that almost every grpc service has CRUD operations and each of those services should be doing some kind of validation for those operations (like checking the request context), especially for create and update operations where it should be checking for non-default presence of mandatory fields where these generic methods could come in handy. So, rather than duplicating the code at a lot of places, it's better to have that at a single place only.
Regarding the overhead, I don't think it would be too much because if a repo is dependent on this module, it would most likely be dependent on the server-utils module, and since they belong to this same repo, maintaining versioning and all shouldn't be that difficult. And anyways, it would still be upto the consumer to use this library or not 🙂
Let's say we want to check for the non-default presence of 10 different fields in a proto object. Then, if we don't have such generic methods, we'd be doing a similar kind of thing which is being done in those generic methods at 10 places, which could make the code quite ugly.
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.
My thinking was that the generated methods like hasXyz or getXyzCount were sufficient, but forgot about primitives, so I buy this.
The point here is that if something changes later, then it requires a two step change. So whether they're using server utils or made the initial decision to consume this library or not doesn't really play in. Worse, if there's a bug here then every consuming repo needs to be updated.
Yeah, basically same point above. For the non-primitive fields I think it's more readable to do the
if(!hasXyz) throwpattern anyway, but given the primitives and default values, I buy this (and then like the consistency of only using one way).