feat: request/response dto support

This commit is contained in:
zaaarf 2024-01-23 10:57:46 +01:00
parent 729bf6f2a7
commit 47b45e2e42
No known key found for this signature in database
GPG key ID: 102E445F4C3F829B
2 changed files with 98 additions and 10 deletions

View file

@ -34,6 +34,16 @@ public class Route {
*/ */
public final boolean deprecated; 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. * 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 deprecated whether the endpoint is deprecated
* @param params {@link Param}s of the endpoint, may be null * @param params {@link Param}s of the endpoint, may be null
*/ */
public Route(String path, RequestMethod[] methods, MediaType consumes, MediaType produces, boolean deprecated, public Route(String path, RequestMethod[] methods, MediaType consumes, MediaType produces,
Param... params) { boolean deprecated, DTO returnType, DTO inputType, Param... params) {
this.path = path; this.path = path;
StringBuilder methodStringBuilder = new StringBuilder("["); StringBuilder methodStringBuilder = new StringBuilder("[");
@ -70,6 +80,9 @@ public class Route {
this.deprecated = deprecated; this.deprecated = deprecated;
this.returnType = returnType;
this.inputType = inputType;
if(params != null) this.params = params; if(params != null) this.params = params;
else this.params = new Param[0]; //just in case else this.params = new Param[0]; //just in case
} }
@ -106,4 +119,31 @@ public class Route {
this.defaultValue = defaultValue; 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;
}
}
} }

View file

@ -11,6 +11,8 @@ import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement; 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.Diagnostic;
import javax.tools.FileObject; import javax.tools.FileObject;
import javax.tools.StandardLocation; import javax.tools.StandardLocation;
@ -18,6 +20,7 @@ import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.*; import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -58,6 +61,7 @@ public class RouteCompass extends AbstractProcessor {
* @return false, letting other processor process the annotations again * @return false, letting other processor process the annotations again
*/ */
@Override @Override
@SuppressWarnings("OptionalGetWithoutIsPresent")
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
for(TypeElement annotationType : annotations) { for(TypeElement annotationType : annotations) {
env.getElementsAnnotatedWith(annotationType) env.getElementsAnnotatedWith(annotationType)
@ -73,7 +77,11 @@ public class RouteCompass extends AbstractProcessor {
this.getConsumedType(annotationType, elem), this.getConsumedType(annotationType, elem),
this.getProducedType(annotationType, elem), this.getProducedType(annotationType, elem),
this.isDeprecated(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 + ")"); if(r.produces != null) out.print("(returns: " + r.produces + ")");
out.println(); out.println();
for(Route.Param p : r.params) { BiConsumer<String, Route.Param[]> printParam = (name, params) -> {
out.print("\t\t- " + p.typeFQN + " " + p.name); if(name != null) out.println("\t\t" + name);
if(p.defaultValue != null) for(Route.Param p : params) {
out.print(" " + "(default: " + p.defaultValue + ")"); out.print(name != null ? "\t\t\t" : "\t\t");
out.println(); 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 * @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. * @return an array of {@link Route.Param} representing the parameters of the request.
*/ */
private Route.Param[] getParams(List<? extends VariableElement> params) { private Route.Param[] getQueryParams(List<? extends VariableElement> params) {
return params.stream() return params.stream()
.map(p -> { .map(p -> {
RequestParam ann = p.getAnnotation(RequestParam.class); RequestParam ann = p.getAnnotation(RequestParam.class);
@ -220,6 +240,34 @@ public class RouteCompass extends AbstractProcessor {
}).filter(Objects::nonNull).toArray(Route.Param[]::new); }).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<VariableElement> 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. * An annotation value.
* @param annotationType the {@link TypeElement} with the annotation we are processing * @param annotationType the {@link TypeElement} with the annotation we are processing