From 3b8288067f8d0c0f0fb228349899c0aaedb67aa9 Mon Sep 17 00:00:00 2001 From: blakeli Date: Tue, 24 Mar 2026 12:25:07 -0400 Subject: [PATCH 01/17] fix: Handle null server address --- .../com/google/api/gax/rpc/EndpointContext.java | 12 +++++++++--- .../google/api/gax/rpc/EndpointContextTest.java | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java index 09e105ccebc4..2a775ec4dd6c 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java @@ -134,6 +134,7 @@ public static EndpointContext getDefaultInstance() { public abstract String resolvedEndpoint(); + @Nullable public abstract String resolvedServerAddress(); @Nullable @@ -410,7 +411,7 @@ private Integer parseServerPort(String endpoint) { return null; } HostAndPort hostAndPort = parseServerHostAndPort(endpoint); - if (!hostAndPort.hasPort()) { + if (hostAndPort == null || !hostAndPort.hasPort()) { return null; } return hostAndPort.getPort(); @@ -466,8 +467,13 @@ public EndpointContext build() throws IOException { setResolvedUniverseDomain(determineUniverseDomain()); String endpoint = determineEndpoint(); setResolvedEndpoint(endpoint); - setResolvedServerAddress(parseServerAddress(resolvedEndpoint())); - setResolvedServerPort(parseServerPort(resolvedEndpoint())); + try { + setResolvedServerAddress(parseServerAddress(resolvedEndpoint())); + setResolvedServerPort(parseServerPort(resolvedEndpoint())); + } catch (Exception throwable) { + // Server address and server port are only used for observability. + // We should ignore any errors parsing them and not affect the main client requests. + } setUseS2A(shouldUseS2A()); return autoBuild(); } diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java index 99a8f63fcf3e..b8c24cd6248b 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java @@ -683,4 +683,19 @@ void endpointContextBuild_resolvesPort() throws IOException { .build(); Truth.assertThat(endpointContext.resolvedServerPort()).isNull(); } + + @Test + void endpointContextBuild_resolvesInvalidEndpointAndPort() throws Exception { + + String endpoint = "localhost:-1"; + + EndpointContext endpointContext = + defaultEndpointContextBuilder + .setClientSettingsEndpoint(endpoint) + .setTransportChannelProviderEndpoint(null) + .build(); + + Truth.assertThat(endpointContext.resolvedServerAddress()).isNull(); + Truth.assertThat(endpointContext.resolvedServerPort()).isNull(); + } } From da23a4201d79f33e0ac2491680077ddfb676a2af Mon Sep 17 00:00:00 2001 From: blakeli Date: Thu, 26 Mar 2026 00:18:40 -0400 Subject: [PATCH 02/17] Filter heuristic resource name extraction by proto package --- .../google/api/pathtemplate/PathTemplate.java | 159 ++++++ .../api/pathtemplate/PathTemplateTest.java | 183 +++++++ ...ractTransportServiceStubClassComposer.java | 218 +++++++- .../GrpcResourceNameExtractorStub.golden | 311 +++++++++++- .../HttpJsonResourceNameExtractorStub.golden | 470 +++++++++++++++++- .../test/protoloader/TestProtoLoader.java | 2 +- .../resource_name_extractor_testing.proto | 86 +++- .../api/gax/rpc/EndpointContextTest.java | 8 +- 8 files changed, 1392 insertions(+), 45 deletions(-) diff --git a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java index cc9f4c110e74..858e1d4399df 100644 --- a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -296,6 +296,162 @@ public Set vars() { return bindings.keySet(); } + /** + * Returns the set of resource literals. A resource literal is a literal followed by a binding or + * inside a binding. + */ + public Set getResourceLiterals() { + Set canonicalSegments = new java.util.LinkedHashSet<>(); + boolean inBinding = false; + for (int i = 0; i < segments.size(); i++) { + Segment seg = segments.get(i); + if (seg.kind() == SegmentKind.BINDING) { + inBinding = true; + } else if (seg.kind() == SegmentKind.END_BINDING) { + inBinding = false; + } else if (seg.kind() == SegmentKind.LITERAL) { + String value = seg.value(); + if (value.matches("^v\\d+.*") || value.matches("^u\\d+.*")) { // just in case + continue; + } + if (inBinding) { + canonicalSegments.add(value); + } else if (i + 1 < segments.size() && segments.get(i + 1).kind() == SegmentKind.BINDING) { + canonicalSegments.add(value); + } + } + } + return canonicalSegments; + } + + /** + * Returns the canonical resource name string. A segment is canonical if it is a literal followed + * by a binding or inside a binding. If a literal is not in knownResources, the extraction stops. + */ + public String getCanonicalResourceName(Set knownResources) { + if (knownResources == null) { + return ""; + } + StringBuilder canonical = new StringBuilder(); + StringBuilder currentSequence = new StringBuilder(); + boolean inBinding = false; + String currentBindingName = ""; + boolean keepBinding = true; + List bindingSegments = new ArrayList<>(); + boolean afterKeptNamedBinding = false; + + for (int i = 0; i < segments.size(); i++) { + Segment seg = segments.get(i); + if (seg.kind() == SegmentKind.BINDING) { + inBinding = true; + currentBindingName = seg.value(); + bindingSegments.clear(); + keepBinding = true; + } else if (seg.kind() == SegmentKind.END_BINDING) { + inBinding = false; + StringBuilder innerPattern = new StringBuilder(); + int literalCount = 0; + for (Segment innerSeg : bindingSegments) { + if (innerSeg.kind() == SegmentKind.LITERAL) { + String value = innerSeg.value(); + if (value.matches("^v\\d+.*") || value.matches("^u\\d+.*")) { + continue; + } + literalCount++; + if (innerPattern.length() > 0) { + innerPattern.append("/"); + } + innerPattern.append(value); + } else if (innerSeg.kind() == SegmentKind.WILDCARD) { + if (innerPattern.length() > 0) { + innerPattern.append("/"); + } + innerPattern.append("*"); + } + } + + boolean extractInner = false; + if (canonical.length() == 0 && currentSequence.length() == 0) { + if (i + 1 < segments.size()) { + Segment nextSeg = segments.get(i + 1); + if (nextSeg.kind() == SegmentKind.LITERAL) { + String nextValue = nextSeg.value(); + if (knownResources.contains(nextValue)) { + extractInner = true; + } + } + } + } + + if (extractInner) { + if (innerPattern.length() > 0) { + if (canonical.length() > 0) { + canonical.append("/"); + } + canonical.append(innerPattern); + } + } else { + if (currentSequence.length() > 0) { + if (canonical.length() > 0) { + canonical.append("/"); + } + canonical.append(currentSequence); + currentSequence.setLength(0); + } + if (canonical.length() > 0) { + canonical.append("/"); + } + + if (literalCount <= 1 || innerPattern.toString().equals("*")) { + canonical.append("{").append(currentBindingName).append("}"); + } else { + canonical + .append("{") + .append(currentBindingName) + .append("=") + .append(innerPattern) + .append("}"); + afterKeptNamedBinding = true; + } + } + } else if (seg.kind() == SegmentKind.LITERAL) { + String value = seg.value(); + if (value.matches("^v\\d+.*") || value.matches("^u\\d+.*")) { + continue; + } + if (inBinding) { + bindingSegments.add(seg); + if (!knownResources.contains(value)) { + keepBinding = false; + } + } else { + if (knownResources.contains(value)) { + if (currentSequence.length() > 0) { + currentSequence.append("/"); + } + currentSequence.append(value); + } else { + if (afterKeptNamedBinding) { + if (currentSequence.length() > 0) { + currentSequence.append("/"); + } + currentSequence.append(value); + } else { + if (canonical.length() > 0 || currentSequence.length() > 0) { + break; + } + } + } + } + } else if (seg.kind() == SegmentKind.WILDCARD) { + if (inBinding) { + bindingSegments.add(seg); + } + } + } + return canonical.toString(); + } + /** * Returns a template for the parent of this template. * @@ -997,6 +1153,9 @@ private static ImmutableList parseTemplate(String template) { } private static boolean isSegmentBeginOrEndInvalid(String seg) { + if (seg.isEmpty()) { + return false; + } // A segment is invalid if it contains only one character and the character is a delimiter if (seg.length() == 1 && COMPLEX_DELIMITER_PATTERN.matcher(seg).find()) { return true; diff --git a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index 6ac45cdf203e..2cd82904ab8d 100644 --- a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -31,6 +31,7 @@ package com.google.api.pathtemplate; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.truth.Truth; import java.util.Map; import java.util.Set; @@ -894,6 +895,188 @@ void testTemplateWithMultipleSimpleBindings() { Truth.assertThat(url).isEqualTo("v1/shelves/s1/books/b1"); } + @Test + void name() { + PathTemplate pathTemplate = + PathTemplate.create("projects/{project}/zones/{zone}/{parent_name}"); + System.out.println( + pathTemplate.instantiate("project", "project1", "zone", "zone1", "parent_name", "name1")); + } + + @Test + void testGetResourceLiterals_simplePath() { + PathTemplate template = + PathTemplate.create("/compute/v1/projects/{project}/locations/{location}/widgets/{widget}"); + Truth.assertThat(template.getResourceLiterals()) + .containsExactly("projects", "locations", "widgets"); + } + + @Test + void testGetResourceLiterals_regexPath() { + PathTemplate template = + PathTemplate.create("v1/projects/{project=projects/*}/instances/{instance_id=instances/*}"); + Truth.assertThat(template.getResourceLiterals()).containsExactly("projects", "instances"); + } + + @Test + void testGetResourceSegments_onlyNonResourceLiterals() { + PathTemplate template = PathTemplate.create("compute/v1/projects"); + Truth.assertThat(template.getResourceLiterals()).isEmpty(); + } + + @Test + void testGetResourceLiterals_nameBinding() { + PathTemplate template = PathTemplate.create("v1/{name=projects/*/instances/*}"); + Truth.assertThat(template.getResourceLiterals()).containsExactly("projects", "instances"); + } + + @Test + void testGetResourceSegments_complexResourceId() { + PathTemplate template = PathTemplate.create("projects/{project}/zones/{zone_a}~{zone_b}"); + Truth.assertThat(template.getResourceLiterals()).containsExactly("projects", "zones"); + } + + @Test + void testGetResourceLiterals_customVerb() { + PathTemplate template = PathTemplate.create("projects/{project}/instances/{instance}:execute"); + Truth.assertThat(template.getResourceLiterals()).containsExactly("projects", "instances"); + } + + @Test + void testGetResourceLiterals_multipleVersions() { + PathTemplate template = + PathTemplate.create( + "v1/compute/v2/projects/{project}/locations/{location}/widgets/{widget}"); + Truth.assertThat(template.getResourceLiterals()) + .containsExactly("projects", "locations", "widgets"); + } + + @Test + void testGetResourceLiterals_namedBindings() { + PathTemplate template = + PathTemplate.create( + "/compute/v1/projects/{project}/zones/{zone}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}"); + Truth.assertThat(template.getResourceLiterals()) + .containsExactly( + "projects", "zones", "reservations", "reservationBlocks", "reservationSubBlocks"); + } + + @Test + void testGetCanonicalResourceName_namedBindings() { + PathTemplate template = + PathTemplate.create( + "/v1/projects/{project}/locations/{location}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/heuristics/{heuristic}"); + + Set resourceLiterals = ImmutableSet.of("projects", "locations", "heuristics"); + Truth.assertThat(template.getCanonicalResourceName(resourceLiterals)) + .isEqualTo( + "projects/{project}/locations/{location}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/heuristics/{heuristic}"); + } + + @Test + void testGetCanonicalResourceName_namedBindingsSimple() { + Set moreKnownResources = ImmutableSet.of("projects", "locations", "bars"); + PathTemplate template = PathTemplate.create("/v1/{bar=projects/*/locations/*/bars/*}"); + Truth.assertThat(template.getCanonicalResourceName(moreKnownResources)) + .isEqualTo("{bar=projects/*/locations/*/bars/*}"); + } + + @Test + void testGetCanonicalResourceName_simplePath() { + Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); + PathTemplate template = + PathTemplate.create("/compute/v1/projects/{project}/locations/{location}/widgets/{widget}"); + Truth.assertThat(template.getCanonicalResourceName(knownResources)) + .isEqualTo("projects/{project}/locations/{location}/widgets/{widget}"); + } + + @Test + void testGetCanonicalResourceName_regexVariables() { + Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); + PathTemplate template = + PathTemplate.create("v1/projects/{project=projects/*}/instances/{instance_id=instances/*}"); + Truth.assertThat(template.getCanonicalResourceName(knownResources)) + .isEqualTo("projects/{project}/instances/{instance_id}"); + } + + @Test + void testGetCanonicalResourceName_noVariables() { + Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); + PathTemplate template = PathTemplate.create("v1/projects/locations"); + Truth.assertThat(template.getCanonicalResourceName(knownResources)).isEmpty(); + } + + @Test + void testGetCanonicalResourceName_unknownResource() { + Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); + PathTemplate template = + PathTemplate.create("v1/projects/{project}/unknownResource/{unknownResource}"); + Truth.assertThat(template.getCanonicalResourceName(knownResources)) + .isEqualTo("projects/{project}"); + } + + @Test + void testGetCanonicalResourceName_ignoreVersions() { + Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); + PathTemplate template = + PathTemplate.create( + "v1/compute/v2/projects/{project}/locations/{location}/widgets/{widget}"); + Truth.assertThat(template.getCanonicalResourceName(knownResources)) + .isEqualTo("projects/{project}/locations/{location}/widgets/{widget}"); + } + + @Test + void testGetCanonicalResourceName_customVerb() { + Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); + PathTemplate template = PathTemplate.create("projects/{project}/instances/{instance}:execute"); + Truth.assertThat(template.getCanonicalResourceName(knownResources)) + .isEqualTo("projects/{project}/instances/{instance}"); + } + + @Test + void testGetCanonicalResourceName_nameBinding() { + Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); + PathTemplate template = PathTemplate.create("v1/{field=projects/*/instances/*}"); + Truth.assertThat(template.getCanonicalResourceName(knownResources)) + .isEqualTo("{field=projects/*/instances/*}"); + } + + @Test + void testGetCanonicalResourceName_nameBindingMixedWithSimpleBinding() { + Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); + PathTemplate template = + PathTemplate.create("v1/{field=projects/*/instances/*}/actions/{action}"); + Truth.assertThat(template.getCanonicalResourceName(knownResources)) + .isEqualTo("{field=projects/*/instances/*}/actions/{action}"); + } + + @Test + void testGetCanonicalResourceName_nameBindingWithUnknownLiterals() { + PathTemplate template = + PathTemplate.create( + "/compute/v1/projects/{project}/zones/{zone}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/reservationSlots/{reservation_slot}"); + String canonical = template.getCanonicalResourceName(template.getResourceLiterals()); + Truth.assertThat(canonical) + .isEqualTo( + "projects/{project}/zones/{zone}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/reservationSlots/{reservation_slot}"); + } + + @Test + void testGetCanonicalResourceName_nameBindingMixedWithSimpleBinding_moreKnownResources() { + Set moreKnownResources = ImmutableSet.of("projects", "instances", "actions"); + PathTemplate template = + PathTemplate.create("v1/{name=projects/*/instances/*}/actions/{action}"); + Truth.assertThat(template.getCanonicalResourceName(moreKnownResources)) + .isEqualTo("projects/*/instances/*/actions/{action}"); + } + + @Test + void testGetCanonicalResourceName_nullKnownResources() { + PathTemplate template = + PathTemplate.create("v1/projects/{project}/locations/{location}/widgets/{widget}"); + Truth.assertThat(template.getCanonicalResourceName(null)).isEmpty(); + } + private static void assertPositionalMatch(Map match, String... expected) { Truth.assertThat(match).isNotNull(); int i = 0; diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java index 98d8a121f758..3e0a7fe84355 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java @@ -85,10 +85,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; @@ -99,6 +101,9 @@ import javax.annotation.Nullable; public abstract class AbstractTransportServiceStubClassComposer implements ClassComposer { + private static final List AIP_STANDARDS_METHODS = + ImmutableList.of( + "Get", "List", "Create", "Delete", "Update", "Patch", "Insert", "AggregatedList"); private static final Statement EMPTY_LINE_STATEMENT = EmptyLineStatement.create(); private static final String METHOD_DESCRIPTOR_NAME_PATTERN = "%sMethodDescriptor"; @@ -109,6 +114,9 @@ public abstract class AbstractTransportServiceStubClassComposer implements Class protected static final String CALLABLE_CLASS_MEMBER_PATTERN = "%sCallable"; private static final String OPERATION_CALLABLE_CLASS_MEMBER_PATTERN = "%sOperationCallable"; + private static final ImmutableList HEURISTIC_ENABLED_PACKAGES = + ImmutableList.of("google.cloud.compute", "google.cloud.sql", "google.cloud.bigquery"); + protected static final TypeStore FIXED_TYPESTORE = createStaticTypes(); private final TransportContext transportContext; @@ -147,6 +155,15 @@ private static TypeStore createStaticTypes() { return new TypeStore(concreteClazzes); } + private static boolean isHeuristicEnabled(String protoPackage) { + for (String prefix : HEURISTIC_ENABLED_PACKAGES) { + if (protoPackage.startsWith(prefix)) { + return true; + } + } + return false; + } + @Override public GapicClass generate(GapicContext context, Service service) { if (!service.hasAnyEnabledMethodsForTransport(getTransportContext().transport())) { @@ -277,11 +294,13 @@ protected List createOperationsStubGetterMethod( } protected Expr createTransportSettingsInitExpr( + Service service, Method method, VariableExpr transportSettingsVarExpr, VariableExpr methodDescriptorVarExpr, List classStatements, - ImmutableMap messageTypes) { + ImmutableMap messageTypes, + Set knownResources) { MethodInvocationExpr callSettingsBuilderExpr = MethodInvocationExpr.builder() .setStaticReferenceType(getTransportContext().transportCallSettingsType()) @@ -331,7 +350,9 @@ protected Expr createTransportSettingsInitExpr( .build(); } - LambdaExpr extractor = createResourceNameExtractorClassInstance(method, messageTypes); + LambdaExpr extractor = + createResourceNameExtractorClassInstance( + service, method, messageTypes, knownResources, classStatements); if (extractor != null) { callSettingsBuilderExpr = MethodInvocationExpr.builder() @@ -774,18 +795,21 @@ protected List createConstructorMethods( .build())) .build()))); + Set knownResources = getKnownResources(service); secondCtorExprs.addAll( service.methods().stream() .filter(x -> x.isSupportedByTransport(getTransportContext().transport())) .map( m -> createTransportSettingsInitExpr( + service, m, javaStyleMethodNameToTransportSettingsVarExprs.get( JavaStyle.toLowerCamelCase(m.name())), protoMethodNameToDescriptorVarExprs.get(m.name()), classStatements, - context.messages())) + context.messages(), + knownResources)) .collect(Collectors.toList())); secondCtorStatements.addAll( secondCtorExprs.stream().map(ExprStatement::withExpr).collect(Collectors.toList())); @@ -1510,29 +1534,141 @@ private static Predicate shouldAutoPopulate(Message methodRequestMessage * resource reference (see {@link Field#hasResourceReference()}) */ @Nullable - protected static LambdaExpr createResourceNameExtractorClassInstance( - Method method, ImmutableMap messageTypes) { + protected LambdaExpr createResourceNameExtractorClassInstance( + Service service, + Method method, + ImmutableMap messageTypes, + Set knownResources, + List classStatements) { Field resourceNameField = getDestinationResourceIdField(method, messageTypes); - if (resourceNameField == null) { + if (resourceNameField != null) { + // Expected expression: request -> request.getField() + VariableExpr requestVarExpr = createRequestVarExpr(method); + List bodyStatements = new ArrayList<>(); + Expr returnExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(requestVarExpr) + .setMethodName( + String.format("get%s", JavaStyle.toUpperCamelCase(resourceNameField.name()))) + .setReturnType(TypeNode.STRING) + .build(); + + return LambdaExpr.builder() + .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) + .setBody(bodyStatements) + .setReturnExpr(returnExpr) + .build(); + } + + if (!isHeuristicEnabled(service.protoPakkage())) { + return null; + } + + if (!method.hasHttpBindings()) { return null; } - // Expected expression: request -> request.getField() + String canonicalPath = + extractCanonicalResourceName(method.httpBindings().pattern(), knownResources); + if (!canonicalPath.contains("{")) { + return null; + } + + // Expected expression: private static final PathTemplate GET_HEURISTIC_RESOURCE_NAME_TEMPLATE = + // PathTemplate.create("projects/{project}/locations/{location}/heuristics/{heuristic}"); + TypeNode pathTemplateType = + TypeNode.withReference(ConcreteReference.withClazz(PathTemplate.class)); + String templateName = + String.format("%s_RESOURCE_NAME_TEMPLATE", JavaStyle.toUpperSnakeCase(method.name())); + Variable pathTemplateVar = + Variable.builder().setType(pathTemplateType).setName(templateName).build(); + Statement pathTemplateClassStatement = + createPathTemplateClassStatement(canonicalPath, pathTemplateType, pathTemplateVar); + + classStatements.add(pathTemplateClassStatement); + VariableExpr requestVarExpr = createRequestVarExpr(method); List bodyStatements = new ArrayList<>(); - Expr returnExpr = + + TypeNode mapType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(Map.class) + .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) + .build()); + TypeNode hashMapType = + TypeNode.withReference( + ConcreteReference.builder() + .setClazz(java.util.HashMap.class) + .setGenerics(TypeNode.STRING.reference(), TypeNode.STRING.reference()) + .build()); + + // Expected expression: Map resourceNameSegments = new HashMap(); + VariableExpr resourceNameSegmentsVarExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("resourceNameSegments").setType(mapType).build()) + .setIsDecl(true) + .build(); + + bodyStatements.add( + ExprStatement.withExpr( + AssignmentExpr.builder() + .setVariableExpr(resourceNameSegmentsVarExpr) + .setValueExpr( + NewObjectExpr.builder().setType(hashMapType).setIsGeneric(true).build()) + .build())); + + VariableExpr resourceNameSegmentsExpr = + VariableExpr.builder() + .setVariable( + Variable.builder().setName("resourceNameSegments").setType(mapType).build()) + .build(); + + Set httpBindings = method.httpBindings().pathParameters(); + + // For each httpBinding, + // generates resourceNameSegments.put("field",String.valueOf(request.getField())); + for (HttpBindings.HttpBinding httpBinding : httpBindings) { + if (!canonicalPath.contains(httpBinding.name())) { + continue; + } + MethodInvocationExpr getFieldExpr = + createRequestFieldGetterExpr( + requestVarExpr, + httpBinding.name(), + httpBinding.field() != null && httpBinding.field().isEnum()); + + MethodInvocationExpr putExpr = + MethodInvocationExpr.builder() + .setExprReferenceExpr(resourceNameSegmentsExpr) + .setMethodName("put") + .setArguments( + ValueExpr.withValue(StringObjectValue.withValue(httpBinding.name())), + MethodInvocationExpr.builder() + .setStaticReferenceType(TypeNode.STRING) + .setMethodName("valueOf") + .setArguments(getFieldExpr) + .setReturnType(TypeNode.STRING) + .build()) + .build(); + bodyStatements.add(ExprStatement.withExpr(putExpr)); + } + + MethodInvocationExpr instantiateExpr = MethodInvocationExpr.builder() - .setExprReferenceExpr(requestVarExpr) - .setMethodName( - String.format("get%s", JavaStyle.toUpperCamelCase(resourceNameField.name()))) + .setExprReferenceExpr(VariableExpr.builder().setVariable(pathTemplateVar).build()) + .setMethodName("instantiate") + .setArguments(resourceNameSegmentsExpr) .setReturnType(TypeNode.STRING) .build(); return LambdaExpr.builder() .setArguments(requestVarExpr.toBuilder().setIsDecl(true).build()) .setBody(bodyStatements) - .setReturnExpr(returnExpr) + .setReturnExpr(instantiateExpr) .build(); } @@ -1698,7 +1834,8 @@ private void createRequestParamsExtractorBodyForRoutingHeaders( Variable.builder().setType(pathTemplateType).setName(pathTemplateName).build(); Expr routingHeaderPatternExpr = VariableExpr.withVariable(pathTemplateVar); Statement pathTemplateClassVar = - createPathTemplateClassStatement(routingHeaderParam, pathTemplateType, pathTemplateVar); + createPathTemplateClassStatement( + routingHeaderParam.pattern(), pathTemplateType, pathTemplateVar); classStatements.add(pathTemplateClassVar); MethodInvocationExpr addParamMethodExpr = MethodInvocationExpr.builder() @@ -1728,9 +1865,7 @@ private void createRequestParamsExtractorBodyForRoutingHeaders( } private Statement createPathTemplateClassStatement( - RoutingHeaderRule.RoutingHeaderParam routingHeaderParam, - TypeNode pathTemplateType, - Variable pathTemplateVar) { + String pattern, TypeNode pathTemplateType, Variable pathTemplateVar) { VariableExpr pathTemplateVarExpr = VariableExpr.builder() .setVariable(pathTemplateVar) @@ -1739,8 +1874,7 @@ private Statement createPathTemplateClassStatement( .setIsFinal(true) .setScope(ScopeNode.PRIVATE) .build(); - ValueExpr valueExpr = - ValueExpr.withValue(StringObjectValue.withValue(routingHeaderParam.pattern())); + ValueExpr valueExpr = ValueExpr.withValue(StringObjectValue.withValue(pattern)); Expr pathTemplateExpr = AssignmentExpr.builder() .setVariableExpr(pathTemplateVarExpr) @@ -1825,4 +1959,52 @@ private static VariableExpr createRequestVarExpr(Method method) { return VariableExpr.withVariable( Variable.builder().setType(method.inputType()).setName("request").build()); } + + private static Set getKnownResources(Service service) { + + Set knownResources = new HashSet<>(); + for (Method method : service.methods()) { + if (!method.hasHttpBindings()) { + continue; + } + if (isAipStandardMethod(AIP_STANDARDS_METHODS, method.name())) { + for (String pattern : method.httpBindings().additionalPatterns()) { + knownResources.addAll(extractLiteralSegments(pattern)); + } + knownResources.addAll(extractLiteralSegments(method.httpBindings().pattern())); + } + } + return knownResources; + } + + private static boolean isAipStandardMethod(List standards, String methodName) { + return standards.stream().anyMatch(standard -> standard.equalsIgnoreCase(methodName)); + } + + private static Set extractLiteralSegments(String pattern) { + if (pattern == null || pattern.isEmpty()) { + return new HashSet<>(); + } + try { + PathTemplate template = PathTemplate.create(pattern); + return template.getResourceLiterals(); + } catch (Exception e) { + return new HashSet<>(); + } + } + + private static String extractCanonicalResourceName(String pattern, Set knownResources) { + if (pattern == null + || pattern.isEmpty() + || knownResources == null + || knownResources.isEmpty()) { + return ""; + } + try { + PathTemplate template = PathTemplate.create(pattern); + return template.getCanonicalResourceName(knownResources); + } catch (Exception e) { + return ""; + } + } } diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcResourceNameExtractorStub.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcResourceNameExtractorStub.golden index 371e29f39464..4657fbe7da7d 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcResourceNameExtractorStub.golden +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/grpc/goldens/GrpcResourceNameExtractorStub.golden @@ -1,4 +1,4 @@ -package com.google.extractor.testing.stub; +package com.google.cloud.bigquery.testing.stub; import com.google.api.gax.core.BackgroundResource; import com.google.api.gax.core.BackgroundResourceAggregation; @@ -7,16 +7,25 @@ import com.google.api.gax.grpc.GrpcStubCallableFactory; import com.google.api.gax.rpc.ClientContext; import com.google.api.gax.rpc.RequestParamsBuilder; import com.google.api.gax.rpc.UnaryCallable; -import com.google.extractor.testing.Bar; -import com.google.extractor.testing.Foo; -import com.google.extractor.testing.GetBarRequest; -import com.google.extractor.testing.GetFooRequest; -import com.google.extractor.testing.ListFoosRequest; -import com.google.extractor.testing.ListFoosResponse; +import com.google.api.pathtemplate.PathTemplate; +import com.google.cloud.bigquery.testing.Bar; +import com.google.cloud.bigquery.testing.Foo; +import com.google.cloud.bigquery.testing.GetBarRequest; +import com.google.cloud.bigquery.testing.GetFooRequest; +import com.google.cloud.bigquery.testing.GetHeuristicRequest; +import com.google.cloud.bigquery.testing.GetHeuristicWithNamedBindingRequest; +import com.google.cloud.bigquery.testing.GetHeuristicWithNestedFieldsRequest; +import com.google.cloud.bigquery.testing.GetHeuristicWithResourceReferenceRequest; +import com.google.cloud.bigquery.testing.Heuristic; +import com.google.cloud.bigquery.testing.ListFoosRequest; +import com.google.cloud.bigquery.testing.ListFoosResponse; +import com.google.cloud.bigquery.testing.ListHeuristicsRequest; +import com.google.cloud.bigquery.testing.ListHeuristicsResponse; import com.google.longrunning.stub.GrpcOperationsStub; import io.grpc.MethodDescriptor; import io.grpc.protobuf.ProtoUtils; import java.io.IOException; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.annotation.Generated; @@ -32,7 +41,7 @@ public class GrpcResourceNameExtractorTestingStub extends ResourceNameExtractorT private static final MethodDescriptor getFooMethodDescriptor = MethodDescriptor.newBuilder() .setType(MethodDescriptor.MethodType.UNARY) - .setFullMethodName("google.extractor.testing.ResourceNameExtractorTesting/GetFoo") + .setFullMethodName("google.cloud.bigquery.testing.ResourceNameExtractorTesting/GetFoo") .setRequestMarshaller(ProtoUtils.marshaller(GetFooRequest.getDefaultInstance())) .setResponseMarshaller(ProtoUtils.marshaller(Foo.getDefaultInstance())) .setSampledToLocalTracing(true) @@ -41,30 +50,126 @@ public class GrpcResourceNameExtractorTestingStub extends ResourceNameExtractorT private static final MethodDescriptor getBarMethodDescriptor = MethodDescriptor.newBuilder() .setType(MethodDescriptor.MethodType.UNARY) - .setFullMethodName("google.extractor.testing.ResourceNameExtractorTesting/GetBar") + .setFullMethodName("google.cloud.bigquery.testing.ResourceNameExtractorTesting/GetBar") .setRequestMarshaller(ProtoUtils.marshaller(GetBarRequest.getDefaultInstance())) .setResponseMarshaller(ProtoUtils.marshaller(Bar.getDefaultInstance())) .setSampledToLocalTracing(true) .build(); + private static final MethodDescriptor + getHeuristicMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.cloud.bigquery.testing.ResourceNameExtractorTesting/GetHeuristic") + .setRequestMarshaller(ProtoUtils.marshaller(GetHeuristicRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Heuristic.getDefaultInstance())) + .setSampledToLocalTracing(true) + .build(); + private static final MethodDescriptor listFoosMethodDescriptor = MethodDescriptor.newBuilder() .setType(MethodDescriptor.MethodType.UNARY) - .setFullMethodName("google.extractor.testing.ResourceNameExtractorTesting/ListFoos") + .setFullMethodName( + "google.cloud.bigquery.testing.ResourceNameExtractorTesting/ListFoos") .setRequestMarshaller(ProtoUtils.marshaller(ListFoosRequest.getDefaultInstance())) .setResponseMarshaller(ProtoUtils.marshaller(ListFoosResponse.getDefaultInstance())) .setSampledToLocalTracing(true) .build(); + private static final MethodDescriptor + getMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.cloud.bigquery.testing.ResourceNameExtractorTesting/Get") + .setRequestMarshaller( + ProtoUtils.marshaller(GetHeuristicWithNamedBindingRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Heuristic.getDefaultInstance())) + .setSampledToLocalTracing(true) + .build(); + + private static final MethodDescriptor + listMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName("google.cloud.bigquery.testing.ResourceNameExtractorTesting/List") + .setRequestMarshaller( + ProtoUtils.marshaller(ListHeuristicsRequest.getDefaultInstance())) + .setResponseMarshaller( + ProtoUtils.marshaller(ListHeuristicsResponse.getDefaultInstance())) + .setSampledToLocalTracing(true) + .build(); + + private static final MethodDescriptor + getHeuristicWithResourceReferenceMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.cloud.bigquery.testing.ResourceNameExtractorTesting/GetHeuristicWithResourceReference") + .setRequestMarshaller( + ProtoUtils.marshaller( + GetHeuristicWithResourceReferenceRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Heuristic.getDefaultInstance())) + .setSampledToLocalTracing(true) + .build(); + + private static final MethodDescriptor + getHeuristicWithNestedFieldsMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.cloud.bigquery.testing.ResourceNameExtractorTesting/GetHeuristicWithNestedFields") + .setRequestMarshaller( + ProtoUtils.marshaller(GetHeuristicWithNestedFieldsRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Heuristic.getDefaultInstance())) + .setSampledToLocalTracing(true) + .build(); + + private static final MethodDescriptor + getHeuristicWithNamedBindingMethodDescriptor = + MethodDescriptor.newBuilder() + .setType(MethodDescriptor.MethodType.UNARY) + .setFullMethodName( + "google.cloud.bigquery.testing.ResourceNameExtractorTesting/GetHeuristicWithNamedBinding") + .setRequestMarshaller( + ProtoUtils.marshaller(GetHeuristicWithNamedBindingRequest.getDefaultInstance())) + .setResponseMarshaller(ProtoUtils.marshaller(Heuristic.getDefaultInstance())) + .setSampledToLocalTracing(true) + .build(); + private final UnaryCallable getFooCallable; private final UnaryCallable getBarCallable; + private final UnaryCallable getHeuristicCallable; private final UnaryCallable listFoosCallable; + private final UnaryCallable getCallable; + private final UnaryCallable listCallable; + private final UnaryCallable + getHeuristicWithResourceReferenceCallable; + private final UnaryCallable + getHeuristicWithNestedFieldsCallable; + private final UnaryCallable + getHeuristicWithNamedBindingCallable; private final BackgroundResource backgroundResources; private final GrpcOperationsStub operationsStub; private final GrpcStubCallableFactory callableFactory; + private static final PathTemplate GET_BAR_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("{bar=projects/*/locations/*/bars/*}"); + private static final PathTemplate GET_HEURISTIC_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("projects/{project}/locations/{location}/heuristics/{heuristic}"); + private static final PathTemplate GET_RESOURCE_NAME_TEMPLATE = + PathTemplate.create( + "projects/{project}/locations/{location}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/heuristics/{heuristic}"); + private static final PathTemplate LIST_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("projects/{project}/locations/{location}"); + private static final PathTemplate GET_HEURISTIC_WITH_NESTED_FIELDS_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("projects/{project}/locations/{location}/heuristics/{heuristic.name}"); + private static final PathTemplate GET_HEURISTIC_WITH_NAMED_BINDING_RESOURCE_NAME_TEMPLATE = + PathTemplate.create( + "projects/{project}/locations/{location}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/heuristics/{heuristic}"); + public static final GrpcResourceNameExtractorTestingStub create( ResourceNameExtractorTestingStubSettings settings) throws IOException { return new GrpcResourceNameExtractorTestingStub(settings, ClientContext.create(settings)); @@ -128,6 +233,32 @@ public class GrpcResourceNameExtractorTestingStub extends ResourceNameExtractorT builder.add("bar", String.valueOf(request.getBar())); return builder.build(); }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("bar", String.valueOf(request.getBar())); + return GET_BAR_RESOURCE_NAME_TEMPLATE.instantiate(resourceNameSegments); + }) + .build(); + GrpcCallSettings getHeuristicTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(getHeuristicMethodDescriptor) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add("heuristic", String.valueOf(request.getHeuristic())); + builder.add("location", String.valueOf(request.getLocation())); + builder.add("project", String.valueOf(request.getProject())); + return builder.build(); + }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("heuristic", String.valueOf(request.getHeuristic())); + resourceNameSegments.put("location", String.valueOf(request.getLocation())); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + return GET_HEURISTIC_RESOURCE_NAME_TEMPLATE.instantiate(resourceNameSegments); + }) .build(); GrpcCallSettings listFoosTransportSettings = GrpcCallSettings.newBuilder() @@ -140,6 +271,109 @@ public class GrpcResourceNameExtractorTestingStub extends ResourceNameExtractorT }) .setResourceNameExtractor(request -> request.getParent()) .build(); + GrpcCallSettings getTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(getMethodDescriptor) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add("heuristic", String.valueOf(request.getHeuristic())); + builder.add("location", String.valueOf(request.getLocation())); + builder.add("parent_name", String.valueOf(request.getParentName())); + builder.add("project", String.valueOf(request.getProject())); + return builder.build(); + }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("heuristic", String.valueOf(request.getHeuristic())); + resourceNameSegments.put("location", String.valueOf(request.getLocation())); + resourceNameSegments.put("parent_name", String.valueOf(request.getParentName())); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + return GET_RESOURCE_NAME_TEMPLATE.instantiate(resourceNameSegments); + }) + .build(); + GrpcCallSettings listTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(listMethodDescriptor) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add("location", String.valueOf(request.getLocation())); + builder.add("project", String.valueOf(request.getProject())); + return builder.build(); + }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("location", String.valueOf(request.getLocation())); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + return LIST_RESOURCE_NAME_TEMPLATE.instantiate(resourceNameSegments); + }) + .build(); + GrpcCallSettings + getHeuristicWithResourceReferenceTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(getHeuristicWithResourceReferenceMethodDescriptor) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add("heuristic", String.valueOf(request.getHeuristic())); + builder.add("location", String.valueOf(request.getLocation())); + builder.add("project", String.valueOf(request.getProject())); + return builder.build(); + }) + .setResourceNameExtractor(request -> request.getFoo()) + .build(); + GrpcCallSettings + getHeuristicWithNestedFieldsTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(getHeuristicWithNestedFieldsMethodDescriptor) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add( + "heuristic.name", String.valueOf(request.getHeuristic().getName())); + builder.add("location", String.valueOf(request.getLocation())); + builder.add("project", String.valueOf(request.getProject())); + return builder.build(); + }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put( + "heuristic.name", String.valueOf(request.getHeuristic().getName())); + resourceNameSegments.put("location", String.valueOf(request.getLocation())); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + return GET_HEURISTIC_WITH_NESTED_FIELDS_RESOURCE_NAME_TEMPLATE.instantiate( + resourceNameSegments); + }) + .build(); + GrpcCallSettings + getHeuristicWithNamedBindingTransportSettings = + GrpcCallSettings.newBuilder() + .setMethodDescriptor(getHeuristicWithNamedBindingMethodDescriptor) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add("heuristic", String.valueOf(request.getHeuristic())); + builder.add("location", String.valueOf(request.getLocation())); + builder.add("parent_name", String.valueOf(request.getParentName())); + builder.add("project", String.valueOf(request.getProject())); + return builder.build(); + }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("heuristic", String.valueOf(request.getHeuristic())); + resourceNameSegments.put("location", String.valueOf(request.getLocation())); + resourceNameSegments.put( + "parent_name", String.valueOf(request.getParentName())); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + return GET_HEURISTIC_WITH_NAMED_BINDING_RESOURCE_NAME_TEMPLATE.instantiate( + resourceNameSegments); + }) + .build(); this.getFooCallable = callableFactory.createUnaryCallable( @@ -147,9 +381,33 @@ public class GrpcResourceNameExtractorTestingStub extends ResourceNameExtractorT this.getBarCallable = callableFactory.createUnaryCallable( getBarTransportSettings, settings.getBarSettings(), clientContext); + this.getHeuristicCallable = + callableFactory.createUnaryCallable( + getHeuristicTransportSettings, settings.getHeuristicSettings(), clientContext); this.listFoosCallable = callableFactory.createUnaryCallable( listFoosTransportSettings, settings.listFoosSettings(), clientContext); + this.getCallable = + callableFactory.createUnaryCallable( + getTransportSettings, settings.getSettings(), clientContext); + this.listCallable = + callableFactory.createUnaryCallable( + listTransportSettings, settings.listSettings(), clientContext); + this.getHeuristicWithResourceReferenceCallable = + callableFactory.createUnaryCallable( + getHeuristicWithResourceReferenceTransportSettings, + settings.getHeuristicWithResourceReferenceSettings(), + clientContext); + this.getHeuristicWithNestedFieldsCallable = + callableFactory.createUnaryCallable( + getHeuristicWithNestedFieldsTransportSettings, + settings.getHeuristicWithNestedFieldsSettings(), + clientContext); + this.getHeuristicWithNamedBindingCallable = + callableFactory.createUnaryCallable( + getHeuristicWithNamedBindingTransportSettings, + settings.getHeuristicWithNamedBindingSettings(), + clientContext); this.backgroundResources = new BackgroundResourceAggregation(clientContext.getBackgroundResources()); @@ -169,11 +427,44 @@ public class GrpcResourceNameExtractorTestingStub extends ResourceNameExtractorT return getBarCallable; } + @Override + public UnaryCallable getHeuristicCallable() { + return getHeuristicCallable; + } + @Override public UnaryCallable listFoosCallable() { return listFoosCallable; } + @Override + public UnaryCallable getCallable() { + return getCallable; + } + + @Override + public UnaryCallable listCallable() { + return listCallable; + } + + @Override + public UnaryCallable + getHeuristicWithResourceReferenceCallable() { + return getHeuristicWithResourceReferenceCallable; + } + + @Override + public UnaryCallable + getHeuristicWithNestedFieldsCallable() { + return getHeuristicWithNestedFieldsCallable; + } + + @Override + public UnaryCallable + getHeuristicWithNamedBindingCallable() { + return getHeuristicWithNamedBindingCallable; + } + @Override public final void close() { try { diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonResourceNameExtractorStub.golden b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonResourceNameExtractorStub.golden index c3296eb84117..ee8722706bd8 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonResourceNameExtractorStub.golden +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/rest/goldens/HttpJsonResourceNameExtractorStub.golden @@ -1,4 +1,4 @@ -package com.google.extractor.testing.stub; +package com.google.cloud.bigquery.testing.stub; import com.google.api.core.InternalApi; import com.google.api.gax.core.BackgroundResource; @@ -12,12 +12,20 @@ import com.google.api.gax.httpjson.ProtoRestSerializer; import com.google.api.gax.rpc.ClientContext; import com.google.api.gax.rpc.RequestParamsBuilder; import com.google.api.gax.rpc.UnaryCallable; -import com.google.extractor.testing.Bar; -import com.google.extractor.testing.Foo; -import com.google.extractor.testing.GetBarRequest; -import com.google.extractor.testing.GetFooRequest; -import com.google.extractor.testing.ListFoosRequest; -import com.google.extractor.testing.ListFoosResponse; +import com.google.api.pathtemplate.PathTemplate; +import com.google.cloud.bigquery.testing.Bar; +import com.google.cloud.bigquery.testing.Foo; +import com.google.cloud.bigquery.testing.GetBarRequest; +import com.google.cloud.bigquery.testing.GetFooRequest; +import com.google.cloud.bigquery.testing.GetHeuristicRequest; +import com.google.cloud.bigquery.testing.GetHeuristicWithNamedBindingRequest; +import com.google.cloud.bigquery.testing.GetHeuristicWithNestedFieldsRequest; +import com.google.cloud.bigquery.testing.GetHeuristicWithResourceReferenceRequest; +import com.google.cloud.bigquery.testing.Heuristic; +import com.google.cloud.bigquery.testing.ListFoosRequest; +import com.google.cloud.bigquery.testing.ListFoosResponse; +import com.google.cloud.bigquery.testing.ListHeuristicsRequest; +import com.google.cloud.bigquery.testing.ListHeuristicsResponse; import com.google.protobuf.TypeRegistry; import java.io.IOException; import java.util.ArrayList; @@ -39,7 +47,7 @@ public class HttpJsonResourceNameExtractorTestingStub extends ResourceNameExtrac private static final ApiMethodDescriptor getFooMethodDescriptor = ApiMethodDescriptor.newBuilder() - .setFullMethodName("google.extractor.testing.ResourceNameExtractorTesting/GetFoo") + .setFullMethodName("google.cloud.bigquery.testing.ResourceNameExtractorTesting/GetFoo") .setHttpMethod("GET") .setType(ApiMethodDescriptor.MethodType.UNARY) .setRequestFormatter( @@ -71,7 +79,7 @@ public class HttpJsonResourceNameExtractorTestingStub extends ResourceNameExtrac private static final ApiMethodDescriptor getBarMethodDescriptor = ApiMethodDescriptor.newBuilder() - .setFullMethodName("google.extractor.testing.ResourceNameExtractorTesting/GetBar") + .setFullMethodName("google.cloud.bigquery.testing.ResourceNameExtractorTesting/GetBar") .setHttpMethod("GET") .setType(ApiMethodDescriptor.MethodType.UNARY) .setRequestFormatter( @@ -101,10 +109,47 @@ public class HttpJsonResourceNameExtractorTestingStub extends ResourceNameExtrac .build()) .build(); + private static final ApiMethodDescriptor + getHeuristicMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName( + "google.cloud.bigquery.testing.ResourceNameExtractorTesting/GetHeuristic") + .setHttpMethod("GET") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1/projects/{project}/locations/{location}/heuristics/{heuristic}", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putPathParam(fields, "heuristic", request.getHeuristic()); + serializer.putPathParam(fields, "location", request.getLocation()); + serializer.putPathParam(fields, "project", request.getProject()); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + return fields; + }) + .setRequestBodyExtractor(request -> null) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(Heuristic.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + private static final ApiMethodDescriptor listFoosMethodDescriptor = ApiMethodDescriptor.newBuilder() - .setFullMethodName("google.extractor.testing.ResourceNameExtractorTesting/ListFoos") + .setFullMethodName( + "google.cloud.bigquery.testing.ResourceNameExtractorTesting/ListFoos") .setHttpMethod("GET") .setType(ApiMethodDescriptor.MethodType.UNARY) .setRequestFormatter( @@ -134,13 +179,220 @@ public class HttpJsonResourceNameExtractorTestingStub extends ResourceNameExtrac .build()) .build(); + private static final ApiMethodDescriptor + getMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.cloud.bigquery.testing.ResourceNameExtractorTesting/Get") + .setHttpMethod("GET") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1/projects/{project}/locations/{location}/{parentName=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/heuristics/{heuristic}", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putPathParam(fields, "heuristic", request.getHeuristic()); + serializer.putPathParam(fields, "location", request.getLocation()); + serializer.putPathParam(fields, "parentName", request.getParentName()); + serializer.putPathParam(fields, "project", request.getProject()); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + return fields; + }) + .setRequestBodyExtractor(request -> null) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(Heuristic.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + + private static final ApiMethodDescriptor + listMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName("google.cloud.bigquery.testing.ResourceNameExtractorTesting/List") + .setHttpMethod("GET") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1/projects/{project}/locations/{location}/heuristics", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putPathParam(fields, "location", request.getLocation()); + serializer.putPathParam(fields, "project", request.getProject()); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + return fields; + }) + .setRequestBodyExtractor(request -> null) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(ListHeuristicsResponse.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + + private static final ApiMethodDescriptor + getHeuristicWithResourceReferenceMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName( + "google.cloud.bigquery.testing.ResourceNameExtractorTesting/GetHeuristicWithResourceReference") + .setHttpMethod("GET") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter + .newBuilder() + .setPath( + "/v1/projects/{project}/locations/{location}/heuristics/{heuristic}", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer + serializer = ProtoRestSerializer.create(); + serializer.putPathParam(fields, "heuristic", request.getHeuristic()); + serializer.putPathParam(fields, "location", request.getLocation()); + serializer.putPathParam(fields, "project", request.getProject()); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer + serializer = ProtoRestSerializer.create(); + serializer.putQueryParam(fields, "foo", request.getFoo()); + return fields; + }) + .setRequestBodyExtractor(request -> null) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(Heuristic.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + + private static final ApiMethodDescriptor + getHeuristicWithNestedFieldsMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName( + "google.cloud.bigquery.testing.ResourceNameExtractorTesting/GetHeuristicWithNestedFields") + .setHttpMethod("GET") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1/projects/{project}/locations/{location}/heuristics/{heuristic.name}", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putPathParam( + fields, "heuristic.name", request.getHeuristic().getName()); + serializer.putPathParam(fields, "location", request.getLocation()); + serializer.putPathParam(fields, "project", request.getProject()); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putQueryParam(fields, "heuristic", request.getHeuristic()); + return fields; + }) + .setRequestBodyExtractor(request -> null) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(Heuristic.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + + private static final ApiMethodDescriptor + getHeuristicWithNamedBindingMethodDescriptor = + ApiMethodDescriptor.newBuilder() + .setFullMethodName( + "google.cloud.bigquery.testing.ResourceNameExtractorTesting/GetHeuristicWithNamedBinding") + .setHttpMethod("GET") + .setType(ApiMethodDescriptor.MethodType.UNARY) + .setRequestFormatter( + ProtoMessageRequestFormatter.newBuilder() + .setPath( + "/v1/projects/{project}/locations/{location}/{parentName=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/heuristics/{heuristic}", + request -> { + Map fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + serializer.putPathParam(fields, "heuristic", request.getHeuristic()); + serializer.putPathParam(fields, "location", request.getLocation()); + serializer.putPathParam(fields, "parentName", request.getParentName()); + serializer.putPathParam(fields, "project", request.getProject()); + return fields; + }) + .setQueryParamsExtractor( + request -> { + Map> fields = new HashMap<>(); + ProtoRestSerializer serializer = + ProtoRestSerializer.create(); + return fields; + }) + .setRequestBodyExtractor(request -> null) + .build()) + .setResponseParser( + ProtoMessageResponseParser.newBuilder() + .setDefaultInstance(Heuristic.getDefaultInstance()) + .setDefaultTypeRegistry(typeRegistry) + .build()) + .build(); + private final UnaryCallable getFooCallable; private final UnaryCallable getBarCallable; + private final UnaryCallable getHeuristicCallable; private final UnaryCallable listFoosCallable; + private final UnaryCallable getCallable; + private final UnaryCallable listCallable; + private final UnaryCallable + getHeuristicWithResourceReferenceCallable; + private final UnaryCallable + getHeuristicWithNestedFieldsCallable; + private final UnaryCallable + getHeuristicWithNamedBindingCallable; private final BackgroundResource backgroundResources; private final HttpJsonStubCallableFactory callableFactory; + private static final PathTemplate GET_BAR_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("{bar=projects/*/locations/*/bars/*}"); + private static final PathTemplate GET_HEURISTIC_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("projects/{project}/locations/{location}/heuristics/{heuristic}"); + private static final PathTemplate GET_RESOURCE_NAME_TEMPLATE = + PathTemplate.create( + "projects/{project}/locations/{location}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/heuristics/{heuristic}"); + private static final PathTemplate LIST_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("projects/{project}/locations/{location}"); + private static final PathTemplate GET_HEURISTIC_WITH_NESTED_FIELDS_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("projects/{project}/locations/{location}/heuristics/{heuristic.name}"); + private static final PathTemplate GET_HEURISTIC_WITH_NAMED_BINDING_RESOURCE_NAME_TEMPLATE = + PathTemplate.create( + "projects/{project}/locations/{location}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/heuristics/{heuristic}"); + public static final HttpJsonResourceNameExtractorTestingStub create( ResourceNameExtractorTestingStubSettings settings) throws IOException { return new HttpJsonResourceNameExtractorTestingStub(settings, ClientContext.create(settings)); @@ -205,6 +457,33 @@ public class HttpJsonResourceNameExtractorTestingStub extends ResourceNameExtrac builder.add("bar", String.valueOf(request.getBar())); return builder.build(); }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("bar", String.valueOf(request.getBar())); + return GET_BAR_RESOURCE_NAME_TEMPLATE.instantiate(resourceNameSegments); + }) + .build(); + HttpJsonCallSettings getHeuristicTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(getHeuristicMethodDescriptor) + .setTypeRegistry(typeRegistry) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add("heuristic", String.valueOf(request.getHeuristic())); + builder.add("location", String.valueOf(request.getLocation())); + builder.add("project", String.valueOf(request.getProject())); + return builder.build(); + }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("heuristic", String.valueOf(request.getHeuristic())); + resourceNameSegments.put("location", String.valueOf(request.getLocation())); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + return GET_HEURISTIC_RESOURCE_NAME_TEMPLATE.instantiate(resourceNameSegments); + }) .build(); HttpJsonCallSettings listFoosTransportSettings = HttpJsonCallSettings.newBuilder() @@ -218,6 +497,114 @@ public class HttpJsonResourceNameExtractorTestingStub extends ResourceNameExtrac }) .setResourceNameExtractor(request -> request.getParent()) .build(); + HttpJsonCallSettings getTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(getMethodDescriptor) + .setTypeRegistry(typeRegistry) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add("heuristic", String.valueOf(request.getHeuristic())); + builder.add("location", String.valueOf(request.getLocation())); + builder.add("parent_name", String.valueOf(request.getParentName())); + builder.add("project", String.valueOf(request.getProject())); + return builder.build(); + }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("heuristic", String.valueOf(request.getHeuristic())); + resourceNameSegments.put("location", String.valueOf(request.getLocation())); + resourceNameSegments.put("parent_name", String.valueOf(request.getParentName())); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + return GET_RESOURCE_NAME_TEMPLATE.instantiate(resourceNameSegments); + }) + .build(); + HttpJsonCallSettings listTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(listMethodDescriptor) + .setTypeRegistry(typeRegistry) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add("location", String.valueOf(request.getLocation())); + builder.add("project", String.valueOf(request.getProject())); + return builder.build(); + }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("location", String.valueOf(request.getLocation())); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + return LIST_RESOURCE_NAME_TEMPLATE.instantiate(resourceNameSegments); + }) + .build(); + HttpJsonCallSettings + getHeuristicWithResourceReferenceTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(getHeuristicWithResourceReferenceMethodDescriptor) + .setTypeRegistry(typeRegistry) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add("heuristic", String.valueOf(request.getHeuristic())); + builder.add("location", String.valueOf(request.getLocation())); + builder.add("project", String.valueOf(request.getProject())); + return builder.build(); + }) + .setResourceNameExtractor(request -> request.getFoo()) + .build(); + HttpJsonCallSettings + getHeuristicWithNestedFieldsTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(getHeuristicWithNestedFieldsMethodDescriptor) + .setTypeRegistry(typeRegistry) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add( + "heuristic.name", String.valueOf(request.getHeuristic().getName())); + builder.add("location", String.valueOf(request.getLocation())); + builder.add("project", String.valueOf(request.getProject())); + return builder.build(); + }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put( + "heuristic.name", String.valueOf(request.getHeuristic().getName())); + resourceNameSegments.put("location", String.valueOf(request.getLocation())); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + return GET_HEURISTIC_WITH_NESTED_FIELDS_RESOURCE_NAME_TEMPLATE.instantiate( + resourceNameSegments); + }) + .build(); + HttpJsonCallSettings + getHeuristicWithNamedBindingTransportSettings = + HttpJsonCallSettings.newBuilder() + .setMethodDescriptor(getHeuristicWithNamedBindingMethodDescriptor) + .setTypeRegistry(typeRegistry) + .setParamsExtractor( + request -> { + RequestParamsBuilder builder = RequestParamsBuilder.create(); + builder.add("heuristic", String.valueOf(request.getHeuristic())); + builder.add("location", String.valueOf(request.getLocation())); + builder.add("parent_name", String.valueOf(request.getParentName())); + builder.add("project", String.valueOf(request.getProject())); + return builder.build(); + }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("heuristic", String.valueOf(request.getHeuristic())); + resourceNameSegments.put("location", String.valueOf(request.getLocation())); + resourceNameSegments.put( + "parent_name", String.valueOf(request.getParentName())); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + return GET_HEURISTIC_WITH_NAMED_BINDING_RESOURCE_NAME_TEMPLATE.instantiate( + resourceNameSegments); + }) + .build(); this.getFooCallable = callableFactory.createUnaryCallable( @@ -225,9 +612,33 @@ public class HttpJsonResourceNameExtractorTestingStub extends ResourceNameExtrac this.getBarCallable = callableFactory.createUnaryCallable( getBarTransportSettings, settings.getBarSettings(), clientContext); + this.getHeuristicCallable = + callableFactory.createUnaryCallable( + getHeuristicTransportSettings, settings.getHeuristicSettings(), clientContext); this.listFoosCallable = callableFactory.createUnaryCallable( listFoosTransportSettings, settings.listFoosSettings(), clientContext); + this.getCallable = + callableFactory.createUnaryCallable( + getTransportSettings, settings.getSettings(), clientContext); + this.listCallable = + callableFactory.createUnaryCallable( + listTransportSettings, settings.listSettings(), clientContext); + this.getHeuristicWithResourceReferenceCallable = + callableFactory.createUnaryCallable( + getHeuristicWithResourceReferenceTransportSettings, + settings.getHeuristicWithResourceReferenceSettings(), + clientContext); + this.getHeuristicWithNestedFieldsCallable = + callableFactory.createUnaryCallable( + getHeuristicWithNestedFieldsTransportSettings, + settings.getHeuristicWithNestedFieldsSettings(), + clientContext); + this.getHeuristicWithNamedBindingCallable = + callableFactory.createUnaryCallable( + getHeuristicWithNamedBindingTransportSettings, + settings.getHeuristicWithNamedBindingSettings(), + clientContext); this.backgroundResources = new BackgroundResourceAggregation(clientContext.getBackgroundResources()); @@ -238,7 +649,13 @@ public class HttpJsonResourceNameExtractorTestingStub extends ResourceNameExtrac List methodDescriptors = new ArrayList<>(); methodDescriptors.add(getFooMethodDescriptor); methodDescriptors.add(getBarMethodDescriptor); + methodDescriptors.add(getHeuristicMethodDescriptor); methodDescriptors.add(listFoosMethodDescriptor); + methodDescriptors.add(getMethodDescriptor); + methodDescriptors.add(listMethodDescriptor); + methodDescriptors.add(getHeuristicWithResourceReferenceMethodDescriptor); + methodDescriptors.add(getHeuristicWithNestedFieldsMethodDescriptor); + methodDescriptors.add(getHeuristicWithNamedBindingMethodDescriptor); return methodDescriptors; } @@ -252,11 +669,44 @@ public class HttpJsonResourceNameExtractorTestingStub extends ResourceNameExtrac return getBarCallable; } + @Override + public UnaryCallable getHeuristicCallable() { + return getHeuristicCallable; + } + @Override public UnaryCallable listFoosCallable() { return listFoosCallable; } + @Override + public UnaryCallable getCallable() { + return getCallable; + } + + @Override + public UnaryCallable listCallable() { + return listCallable; + } + + @Override + public UnaryCallable + getHeuristicWithResourceReferenceCallable() { + return getHeuristicWithResourceReferenceCallable; + } + + @Override + public UnaryCallable + getHeuristicWithNestedFieldsCallable() { + return getHeuristicWithNestedFieldsCallable; + } + + @Override + public UnaryCallable + getHeuristicWithNamedBindingCallable() { + return getHeuristicWithNamedBindingCallable; + } + @Override public final void close() { try { diff --git a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java index a4c4a3afb5c3..b14d36e82810 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java +++ b/sdk-platform-java/gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java @@ -31,9 +31,9 @@ import com.google.api.version.test.ApiVersionTestingOuterClass; import com.google.auto.populate.field.AutoPopulateFieldTestingOuterClass; import com.google.bookshop.v1beta1.BookshopProto; +import com.google.cloud.bigquery.testing.ResourceNameExtractorTestingOuterClass; import com.google.cloud.bigquery.v2.JobProto; import com.google.explicit.dynamic.routing.header.ExplicitDynamicRoutingHeaderTestingOuterClass; -import com.google.extractor.testing.ResourceNameExtractorTestingOuterClass; import com.google.logging.v2.LogEntryProto; import com.google.logging.v2.LoggingConfigProto; import com.google.logging.v2.LoggingMetricsProto; diff --git a/sdk-platform-java/gapic-generator-java/src/test/proto/resource_name_extractor_testing.proto b/sdk-platform-java/gapic-generator-java/src/test/proto/resource_name_extractor_testing.proto index f339db4b93ee..1dd59b24cf99 100644 --- a/sdk-platform-java/gapic-generator-java/src/test/proto/resource_name_extractor_testing.proto +++ b/sdk-platform-java/gapic-generator-java/src/test/proto/resource_name_extractor_testing.proto @@ -1,10 +1,10 @@ syntax = "proto3"; -package google.extractor.testing; +package google.cloud.bigquery.testing; option java_multiple_files = true; option java_outer_classname = "ResourceNameExtractorTestingOuterClass"; -option java_package = "com.google.extractor.testing"; +option java_package = "com.google.cloud.bigquery.testing"; import "google/api/annotations.proto"; import "google/api/client.proto"; @@ -27,14 +27,73 @@ service ResourceNameExtractorTesting { option (google.api.method_signature) = "bar"; } + rpc GetHeuristic(GetHeuristicRequest) returns (Heuristic) { + option (google.api.http) = { + get: "/v1/projects/{project}/locations/{location}/heuristics/{heuristic}" + }; + } + rpc ListFoos(ListFoosRequest) returns (ListFoosResponse) { option (google.api.http) = { get: "/v1/{parent=projects/*/locations/*}/foos" }; option (google.api.method_signature) = "parent"; } + + // Exact AIP Method for populating known resources + rpc Get(GetHeuristicWithNamedBindingRequest) returns (Heuristic) { + option (google.api.http) = { + get: "/v1/projects/{project}/locations/{location}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/heuristics/{heuristic}" + }; + } + + rpc List(ListHeuristicsRequest) returns (ListHeuristicsResponse) { + option (google.api.http) = { + get: "/v1/projects/{project}/locations/{location}/heuristics" + }; + } + + // Should NOT generate a heuristic, because it has a resource_reference field + rpc GetHeuristicWithResourceReference(GetHeuristicWithResourceReferenceRequest) returns (Heuristic) { + option (google.api.http) = { + get: "/v1/projects/{project}/locations/{location}/heuristics/{heuristic}" + }; + } + + + rpc GetHeuristicWithNestedFields(GetHeuristicWithNestedFieldsRequest) returns (Heuristic) { + option (google.api.http) = { + get: "/v1/projects/{project}/locations/{location}/heuristics/{heuristic.name}" + }; + } + + rpc GetHeuristicWithNamedBinding(GetHeuristicWithNamedBindingRequest) returns (Heuristic) { + option (google.api.http) = { + get: "/v1/projects/{project}/locations/{location}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/heuristics/{heuristic}" + }; + } +} + +message ListHeuristicsRequest { + string project = 1; + string location = 2; +} +message ListHeuristicsResponse { + repeated Heuristic heuristics = 1; +} + +message GetHeuristicWithResourceReferenceRequest { + string project = 1; + string location = 2; + string heuristic = 3; + string foo = 4 [ + (google.api.resource_reference) = { + type: "extractor.googleapis.com/Foo" + } + ]; } + message Foo { option (google.api.resource) = { type: "extractor.googleapis.com/Foo" @@ -47,6 +106,29 @@ message Bar { string name = 1; } +message GetHeuristicRequest { + string project = 1; + string location = 2; + string heuristic = 3; +} + +message GetHeuristicWithNestedFieldsRequest { + string project = 1; + string location = 2; + Heuristic heuristic = 3; +} + +message GetHeuristicWithNamedBindingRequest { + string project = 1; + string location = 2; + string parent_name = 3; + string heuristic = 4; +} + +message Heuristic { + string name = 1; +} + message GetFooRequest { string name = 1 [ (google.api.resource_reference) = { diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java index b8c24cd6248b..d517614ba4a0 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java @@ -690,10 +690,10 @@ void endpointContextBuild_resolvesInvalidEndpointAndPort() throws Exception { String endpoint = "localhost:-1"; EndpointContext endpointContext = - defaultEndpointContextBuilder - .setClientSettingsEndpoint(endpoint) - .setTransportChannelProviderEndpoint(null) - .build(); + defaultEndpointContextBuilder + .setClientSettingsEndpoint(endpoint) + .setTransportChannelProviderEndpoint(null) + .build(); Truth.assertThat(endpointContext.resolvedServerAddress()).isNull(); Truth.assertThat(endpointContext.resolvedServerPort()).isNull(); From 366075bf78796bf93e4e438d17809319f89c8204 Mon Sep 17 00:00:00 2001 From: blakeli Date: Thu, 26 Mar 2026 00:22:32 -0400 Subject: [PATCH 03/17] Revert "fix: Handle null server address" --- .../com/google/api/gax/rpc/EndpointContext.java | 12 +++--------- .../google/api/gax/rpc/EndpointContextTest.java | 14 -------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java index 2a775ec4dd6c..09e105ccebc4 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java @@ -134,7 +134,6 @@ public static EndpointContext getDefaultInstance() { public abstract String resolvedEndpoint(); - @Nullable public abstract String resolvedServerAddress(); @Nullable @@ -411,7 +410,7 @@ private Integer parseServerPort(String endpoint) { return null; } HostAndPort hostAndPort = parseServerHostAndPort(endpoint); - if (hostAndPort == null || !hostAndPort.hasPort()) { + if (!hostAndPort.hasPort()) { return null; } return hostAndPort.getPort(); @@ -467,13 +466,8 @@ public EndpointContext build() throws IOException { setResolvedUniverseDomain(determineUniverseDomain()); String endpoint = determineEndpoint(); setResolvedEndpoint(endpoint); - try { - setResolvedServerAddress(parseServerAddress(resolvedEndpoint())); - setResolvedServerPort(parseServerPort(resolvedEndpoint())); - } catch (Exception throwable) { - // Server address and server port are only used for observability. - // We should ignore any errors parsing them and not affect the main client requests. - } + setResolvedServerAddress(parseServerAddress(resolvedEndpoint())); + setResolvedServerPort(parseServerPort(resolvedEndpoint())); setUseS2A(shouldUseS2A()); return autoBuild(); } diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java index d517614ba4a0..eb296fc938db 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java @@ -684,18 +684,4 @@ void endpointContextBuild_resolvesPort() throws IOException { Truth.assertThat(endpointContext.resolvedServerPort()).isNull(); } - @Test - void endpointContextBuild_resolvesInvalidEndpointAndPort() throws Exception { - - String endpoint = "localhost:-1"; - - EndpointContext endpointContext = - defaultEndpointContextBuilder - .setClientSettingsEndpoint(endpoint) - .setTransportChannelProviderEndpoint(null) - .build(); - - Truth.assertThat(endpointContext.resolvedServerAddress()).isNull(); - Truth.assertThat(endpointContext.resolvedServerPort()).isNull(); - } } From 2e56ceeb064a9f6ea2c08d30b57011e3b19e2c63 Mon Sep 17 00:00:00 2001 From: blakeli Date: Thu, 26 Mar 2026 00:24:20 -0400 Subject: [PATCH 04/17] Remove empty line at the end of EndpointContextTest.java --- .../test/java/com/google/api/gax/rpc/EndpointContextTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java index eb296fc938db..99a8f63fcf3e 100644 --- a/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java +++ b/sdk-platform-java/gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java @@ -683,5 +683,4 @@ void endpointContextBuild_resolvesPort() throws IOException { .build(); Truth.assertThat(endpointContext.resolvedServerPort()).isNull(); } - } From 4efed5a8323609b82cb32a94447daf122fe48355 Mon Sep 17 00:00:00 2001 From: blakeli Date: Thu, 26 Mar 2026 00:40:03 -0400 Subject: [PATCH 05/17] tests: fix tests --- .../api/pathtemplate/PathTemplateTest.java | 34 ++----------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index 2cd82904ab8d..7ee9b6096d77 100644 --- a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -895,14 +895,6 @@ void testTemplateWithMultipleSimpleBindings() { Truth.assertThat(url).isEqualTo("v1/shelves/s1/books/b1"); } - @Test - void name() { - PathTemplate pathTemplate = - PathTemplate.create("projects/{project}/zones/{zone}/{parent_name}"); - System.out.println( - pathTemplate.instantiate("project", "project1", "zone", "zone1", "parent_name", "name1")); - } - @Test void testGetResourceLiterals_simplePath() { PathTemplate template = @@ -919,7 +911,7 @@ void testGetResourceLiterals_regexPath() { } @Test - void testGetResourceSegments_onlyNonResourceLiterals() { + void testGetResourceLiterals_onlyNonResourceLiterals() { PathTemplate template = PathTemplate.create("compute/v1/projects"); Truth.assertThat(template.getResourceLiterals()).isEmpty(); } @@ -931,7 +923,7 @@ void testGetResourceLiterals_nameBinding() { } @Test - void testGetResourceSegments_complexResourceId() { + void testGetResourceLiterals_complexResourceId() { PathTemplate template = PathTemplate.create("projects/{project}/zones/{zone_a}~{zone_b}"); Truth.assertThat(template.getResourceLiterals()).containsExactly("projects", "zones"); } @@ -951,28 +943,6 @@ void testGetResourceLiterals_multipleVersions() { .containsExactly("projects", "locations", "widgets"); } - @Test - void testGetResourceLiterals_namedBindings() { - PathTemplate template = - PathTemplate.create( - "/compute/v1/projects/{project}/zones/{zone}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}"); - Truth.assertThat(template.getResourceLiterals()) - .containsExactly( - "projects", "zones", "reservations", "reservationBlocks", "reservationSubBlocks"); - } - - @Test - void testGetCanonicalResourceName_namedBindings() { - PathTemplate template = - PathTemplate.create( - "/v1/projects/{project}/locations/{location}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/heuristics/{heuristic}"); - - Set resourceLiterals = ImmutableSet.of("projects", "locations", "heuristics"); - Truth.assertThat(template.getCanonicalResourceName(resourceLiterals)) - .isEqualTo( - "projects/{project}/locations/{location}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/heuristics/{heuristic}"); - } - @Test void testGetCanonicalResourceName_namedBindingsSimple() { Set moreKnownResources = ImmutableSet.of("projects", "locations", "bars"); From 409522d9434e87e1cd69d91e7a8d20dc9ce4cfa3 Mon Sep 17 00:00:00 2001 From: blakeli Date: Thu, 26 Mar 2026 01:38:37 -0400 Subject: [PATCH 06/17] tests: update integration tests goldens. --- .../v1small/stub/HttpJsonAddressesStub.java | 39 +++++++++++++++++++ .../stub/HttpJsonRegionOperationsStub.java | 22 +++++++++++ 2 files changed, 61 insertions(+) diff --git a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/stub/HttpJsonAddressesStub.java b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/stub/HttpJsonAddressesStub.java index 9bb9f6c18179..545ecf7a36a2 100644 --- a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/stub/HttpJsonAddressesStub.java +++ b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/stub/HttpJsonAddressesStub.java @@ -34,6 +34,7 @@ import com.google.api.gax.rpc.OperationCallable; import com.google.api.gax.rpc.RequestParamsBuilder; import com.google.api.gax.rpc.UnaryCallable; +import com.google.api.pathtemplate.PathTemplate; import com.google.cloud.compute.v1small.AddressAggregatedList; import com.google.cloud.compute.v1small.AddressList; import com.google.cloud.compute.v1small.AggregatedListAddressesRequest; @@ -274,6 +275,15 @@ public class HttpJsonAddressesStub extends AddressesStub { private final HttpJsonRegionOperationsStub httpJsonOperationsStub; private final HttpJsonStubCallableFactory callableFactory; + private static final PathTemplate AGGREGATED_LIST_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("projects/{project}"); + private static final PathTemplate DELETE_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("projects/{project}/regions/{region}/addresses/{address}"); + private static final PathTemplate INSERT_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("projects/{project}/regions/{region}"); + private static final PathTemplate LIST_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("projects/{project}/regions/{region}"); + public static final HttpJsonAddressesStub create(AddressesStubSettings settings) throws IOException { return new HttpJsonAddressesStub(settings, ClientContext.create(settings)); @@ -324,6 +334,13 @@ protected HttpJsonAddressesStub( builder.add("project", String.valueOf(request.getProject())); return builder.build(); }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + return AGGREGATED_LIST_RESOURCE_NAME_TEMPLATE.instantiate( + resourceNameSegments); + }) .build(); HttpJsonCallSettings deleteTransportSettings = HttpJsonCallSettings.newBuilder() @@ -337,6 +354,14 @@ protected HttpJsonAddressesStub( builder.add("region", String.valueOf(request.getRegion())); return builder.build(); }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("address", String.valueOf(request.getAddress())); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + resourceNameSegments.put("region", String.valueOf(request.getRegion())); + return DELETE_RESOURCE_NAME_TEMPLATE.instantiate(resourceNameSegments); + }) .build(); HttpJsonCallSettings insertTransportSettings = HttpJsonCallSettings.newBuilder() @@ -349,6 +374,13 @@ protected HttpJsonAddressesStub( builder.add("region", String.valueOf(request.getRegion())); return builder.build(); }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + resourceNameSegments.put("region", String.valueOf(request.getRegion())); + return INSERT_RESOURCE_NAME_TEMPLATE.instantiate(resourceNameSegments); + }) .build(); HttpJsonCallSettings listTransportSettings = HttpJsonCallSettings.newBuilder() @@ -361,6 +393,13 @@ protected HttpJsonAddressesStub( builder.add("region", String.valueOf(request.getRegion())); return builder.build(); }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + resourceNameSegments.put("region", String.valueOf(request.getRegion())); + return LIST_RESOURCE_NAME_TEMPLATE.instantiate(resourceNameSegments); + }) .build(); this.aggregatedListCallable = diff --git a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/stub/HttpJsonRegionOperationsStub.java b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/stub/HttpJsonRegionOperationsStub.java index e9e339169f9b..d0ef4b45db7c 100644 --- a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/stub/HttpJsonRegionOperationsStub.java +++ b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/stub/HttpJsonRegionOperationsStub.java @@ -32,6 +32,7 @@ import com.google.api.gax.rpc.LongRunningClient; import com.google.api.gax.rpc.RequestParamsBuilder; import com.google.api.gax.rpc.UnaryCallable; +import com.google.api.pathtemplate.PathTemplate; import com.google.cloud.compute.v1small.GetRegionOperationRequest; import com.google.cloud.compute.v1small.Operation; import com.google.cloud.compute.v1small.Operation.Status; @@ -153,6 +154,11 @@ public class HttpJsonRegionOperationsStub extends RegionOperationsStub { private final LongRunningClient longRunningClient; private final HttpJsonStubCallableFactory callableFactory; + private static final PathTemplate GET_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("projects/{project}/regions/{region}/operations/{operation}"); + private static final PathTemplate WAIT_RESOURCE_NAME_TEMPLATE = + PathTemplate.create("projects/projects/{project}/regions/{region}/operations/{operation}"); + public static final HttpJsonRegionOperationsStub create(RegionOperationsStubSettings settings) throws IOException { return new HttpJsonRegionOperationsStub(settings, ClientContext.create(settings)); @@ -204,6 +210,14 @@ protected HttpJsonRegionOperationsStub( builder.add("region", String.valueOf(request.getRegion())); return builder.build(); }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("operation", String.valueOf(request.getOperation())); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + resourceNameSegments.put("region", String.valueOf(request.getRegion())); + return GET_RESOURCE_NAME_TEMPLATE.instantiate(resourceNameSegments); + }) .build(); HttpJsonCallSettings waitTransportSettings = HttpJsonCallSettings.newBuilder() @@ -217,6 +231,14 @@ protected HttpJsonRegionOperationsStub( builder.add("region", String.valueOf(request.getRegion())); return builder.build(); }) + .setResourceNameExtractor( + request -> { + Map resourceNameSegments = new HashMap(); + resourceNameSegments.put("operation", String.valueOf(request.getOperation())); + resourceNameSegments.put("project", String.valueOf(request.getProject())); + resourceNameSegments.put("region", String.valueOf(request.getRegion())); + return WAIT_RESOURCE_NAME_TEMPLATE.instantiate(resourceNameSegments); + }) .build(); this.getCallable = From 877fec6d05e14c1d1ed6cbf42aeb8cc3a554dc7e Mon Sep 17 00:00:00 2001 From: blakeli Date: Thu, 26 Mar 2026 22:23:02 -0400 Subject: [PATCH 07/17] fix: Update the heuristic logic per latest discussion. --- .../google/api/pathtemplate/PathTemplate.java | 157 +++++++----------- .../api/pathtemplate/PathTemplateTest.java | 38 ++--- 2 files changed, 78 insertions(+), 117 deletions(-) diff --git a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 858e1d4399df..a2a5a7c7c5ef 100644 --- a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -332,124 +332,87 @@ public String getCanonicalResourceName(Set knownResources) { if (knownResources == null) { return ""; } - StringBuilder canonical = new StringBuilder(); - StringBuilder currentSequence = new StringBuilder(); + + int firstBindingIndex = -1; + for (int i = 0; i < segments.size(); i++) { + if (segments.get(i).kind() == SegmentKind.BINDING) { + firstBindingIndex = i; + break; + } + } + + if (firstBindingIndex == -1) { + return ""; + } + + int startIndex = 0; + for (int i = firstBindingIndex - 1; i >= 0; i--) { + Segment seg = segments.get(i); + if (seg.kind() == SegmentKind.LITERAL) { + String value = seg.value(); + if (value.matches("^v\\d+.*") || value.matches("^u\\d+.*")) { + startIndex = i + 1; + break; + } + } + } + + int lastValidEndBindingIndex = -1; boolean inBinding = false; - String currentBindingName = ""; - boolean keepBinding = true; - List bindingSegments = new ArrayList<>(); - boolean afterKeptNamedBinding = false; + int literalCountInBinding = 0; + int currentBindingStartIndex = -1; for (int i = 0; i < segments.size(); i++) { Segment seg = segments.get(i); if (seg.kind() == SegmentKind.BINDING) { inBinding = true; - currentBindingName = seg.value(); - bindingSegments.clear(); - keepBinding = true; + literalCountInBinding = 0; + currentBindingStartIndex = i; } else if (seg.kind() == SegmentKind.END_BINDING) { inBinding = false; - StringBuilder innerPattern = new StringBuilder(); - int literalCount = 0; - for (Segment innerSeg : bindingSegments) { - if (innerSeg.kind() == SegmentKind.LITERAL) { - String value = innerSeg.value(); - if (value.matches("^v\\d+.*") || value.matches("^u\\d+.*")) { - continue; - } - literalCount++; - if (innerPattern.length() > 0) { - innerPattern.append("/"); - } - innerPattern.append(value); - } else if (innerSeg.kind() == SegmentKind.WILDCARD) { - if (innerPattern.length() > 0) { - innerPattern.append("/"); - } - innerPattern.append("*"); - } - } + boolean isValidPair = false; - boolean extractInner = false; - if (canonical.length() == 0 && currentSequence.length() == 0) { - if (i + 1 < segments.size()) { - Segment nextSeg = segments.get(i + 1); - if (nextSeg.kind() == SegmentKind.LITERAL) { - String nextValue = nextSeg.value(); - if (knownResources.contains(nextValue)) { - extractInner = true; + if (literalCountInBinding > 1) { + // Named bindings are unconditionally considered pairs + isValidPair = true; + } else { + // Check inner literals + for (int j = currentBindingStartIndex + 1; j < i; j++) { + if (segments.get(j).kind() == SegmentKind.LITERAL) { + if (knownResources.contains(segments.get(j).value())) { + isValidPair = true; + break; } } } - } - - if (extractInner) { - if (innerPattern.length() > 0) { - if (canonical.length() > 0) { - canonical.append("/"); + // If not valid yet, check preceding literal + if (!isValidPair && currentBindingStartIndex > 0) { + Segment prevSeg = segments.get(currentBindingStartIndex - 1); + if (prevSeg.kind() == SegmentKind.LITERAL && knownResources.contains(prevSeg.value())) { + isValidPair = true; } - canonical.append(innerPattern); - } - } else { - if (currentSequence.length() > 0) { - if (canonical.length() > 0) { - canonical.append("/"); - } - canonical.append(currentSequence); - currentSequence.setLength(0); - } - if (canonical.length() > 0) { - canonical.append("/"); } + } - if (literalCount <= 1 || innerPattern.toString().equals("*")) { - canonical.append("{").append(currentBindingName).append("}"); - } else { - canonical - .append("{") - .append(currentBindingName) - .append("=") - .append(innerPattern) - .append("}"); - afterKeptNamedBinding = true; - } + if (isValidPair) { + lastValidEndBindingIndex = i; } } else if (seg.kind() == SegmentKind.LITERAL) { - String value = seg.value(); - if (value.matches("^v\\d+.*") || value.matches("^u\\d+.*")) { - continue; - } if (inBinding) { - bindingSegments.add(seg); - if (!knownResources.contains(value)) { - keepBinding = false; - } - } else { - if (knownResources.contains(value)) { - if (currentSequence.length() > 0) { - currentSequence.append("/"); - } - currentSequence.append(value); - } else { - if (afterKeptNamedBinding) { - if (currentSequence.length() > 0) { - currentSequence.append("/"); - } - currentSequence.append(value); - } else { - if (canonical.length() > 0 || currentSequence.length() > 0) { - break; - } - } + String value = seg.value(); + if (!value.matches("^v\\d+.*") && !value.matches("^u\\d+.*")) { + literalCountInBinding++; } } - } else if (seg.kind() == SegmentKind.WILDCARD) { - if (inBinding) { - bindingSegments.add(seg); - } } } - return canonical.toString(); + + if (lastValidEndBindingIndex == -1 || lastValidEndBindingIndex < startIndex) { + return ""; + } + + List canonicalSegments = segments.subList(startIndex, lastValidEndBindingIndex + 1); + return toSyntax(canonicalSegments, true); } /** @@ -1278,7 +1241,7 @@ private String decodeUrl(String url) { // the list iterator in its state. private static boolean peek(ListIterator segments, SegmentKind... kinds) { int start = segments.nextIndex(); - boolean success = false; + boolean success = true; for (SegmentKind kind : kinds) { if (!segments.hasNext() || segments.next().kind() != kind) { success = false; diff --git a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index 7ee9b6096d77..37c130005fe4 100644 --- a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -966,7 +966,7 @@ void testGetCanonicalResourceName_regexVariables() { PathTemplate template = PathTemplate.create("v1/projects/{project=projects/*}/instances/{instance_id=instances/*}"); Truth.assertThat(template.getCanonicalResourceName(knownResources)) - .isEqualTo("projects/{project}/instances/{instance_id}"); + .isEqualTo("projects/{project=projects/*}/instances/{instance_id=instances/*}"); } @Test @@ -1004,40 +1004,38 @@ void testGetCanonicalResourceName_customVerb() { } @Test - void testGetCanonicalResourceName_nameBinding() { + void testGetCanonicalResourceName_nameBindingMixedWithSimpleBinding() { Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); - PathTemplate template = PathTemplate.create("v1/{field=projects/*/instances/*}"); + PathTemplate template = + PathTemplate.create("v1/{field=projects/*/instances/*}/actions/{action}"); Truth.assertThat(template.getCanonicalResourceName(knownResources)) .isEqualTo("{field=projects/*/instances/*}"); } @Test - void testGetCanonicalResourceName_nameBindingMixedWithSimpleBinding() { - Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); - PathTemplate template = - PathTemplate.create("v1/{field=projects/*/instances/*}/actions/{action}"); + void testGetCanonicalResourceName_multipleLiteralsWithSimpleBinding() { + Set knownResources = ImmutableSet.of("actions"); + PathTemplate template = PathTemplate.create("v1/locations/global/actions/{action}"); Truth.assertThat(template.getCanonicalResourceName(knownResources)) - .isEqualTo("{field=projects/*/instances/*}/actions/{action}"); + .isEqualTo("locations/global/actions/{action}"); } @Test - void testGetCanonicalResourceName_nameBindingWithUnknownLiterals() { + void testGetCanonicalResourceName_multipleLiteralsWithMultipleBindings() { + Set knownResources = ImmutableSet.of("instances", "actions"); PathTemplate template = - PathTemplate.create( - "/compute/v1/projects/{project}/zones/{zone}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/reservationSlots/{reservation_slot}"); - String canonical = template.getCanonicalResourceName(template.getResourceLiterals()); - Truth.assertThat(canonical) - .isEqualTo( - "projects/{project}/zones/{zone}/{parent_name=reservations/*/reservationBlocks/*/reservationSubBlocks/*}/reservationSlots/{reservation_slot}"); + PathTemplate.create("v1/locations/global/instances/{instance}/actions/{action}"); + Truth.assertThat(template.getCanonicalResourceName(knownResources)) + .isEqualTo("locations/global/instances/{instance}/actions/{action}"); } @Test - void testGetCanonicalResourceName_nameBindingMixedWithSimpleBinding_moreKnownResources() { - Set moreKnownResources = ImmutableSet.of("projects", "instances", "actions"); + void testGetCanonicalResourceName_multipleLiteralsBetweenMultipleBindings() { + Set knownResources = ImmutableSet.of("instances", "actions"); PathTemplate template = - PathTemplate.create("v1/{name=projects/*/instances/*}/actions/{action}"); - Truth.assertThat(template.getCanonicalResourceName(moreKnownResources)) - .isEqualTo("projects/*/instances/*/actions/{action}"); + PathTemplate.create("v1/instances/{instance}/locations/global/actions/{action}"); + Truth.assertThat(template.getCanonicalResourceName(knownResources)) + .isEqualTo("instances/{instance}/locations/global/actions/{action}"); } @Test From 1b4b5e94bab8e2975a9cf50ffa8a40d78e860adf Mon Sep 17 00:00:00 2001 From: blakeli Date: Thu, 26 Mar 2026 22:29:51 -0400 Subject: [PATCH 08/17] fix: update docs. --- .../google/api/pathtemplate/PathTemplate.java | 16 ++++++---- .../compute/v1small/AddressesClientTest.java | 32 +++++++++---------- .../v1small/RegionOperationsClientTest.java | 24 +++++++------- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java index a2a5a7c7c5ef..7b9c043d20ea 100644 --- a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -297,8 +297,8 @@ public Set vars() { } /** - * Returns the set of resource literals. A resource literal is a literal followed by a binding or - * inside a binding. + * Returns the set of resource literals. A resource literal is a literal followed by a binding. + * For example, projects/{project} is a literal/binding pair and projects is a resource literal. */ public Set getResourceLiterals() { Set canonicalSegments = new java.util.LinkedHashSet<>(); @@ -325,14 +325,16 @@ public Set getResourceLiterals() { } /** - * Returns the canonical resource name string. A segment is canonical if it is a literal followed - * by a binding or inside a binding. If a literal is not in knownResources, the extraction stops. + * Returns the canonical resource name string. A canonical resource name is extracted from the template by finding the version literal, + * then finding the last binding that is a literal/binding pair or named binding, + * and then extracting the segments between the version literal and the last binding. + * For examplem, projects/{project} is a literal/binding pair. {bar=projects/*/locations/*/bars/*} is a named binding. */ public String getCanonicalResourceName(Set knownResources) { if (knownResources == null) { return ""; } - + int firstBindingIndex = -1; for (int i = 0; i < segments.size(); i++) { if (segments.get(i).kind() == SegmentKind.BINDING) { @@ -371,7 +373,7 @@ public String getCanonicalResourceName(Set knownResources) { } else if (seg.kind() == SegmentKind.END_BINDING) { inBinding = false; boolean isValidPair = false; - + if (literalCountInBinding > 1) { // Named bindings are unconditionally considered pairs isValidPair = true; @@ -393,7 +395,7 @@ public String getCanonicalResourceName(Set knownResources) { } } } - + if (isValidPair) { lastValidEndBindingIndex = i; } diff --git a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/AddressesClientTest.java b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/AddressesClientTest.java index 39726de4ce25..b7943add8593 100644 --- a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/AddressesClientTest.java +++ b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/AddressesClientTest.java @@ -90,7 +90,7 @@ public void aggregatedListTest() throws Exception { .build(); mockService.addResponse(expectedResponse); - String project = "project-6911"; + String project = "project-309310695"; AggregatedListPagedResponse pagedListResponse = client.aggregatedList(project); @@ -124,7 +124,7 @@ public void aggregatedListExceptionTest() throws Exception { mockService.addException(exception); try { - String project = "project-6911"; + String project = "project-309310695"; client.aggregatedList(project); Assert.fail("No exception raised"); } catch (InvalidArgumentException e) { @@ -162,9 +162,9 @@ public void deleteTest() throws Exception { .build(); mockService.addResponse(expectedResponse); - String project = "project-6911"; - String region = "region-9622"; - String address = "address-4954"; + String project = "project-309310695"; + String region = "region-934795532"; + String address = "address-1147692044"; Operation actualResponse = client.deleteAsync(project, region, address).get(); Assert.assertEquals(expectedResponse, actualResponse); @@ -192,9 +192,9 @@ public void deleteExceptionTest() throws Exception { mockService.addException(exception); try { - String project = "project-6911"; - String region = "region-9622"; - String address = "address-4954"; + String project = "project-309310695"; + String region = "region-934795532"; + String address = "address-1147692044"; client.deleteAsync(project, region, address).get(); Assert.fail("No exception raised"); } catch (ExecutionException e) { @@ -231,8 +231,8 @@ public void insertTest() throws Exception { .build(); mockService.addResponse(expectedResponse); - String project = "project-6911"; - String region = "region-9622"; + String project = "project-309310695"; + String region = "region-934795532"; Address addressResource = Address.newBuilder().build(); Operation actualResponse = client.insertAsync(project, region, addressResource).get(); @@ -261,8 +261,8 @@ public void insertExceptionTest() throws Exception { mockService.addException(exception); try { - String project = "project-6911"; - String region = "region-9622"; + String project = "project-309310695"; + String region = "region-934795532"; Address addressResource = Address.newBuilder().build(); client.insertAsync(project, region, addressResource).get(); Assert.fail("No exception raised"); @@ -280,8 +280,8 @@ public void listTest() throws Exception { .build(); mockService.addResponse(expectedResponse); - String project = "project-6911"; - String region = "region-9622"; + String project = "project-309310695"; + String region = "region-934795532"; String orderBy = "orderBy-1207110587"; ListPagedResponse pagedListResponse = client.list(project, region, orderBy); @@ -314,8 +314,8 @@ public void listExceptionTest() throws Exception { mockService.addException(exception); try { - String project = "project-6911"; - String region = "region-9622"; + String project = "project-309310695"; + String region = "region-934795532"; String orderBy = "orderBy-1207110587"; client.list(project, region, orderBy); Assert.fail("No exception raised"); diff --git a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/RegionOperationsClientTest.java b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/RegionOperationsClientTest.java index c1181c1c7088..44f91f84a2b3 100644 --- a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/RegionOperationsClientTest.java +++ b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/RegionOperationsClientTest.java @@ -103,9 +103,9 @@ public void getTest() throws Exception { .build(); mockService.addResponse(expectedResponse); - String project = "project-6911"; - String region = "region-9622"; - String operation = "operation-3971"; + String project = "project-309310695"; + String region = "region-934795532"; + String operation = "operation1662702951"; Operation actualResponse = client.get(project, region, operation); Assert.assertEquals(expectedResponse, actualResponse); @@ -133,9 +133,9 @@ public void getExceptionTest() throws Exception { mockService.addException(exception); try { - String project = "project-6911"; - String region = "region-9622"; - String operation = "operation-3971"; + String project = "project-309310695"; + String region = "region-934795532"; + String operation = "operation1662702951"; client.get(project, region, operation); Assert.fail("No exception raised"); } catch (InvalidArgumentException e) { @@ -173,9 +173,9 @@ public void waitTest() throws Exception { .build(); mockService.addResponse(expectedResponse); - String project = "project-6911"; - String region = "region-9622"; - String operation = "operation-3971"; + String project = "project-309310695"; + String region = "region-934795532"; + String operation = "operation1662702951"; Operation actualResponse = client.wait(project, region, operation); Assert.assertEquals(expectedResponse, actualResponse); @@ -203,9 +203,9 @@ public void waitExceptionTest() throws Exception { mockService.addException(exception); try { - String project = "project-6911"; - String region = "region-9622"; - String operation = "operation-3971"; + String project = "project-309310695"; + String region = "region-934795532"; + String operation = "operation1662702951"; client.wait(project, region, operation); Assert.fail("No exception raised"); } catch (InvalidArgumentException e) { From c18077a2aa7231ee59cefe2060a0ac5e0db7cfee Mon Sep 17 00:00:00 2001 From: blakeli Date: Thu, 26 Mar 2026 22:39:31 -0400 Subject: [PATCH 09/17] fix: update version logic --- .../google/api/pathtemplate/PathTemplate.java | 25 +++++++++++-------- .../api/pathtemplate/PathTemplateTest.java | 10 ++++++++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 7b9c043d20ea..462e953430e0 100644 --- a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -298,7 +298,8 @@ public Set vars() { /** * Returns the set of resource literals. A resource literal is a literal followed by a binding. - * For example, projects/{project} is a literal/binding pair and projects is a resource literal. + * For example, projects/{project} is a literal/binding pair and projects is a resource + * literal. */ public Set getResourceLiterals() { Set canonicalSegments = new java.util.LinkedHashSet<>(); @@ -311,7 +312,7 @@ public Set getResourceLiterals() { inBinding = false; } else if (seg.kind() == SegmentKind.LITERAL) { String value = seg.value(); - if (value.matches("^v\\d+.*") || value.matches("^u\\d+.*")) { // just in case + if (value.matches("^v\\d+[a-zA-Z0-9]*$")) { // just in case continue; } if (inBinding) { @@ -325,16 +326,18 @@ public Set getResourceLiterals() { } /** - * Returns the canonical resource name string. A canonical resource name is extracted from the template by finding the version literal, - * then finding the last binding that is a literal/binding pair or named binding, - * and then extracting the segments between the version literal and the last binding. - * For examplem, projects/{project} is a literal/binding pair. {bar=projects/*/locations/*/bars/*} is a named binding. + * Returns the canonical resource name string. A canonical resource name is extracted from the + * template by finding the version literal, then finding the last binding that is a + * literal/binding pair or named binding, and then extracting the segments between the version + * literal and the last binding. */ + // For example, projects/{project} is a literal/binding pair. {bar=projects/*/locations/*/bars/*} + // is a named binding. public String getCanonicalResourceName(Set knownResources) { if (knownResources == null) { return ""; } - + int firstBindingIndex = -1; for (int i = 0; i < segments.size(); i++) { if (segments.get(i).kind() == SegmentKind.BINDING) { @@ -352,7 +355,7 @@ public String getCanonicalResourceName(Set knownResources) { Segment seg = segments.get(i); if (seg.kind() == SegmentKind.LITERAL) { String value = seg.value(); - if (value.matches("^v\\d+.*") || value.matches("^u\\d+.*")) { + if (value.matches("^v\\d+[a-zA-Z0-9]*$")) { startIndex = i + 1; break; } @@ -373,7 +376,7 @@ public String getCanonicalResourceName(Set knownResources) { } else if (seg.kind() == SegmentKind.END_BINDING) { inBinding = false; boolean isValidPair = false; - + if (literalCountInBinding > 1) { // Named bindings are unconditionally considered pairs isValidPair = true; @@ -395,14 +398,14 @@ public String getCanonicalResourceName(Set knownResources) { } } } - + if (isValidPair) { lastValidEndBindingIndex = i; } } else if (seg.kind() == SegmentKind.LITERAL) { if (inBinding) { String value = seg.value(); - if (!value.matches("^v\\d+.*") && !value.matches("^u\\d+.*")) { + if (!value.matches("^v\\d+[a-zA-Z0-9]*$")) { literalCountInBinding++; } } diff --git a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index 37c130005fe4..4b159de3835d 100644 --- a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -960,6 +960,16 @@ void testGetCanonicalResourceName_simplePath() { .isEqualTo("projects/{project}/locations/{location}/widgets/{widget}"); } + @Test + void testGetCanonicalResourceName_v1beta1WithSimplePath() { + Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); + PathTemplate template = + PathTemplate.create( + "/compute/v1beta1/projects/{project}/locations/{location}/widgets/{widget}"); + Truth.assertThat(template.getCanonicalResourceName(knownResources)) + .isEqualTo("projects/{project}/locations/{location}/widgets/{widget}"); + } + @Test void testGetCanonicalResourceName_regexVariables() { Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); From 523573eb39d47b4ff105b1299c7892e787171132 Mon Sep 17 00:00:00 2001 From: blakeli Date: Thu, 26 Mar 2026 22:44:19 -0400 Subject: [PATCH 10/17] fix: update javadocs --- .../java/com/google/api/pathtemplate/PathTemplate.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 462e953430e0..b38699e6c4c2 100644 --- a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -296,11 +296,8 @@ public Set vars() { return bindings.keySet(); } - /** - * Returns the set of resource literals. A resource literal is a literal followed by a binding. - * For example, projects/{project} is a literal/binding pair and projects is a resource - * literal. - */ + /** Returns the set of resource literals. A resource literal is a literal followed by a binding */ + // For example, projects/{project} is a literal/binding pair and projects is a resource literal. public Set getResourceLiterals() { Set canonicalSegments = new java.util.LinkedHashSet<>(); boolean inBinding = false; From 3edd75f984486e1df3b6d4bdbd5d05449c5731a2 Mon Sep 17 00:00:00 2001 From: blakeli Date: Fri, 27 Mar 2026 01:08:09 -0400 Subject: [PATCH 11/17] fix: Revert necessary changes. --- .../java/com/google/api/pathtemplate/PathTemplate.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java index b38699e6c4c2..6cf6d73c1090 100644 --- a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -414,7 +414,7 @@ public String getCanonicalResourceName(Set knownResources) { } List canonicalSegments = segments.subList(startIndex, lastValidEndBindingIndex + 1); - return toSyntax(canonicalSegments, true); + return toSyntax(canonicalSegments, true).replace("=*}", "}"); } /** @@ -1118,9 +1118,6 @@ private static ImmutableList parseTemplate(String template) { } private static boolean isSegmentBeginOrEndInvalid(String seg) { - if (seg.isEmpty()) { - return false; - } // A segment is invalid if it contains only one character and the character is a delimiter if (seg.length() == 1 && COMPLEX_DELIMITER_PATTERN.matcher(seg).find()) { return true; @@ -1243,7 +1240,7 @@ private String decodeUrl(String url) { // the list iterator in its state. private static boolean peek(ListIterator segments, SegmentKind... kinds) { int start = segments.nextIndex(); - boolean success = true; + boolean success = false; for (SegmentKind kind : kinds) { if (!segments.hasNext() || segments.next().kind() != kind) { success = false; From a8199807cea33d82fdf25a8e260bf1d9a8f9e59c Mon Sep 17 00:00:00 2001 From: blakeli Date: Fri, 27 Mar 2026 01:28:19 -0400 Subject: [PATCH 12/17] fix: fix integration tests --- .../compute/v1small/AddressesClientTest.java | 32 +++++++++---------- .../v1small/RegionOperationsClientTest.java | 28 ++++++++-------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/AddressesClientTest.java b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/AddressesClientTest.java index b7943add8593..c6cbca80b34b 100644 --- a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/AddressesClientTest.java +++ b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/AddressesClientTest.java @@ -90,7 +90,7 @@ public void aggregatedListTest() throws Exception { .build(); mockService.addResponse(expectedResponse); - String project = "project-309310695"; + String project = "project-6911"; AggregatedListPagedResponse pagedListResponse = client.aggregatedList(project); @@ -124,7 +124,7 @@ public void aggregatedListExceptionTest() throws Exception { mockService.addException(exception); try { - String project = "project-309310695"; + String project = "project-6911"; client.aggregatedList(project); Assert.fail("No exception raised"); } catch (InvalidArgumentException e) { @@ -149,7 +149,7 @@ public void deleteTest() throws Exception { .setName("name3373707") .setOperationType("operationType91999553") .setProgress(-1001078227) - .setRegion("region-934795532") + .setRegion("region-9622") .setSelfLink("selfLink1191800166") .setStartTime("startTime-2129294769") .setStatus(Status.DONE) @@ -162,8 +162,8 @@ public void deleteTest() throws Exception { .build(); mockService.addResponse(expectedResponse); - String project = "project-309310695"; - String region = "region-934795532"; + String project = "project-6911"; + String region = "region-9622"; String address = "address-1147692044"; Operation actualResponse = client.deleteAsync(project, region, address).get(); @@ -192,8 +192,8 @@ public void deleteExceptionTest() throws Exception { mockService.addException(exception); try { - String project = "project-309310695"; - String region = "region-934795532"; + String project = "project-6911"; + String region = "region-9622"; String address = "address-1147692044"; client.deleteAsync(project, region, address).get(); Assert.fail("No exception raised"); @@ -218,7 +218,7 @@ public void insertTest() throws Exception { .setName("name3373707") .setOperationType("operationType91999553") .setProgress(-1001078227) - .setRegion("region-934795532") + .setRegion("region-9622") .setSelfLink("selfLink1191800166") .setStartTime("startTime-2129294769") .setStatus(Status.DONE) @@ -231,8 +231,8 @@ public void insertTest() throws Exception { .build(); mockService.addResponse(expectedResponse); - String project = "project-309310695"; - String region = "region-934795532"; + String project = "project-6911"; + String region = "region-9622"; Address addressResource = Address.newBuilder().build(); Operation actualResponse = client.insertAsync(project, region, addressResource).get(); @@ -261,8 +261,8 @@ public void insertExceptionTest() throws Exception { mockService.addException(exception); try { - String project = "project-309310695"; - String region = "region-934795532"; + String project = "project-6911"; + String region = "region-9622"; Address addressResource = Address.newBuilder().build(); client.insertAsync(project, region, addressResource).get(); Assert.fail("No exception raised"); @@ -280,8 +280,8 @@ public void listTest() throws Exception { .build(); mockService.addResponse(expectedResponse); - String project = "project-309310695"; - String region = "region-934795532"; + String project = "project-6911"; + String region = "region-9622"; String orderBy = "orderBy-1207110587"; ListPagedResponse pagedListResponse = client.list(project, region, orderBy); @@ -314,8 +314,8 @@ public void listExceptionTest() throws Exception { mockService.addException(exception); try { - String project = "project-309310695"; - String region = "region-934795532"; + String project = "project-6911"; + String region = "region-9622"; String orderBy = "orderBy-1207110587"; client.list(project, region, orderBy); Assert.fail("No exception raised"); diff --git a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/RegionOperationsClientTest.java b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/RegionOperationsClientTest.java index 44f91f84a2b3..d1be50498b9e 100644 --- a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/RegionOperationsClientTest.java +++ b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/RegionOperationsClientTest.java @@ -90,7 +90,7 @@ public void getTest() throws Exception { .setName("name3373707") .setOperationType("operationType91999553") .setProgress(-1001078227) - .setRegion("region-934795532") + .setRegion("region-9622") .setSelfLink("selfLink1191800166") .setStartTime("startTime-2129294769") .setStatus(Status.DONE) @@ -103,9 +103,9 @@ public void getTest() throws Exception { .build(); mockService.addResponse(expectedResponse); - String project = "project-309310695"; - String region = "region-934795532"; - String operation = "operation1662702951"; + String project = "project-6911"; + String region = "region-9622"; + String operation = "operation-3971"; Operation actualResponse = client.get(project, region, operation); Assert.assertEquals(expectedResponse, actualResponse); @@ -133,9 +133,9 @@ public void getExceptionTest() throws Exception { mockService.addException(exception); try { - String project = "project-309310695"; - String region = "region-934795532"; - String operation = "operation1662702951"; + String project = "project-6911"; + String region = "region-9622"; + String operation = "operation-3971"; client.get(project, region, operation); Assert.fail("No exception raised"); } catch (InvalidArgumentException e) { @@ -160,7 +160,7 @@ public void waitTest() throws Exception { .setName("name3373707") .setOperationType("operationType91999553") .setProgress(-1001078227) - .setRegion("region-934795532") + .setRegion("region-9622") .setSelfLink("selfLink1191800166") .setStartTime("startTime-2129294769") .setStatus(Status.DONE) @@ -173,9 +173,9 @@ public void waitTest() throws Exception { .build(); mockService.addResponse(expectedResponse); - String project = "project-309310695"; - String region = "region-934795532"; - String operation = "operation1662702951"; + String project = "project-6911"; + String region = "region-9622"; + String operation = "operation-3971"; Operation actualResponse = client.wait(project, region, operation); Assert.assertEquals(expectedResponse, actualResponse); @@ -203,9 +203,9 @@ public void waitExceptionTest() throws Exception { mockService.addException(exception); try { - String project = "project-309310695"; - String region = "region-934795532"; - String operation = "operation1662702951"; + String project = "project-6911"; + String region = "region-9622"; + String operation = "operation-3971"; client.wait(project, region, operation); Assert.fail("No exception raised"); } catch (InvalidArgumentException e) { From ad9bc737890a6d703a4282a24a6092411cd359ac Mon Sep 17 00:00:00 2001 From: blakeli Date: Fri, 27 Mar 2026 11:43:33 -0400 Subject: [PATCH 13/17] fix: fix integration tests --- .../google/cloud/compute/v1small/AddressesClientTest.java | 8 ++++---- .../cloud/compute/v1small/RegionOperationsClientTest.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/AddressesClientTest.java b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/AddressesClientTest.java index c6cbca80b34b..39726de4ce25 100644 --- a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/AddressesClientTest.java +++ b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/AddressesClientTest.java @@ -149,7 +149,7 @@ public void deleteTest() throws Exception { .setName("name3373707") .setOperationType("operationType91999553") .setProgress(-1001078227) - .setRegion("region-9622") + .setRegion("region-934795532") .setSelfLink("selfLink1191800166") .setStartTime("startTime-2129294769") .setStatus(Status.DONE) @@ -164,7 +164,7 @@ public void deleteTest() throws Exception { String project = "project-6911"; String region = "region-9622"; - String address = "address-1147692044"; + String address = "address-4954"; Operation actualResponse = client.deleteAsync(project, region, address).get(); Assert.assertEquals(expectedResponse, actualResponse); @@ -194,7 +194,7 @@ public void deleteExceptionTest() throws Exception { try { String project = "project-6911"; String region = "region-9622"; - String address = "address-1147692044"; + String address = "address-4954"; client.deleteAsync(project, region, address).get(); Assert.fail("No exception raised"); } catch (ExecutionException e) { @@ -218,7 +218,7 @@ public void insertTest() throws Exception { .setName("name3373707") .setOperationType("operationType91999553") .setProgress(-1001078227) - .setRegion("region-9622") + .setRegion("region-934795532") .setSelfLink("selfLink1191800166") .setStartTime("startTime-2129294769") .setStatus(Status.DONE) diff --git a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/RegionOperationsClientTest.java b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/RegionOperationsClientTest.java index d1be50498b9e..c1181c1c7088 100644 --- a/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/RegionOperationsClientTest.java +++ b/sdk-platform-java/test/integration/goldens/compute/src/com/google/cloud/compute/v1small/RegionOperationsClientTest.java @@ -90,7 +90,7 @@ public void getTest() throws Exception { .setName("name3373707") .setOperationType("operationType91999553") .setProgress(-1001078227) - .setRegion("region-9622") + .setRegion("region-934795532") .setSelfLink("selfLink1191800166") .setStartTime("startTime-2129294769") .setStatus(Status.DONE) @@ -160,7 +160,7 @@ public void waitTest() throws Exception { .setName("name3373707") .setOperationType("operationType91999553") .setProgress(-1001078227) - .setRegion("region-9622") + .setRegion("region-934795532") .setSelfLink("selfLink1191800166") .setStartTime("startTime-2129294769") .setStatus(Status.DONE) From 3bf58f05095ac320745e27eb3db0df9a6a14234e Mon Sep 17 00:00:00 2001 From: blakeli Date: Fri, 27 Mar 2026 19:02:52 -0400 Subject: [PATCH 14/17] docs: add comments. --- .../java/com/google/api/pathtemplate/PathTemplate.java | 6 ++++++ .../com/google/api/pathtemplate/PathTemplateTest.java | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 6cf6d73c1090..47fd6814c3ed 100644 --- a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -313,8 +313,11 @@ public Set getResourceLiterals() { continue; } if (inBinding) { + // This is for extracting "projects" and "locations" from named binding + // {name=projects/*/locations/*} canonicalSegments.add(value); } else if (i + 1 < segments.size() && segments.get(i + 1).kind() == SegmentKind.BINDING) { + // This is for regular cases projects/{project}/locations/{location} canonicalSegments.add(value); } } @@ -330,6 +333,9 @@ public Set getResourceLiterals() { */ // For example, projects/{project} is a literal/binding pair. {bar=projects/*/locations/*/bars/*} // is a named binding. + // If a template is /compute/v1/projects/{project}/locations/{location}, known resources are + // "projects" and "locations", the canonical resource name is + // projects/{project}/locations/{location}. See unit tests for all cases. public String getCanonicalResourceName(Set knownResources) { if (knownResources == null) { return ""; diff --git a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index 4b159de3835d..93c397ffc98e 100644 --- a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -903,6 +903,15 @@ void testGetResourceLiterals_simplePath() { .containsExactly("projects", "locations", "widgets"); } + @Test + void testGetResourceLiterals_multipleLiterals() { + PathTemplate template = + PathTemplate.create( + "/compute/v1/projects/{project}/global/locations/{location}/widgets/{widget}"); + Truth.assertThat(template.getResourceLiterals()) + .containsExactly("projects", "locations", "widgets"); + } + @Test void testGetResourceLiterals_regexPath() { PathTemplate template = From e5142f121417153e220194dbf97f27955d5f5c16 Mon Sep 17 00:00:00 2001 From: blakeli Date: Sat, 28 Mar 2026 00:04:39 -0400 Subject: [PATCH 15/17] fix: simplify logics to get the start of the binding. --- .../google/api/pathtemplate/PathTemplate.java | 14 +------------- .../api/pathtemplate/PathTemplateTest.java | 19 ------------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 47fd6814c3ed..44ca54fb51b0 100644 --- a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -341,20 +341,8 @@ public String getCanonicalResourceName(Set knownResources) { return ""; } - int firstBindingIndex = -1; - for (int i = 0; i < segments.size(); i++) { - if (segments.get(i).kind() == SegmentKind.BINDING) { - firstBindingIndex = i; - break; - } - } - - if (firstBindingIndex == -1) { - return ""; - } - int startIndex = 0; - for (int i = firstBindingIndex - 1; i >= 0; i--) { + for (int i = 0; i < segments.size(); i++) { Segment seg = segments.get(i); if (seg.kind() == SegmentKind.LITERAL) { String value = seg.value(); diff --git a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index 93c397ffc98e..52496b8cf049 100644 --- a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -943,15 +943,6 @@ void testGetResourceLiterals_customVerb() { Truth.assertThat(template.getResourceLiterals()).containsExactly("projects", "instances"); } - @Test - void testGetResourceLiterals_multipleVersions() { - PathTemplate template = - PathTemplate.create( - "v1/compute/v2/projects/{project}/locations/{location}/widgets/{widget}"); - Truth.assertThat(template.getResourceLiterals()) - .containsExactly("projects", "locations", "widgets"); - } - @Test void testGetCanonicalResourceName_namedBindingsSimple() { Set moreKnownResources = ImmutableSet.of("projects", "locations", "bars"); @@ -1004,16 +995,6 @@ void testGetCanonicalResourceName_unknownResource() { .isEqualTo("projects/{project}"); } - @Test - void testGetCanonicalResourceName_ignoreVersions() { - Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); - PathTemplate template = - PathTemplate.create( - "v1/compute/v2/projects/{project}/locations/{location}/widgets/{widget}"); - Truth.assertThat(template.getCanonicalResourceName(knownResources)) - .isEqualTo("projects/{project}/locations/{location}/widgets/{widget}"); - } - @Test void testGetCanonicalResourceName_customVerb() { Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); From 67168ada6590149d4a36008d6865a10a06ecc885 Mon Sep 17 00:00:00 2001 From: blakeli Date: Sat, 28 Mar 2026 00:39:13 -0400 Subject: [PATCH 16/17] fix: simplify logics to get the start of the binding. --- .../google/api/pathtemplate/PathTemplate.java | 70 +++++++++---------- .../api/pathtemplate/PathTemplateTest.java | 8 +++ 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 44ca54fb51b0..ee77afd903f5 100644 --- a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -354,51 +354,51 @@ public String getCanonicalResourceName(Set knownResources) { } int lastValidEndBindingIndex = -1; - boolean inBinding = false; - int literalCountInBinding = 0; - int currentBindingStartIndex = -1; - - for (int i = 0; i < segments.size(); i++) { + // Iterate from the end of the segments to find the last valid resource binding. + // Searching backwards allows us to stop immediately once the last valid pair is found. + for (int i = segments.size() - 1; i >= 0; i--) { Segment seg = segments.get(i); - if (seg.kind() == SegmentKind.BINDING) { - inBinding = true; - literalCountInBinding = 0; - currentBindingStartIndex = i; - } else if (seg.kind() == SegmentKind.END_BINDING) { - inBinding = false; + + // We are looking for the end of a binding (e.g., "}" in "{project}" or "{name=projects/*}") + if (seg.kind() == SegmentKind.END_BINDING) { + int bindingStartIndex = -1; + int literalCountInBinding = 0; boolean isValidPair = false; - if (literalCountInBinding > 1) { - // Named bindings are unconditionally considered pairs - isValidPair = true; - } else { - // Check inner literals - for (int j = currentBindingStartIndex + 1; j < i; j++) { - if (segments.get(j).kind() == SegmentKind.LITERAL) { - if (knownResources.contains(segments.get(j).value())) { - isValidPair = true; - break; - } - } + // Traverse backwards to find the start of this specific binding + // and count the literals captured inside it. + for (int j = i - 1; j >= 0; j--) { + Segment innerSeg = segments.get(j); + if (innerSeg.kind() == SegmentKind.BINDING) { + bindingStartIndex = j; + break; + } else if (innerSeg.kind() == SegmentKind.LITERAL) { + literalCountInBinding++; } - // If not valid yet, check preceding literal - if (!isValidPair && currentBindingStartIndex > 0) { - Segment prevSeg = segments.get(currentBindingStartIndex - 1); + } + + if (bindingStartIndex != -1) { + // 1. If the binding contains any literals, it is considered a valid named resource binding. + if (literalCountInBinding > 0) { + isValidPair = true; + } else if (bindingStartIndex > 0) { + // 2. For simple bindings like "{project}", the binding itself has no inner literal resources. + // Instead, we check if the literal segment immediately preceding it (e.g., "projects/") + // is a known resource. + Segment prevSeg = segments.get(bindingStartIndex - 1); if (prevSeg.kind() == SegmentKind.LITERAL && knownResources.contains(prevSeg.value())) { isValidPair = true; } } - } - if (isValidPair) { - lastValidEndBindingIndex = i; - } - } else if (seg.kind() == SegmentKind.LITERAL) { - if (inBinding) { - String value = seg.value(); - if (!value.matches("^v\\d+[a-zA-Z0-9]*$")) { - literalCountInBinding++; + if (isValidPair) { + // We successfully found the last valid binding! Record its end index and terminate the search. + lastValidEndBindingIndex = i; + break; } + // The current binding wasn't a valid resource pair. + // Skip over all inner segments of this invalid binding so we don't evaluate them again. + i = bindingStartIndex; } } } diff --git a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index 52496b8cf049..4c09639e5f44 100644 --- a/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -951,6 +951,14 @@ void testGetCanonicalResourceName_namedBindingsSimple() { .isEqualTo("{bar=projects/*/locations/*/bars/*}"); } + @Test + void testGetCanonicalResourceName_namedBindingsWithUnknownResource() { + Set knownResources = ImmutableSet.of(); + PathTemplate template = PathTemplate.create("/v1/{bar=projects/*/locations/*/unknown/*}"); + Truth.assertThat(template.getCanonicalResourceName(knownResources)) + .isEqualTo("{bar=projects/*/locations/*/unknown/*}"); + } + @Test void testGetCanonicalResourceName_simplePath() { Set knownResources = ImmutableSet.of("projects", "locations", "instances", "widgets"); From f63e3eb3efb2eea25b41dc8ef6e282b3b1e6ef06 Mon Sep 17 00:00:00 2001 From: blakeli Date: Sat, 28 Mar 2026 00:54:39 -0400 Subject: [PATCH 17/17] fix: format --- .../com/google/api/pathtemplate/PathTemplate.java | 11 +++++++---- .../AbstractTransportServiceStubClassComposer.java | 5 ++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java index ee77afd903f5..1fd7df80c463 100644 --- a/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -358,7 +358,7 @@ public String getCanonicalResourceName(Set knownResources) { // Searching backwards allows us to stop immediately once the last valid pair is found. for (int i = segments.size() - 1; i >= 0; i--) { Segment seg = segments.get(i); - + // We are looking for the end of a binding (e.g., "}" in "{project}" or "{name=projects/*}") if (seg.kind() == SegmentKind.END_BINDING) { int bindingStartIndex = -1; @@ -378,11 +378,13 @@ public String getCanonicalResourceName(Set knownResources) { } if (bindingStartIndex != -1) { - // 1. If the binding contains any literals, it is considered a valid named resource binding. + // 1. If the binding contains any literals, it is considered a valid named resource + // binding. if (literalCountInBinding > 0) { isValidPair = true; } else if (bindingStartIndex > 0) { - // 2. For simple bindings like "{project}", the binding itself has no inner literal resources. + // 2. For simple bindings like "{project}", the binding itself has no inner literal + // resources. // Instead, we check if the literal segment immediately preceding it (e.g., "projects/") // is a known resource. Segment prevSeg = segments.get(bindingStartIndex - 1); @@ -392,7 +394,8 @@ public String getCanonicalResourceName(Set knownResources) { } if (isValidPair) { - // We successfully found the last valid binding! Record its end index and terminate the search. + // We successfully found the last valid binding! Record its end index and terminate the + // search. lastValidEndBindingIndex = i; break; } diff --git a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java index 3e0a7fe84355..922dda48dcc9 100644 --- a/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java +++ b/sdk-platform-java/gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractTransportServiceStubClassComposer.java @@ -96,6 +96,7 @@ import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; +import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.annotation.Generated; import javax.annotation.Nullable; @@ -1632,7 +1633,9 @@ protected LambdaExpr createResourceNameExtractorClassInstance( // For each httpBinding, // generates resourceNameSegments.put("field",String.valueOf(request.getField())); for (HttpBindings.HttpBinding httpBinding : httpBindings) { - if (!canonicalPath.contains(httpBinding.name())) { + if (!Pattern.compile("\\{" + httpBinding.name() + "(?:=.*?)?\\}") + .matcher(canonicalPath) + .find()) { continue; } MethodInvocationExpr getFieldExpr =