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

Commit a03105c

Browse files
committed
APITool/Swagger: support custom tagger (group by) fix jooby-project#1097
1 parent d7ab503 commit a03105c

File tree

14 files changed

+387
-303
lines changed

14 files changed

+387
-303
lines changed

modules/jooby-apitool/src/main/java/org/jooby/apitool/ApiTool.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@
235235
import java.util.Objects;
236236
import java.util.Optional;
237237
import java.util.function.Consumer;
238+
import java.util.function.Function;
238239
import java.util.function.Predicate;
239240
import java.util.stream.Stream;
240241

@@ -556,6 +557,8 @@ public static class Options {
556557

557558
String file;
558559

560+
Function<RouteMethod, String> tagger = DEFAULT_TAGGER;
561+
559562
private Options() {
560563
}
561564

@@ -661,8 +664,34 @@ public Options use(Path file) {
661664
this.file = Objects.requireNonNull(file, "File location required.").toString();
662665
return this;
663666
}
667+
668+
/**
669+
* Set a custom tagger (a.k.a as groupBy operator). This function creates custom Swagger tags
670+
* (has no effects on RAML).
671+
*
672+
* @param tagger Custom tagger.
673+
* @return This options.
674+
*/
675+
public Options tagger(Function<RouteMethod, String> tagger) {
676+
this.tagger = Objects.requireNonNull(tagger, "Swagger tagger required.");
677+
return this;
678+
}
664679
}
665680

681+
static final Function<RouteMethod, String> DEFAULT_TAGGER = r -> {
682+
Map<String, Object> attributes = r.attributes();
683+
if (attributes == null) {
684+
return r.pattern();
685+
}
686+
687+
return Stream
688+
.of(attributes.get("swagger.tag"), attributes.get("route.tag"))
689+
.filter(Objects::nonNull)
690+
.findFirst()
691+
.map(Objects::toString)
692+
.orElse(r.pattern());
693+
};
694+
666695
private static final TypeLiteral<List<RouteMethod>> M = new TypeLiteral<List<RouteMethod>>() {
667696
};
668697

@@ -956,7 +985,7 @@ private static void swagger(String contextPath, Router router, Options options,
956985
Map<String, Object> hash = conf.getConfig("swagger").root().unwrapped();
957986
Swagger base = Json.mapper().convertValue(hash, Swagger.class);
958987
boolean json = req.path().endsWith(".json");
959-
Swagger swagger = new SwaggerBuilder().build(base, req.require(M));
988+
Swagger swagger = new SwaggerBuilder(options.tagger).build(base, req.require(M));
960989
if (configurer != null) {
961990
configurer.accept(swagger);
962991
}

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ public List<RouteMethod> parse(String classname) throws Exception {
429429
RouteMethod route = new RouteMethod(lambda.name, lambda.pattern,
430430
routeResponse).parameters(parameters);
431431
if (lambda.tag != null) {
432-
route.attribute("route.tag", lambda.tag);
432+
route.attribute("route.tag", scriptRouteTag(lambda.tag));
433433
}
434434
javadoc(route, javadoc.pop(lambda.declaringClass, lambda.name, lambda.pattern));
435435
methods.add(route);
@@ -441,6 +441,14 @@ public List<RouteMethod> parse(String classname) throws Exception {
441441
return typeAnalizer(methods);
442442
}
443443

444+
private String scriptRouteTag(String tag) {
445+
String value = Stream.of(tag.split("/"))
446+
.filter(it -> it.length() > 0)
447+
.map(it -> Character.toUpperCase(it.charAt(0)) + it.substring(1))
448+
.collect(Collectors.joining());
449+
return value;
450+
}
451+
444452
private List<RouteMethod> typeAnalizer(List<RouteMethod> methods) {
445453
methods.forEach(this::typeAnalizer);
446454
return methods;
@@ -608,10 +616,25 @@ private void mvcRoutes(String path, final Class type, Consumer<RouteMethod> call
608616
if (path.length() > 0) {
609617
method.pattern(Route.normalize(path) + method.pattern());
610618
}
619+
// Set default tag
620+
Annotation rootPath = type.getAnnotation(org.jooby.mvc.Path.class);
621+
if (rootPath != null) {
622+
method.attribute("route.tag", mvcRouteTag(type.getSimpleName()));
623+
}
611624
callback.accept(method);
612625
});
613626
}
614627

628+
private String mvcRouteTag(String name) {
629+
/** Replace commons class suffix for Mvc classes: */
630+
return name.replace("Controller", "")
631+
.replace("Manager", "")
632+
.replace("Api", "")
633+
.replace("API", "")
634+
.replace("Mvc", "")
635+
.replace("MVC", "");
636+
}
637+
615638
@SuppressWarnings("unchecked")
616639
private List<Object> kotlinSource(final ClassLoader loader, final ClassNode owner) {
617640
List<Object> result = kotlinLambdas(loader, owner);
@@ -1028,7 +1051,8 @@ private java.lang.reflect.Type parameterType(final ClassLoader loader, final Abs
10281051
AbstractInsnNode next = n.getNext();
10291052
if (next instanceof MethodInsnNode) {
10301053
if (((MethodInsnNode) next).name.equals("toOptional")) {
1031-
return Types.newParameterizedType(Optional.class, loadType(loader, ((FieldInsnNode) n).owner));
1054+
return Types
1055+
.newParameterizedType(Optional.class, loadType(loader, ((FieldInsnNode) n).owner));
10321056
} else if (((MethodInsnNode) next).name.equals("getOrCreateKotlinClass")) {
10331057
return loadType(loader, ((FieldInsnNode) n).owner);
10341058
}

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

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -251,28 +251,13 @@
251251
import java.util.Iterator;
252252
import java.util.List;
253253
import java.util.Map;
254-
import java.util.Objects;
255254
import java.util.Optional;
256255
import java.util.function.Function;
257256
import java.util.function.Supplier;
258257
import java.util.stream.Collectors;
259258
import java.util.stream.Stream;
260259

261260
public class SwaggerBuilder {
262-
private static final Function<RouteMethod, String> TAG_PROVIDER = r -> {
263-
Map<String, Object> attributes = r.attributes();
264-
if (attributes == null) {
265-
return r.pattern();
266-
}
267-
return Stream
268-
.of(attributes.get("tag"), attributes.get("swagger.tag"), attributes.get("route.tag"))
269-
.filter(Objects::nonNull)
270-
.findFirst()
271-
.map(Objects::toString)
272-
.map(path -> Stream.of(path.split("/")).reduce((head, tail) -> tail).orElse(r.pattern()))
273-
.orElse(r.pattern());
274-
};
275-
private Function<RouteMethod, String> tagger = TAG_PROVIDER;
276261

277262
static {
278263
/** Convert Upload to Swagger FileProperty: */
@@ -311,12 +296,10 @@ public class SwaggerBuilder {
311296
Yaml.mapper().registerModule(jdk8);
312297
}
313298

314-
public SwaggerBuilder() {
315-
}
299+
private Function<RouteMethod, String> tagger;
316300

317-
public SwaggerBuilder groupBy(Function<RouteMethod, String> tag) {
318-
this.tagger = tag;
319-
return this;
301+
public SwaggerBuilder(Function<RouteMethod, String> tagger) {
302+
this.tagger = tagger;
320303
}
321304

322305
public Swagger build(Swagger base, final List<RouteMethod> routes) throws Exception {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package apps;
2+
3+
import org.jooby.Jooby;
4+
5+
public class App1097 extends Jooby {
6+
7+
{
8+
path("/v5/api", () -> {
9+
path("/song", () -> {
10+
get(() -> "...");
11+
});
12+
path("/album", () -> {
13+
get(() -> "...");
14+
});
15+
path("/artist", () -> {
16+
get(() -> "...");
17+
});
18+
});
19+
}
20+
}

0 commit comments

Comments
 (0)