From 5e0b8f89f7728d39d82a62d98fb265aeefa491eb Mon Sep 17 00:00:00 2001 From: Blake Li Date: Fri, 4 Feb 2022 14:00:52 -0500 Subject: [PATCH] Support delimiters(_-.~) as start or end characters for a segment. (#336) Support delimiters(_-.~) as start or end characters for a segment if the segment does not contain complex resource names. A segment can start with a delimiter, as long as there is no { right after it. A segment can end with a delimiter, as long as there is no } right before it. A segment like .{well}-{known} or {well}-{known}. is invalid. A segment like .well-known, .well-{known} or .-~{well-known} is considered a literal hence is valid. --- .../google/api/pathtemplate/PathTemplate.java | 18 ++++- .../api/pathtemplate/PathTemplateTest.java | 77 ++++++++++++++++++- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 4e4e770e6e..4458e5ed66 100644 --- a/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -887,9 +887,7 @@ private static ImmutableList parseTemplate(String template) { boolean isLastSegment = (template.indexOf(seg) + seg.length()) == template.length(); boolean isCollectionWildcard = !isLastSegment && (seg.equals("-") || seg.equals("-}")); - if (!isCollectionWildcard - && (COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(0, 1)).find() - || COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(seg.length() - 1)).find())) { + if (!isCollectionWildcard && isSegmentBeginOrEndInvalid(seg)) { throw new ValidationException("parse error: invalid begin or end character in '%s'", seg); } // Disallow zero or multiple delimiters between variable names. @@ -1015,6 +1013,20 @@ private static ImmutableList parseTemplate(String template) { return builder.build(); } + private static boolean isSegmentBeginOrEndInvalid(String seg) { + // 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; + } + // A segment can start with a delimiter, as long as there is no { right after it. + // A segment can end with a delimiter, as long as there is no } right before it. + // e.g. Invalid: .{well}-{known}, {well}-{known}- + // Valid: .well-known, .well-{known}, .-~{well-known}, these segments are all considered as literals for matching + return (COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(0, 1)).find() && seg.charAt(1) == '{') + || (COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(seg.length() - 1)).find() + && seg.charAt(seg.length() - 2) == '}'); + } + private static List parseComplexResourceId(String seg) { List segments = new ArrayList<>(); List separatorIndices = new ArrayList<>(); diff --git a/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index 49b64e6d4e..6e4f02557b 100644 --- a/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -487,9 +487,6 @@ public void complexResourceBasicInvalidIds() { @Test public void complexResourceMultipleDelimiters() { thrown.expect(ValidationException.class); - PathTemplate.create("projects/*/zones/.-~{zone_a}"); - thrown.expectMessage( - String.format("parse error: invalid begin or end character in '%s'", ".-~{zone_a}")); PathTemplate.create("projects/*/zones/{zone_a}~.{zone_b}"); thrown.expectMessage( @@ -717,6 +714,80 @@ public void instantiateWithCustomVerbs() { Truth.assertThat(template.matches(templateInstance)).isTrue(); } + @Test + public void instantiateWithASegmentStartsWithADelimiter() { + PathTemplate pathTemplate = + PathTemplate.create( + "v1beta1/{parent=projects/*/locations/*/clusters/*}/.well-known/openid-configuration"); + String pattern = + "v1beta1/projects/abc/locations/def/clusters/yte/.well-known/openid-configuration"; + Truth.assertThat(pathTemplate.matches(pattern)).isTrue(); + } + + @Test + public void instantiateWithASegmentContainingComplexResourceNamesAndStartsWithADelimiter() { + thrown.expect(ValidationException.class); + PathTemplate.create( + "v1beta1/{parent=projects/*/locations/*/clusters/*}/.{well}-{known}/openid-configuration"); + thrown.expectMessage( + String.format("parse error: invalid begin or end character in '%s'", ".{well}-{known}")); + } + + @Test + public void + instantiateWithASegmentContainingNoComplexResourceNamesAndStartsWithMultipleDelimiters() { + PathTemplate pathTemplate = + PathTemplate.create( + "v1beta1/{parent=projects/*/locations/*/clusters/*}/.-~well-known/openid-configuration"); + String pattern = + "v1beta1/projects/abc/locations/def/clusters/yte/.-~well-known/openid-configuration"; + Truth.assertThat(pathTemplate.matches(pattern)).isTrue(); + } + + @Test + public void instantiateWithASegmentOnlyContainingOneDelimiter() { + thrown.expect(ValidationException.class); + PathTemplate.create("v1/publishers/{publisher}/books/."); + thrown.expectMessage(String.format("parse error: invalid begin or end character in '%s'", ".")); + } + + @Test + public void instantiateWithASegmentOnlyContainingOneCharacter() { + PathTemplate pathTemplate = PathTemplate.create("v1/publishers/{publisher}/books/a"); + String pattern = "v1/publishers/o'reilly/books/a"; + Truth.assertThat(pathTemplate.matches(pattern)).isTrue(); + } + + @Test + public void instantiateWithASegmentEndsWithADelimiter() { + PathTemplate pathTemplate = + PathTemplate.create( + "v1beta1/{parent=projects/*/locations/*/clusters/*}/well-known./openid-configuration"); + String pattern = + "v1beta1/projects/abc/locations/def/clusters/yte/well-known./openid-configuration"; + Truth.assertThat(pathTemplate.matches(pattern)).isTrue(); + } + + @Test + public void instantiateWithASegmentContainingComplexResourceNamesAndEndsWithADelimiter() { + thrown.expect(ValidationException.class); + PathTemplate.create( + "v1beta1/{parent=projects/*/locations/*/clusters/*}/{well}-{known}./openid-configuration"); + thrown.expectMessage( + String.format("parse error: invalid begin or end character in '%s'", "{well}-{known}.")); + } + + @Test + public void + instantiateWithASegmentContainingNoComplexResourceNamesAndEndsWithMultipleDelimiters() { + PathTemplate pathTemplate = + PathTemplate.create( + "v1beta1/{parent=projects/*/locations/*/clusters/*}/well-known.-~/openid-configuration"); + String pattern = + "v1beta1/projects/abc/locations/def/clusters/yte/well-known.-~/openid-configuration"; + Truth.assertThat(pathTemplate.matches(pattern)).isTrue(); + } + // Other // =====