Skip to content
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

Draft: Selective api poc #3129

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c20c974
temp: local generate java-common-protos from googleapis with client.p…
zhumin8 Aug 12, 2024
cbcd5bc
poc for typical cases on selective generation.
zhumin8 Aug 12, 2024
cf87622
minor fixes and add comments.
zhumin8 Aug 15, 2024
e877473
temp commit: needs cleanups - experimenting with resources.:
zhumin8 Aug 16, 2024
a89bb9f
add composer test to verify methods in client generated.
zhumin8 Aug 21, 2024
7dcdf56
fix service yaml library_settings.version. add verification that it m…
zhumin8 Aug 23, 2024
f47c93a
minor code cleanups.
zhumin8 Aug 23, 2024
2496716
add note about cleanups for message and resource names parsing.
zhumin8 Aug 23, 2024
c29a319
rename method name.
zhumin8 Aug 23, 2024
7d8480a
minor fix: use parent interface.
zhumin8 Aug 23, 2024
395fa47
update test proto and config comments and namings.
zhumin8 Aug 23, 2024
950069e
review comments, combining methods for readability.
zhumin8 Aug 30, 2024
fa63021
minor cleanup.
zhumin8 Aug 30, 2024
e013458
update ComposerTest to check on non-included methods.
zhumin8 Aug 30, 2024
5a89def
update test.
zhumin8 Aug 30, 2024
a99af2c
move test for helper resource name from ComposerTest to ProtoTest.
zhumin8 Aug 30, 2024
e8f5388
review feedback, log warning text.
zhumin8 Sep 13, 2024
2012a82
fix regression when combining logic in 950069e58.
zhumin8 Sep 14, 2024
7978c15
trim proto for test. rm composer test.
zhumin8 Sep 14, 2024
c2967e9
expose private method to test, add test for version mismatch, add moc…
zhumin8 Sep 16, 2024
819bdd5
trying to refactor 2 parser tests with mockito mocks.
zhumin8 Sep 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package com.google.api.generator.gapic.protoparser;

import com.google.api.ClientLibrarySettings;
import com.google.api.ClientProto;
import com.google.api.DocumentationRule;
import com.google.api.FieldBehavior;
Expand Down Expand Up @@ -154,6 +155,14 @@ public static GapicContext parse(CodeGeneratorRequest request) {
// Keep message and resource name parsing separate for cleaner logic.
// While this takes an extra pass through the protobufs, the extra time is relatively trivial
// and is worth the larger reduced maintenance cost.

// Note that if selective api generation is configured via service yaml, resource names
// and messages corresponding to RPC's methods excluded for generation are not removed.
// However, these resource names will not be composed.
// refer to ComposerTest.testComposeSelectively_shouldComposeOnlyOneHelperResource for
// verification.
// TODO: remove messages and resource names that's only used by excluded RPCs configured
// via selective api generation from parsed GapicContext.
Map<String, Message> messages = parseMessages(request, outputResourceReferencesSeen);

Map<String, ResourceName> resourceNames = parseResourceNames(request);
Expand Down Expand Up @@ -425,6 +434,47 @@ public static List<Service> parseService(
Transport.GRPC);
}

private static Optional<List<String>> getInclusionMethodListFromServiceYaml(
Optional<com.google.api.Service> serviceYamlProtoOpt, String protoPackage) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is usually preferred to not passing around Optional if we can check it before calling the method.

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 want to follow-up on this from our discussion earlier and get @blakeli0 your opinion on this. (FYI, I've combined this method into shouldIncludeMethodInGeneration() )
For now, I am slightly inclined to keep this Optional<com.google.api.Service> serviceYamlProtoOpt as input for 2 reasons:

  • The pattern of passing in this Optional is repeated in this Parser class, a larger scale refactor is needed to remove these.
  • Because this service yaml is an optional config file that only some libraries have, it sort of make sense to have this Optional representation. And I find the current way can better encapsulate this piece of logic: shouldIncludeMethodInGeneration() has all the information to decide if this method should or not be included. I could check serviceYamlProtoOpt before calling the method, but the 2 occurrences would repeat the logic in a reverse way, and it might be less readable for debugging in the future.

Does this makes sense to you? WDYT?

if (!serviceYamlProtoOpt.isPresent()
|| serviceYamlProtoOpt.get().getPublishing().getLibrarySettingsCount() == 0) {
return Optional.empty();
}
List<ClientLibrarySettings> librarySettingsList =
serviceYamlProtoOpt.get().getPublishing().getLibrarySettingsList();
// verify library_settings.version matches with proto package name
// This should be validated in upstream. Give warnings if not match.
if (!librarySettingsList.get(0).getVersion().isEmpty()
zhumin8 marked this conversation as resolved.
Show resolved Hide resolved
&& !protoPackage.equals(librarySettingsList.get(0).getVersion())) {
LOGGER.warning(
"Service yaml config may be misconfigured. "
zhumin8 marked this conversation as resolved.
Show resolved Hide resolved
+ "Version in publishing.library_settings does not match proto package."
zhumin8 marked this conversation as resolved.
Show resolved Hide resolved
+ "Disregarding selective generation settings.");
return Optional.empty();
}
List<String> includeMethodsList =
librarySettingsList
.get(0)
.getJavaSettings()
.getCommon()
.getSelectiveGapicGeneration()
.getMethodsList();
return Optional.of(includeMethodsList);
}

private static boolean shouldIncludeMethodInGeneration(
zhumin8 marked this conversation as resolved.
Show resolved Hide resolved
MethodDescriptor method, Optional<List<String>> optionalIncludeMethodsList) {
// default to include all when no service yaml or no library setting section.
if (!optionalIncludeMethodsList.isPresent()) {
return true;
}
// default to include all when nothing specified.
if (optionalIncludeMethodsList.get().isEmpty()) {
return true;
}
zhumin8 marked this conversation as resolved.
Show resolved Hide resolved
return optionalIncludeMethodsList.get().contains(method.getFullName());
}

public static List<Service> parseService(
FileDescriptor fileDescriptor,
Map<String, Message> messageTypes,
Expand All @@ -433,18 +483,27 @@ public static List<Service> parseService(
Optional<GapicServiceConfig> serviceConfigOpt,
Set<ResourceName> outputArgResourceNames,
Transport transport) {

String protoPackage = fileDescriptor.getPackage();
Optional<List<String>> inclusionMethodListFromServiceYaml =
getInclusionMethodListFromServiceYaml(serviceYamlProtoOpt, protoPackage);
return fileDescriptor.getServices().stream()
.filter(
serviceDescriptor -> {
List<MethodDescriptor> methodsList = serviceDescriptor.getMethods();
if (methodsList.isEmpty()) {
List<MethodDescriptor> methodListSelected =
Copy link
Collaborator

Choose a reason for hiding this comment

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

This scenario is in case that all methods from a service are excluded?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. And this is likely now e.g. in vertexai's requirement it only exposes methods from 2 services.

methodsList.stream()
.filter(
method -> {
return shouldIncludeMethodInGeneration(method, inclusionMethodListFromServiceYaml);
})
.collect(Collectors.toList());
if (methodListSelected.isEmpty()) {
LOGGER.warning(
String.format(
"Service %s has no RPC methods and will not be generated",
serviceDescriptor.getName()));
}
return !methodsList.isEmpty();
return !methodListSelected.isEmpty();
})
.map(
s -> {
Expand Down Expand Up @@ -498,6 +557,8 @@ public static List<Service> parseService(
String pakkage = TypeParser.getPackage(fileDescriptor);
String originalJavaPackage = pakkage;
// Override Java package with that specified in gapic.yaml.
// this override is deprecated and legacy support only
// see go/client-user-guide#configure-long-running-operation-polling-timeouts-optional
if (serviceConfigOpt.isPresent()
&& serviceConfigOpt.get().getLanguageSettingsOpt().isPresent()) {
GapicLanguageSettings languageSettings =
Expand All @@ -518,6 +579,7 @@ public static List<Service> parseService(
.setMethods(
parseMethods(
s,
inclusionMethodListFromServiceYaml,
pakkage,
messageTypes,
resourceNames,
Expand Down Expand Up @@ -709,6 +771,7 @@ public static Map<String, ResourceName> parseResourceNames(
@VisibleForTesting
static List<Method> parseMethods(
ServiceDescriptor serviceDescriptor,
Optional<List<String>> inclusionMethodListFromServiceYaml,
String servicePackage,
Map<String, Message> messageTypes,
Map<String, ResourceName> resourceNames,
Expand All @@ -721,8 +784,10 @@ static List<Method> parseMethods(
// Parse the serviceYaml for autopopulated methods and fields once and put into a map
Map<String, List<String>> autoPopulatedMethodsWithFields =
parseAutoPopulatedMethodsAndFields(serviceYamlProtoOpt);

for (MethodDescriptor protoMethod : serviceDescriptor.getMethods()) {
if (!shouldIncludeMethodInGeneration(protoMethod, inclusionMethodListFromServiceYaml)) {
continue;
}
// Parse the method.
TypeNode inputType = TypeParser.parseType(protoMethod.getInputType());
Method.Builder methodBuilder = Method.builder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.google.api.generator.engine.ast.ClassDefinition;
import com.google.api.generator.engine.ast.MethodDefinition;
import com.google.api.generator.engine.ast.ScopeNode;
import com.google.api.generator.engine.writer.JavaWriterVisitor;
import com.google.api.generator.gapic.composer.comment.CommentComposer;
Expand Down Expand Up @@ -172,6 +173,50 @@ void testComposePackageInfo_emptyGapicContext_returnsNull() {
assertNull(Composer.composePackageInfo(GapicContext.EMPTY));
}

@Test
void testComposeSelectively_shouldComposeOnlyOneHelperResource() {
zhumin8 marked this conversation as resolved.
Show resolved Hide resolved
GapicContext context = GrpcTestProtoLoader.instance().parseSelectiveGenerationTesting();
List<GapicClass> resourceNameHelperClasses =
Composer.generateResourceNameHelperClasses(context);
assertEquals(1, resourceNameHelperClasses.size());
for (GapicClass clazz : resourceNameHelperClasses) {
String className = clazz.classDefinition().classIdentifier().name();
Assert.assertGoldenClass(
this.getClass(), clazz, "SelectiveGenerated" + className + ".golden");
}
}

@Test
void testComposeSelectively_serviceClientShouldOnlyContainSelectedMethods() {
zhumin8 marked this conversation as resolved.
Show resolved Hide resolved
GapicContext context = GrpcTestProtoLoader.instance().parseSelectiveGenerationTesting();
List<GapicClass> serviceClasses = Composer.composeServiceClasses(context);
assertEquals(10, serviceClasses.size());
for (GapicClass clazz : serviceClasses) {
if (clazz
.classDefinition()
.classIdentifier()
.name()
.equals("EchoServiceShouldGeneratePartialClient")) {
assertEquals(25, clazz.classDefinition().methods().size());
for (MethodDefinition method : clazz.classDefinition().methods()) {
String methodName = method.methodIdentifier().name();
if (method.isConstructor()) {
continue;
}
assertTrue(
methodName.startsWith("echo")
|| methodName.startsWith("chat")
|| methodName.startsWith("create")
|| methodName.startsWith("get")
|| methodName.startsWith("close")
|| methodName.startsWith("shutdown")
|| methodName.startsWith("is")
|| methodName.startsWith("awaitTermination"));
}
}
}
}

private List<GapicClass> getTestClassListFromService(Service testService) {
GapicClass testClass =
GrpcServiceCallableFactoryClassComposer.instance()
Expand Down
Loading