Skip to content
This repository was archived by the owner on May 14, 2026. It is now read-only.

Commit 60f65ea

Browse files
authored
[ggj][nestedsig] feat: add support for nested method sigs (aip.dev/4232) (#626)
* fix: fix dep ordering in Bazel dedupe rules * feat: add a basic trie * fix: add hashCode() and equals() to Field * fix: add equals() and hashCode() for MethodArgument * feat: add a TriFunction * feat: add DFS traversal to Trie * feat: add support for nested method sigs (aip.dev/4232)
1 parent b790fdf commit 60f65ea

4 files changed

Lines changed: 177 additions & 18 deletions

File tree

src/main/java/com/google/api/generator/gapic/composer/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ java_library(
2323
"//src/main/java/com/google/api/generator/gapic/composer/utils",
2424
"//src/main/java/com/google/api/generator/gapic/model",
2525
"//src/main/java/com/google/api/generator/gapic/utils",
26+
"//src/main/java/com/google/api/generator/util",
2627
"@com_google_api_api_common//jar",
2728
"@com_google_api_gax_java//gax",
2829
"@com_google_api_gax_java//gax-grpc:gax_grpc",

src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
import com.google.api.generator.gapic.model.MethodArgument;
6969
import com.google.api.generator.gapic.model.Service;
7070
import com.google.api.generator.gapic.utils.JavaStyle;
71+
import com.google.api.generator.util.TriFunction;
72+
import com.google.api.generator.util.Trie;
7173
import com.google.common.annotations.VisibleForTesting;
7274
import com.google.common.base.Preconditions;
7375
import com.google.common.base.Strings;
@@ -84,6 +86,7 @@
8486
import java.util.Map;
8587
import java.util.Objects;
8688
import java.util.concurrent.TimeUnit;
89+
import java.util.function.BiFunction;
8790
import java.util.function.Function;
8891
import java.util.stream.Collectors;
8992
import javax.annotation.Generated;
@@ -1336,23 +1339,68 @@ private static ClassDefinition createNestedRpcFixedSizeCollectionClass(
13361339
static Expr createRequestBuilderExpr(
13371340
Method method, List<MethodArgument> signature, TypeStore typeStore) {
13381341
TypeNode methodInputType = method.inputType();
1339-
MethodInvocationExpr newBuilderExpr =
1342+
Expr newBuilderExpr =
13401343
MethodInvocationExpr.builder()
13411344
.setMethodName("newBuilder")
13421345
.setStaticReferenceType(methodInputType)
13431346
.build();
1344-
for (MethodArgument argument : signature) {
1345-
newBuilderExpr = buildNestedSetterInvocationExpr(argument, newBuilderExpr);
1347+
// Maintain the args' order of appearance for better determinism.
1348+
List<Field> rootFields = new ArrayList<>();
1349+
Map<Field, Trie<Field>> rootFieldToTrie = new HashMap<>();
1350+
for (MethodArgument arg : signature) {
1351+
Field rootField = arg.nestedFields().isEmpty() ? arg.field() : arg.nestedFields().get(0);
1352+
if (!rootFields.contains(rootField)) {
1353+
rootFields.add(rootField);
1354+
}
1355+
Trie<Field> updatedTrie =
1356+
rootFieldToTrie.containsKey(rootField) ? rootFieldToTrie.get(rootField) : new Trie();
1357+
List<Field> nestedFieldsWithChild = new ArrayList<>(arg.nestedFields());
1358+
nestedFieldsWithChild.add(arg.field());
1359+
updatedTrie.insert(nestedFieldsWithChild);
1360+
rootFieldToTrie.put(rootField, updatedTrie);
13461361
}
13471362

1348-
MethodInvocationExpr builderExpr =
1349-
MethodInvocationExpr.builder()
1350-
.setMethodName("build")
1351-
.setExprReferenceExpr(newBuilderExpr)
1352-
.setReturnType(methodInputType)
1353-
.build();
1363+
Function<Field, Expr> parentPreprocFn =
1364+
field ->
1365+
(Expr)
1366+
MethodInvocationExpr.builder()
1367+
.setStaticReferenceType(field.type())
1368+
.setMethodName("newBuilder")
1369+
.build();
1370+
TriFunction<Field, Expr, Expr, Expr> parentPostprocFn =
1371+
(field, baseRefExpr, leafProcessedExpr) -> {
1372+
boolean isRootNode = field == null;
1373+
return isRootNode
1374+
? leafProcessedExpr
1375+
: MethodInvocationExpr.builder()
1376+
.setExprReferenceExpr(baseRefExpr)
1377+
.setMethodName(String.format("set%s", JavaStyle.toUpperCamelCase(field.name())))
1378+
.setArguments(
1379+
MethodInvocationExpr.builder()
1380+
.setExprReferenceExpr(leafProcessedExpr)
1381+
.setMethodName("build")
1382+
.build())
1383+
.build();
1384+
};
1385+
1386+
final Map<Field, MethodArgument> fieldToMethodArg =
1387+
signature.stream().collect(Collectors.toMap(a -> a.field(), a -> a));
1388+
BiFunction<Field, Expr, Expr> leafProcFn =
1389+
(field, parentBaseRefExpr) ->
1390+
(Expr) buildNestedSetterInvocationExpr(fieldToMethodArg.get(field), parentBaseRefExpr);
1391+
1392+
for (Field rootField : rootFields) {
1393+
newBuilderExpr =
1394+
rootFieldToTrie
1395+
.get(rootField)
1396+
.dfsTraverseAndReduce(parentPreprocFn, parentPostprocFn, leafProcFn, newBuilderExpr);
1397+
}
13541398

1355-
return builderExpr;
1399+
return MethodInvocationExpr.builder()
1400+
.setExprReferenceExpr(newBuilderExpr)
1401+
.setMethodName("build")
1402+
.setReturnType(methodInputType)
1403+
.build();
13561404
}
13571405

13581406
@VisibleForTesting

src/test/java/com/google/api/generator/gapic/composer/goldens/IdentityClient.golden

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,7 @@ public class IdentityClient implements BackgroundResource {
133133
CreateUserRequest request =
134134
CreateUserRequest.newBuilder()
135135
.setParent(parent)
136-
.setDisplayName(displayName)
137-
.setEmail(email)
136+
.setUser(User.newBuilder().setDisplayName(displayName).setEmail(email).build())
138137
.build();
139138
return createUser(request);
140139
}
@@ -161,12 +160,71 @@ public class IdentityClient implements BackgroundResource {
161160
CreateUserRequest request =
162161
CreateUserRequest.newBuilder()
163162
.setParent(parent)
164-
.setDisplayName(displayName)
165-
.setEmail(email)
166-
.setAge(age)
167-
.setNickname(nickname)
168-
.setEnableNotifications(enableNotifications)
169-
.setHeightFeet(heightFeet)
163+
.setUser(
164+
User.newBuilder()
165+
.setDisplayName(displayName)
166+
.setEmail(email)
167+
.setAge(age)
168+
.setNickname(nickname)
169+
.setEnableNotifications(enableNotifications)
170+
.setHeightFeet(heightFeet)
171+
.build())
172+
.build();
173+
return createUser(request);
174+
}
175+
176+
// AUTO-GENERATED DOCUMENTATION AND METHOD.
177+
/**
178+
* @param parent
179+
* @param displayName
180+
* @param email
181+
* @param hobbyName
182+
* @param songName
183+
* @param weeklyFrequency
184+
* @param companyName
185+
* @param title
186+
* @param subject
187+
* @param artistName
188+
* @throws com.google.api.gax.rpc.ApiException if the remote call fails
189+
*/
190+
public final User createUser(
191+
String parent,
192+
String displayName,
193+
String email,
194+
String hobbyName,
195+
String songName,
196+
int weeklyFrequency,
197+
String companyName,
198+
String title,
199+
String subject,
200+
String artistName) {
201+
CreateUserRequest request =
202+
CreateUserRequest.newBuilder()
203+
.setParent(parent)
204+
.setUser(
205+
User.newBuilder()
206+
.setDisplayName(displayName)
207+
.setEmail(email)
208+
.setHobby(
209+
Hobby.newBuilder()
210+
.setHobbyName(hobbyName)
211+
.setWeeklyFrequency(weeklyFrequency)
212+
.setInstructionManual(
213+
Book.newBuilder().setTitle(title).setSubject(subject).build())
214+
.build())
215+
.setSong(
216+
Song.newBuilder()
217+
.setSongName(songName)
218+
.setSongArtist(
219+
Artist.newBuilder()
220+
.setRecordingCompany(
221+
RecordingCompany.newBuilder()
222+
.setCompanyName(companyName)
223+
.build())
224+
.setArtistName(artistName)
225+
.build())
226+
.build())
227+
.build())
170228
.build();
171229
return createUser(request);
172230
}

src/test/java/com/google/api/generator/gapic/testdata/identity.proto

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ service Identity {
4646
"parent,user.display_name,user.email";
4747
option (google.api.method_signature) =
4848
"parent,user.display_name,user.email,user.age,user.nickname,user.enable_notifications,user.height_feet";
49+
// Test nested method arguments and ensure that order doesn't matter.
50+
option (google.api.method_signature) =
51+
"parent,user.display_name,user.email,user.hobby.hobby_name,user.song.song_name,user.hobby.weekly_frequency,user.song.song_artist.recording_company.company_name,user.hobby.instruction_manual.title,user.hobby.instruction_manual.subject,user.song.song_artist.artist_name";
4952
}
5053

5154
// Retrieves the User with the given uri.
@@ -121,6 +124,55 @@ message User {
121124
// (-- aip.dev/not-precedent: The default for the feature is true.
122125
// Ordinarily, the default for a `bool` field should be false. --)
123126
optional bool enable_notifications = 9;
127+
128+
// The user's favorite hobby.
129+
optional Hobby hobby = 10;
130+
131+
// The user's song.
132+
optional Song song = 11;
133+
}
134+
135+
message Hobby {
136+
// The name of the hobby.
137+
string hobby_name = 1;
138+
139+
// Weekly frequency this hobby is performed.
140+
int32 weekly_frequency = 2;
141+
142+
// The instruction manual for the hobby.
143+
Book instruction_manual = 3;
144+
}
145+
146+
message Book {
147+
// The title of the instruction manual.
148+
string title = 1;
149+
150+
// The subject of the instruction manual.
151+
string subject = 2;
152+
}
153+
154+
message Song {
155+
// The name of the song.
156+
string song_name = 1;
157+
158+
// The song's artist.
159+
Artist song_artist = 2;
160+
}
161+
162+
message Artist {
163+
// The name of the artist.
164+
string artist_name = 1;
165+
166+
// The artist's recording company.
167+
RecordingCompany recording_company = 2;
168+
}
169+
170+
message RecordingCompany {
171+
// The recording company's name
172+
string company_name = 1;
173+
174+
// The year the company was founded.
175+
int32 founding_year = 2;
124176
}
125177

126178
// The request message for the google.showcase.v1beta1.Identity\CreateUser

0 commit comments

Comments
 (0)