Skip to content

Commit e77a5cc

Browse files
authored
Fix PathTemplate matcher to match path with protocol and hostname (#68)
* Fix PathTemplate matcher to match path with protocol and hostname
1 parent 447b50f commit e77a5cc

2 files changed

Lines changed: 59 additions & 5 deletions

File tree

sdk-platform-java/api-common-java/src/main/java/com/google/api/pathtemplate/PathTemplate.java

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,17 @@ public class PathTemplate {
125125

126126
/**
127127
* A constant identifying the special variable used for endpoint bindings in the result of
128-
* {@link #matchFromFullName(String)}.
128+
* {@link #matchFromFullName(String)}. It may also contain protocol string, if its provided in the
129+
* input.
129130
*/
130131
public static final String HOSTNAME_VAR = "$hostname";
131132

132133
// A regexp to match a custom verb at the end of a path.
133134
private static final Pattern CUSTOM_VERB_PATTERN = Pattern.compile(":([^/*}{=]+)$");
134135

136+
// A regex to match a hostname with or without protocol.
137+
private static final Pattern HOSTNAME_PATTERN = Pattern.compile("^(\\w+:)?//");
138+
135139
// A splitter on slash.
136140
private static final Splitter SLASH_SPLITTER = Splitter.on('/').trimResults();
137141

@@ -533,10 +537,10 @@ private Map<String, String> match(String path, boolean forceHostName) {
533537
path = path.substring(0, matcher.start(0));
534538
}
535539

536-
// Do full match.
537-
boolean withHostName = path.startsWith("//");
540+
Matcher matcher = HOSTNAME_PATTERN.matcher(path);
541+
boolean withHostName = matcher.find();
538542
if (withHostName) {
539-
path = path.substring(2);
543+
path = matcher.replaceFirst("");
540544
}
541545
List<String> input = SLASH_SPLITTER.splitToList(path);
542546
int inPos = 0;
@@ -548,16 +552,42 @@ private Map<String, String> match(String path, boolean forceHostName) {
548552
String hostName = input.get(inPos++);
549553
if (withHostName) {
550554
// Put the // back, so we can distinguish this case from forceHostName.
551-
hostName = "//" + hostName;
555+
hostName = matcher.group(0) + hostName;
552556
}
553557
values.put(HOSTNAME_VAR, hostName);
554558
}
559+
if (withHostName) {
560+
inPos = alignInputToAlignableSegment(input, inPos, segments.get(0));
561+
}
555562
if (!match(input, inPos, segments, 0, values)) {
556563
return null;
557564
}
558565
return ImmutableMap.copyOf(values);
559566
}
560567

568+
// Aligns input to start of literal value of literal or binding segment if input contains hostname.
569+
private int alignInputToAlignableSegment(List<String> input, int inPos, Segment segment) {
570+
switch (segment.kind()) {
571+
case BINDING:
572+
inPos = alignInputPositionToLiteral(input, inPos, segment.value() + "s");
573+
return inPos + 1;
574+
case LITERAL:
575+
return alignInputPositionToLiteral(input, inPos, segment.value());
576+
}
577+
return inPos;
578+
}
579+
580+
// Aligns input to start of literal value if input contains hostname.
581+
private int alignInputPositionToLiteral(
582+
List<String> input, int inPos, String literalSegmentValue) {
583+
for (; inPos < input.size(); inPos++) {
584+
if (literalSegmentValue.equals(input.get(inPos))) {
585+
return inPos;
586+
}
587+
}
588+
return inPos;
589+
}
590+
561591
// Tries to match the input based on the segments at given positions. Returns a boolean
562592
// indicating whether the match was successful.
563593
private boolean match(

sdk-platform-java/api-common-java/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,30 @@ public void matchWithHostName() {
8383
Truth.assertThat(match.get("$1")).isEqualTo("o");
8484
}
8585

86+
@Test
87+
public void matchWithHostNameAndProtocol() {
88+
PathTemplate template = PathTemplate.create("projects/{project}/zones/{zone}");
89+
Map<String, String> match =
90+
template.match(
91+
"https://www.googleapis.com/compute/v1/projects/project-123/zones/europe-west3-c");
92+
Truth.assertThat(match).isNotNull();
93+
Truth.assertThat(match.get(PathTemplate.HOSTNAME_VAR)).isEqualTo("https://www.googleapis.com");
94+
Truth.assertThat(match.get("project")).isEqualTo("project-123");
95+
Truth.assertThat(match.get("zone")).isEqualTo("europe-west3-c");
96+
}
97+
98+
@Test
99+
public void matchWithHostNameAndProtocolWithTemplateStartWithBinding() {
100+
PathTemplate template = PathTemplate.create("{project}/zones/{zone}");
101+
Map<String, String> match =
102+
template.match(
103+
"https://www.googleapis.com/compute/v1/projects/project-123/zones/europe-west3-c");
104+
Truth.assertThat(match).isNotNull();
105+
Truth.assertThat(match.get(PathTemplate.HOSTNAME_VAR)).isEqualTo("https://www.googleapis.com");
106+
Truth.assertThat(match.get("project")).isEqualTo("project-123");
107+
Truth.assertThat(match.get("zone")).isEqualTo("europe-west3-c");
108+
}
109+
86110
@Test
87111
public void matchWithCustomMethod() {
88112
PathTemplate template = PathTemplate.create("buckets/*/objects/*:custom");

0 commit comments

Comments
 (0)