Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.

Commit ec9d2e9

Browse files
committed
add source information to route fix jooby-project#398
1 parent 7d8fc6b commit ec9d2e9

File tree

16 files changed

+359
-39
lines changed

16 files changed

+359
-39
lines changed

coverage-report/src/test/java/org/jooby/RouteReferenceFeature.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ public class RouteReferenceFeature extends ServerFeature {
1717
assertEquals("GET", route.method());
1818
assertEquals("/", route.path());
1919
assertEquals("/", route.pattern());
20-
assertEquals("GET /\n" +
21-
" pattern: /\n" +
22-
" name: /anonymous\n" +
23-
" vars: {}\n" +
24-
" consumes: [*/*]\n" +
25-
" produces: [*/*]\n", req.toString());
20+
assertEquals(
21+
"| Method | Path | Source | Name | Pattern | Consumes | Produces |\n" +
22+
"|--------|------|------------------------------------|------------|---------|----------|----------|\n" +
23+
"| GET | / | org.jooby.RouteReferenceFeature:13 | /anonymous | / | [*/*] | [*/*] |" +
24+
"",
25+
req.toString());
2626
rsp.send("done");
2727
});
2828

coverage-report/src/test/java/org/jooby/issues/Issue393.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ public Object type(final int value) {
4646
use(Resource.class);
4747

4848
err((req, rsp, err) -> {
49-
log.error("issue 393 {}", req.route(), err);
49+
log.error("execution of: {}{} resulted in exception\nRoute:\n{}\n\nStacktrace:",
50+
req.method(), req.path(), req.route().print(6), err);
51+
5052
rsp.send(err.getMessage());
5153
});
5254
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.jooby.issues;
2+
3+
import org.jooby.Route;
4+
import org.jooby.mvc.Path;
5+
import org.jooby.test.ServerFeature;
6+
import org.junit.Test;
7+
8+
public class Issue398 extends ServerFeature {
9+
10+
@Path("/r")
11+
public static class Resource {
12+
13+
@Path("/src")
14+
public Object src(final Route route) {
15+
return route.source();
16+
}
17+
}
18+
19+
{
20+
get("/src", req -> {
21+
return req.route().source();
22+
});
23+
24+
use("/g")
25+
.get("/src", req -> {
26+
return req.route().source();
27+
});
28+
29+
get("/src1", "src2", req -> {
30+
return req.route().source();
31+
});
32+
33+
use(Resource.class);
34+
}
35+
36+
@Test
37+
public void directSrc() throws Exception {
38+
request()
39+
.get("/src")
40+
.expect("org.jooby.issues.Issue398:20");
41+
}
42+
43+
@Test
44+
public void groupSrc() throws Exception {
45+
request()
46+
.get("/g/src")
47+
.expect("org.jooby.issues.Issue398:25");
48+
}
49+
50+
@Test
51+
public void collection() throws Exception {
52+
request()
53+
.get("/src1")
54+
.expect("org.jooby.issues.Issue398:29");
55+
}
56+
57+
@Test
58+
public void resource() throws Exception {
59+
request()
60+
.get("/r/src")
61+
.expect("org.jooby.issues.Issue398$Resource:14");
62+
}
63+
}

coverage-report/src/test/java/org/jooby/pac4j/WebContextFeature.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ public void webContext() throws Exception {
4242
request()
4343
.get("/auth/ctx?p1=v1")
4444
.expect("{\n" +
45-
" \"fullRequestURL\" : \"http://localhost:" + port + "/auth/ctx\",\n" +
45+
" \"fullRequestURL\" : \"http://localhost:9999/auth/ctx\",\n" +
4646
" \"requestMethod\" : \"GET\",\n" +
4747
" \"requestParameters\" : {\n" +
4848
" \"p1\" : [ \"v1\" ]\n" +
4949
" },\n" +
5050
" \"scheme\" : \"http\",\n" +
5151
" \"serverName\" : \"localhost\",\n" +
52-
" \"serverPort\" : " + port + ",\n" +
53-
" \"toString\" : \"GET /auth/ctx\\n pattern: /auth/ctx\\n name: /anonymous\\n vars: {}\\n consumes: [*/*]\\n produces: [*/*]\\n\"\n" +
52+
" \"serverPort\" : 9999,\n" +
53+
" \"toString\" : \"| Method | Path | Source | Name | Pattern | Consumes | Produces |\\n|--------|-----------|--------------------------------------|------------|-----------|----------|----------|\\n| GET | /auth/ctx | org.jooby.pac4j.WebContextFeature:18 | /anonymous | /auth/ctx | [*/*] | [*/*] |\"\n" +
5454
"}");
5555
}
5656

jooby-mongodb-rx/src/test/java/apps/MongoRxApp.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@ public class MongoRxApp extends Jooby {
1717
use(new MongoRx("mongodb://localhost/pets.Pet")
1818
.observableAdapter(observable -> observable.observeOn(Schedulers.io())));
1919

20-
get("/list", req -> req.require(MongoCollection.class)
21-
.find());
20+
use("/")
21+
.get("/x", req -> null)
22+
.get("/x", req -> null);
23+
24+
get("/list", req -> {
25+
return req.require(MongoCollection.class)
26+
.find();
27+
});
2228

2329
get("/db", req -> req.require(MongoClient.class)
2430
.listDatabaseNames());

jooby-whoops/src/main/java/org/jooby/whoops/Whoops.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,8 @@ private static Handler prettyPage(final ClassLoader loader, final SourceLocator
250250

251251
template.get().evaluate(writer, context);
252252

253-
log.error("execution of: " + req.method() + req.path() + " resulted in exception", err);
253+
log.error("execution of: {}{} resulted in exception\nRoute:\n{}\n\nStacktrace:",
254+
req.method(), req.path(), req.route().print(6), err);
254255
rsp.send(writer.toString());
255256
}
256257
};

jooby/src/main/java/org/jooby/Err.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ public static class DefHandler implements Err.Handler {
5858

5959
@Override
6060
public void handle(final Request req, final Response rsp, final Err ex) throws Throwable {
61-
log.error("execution of: " + req.method() + req.path() + " resulted in exception", ex);
61+
log.error("execution of: {}{} resulted in exception\nRoute:\n{}\n\nStacktrace:",
62+
req.method(), req.path(), req.route().print(6), ex);
6263

6364
rsp.send(
6465
Results

jooby/src/main/java/org/jooby/Route.java

Lines changed: 137 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@
3434
import java.util.Map;
3535
import java.util.Optional;
3636
import java.util.Set;
37+
import java.util.function.BiConsumer;
38+
import java.util.function.Function;
3739
import java.util.stream.Collectors;
3840

3941
import org.jooby.internal.DeferredExecution;
4042
import org.jooby.internal.RouteImpl;
4143
import org.jooby.internal.RouteMatcher;
4244
import org.jooby.internal.RoutePattern;
45+
import org.jooby.internal.RouteSourceImpl;
4346

4447
import com.google.common.base.CaseFormat;
4548
import com.google.common.base.Strings;
@@ -48,6 +51,7 @@
4851
import com.google.common.primitives.Primitives;
4952
import com.google.inject.Key;
5053
import com.google.inject.TypeLiteral;
54+
import com.google.inject.internal.util.SourceProvider;
5155

5256
import javaslang.CheckedFunction1;
5357

@@ -215,6 +219,43 @@
215219
*/
216220
public interface Route {
217221

222+
/**
223+
* Provides useful information about where the route was defined.
224+
*
225+
* @author edgar
226+
* @since 1.0.0.CR4
227+
*/
228+
interface Source {
229+
230+
Source UNKNOWN = new Source() {
231+
232+
@Override
233+
public int line() {
234+
return -1;
235+
}
236+
237+
@Override
238+
public Optional<String> declaringClass() {
239+
return Optional.empty();
240+
}
241+
242+
@Override
243+
public String toString() {
244+
return "~unknown:" + line();
245+
}
246+
};
247+
248+
/**
249+
* @return Line number where the route was defined or <code>-1</code> when not available.
250+
*/
251+
int line();
252+
253+
/**
254+
* @return Class where the route
255+
*/
256+
Optional<String> declaringClass();
257+
}
258+
218259
/**
219260
* The map operator converts a route output to something else
220261
*
@@ -966,6 +1007,9 @@ public Collection map(final Mapper<?> mapper) {
9661007
*/
9671008
class Definition implements Props<Definition> {
9681009

1010+
private static final SourceProvider SRC = SourceProvider.DEFAULT_INSTANCE
1011+
.plusSkippedClasses(Definition.class, Jooby.class, Collection.class, Group.class);
1012+
9691013
/**
9701014
* Route's name.
9711015
*/
@@ -1009,6 +1053,10 @@ class Definition implements Props<Definition> {
10091053

10101054
private Mapper<?> mapper;
10111055

1056+
private int line;
1057+
1058+
private String declaringClass;
1059+
10121060
/**
10131061
* Creates a new route definition.
10141062
*
@@ -1062,6 +1110,9 @@ public Definition(final String method, final String pattern,
10621110
// normalized pattern
10631111
this.pattern = cpattern.pattern();
10641112
this.filter = filter;
1113+
StackTraceElement source = SRC.get(new Throwable().getStackTrace());
1114+
this.line = source.getLineNumber();
1115+
this.declaringClass = source.getClassName();
10651116
}
10661117

10671118
/**
@@ -1201,7 +1252,8 @@ public Optional<Route> matches(final String method,
12011252
// keep accept when */*
12021253
List<MediaType> produces = result.size() == 1 && result.get(0).name().equals("*/*")
12031254
? accept : this.produces;
1204-
return Optional.of(asRoute(method, matcher, produces));
1255+
return Optional
1256+
.of(asRoute(method, matcher, produces, new RouteSourceImpl(declaringClass, line)));
12051257
}
12061258
}
12071259
return Optional.empty();
@@ -1364,6 +1416,28 @@ public Definition map(final Mapper<?> mapper) {
13641416
return this;
13651417
}
13661418

1419+
/**
1420+
* Set the line where this route is defined.
1421+
*
1422+
* @param line Line number.
1423+
* @return This instance.
1424+
*/
1425+
public Definition line(final int line) {
1426+
this.line = line;
1427+
return this;
1428+
}
1429+
1430+
/**
1431+
* Set the class where this route is defined.
1432+
*
1433+
* @param declaringClass A source class.
1434+
* @return This instance.
1435+
*/
1436+
public Definition declaringClass(final String declaringClass) {
1437+
this.declaringClass = declaringClass;
1438+
return this;
1439+
}
1440+
13671441
@Override
13681442
public String toString() {
13691443
StringBuilder buffer = new StringBuilder();
@@ -1381,12 +1455,13 @@ public String toString() {
13811455
* @param method A HTTP verb.
13821456
* @param matcher A route matcher.
13831457
* @param produces List of produces types.
1458+
* @param source Route source.
13841459
* @return A new route.
13851460
*/
13861461
private Route asRoute(final String method, final RouteMatcher matcher,
1387-
final List<MediaType> produces) {
1462+
final List<MediaType> produces, final Route.Source source) {
13881463
return new RouteImpl(filter, this, method, matcher.path(), produces,
1389-
matcher.vars(), mapper);
1464+
matcher.vars(), mapper, source);
13901465
}
13911466

13921467
}
@@ -1473,6 +1548,21 @@ public String reverse(final Object... values) {
14731548
return route.reverse(values);
14741549
}
14751550

1551+
@Override
1552+
public Source source() {
1553+
return route.source();
1554+
}
1555+
1556+
@Override
1557+
public String print() {
1558+
return route.print();
1559+
}
1560+
1561+
@Override
1562+
public String print(final int indent) {
1563+
return route.print(indent);
1564+
}
1565+
14761566
@Override
14771567
public String toString() {
14781568
return route.toString();
@@ -2148,4 +2238,48 @@ default <T> T attr(final String name) {
21482238
static String normalize(final String path) {
21492239
return RoutePattern.normalize(path);
21502240
}
2241+
2242+
/**
2243+
* @return Source information.
2244+
*/
2245+
Route.Source source();
2246+
2247+
/**
2248+
* Print route information like: method, path, source, etc... Useful for debugging.
2249+
*
2250+
* @param indent Indent level
2251+
* @return Output.
2252+
*/
2253+
default String print(final int indent) {
2254+
StringBuilder buff = new StringBuilder();
2255+
String[] header = {"Method", "Path", "Source", "Name", "Pattern", "Consumes", "Produces" };
2256+
String[] values = {method(), path(), source().toString(), name(), pattern(),
2257+
consumes().toString(), produces().toString() };
2258+
2259+
BiConsumer<Function<Integer, String>, Character> format = (v, s) -> {
2260+
buff.append(Strings.padEnd("", indent, ' '))
2261+
.append("|").append(s);
2262+
for (int i = 0; i < header.length; i++) {
2263+
buff
2264+
.append(Strings.padEnd(v.apply(i), Math.max(header[i].length(), values[i].length()), s))
2265+
.append(s).append("|").append(s);
2266+
}
2267+
buff.setLength(buff.length() - 1);
2268+
};
2269+
format.accept(i -> header[i], ' ');
2270+
buff.append("\n");
2271+
format.accept(i -> "-", '-');
2272+
buff.append("\n");
2273+
format.accept(i -> values[i], ' ');
2274+
return buff.toString();
2275+
}
2276+
2277+
/**
2278+
* Print route information like: method, path, source, etc... Useful for debugging.
2279+
*
2280+
* @return Output.
2281+
*/
2282+
default String print() {
2283+
return print(0);
2284+
}
21512285
}

0 commit comments

Comments
 (0)