From 47b45e2e42241c59011276572181c6efd016a378 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Tue, 23 Jan 2024 10:57:46 +0100 Subject: feat: request/response dto support --- src/main/java/foo/zaaarf/routecompass/Route.java | 44 ++++++++++++++- .../java/foo/zaaarf/routecompass/RouteCompass.java | 64 +++++++++++++++++++--- 2 files changed, 98 insertions(+), 10 deletions(-) diff --git a/src/main/java/foo/zaaarf/routecompass/Route.java b/src/main/java/foo/zaaarf/routecompass/Route.java index db1492a..7c7c091 100644 --- a/src/main/java/foo/zaaarf/routecompass/Route.java +++ b/src/main/java/foo/zaaarf/routecompass/Route.java @@ -34,6 +34,16 @@ public class Route { */ public final boolean deprecated; + /** + * A {@link DTO} representing the response body. + */ + public final DTO returnType; + + /** + * A {@link DTO} representing the request body. + */ + public final DTO inputType; + /** * An array of {@link Param}s, representing parameters accepted by the endpoint. */ @@ -48,8 +58,8 @@ public class Route { * @param deprecated whether the endpoint is deprecated * @param params {@link Param}s of the endpoint, may be null */ - public Route(String path, RequestMethod[] methods, MediaType consumes, MediaType produces, boolean deprecated, - Param... params) { + public Route(String path, RequestMethod[] methods, MediaType consumes, MediaType produces, + boolean deprecated, DTO returnType, DTO inputType, Param... params) { this.path = path; StringBuilder methodStringBuilder = new StringBuilder("["); @@ -70,6 +80,9 @@ public class Route { this.deprecated = deprecated; + this.returnType = returnType; + this.inputType = inputType; + if(params != null) this.params = params; else this.params = new Param[0]; //just in case } @@ -106,4 +119,31 @@ public class Route { this.defaultValue = defaultValue; } } + + /** + * Representation of a DTO type. + */ + public static class DTO { + + /** + * Fully-qualified name of the type. + */ + public final String FQN; + + /** + * An array of {@link Param} representing the type's fields. + */ + public final Route.Param[] fields; + + /** + * The one and only constructor. + * @param FQN the fully-qualified name + * @param fields the {@link Param}s representing the fields + */ + public DTO(String FQN, Route.Param ... fields) { + this.FQN = FQN; + if(fields == null) this.fields = new Route.Param[0]; + else this.fields = fields; + } + } } diff --git a/src/main/java/foo/zaaarf/routecompass/RouteCompass.java b/src/main/java/foo/zaaarf/routecompass/RouteCompass.java index 0847002..58c0d89 100644 --- a/src/main/java/foo/zaaarf/routecompass/RouteCompass.java +++ b/src/main/java/foo/zaaarf/routecompass/RouteCompass.java @@ -11,6 +11,8 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.StandardLocation; @@ -18,6 +20,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.lang.annotation.Annotation; import java.util.*; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.stream.Collectors; @@ -58,6 +61,7 @@ public class RouteCompass extends AbstractProcessor { * @return false, letting other processor process the annotations again */ @Override + @SuppressWarnings("OptionalGetWithoutIsPresent") public boolean process(Set annotations, RoundEnvironment env) { for(TypeElement annotationType : annotations) { env.getElementsAnnotatedWith(annotationType) @@ -73,7 +77,11 @@ public class RouteCompass extends AbstractProcessor { this.getConsumedType(annotationType, elem), this.getProducedType(annotationType, elem), this.isDeprecated(elem), - this.getParams(elem.getParameters()) + this.getDTO(this.processingEnv.getTypeUtils().asElement(elem.getReturnType())), + this.getDTO(elem.getParameters().stream() + .filter(e -> e.getAnnotation(RequestBody.class) != null) + .findFirst().get()), + this.getQueryParams(elem.getParameters()) )); }); } @@ -96,12 +104,24 @@ public class RouteCompass extends AbstractProcessor { if(r.produces != null) out.print("(returns: " + r.produces + ")"); out.println(); - for(Route.Param p : r.params) { - out.print("\t\t- " + p.typeFQN + " " + p.name); - if(p.defaultValue != null) - out.print(" " + "(default: " + p.defaultValue + ")"); - out.println(); - } + BiConsumer printParam = (name, params) -> { + if(name != null) out.println("\t\t" + name); + for(Route.Param p : params) { + out.print(name != null ? "\t\t\t" : "\t\t"); + out.print("- " + p.typeFQN + " " + p.name); + if(p.defaultValue != null) + out.print(" " + "(default: " + p.defaultValue + ")"); + out.println(); + } + }; + + printParam.accept(null, r.params); + + if(r.inputType != null) + printParam.accept("input: " + r.inputType.FQN, r.inputType.fields); + + if(r.returnType != null) + printParam.accept("output: " + r.returnType.FQN, r.returnType.fields); } } @@ -198,7 +218,7 @@ public class RouteCompass extends AbstractProcessor { * @param params the {@link VariableElement}s representing the parameters of a request * @return an array of {@link Route.Param} representing the parameters of the request. */ - private Route.Param[] getParams(List params) { + private Route.Param[] getQueryParams(List params) { return params.stream() .map(p -> { RequestParam ann = p.getAnnotation(RequestParam.class); @@ -220,6 +240,34 @@ public class RouteCompass extends AbstractProcessor { }).filter(Objects::nonNull).toArray(Route.Param[]::new); } + /** + * Gets a representation of a DTO type. + * @param type the {@link TypeElement} to examine + * @return a {@link Route.DTO} representing the given type + */ + private Route.DTO getDTO(Element type) { + if(!(type instanceof TypeElement)) //doubles as null check + return null; + + List fieldElements = new ArrayList<>(); + TypeElement typeElement = (TypeElement) type; + do { + fieldElements.addAll(typeElement + .getEnclosedElements() + .stream().filter(e -> e instanceof VariableElement) + .map(e -> (VariableElement) e) + .collect(Collectors.toList())); + TypeMirror superclass = typeElement.getSuperclass(); + if(superclass.getKind() == TypeKind.DECLARED) + typeElement = (TypeElement) this.processingEnv.getTypeUtils().asElement(superclass); + else typeElement = null; + } while(typeElement != null); + + return new Route.DTO(type.asType().toString(), fieldElements.stream() //TODO @JsonIgnore + .map(e -> new Route.Param(e.asType().toString(), e.getSimpleName().toString(), null)) + .toArray(Route.Param[]::new)); + } + /** * An annotation value. * @param annotationType the {@link TypeElement} with the annotation we are processing -- cgit v1.2.3-56-ga3b1