diff --git a/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 9ed440bcf..6edcddcca 100644 --- a/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -125,13 +125,17 @@ public class PathTemplate { /** * A constant identifying the special variable used for endpoint bindings in the result of - * {@link #matchFromFullName(String)}. + * {@link #matchFromFullName(String)}. It may also contain protocol string, if its provided in the + * input. */ public static final String HOSTNAME_VAR = "$hostname"; // A regexp to match a custom verb at the end of a path. private static final Pattern CUSTOM_VERB_PATTERN = Pattern.compile(":([^/*}{=]+)$"); + // A regex to match a hostname with or without protocol. + private static final Pattern HOSTNAME_PATTERN = Pattern.compile("^(\\w+:)?//"); + // A splitter on slash. private static final Splitter SLASH_SPLITTER = Splitter.on('/').trimResults(); @@ -533,10 +537,10 @@ private Map match(String path, boolean forceHostName) { path = path.substring(0, matcher.start(0)); } - // Do full match. - boolean withHostName = path.startsWith("//"); + Matcher matcher = HOSTNAME_PATTERN.matcher(path); + boolean withHostName = matcher.find(); if (withHostName) { - path = path.substring(2); + path = matcher.replaceFirst(""); } List input = SLASH_SPLITTER.splitToList(path); int inPos = 0; @@ -548,16 +552,42 @@ private Map match(String path, boolean forceHostName) { String hostName = input.get(inPos++); if (withHostName) { // Put the // back, so we can distinguish this case from forceHostName. - hostName = "//" + hostName; + hostName = matcher.group(0) + hostName; } values.put(HOSTNAME_VAR, hostName); } + if (withHostName) { + inPos = alignInputToAlignableSegment(input, inPos, segments.get(0)); + } if (!match(input, inPos, segments, 0, values)) { return null; } return ImmutableMap.copyOf(values); } + // Aligns input to start of literal value of literal or binding segment if input contains hostname. + private int alignInputToAlignableSegment(List input, int inPos, Segment segment) { + switch (segment.kind()) { + case BINDING: + inPos = alignInputPositionToLiteral(input, inPos, segment.value() + "s"); + return inPos + 1; + case LITERAL: + return alignInputPositionToLiteral(input, inPos, segment.value()); + } + return inPos; + } + + // Aligns input to start of literal value if input contains hostname. + private int alignInputPositionToLiteral( + List input, int inPos, String literalSegmentValue) { + for (; inPos < input.size(); inPos++) { + if (literalSegmentValue.equals(input.get(inPos))) { + return inPos; + } + } + return inPos; + } + // Tries to match the input based on the segments at given positions. Returns a boolean // indicating whether the match was successful. private boolean match( diff --git a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index b5149ddb2..69d935fc1 100644 --- a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -83,6 +83,30 @@ public void matchWithHostName() { Truth.assertThat(match.get("$1")).isEqualTo("o"); } + @Test + public void matchWithHostNameAndProtocol() { + PathTemplate template = PathTemplate.create("projects/{project}/zones/{zone}"); + Map match = + template.match( + "https://www.googleapis.com/compute/v1/projects/project-123/zones/europe-west3-c"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get(PathTemplate.HOSTNAME_VAR)).isEqualTo("https://www.googleapis.com"); + Truth.assertThat(match.get("project")).isEqualTo("project-123"); + Truth.assertThat(match.get("zone")).isEqualTo("europe-west3-c"); + } + + @Test + public void matchWithHostNameAndProtocolWithTemplateStartWithBinding() { + PathTemplate template = PathTemplate.create("{project}/zones/{zone}"); + Map match = + template.match( + "https://www.googleapis.com/compute/v1/projects/project-123/zones/europe-west3-c"); + Truth.assertThat(match).isNotNull(); + Truth.assertThat(match.get(PathTemplate.HOSTNAME_VAR)).isEqualTo("https://www.googleapis.com"); + Truth.assertThat(match.get("project")).isEqualTo("project-123"); + Truth.assertThat(match.get("zone")).isEqualTo("europe-west3-c"); + } + @Test public void matchWithCustomMethod() { PathTemplate template = PathTemplate.create("buckets/*/objects/*:custom");