diff --git a/src/main/java/com/google/api/generator/gapic/composer/ResourceNameTokenizer.java b/src/main/java/com/google/api/generator/gapic/composer/ResourceNameTokenizer.java index 671b5b6bcf..67d295d1a5 100644 --- a/src/main/java/com/google/api/generator/gapic/composer/ResourceNameTokenizer.java +++ b/src/main/java/com/google/api/generator/gapic/composer/ResourceNameTokenizer.java @@ -15,6 +15,7 @@ package com.google.api.generator.gapic.composer; import com.google.api.pathtemplate.PathTemplate; +import com.google.common.base.Preconditions; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -22,9 +23,13 @@ import java.util.stream.Collectors; public class ResourceNameTokenizer { - private static final String SLASH = "/"; private static final String LEFT_BRACE = "{"; private static final String RIGHT_BRACE = "}"; + private static final String SLASH = "/"; + private static final String EMPTY = ""; + + private static final String EQUALS_WILDCARD = "=*"; + private static final String EQUALS_PATH_WILDCARD = "=**"; static List> parseTokenHierarchy(List patterns) { List nonSlashSepStrings = Arrays.asList("}_{", "}-{", "}.{", "}~{"); @@ -36,7 +41,9 @@ static List> parseTokenHierarchy(List patterns) { String[] patternTokens = pattern.split(SLASH); for (String patternToken : patternTokens) { if (patternToken.startsWith(LEFT_BRACE) && patternToken.endsWith(RIGHT_BRACE)) { - String processedPatternToken = patternToken; + String processedPatternToken = + // Replacement order matters - ensure the first is not a subcomponent of the second. + patternToken.replace(EQUALS_PATH_WILDCARD, EMPTY).replace(EQUALS_WILDCARD, EMPTY); // Handle non-slash separators. if (nonSlashSepStrings.stream().anyMatch(s -> patternToken.contains(s))) { @@ -44,14 +51,28 @@ static List> parseTokenHierarchy(List patterns) { processedPatternToken = processedPatternToken.replace(str, "_"); } } else { + final int processedPatternTokenLength = processedPatternToken.length(); // Handles wildcards. - processedPatternToken = + List candidateVars = vars.stream() - .filter(v -> patternToken.contains(v)) - .collect(Collectors.toList()) - .get(0); + // Check that the token size is within ~3 of the var, to avoid mismatching on + // variables with same-named subcomponents. + // Otherwise, "customer_client_link" will match with "customer". + .filter( + v -> + patternToken.contains(v) + // Accounting for braces. + && processedPatternTokenLength - v.length() < 3) + .collect(Collectors.toList()); + Preconditions.checkState( + !candidateVars.isEmpty(), + String.format( + "No variable candidates found for token %s in pattern %s", + processedPatternToken, pattern)); + processedPatternToken = candidateVars.get(0); } - hierarchy.add(processedPatternToken.replace("{", "").replace("}", "")); + hierarchy.add( + processedPatternToken.replace(LEFT_BRACE, EMPTY).replace(RIGHT_BRACE, EMPTY)); } } tokenHierachies.add(hierarchy); diff --git a/src/test/java/com/google/api/generator/gapic/composer/ResourceNameTokenizerTest.java b/src/test/java/com/google/api/generator/gapic/composer/ResourceNameTokenizerTest.java index aed8624d92..3c61e206ba 100644 --- a/src/test/java/com/google/api/generator/gapic/composer/ResourceNameTokenizerTest.java +++ b/src/test/java/com/google/api/generator/gapic/composer/ResourceNameTokenizerTest.java @@ -51,6 +51,16 @@ public void parseTokenHierarchy_basic() { assertThat(tokenHierarchies.get(2)).containsExactly("project", "autoscaling_policy"); } + @Test + public void parseTokenHierarchy_substringsInPattern() { + List patterns = + Arrays.asList( + "customers/{customer}/customerExtensionSettings/{customer_extension_setting}"); + List> tokenHierarchies = ResourceNameTokenizer.parseTokenHierarchy(patterns); + assertEquals(1, tokenHierarchies.size()); + assertThat(tokenHierarchies.get(0)).containsExactly("customer", "customer_extension_setting"); + } + @Test public void parseTokenHierarchy_wildcards() { List patterns =