Skip to content

Commit d76201d

Browse files
committed
support nested path expression fix #946
+ update apitool to work with nested paths
1 parent fe93a7b commit d76201d

File tree

7 files changed

+180
-45
lines changed

7 files changed

+180
-45
lines changed

jooby/src/main/java/org/jooby/Jooby.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,6 @@
228228
import com.typesafe.config.ConfigFactory;
229229
import com.typesafe.config.ConfigObject;
230230
import com.typesafe.config.ConfigValue;
231-
import com.typesafe.config.ConfigValueFactory;
232231
import static com.typesafe.config.ConfigValueFactory.fromAnyRef;
233232
import static java.util.Objects.requireNonNull;
234233
import static org.jooby.Route.CONNECT;
@@ -289,7 +288,6 @@
289288
import javax.inject.Singleton;
290289
import javax.net.ssl.SSLContext;
291290
import java.io.File;
292-
import java.lang.reflect.Modifier;
293291
import java.lang.reflect.Type;
294292
import java.nio.charset.Charset;
295293
import java.nio.file.Path;
@@ -305,6 +303,7 @@
305303
import java.util.HashMap;
306304
import java.util.HashSet;
307305
import java.util.LinkedHashSet;
306+
import java.util.LinkedList;
308307
import java.util.List;
309308
import java.util.Locale;
310309
import java.util.Map;
@@ -883,7 +882,7 @@ public EnvDep(final Predicate<String> predicate, final Consumer<Config> callback
883882

884883
private transient List<Jooby> apprefs;
885884

886-
private transient String path;
885+
private transient LinkedList<String> path = new LinkedList<>();
887886

888887
private transient String confname;
889888

@@ -912,21 +911,28 @@ public Jooby(final String prefix) {
912911

913912
@Override
914913
public Route.Collection path(String path, Runnable action) {
915-
this.path = Route.normalize(path);
914+
this.path.addLast(Route.normalize(path));
916915
Route.Collection collection = with(action);
917-
this.path = null;
916+
this.path.removeLast();
918917
return collection;
919918
}
920919

921920
@Override
922921
public Jooby use(final Jooby app) {
923-
return use(Optional.ofNullable(path), app);
922+
return use(prefixPath(null), app);
923+
}
924+
925+
private Optional<String> prefixPath(String tail) {
926+
return path.size() == 0
927+
? tail == null ? Optional.empty() : Optional.of(Route.normalize(tail))
928+
: Optional.of(path.stream()
929+
.collect(Collectors.joining("", "", tail == null
930+
? "" : Route.normalize(tail))));
924931
}
925932

926933
@Override
927934
public Jooby use(final String path, final Jooby app) {
928-
return use(Optional.of(Optional.ofNullable(this.path).map(p -> p + '/').orElse("") + path),
929-
app);
935+
return use(prefixPath(path), app);
930936
}
931937

932938
/**
@@ -1935,7 +1941,7 @@ public Route.Definition assets(final String path, final AssetHandler handler) {
19351941

19361942
@Override
19371943
public Route.Collection use(final Class<?> routeClass) {
1938-
return use("", routeClass);
1944+
return use("", routeClass);
19391945
}
19401946

19411947
@Override
@@ -1956,7 +1962,7 @@ public Route.Collection use(final String path, final Class<?> routeClass) {
19561962
* @return The same route definition.
19571963
*/
19581964
private Route.Definition appendDefinition(String method, String pattern, Route.Filter filter) {
1959-
String pathPattern = this.path == null ? pattern : this.path + "/" + pattern;
1965+
String pathPattern = prefixPath(pattern).orElse(pattern);
19601966
Route.Definition route = new Route.Definition(method, pathPattern, filter,
19611967
caseSensitiveRouting);
19621968
if (prefix != null) {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.jooby.issues;
2+
3+
import org.jooby.test.ServerFeature;
4+
import org.junit.Test;
5+
6+
public class Issue946 extends ServerFeature {
7+
8+
{
9+
path("/946/api/some", () -> {
10+
path("/:id", () -> {
11+
get(req -> req.param("id").value());
12+
13+
get("/enabled", req -> req.param("id").value());
14+
});
15+
});
16+
}
17+
18+
@Test
19+
public void nestedPathExpression() throws Exception {
20+
request().get("/946/api/some/1")
21+
.expect("1");
22+
request().get("/946/api/some/2/enabled")
23+
.expect("2");
24+
}
25+
26+
}

modules/jooby-apitool/src/main/antlr4/org/jooby/internal/apitool/javadoc/FuzzyDoc.g4

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use:
1010
| doc=DOC 'use' '(' pattern=STRING comma+=',';
1111

1212
path:
13-
doc=DOC 'path' '(' pattern=STRING ',' '(' ')' '->' scripts
14-
| doc=DOC 'path' '(' pattern=STRING ')' scripts;
13+
doc=DOC? 'path' '(' pattern=STRING ',' '(' ')' '->' scripts
14+
| doc=DOC? 'path' '(' pattern=STRING ')' scripts;
1515

1616
route: doc=DOC 'route' '(' pattern=STRING ')' scripts;
1717

@@ -37,7 +37,8 @@ script:
3737
| doc=DOC dot='.'? method=METHOD '(' '(' ')' scriptBody
3838
| doc=DOC dot='.'? method=METHOD '(' '(' 'req' ')' scriptBody
3939
| doc=DOC dot='.'? method=METHOD '(' '(' 'req' ',' 'rsp' ')' scriptBody
40-
| doc=DOC dot='.'? method=METHOD '(' '(' 'req' ',' 'rsp' ',' 'chain' ')' scriptBody;
40+
| doc=DOC dot='.'? method=METHOD '(' '(' 'req' ',' 'rsp' ',' 'chain' ')' scriptBody
41+
| path;
4142

4243
scriptBody:
4344
'->' '{' (scriptBody | .)*? '}' ')'

modules/jooby-apitool/src/main/java/org/jooby/internal/apitool/DocParser.java

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@
240240
import java.util.Arrays;
241241
import java.util.HashMap;
242242
import java.util.LinkedHashMap;
243+
import java.util.LinkedList;
243244
import java.util.List;
244245
import java.util.Map;
245246
import java.util.Optional;
@@ -255,13 +256,13 @@
255256
class DocParser {
256257

257258
private static class DocCollector extends FuzzyDocBaseListener {
258-
String prefix = "";
259+
LinkedList<String> prefix = new LinkedList<>();
259260

260261
boolean insideRoute;
261262

262263
List<DocItem> doc;
263264

264-
String summary = "";
265+
LinkedList<String> summary = new LinkedList<>();
265266

266267
Path file;
267268

@@ -304,22 +305,26 @@ public void syntaxError(final Recognizer<?, ?> recognizer, final Object offendin
304305
String method = Optional.ofNullable(ctx.method).map(it -> str(it.getText())).orElse("*");
305306
String pattern = Optional.ofNullable(ctx.pattern).map(it -> str(it.getText()))
306307
.orElse("*");
307-
doc.add(doc(method, normalize(pattern), file, summary, comment));
308+
doc.add(
309+
doc(method, normalize(pattern), file, summary(), comment));
308310
} else {
309-
this.prefix = str(ctx.pattern.getText());
311+
this.prefix.addLast(Route.normalize(str(ctx.pattern.getText())));
310312
this.insideRoute = false;
311-
this.summary = cleanJavadoc(file, ctx.doc.getText());
313+
this.summary.addLast(cleanJavadoc(file, ctx.doc.getText()));
312314
}
313315
}
314316

315317
@Override public void enterPath(FuzzyDocParser.PathContext ctx) {
316-
this.prefix = str(ctx.pattern.getText());
318+
this.prefix.addLast(Route.normalize(str(ctx.pattern.getText())));
317319
this.insideRoute = true;
318-
this.summary = cleanJavadoc(file, ctx.doc.getText());
320+
if (ctx.doc != null) {
321+
this.summary.addLast(cleanJavadoc(file, ctx.doc.getText()));
322+
}
319323
}
320324

321325
@Override public void exitPath(FuzzyDocParser.PathContext ctx) {
322-
this.prefix = "";
326+
popPrefix();
327+
popSummary();
323328
this.insideRoute = false;
324329
}
325330

@@ -330,13 +335,14 @@ public void syntaxError(final Recognizer<?, ?> recognizer, final Object offendin
330335
* @param ctx
331336
*/
332337
@Override public void enterRoute(final FuzzyDocParser.RouteContext ctx) {
333-
this.prefix = str(ctx.pattern.getText());
338+
this.prefix.addLast(Route.normalize(str(ctx.pattern.getText())));
334339
this.insideRoute = true;
335-
this.summary = cleanJavadoc(file, ctx.doc.getText());
340+
this.summary.addLast(cleanJavadoc(file, ctx.doc.getText()));
336341
}
337342

338343
@Override public void exitRoute(final FuzzyDocParser.RouteContext ctx) {
339-
this.prefix = "";
344+
popPrefix();
345+
popSummary();
340346
this.insideRoute = false;
341347
}
342348

@@ -354,15 +360,28 @@ public void syntaxError(final Recognizer<?, ?> recognizer, final Object offendin
354360
*
355361
* but ignore dot when route(prefix) is present (kotlin)
356362
*/
357-
this.prefix = "";
358-
this.summary = "";
363+
popPrefix();
364+
popSummary();
365+
}
366+
String comment = Optional.ofNullable(ctx.doc).map(it -> it.getText()).orElse("");
367+
if (ctx.method != null) {
368+
String method = ctx.method.getText();
369+
String pattern =
370+
prefix.stream().collect(Collectors.joining("")) + "/" + Optional.ofNullable(ctx.pattern)
371+
.map(it -> str(it.getText()))
372+
.orElse("/");
373+
doc.add(doc(method, normalize(pattern), file, summary(), comment));
374+
}
375+
}
376+
377+
private void popSummary() {
378+
if (summary.size() > 0) {
379+
summary.removeLast();
359380
}
360-
String comment = ctx.doc.getText();
361-
String method = ctx.method.getText();
362-
String pattern =
363-
prefix + "/" + Optional.ofNullable(ctx.pattern).map(it -> str(it.getText()))
364-
.orElse("/");
365-
doc.add(doc(method, normalize(pattern), file, summary, comment));
381+
}
382+
383+
private String summary() {
384+
return summary.stream().collect(Collectors.joining());
366385
}
367386

368387
@Override public void enterClazz(final FuzzyDocParser.ClazzContext ctx) {
@@ -374,27 +393,33 @@ public void syntaxError(final Recognizer<?, ?> recognizer, final Object offendin
374393
*/
375394
boolean isClass = ctx.isClass != null;
376395
if (isClass) {
377-
this.prefix = normalize(pattern);
378-
this.summary = cleanJavadoc(file, ctx.doc.getText());
396+
this.prefix.addLast(normalize(pattern));
397+
this.summary.addLast(cleanJavadoc(file, ctx.doc.getText()));
379398
} else {
380-
this.prefix = "";
381-
this.summary = "";
399+
popPrefix();
400+
popSummary();
382401
List<String> methods = methods(ctx.annotations);
383402
String comment = ctx.doc.getText();
384403
if (methods.size() == 0) {
385-
doc.add(doc("get", normalize(pattern), file, summary, comment));
404+
doc.add(doc("get", normalize(pattern), file, summary(), comment));
386405
} else {
387406
methods.stream()
388-
.forEach(it -> doc.add(doc(it, normalize(pattern), file, summary, comment)));
407+
.forEach(it -> doc.add(doc(it, normalize(pattern), file, summary(), comment)));
389408
}
390409
}
391410
} else {
392-
this.prefix = "";
411+
popPrefix();
393412
}
394413
}
395414

396415
@Override public void exitClazz(final FuzzyDocParser.ClazzContext ctx) {
397-
this.prefix = "";
416+
popPrefix();
417+
}
418+
419+
private void popPrefix() {
420+
if (prefix.size() > 0) {
421+
prefix.pop();
422+
}
398423
}
399424

400425
private String pattern(List<FuzzyDocParser.AnnotationContext> annotations) {
@@ -420,12 +445,14 @@ private List<String> methods(List<FuzzyDocParser.AnnotationContext> annotations)
420445
return;
421446
}
422447
String comment = ctx.doc.getText();
423-
String pattern = prefix + "/" + Optional.ofNullable(path).orElse("/");
448+
String pattern =
449+
prefix.stream().collect(Collectors.joining()) + "/" + Optional.ofNullable(path)
450+
.orElse("/");
424451
if (methods.size() == 0) {
425-
doc.add(doc("get", normalize(pattern), file, summary, comment));
452+
doc.add(doc("get", normalize(pattern), file, summary(), comment));
426453
} else {
427454
methods.stream()
428-
.forEach(method -> doc.add(doc(method, normalize(pattern), file, summary, comment)));
455+
.forEach(method -> doc.add(doc(method, normalize(pattern), file, summary(), comment)));
429456
}
430457
}
431458

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package apps;
2+
3+
import org.jooby.Jooby;
4+
import org.jooby.Results;
5+
6+
public class App946 extends Jooby {
7+
{
8+
/**
9+
* Top.
10+
*/
11+
path("/some/path", () -> {
12+
13+
path("/:id", () -> {
14+
/**
15+
* GET.
16+
* @param id Param ID.
17+
*/
18+
get(req -> {
19+
return req.param("id").intValue();
20+
});
21+
22+
/**
23+
* GET foo.
24+
* @param id Param ID.
25+
*/
26+
get("/foo", req -> {
27+
return req.param("id").intValue();
28+
});
29+
});
30+
});
31+
}
32+
}

modules/jooby-apitool/src/test/java/org/jooby/apitool/CommonPathTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package org.jooby.apitool;
22

3-
import apps.Tag;
4-
import apps.VarApp;
53
import org.junit.Test;
64
import parser.CommonPathApp;
75

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.jooby.apitool;
2+
3+
import apps.App946;
4+
import org.junit.Test;
5+
6+
import java.nio.file.Path;
7+
import java.nio.file.Paths;
8+
9+
public class Issue946 {
10+
11+
@Test
12+
public void shouldProcessNestedPath() throws Exception {
13+
new RouteMethodAssert(new ApiParser(dir()).parseFully(new App946()))
14+
.next(r -> {
15+
r.returnType(int.class);
16+
r.pattern("/some/path/{id}");
17+
r.description("GET.");
18+
r.summary("Top.");
19+
r.param(p -> {
20+
p.name("id");
21+
p.type(int.class);
22+
p.description("Param ID.");
23+
});
24+
})
25+
.next(r -> {
26+
r.returnType(int.class);
27+
r.pattern("/some/path/{id}/foo");
28+
r.description("GET foo.");
29+
r.summary("Top.");
30+
r.param(p -> {
31+
p.name("id");
32+
p.type(int.class);
33+
p.description("Param ID.");
34+
});
35+
}).done();
36+
}
37+
38+
private Path dir() {
39+
Path userdir = Paths.get(System.getProperty("user.dir"));
40+
if (!userdir.toString().endsWith("jooby-apitool")) {
41+
userdir = userdir.resolve("modules").resolve("jooby-apitool");
42+
}
43+
return userdir;
44+
}
45+
}

0 commit comments

Comments
 (0)