Skip to content

Commit

Permalink
Add possibility to use custom annotations to detect Spring query para…
Browse files Browse the repository at this point in the history
…meters

Adds possibility to detect Spring request parameters using custom annotations
Adds possibility to detect Spring request body using custom annotations

Fixes vojtechhabarta#747

Signed-off-by: Oleksandr Porunov <[email protected]>
  • Loading branch information
porunov committed Jun 7, 2022
1 parent 7a33aa0 commit 8e29e56
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ public class Settings {
public boolean generateSpringApplicationInterface = false;
public boolean generateSpringApplicationClient = false;
public boolean scanSpringApplication;
public List<Class<? extends Annotation>> springCustomQueryParameterAnnotations = new ArrayList<>();
public List<Class<? extends Annotation>> springCustomRequestBodyAnnotations = new ArrayList<>();
@Deprecated public RestNamespacing jaxrsNamespacing;
@Deprecated public Class<? extends Annotation> jaxrsNamespacingAnnotation = null;
@Deprecated public String jaxrsNamespacingAnnotationElement; // default is "value"
Expand Down Expand Up @@ -255,6 +257,14 @@ public void loadIncludePropertyAnnotations(ClassLoader classLoader, List<String>
this.includePropertyAnnotations = loadClasses(classLoader, includePropertyAnnotations, Annotation.class);
}

public void loadSpringCustomQueryParameterAnnotations(ClassLoader classLoader, List<String> springCustomQueryParameterAnnotations) {
this.springCustomQueryParameterAnnotations = loadClasses(classLoader, springCustomQueryParameterAnnotations, Annotation.class);
}

public void loadSpringCustomRequestBodyAnnotations(ClassLoader classLoader, List<String> springCustomRequestBodyAnnotations) {
this.springCustomRequestBodyAnnotations = loadClasses(classLoader, springCustomRequestBodyAnnotations, Annotation.class);
}

public void loadExcludePropertyAnnotations(ClassLoader classLoader, List<String> excludePropertyAnnotations) {
this.excludePropertyAnnotations = loadClasses(classLoader, excludePropertyAnnotations, Annotation.class);
}
Expand Down Expand Up @@ -292,7 +302,7 @@ public static Map<String, String> convertToMap(List<String> items, String itemNa
}
return result;
}

public void validate() {
if (classLoader == null) {
classLoader = Thread.currentThread().getContextClassLoader();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public class GenerateTask extends DefaultTask {
public List<String> excludeClassPatterns;
public List<String> includePropertyAnnotations;
public List<String> excludePropertyAnnotations;
public List<String> springCustomQueryParameterAnnotations;
public List<String> springCustomRequestBodyAnnotations;
public JsonLibrary jsonLibrary;
public Jackson2Configuration jackson2Configuration;
public GsonConfiguration gsonConfiguration;
Expand Down Expand Up @@ -204,6 +206,8 @@ private Settings createSettings(URLClassLoader classLoader) {
settings.loadExtensions(classLoader, Utils.concat(extensionClasses, extensions), extensionsWithConfiguration);
settings.loadIncludePropertyAnnotations(classLoader, includePropertyAnnotations);
settings.loadExcludePropertyAnnotations(classLoader, excludePropertyAnnotations);
settings.loadSpringCustomQueryParameterAnnotations(classLoader, springCustomQueryParameterAnnotations);
settings.loadSpringCustomRequestBodyAnnotations(classLoader, springCustomRequestBodyAnnotations);
settings.loadOptionalAnnotations(classLoader, optionalAnnotations);
settings.loadRequiredAnnotations(classLoader, requiredAnnotations);
settings.loadNullableAnnotations(classLoader, nullableAnnotations);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,22 @@ public class GenerateMojo extends AbstractMojo {
@Parameter
private boolean scanSpringApplication;

/**
* Additional annotations which are treated as optional request parameters during Spring controllers parsing when Spring's
* RequestParam or ModelAttribute is not used on a specific method's argument.
* This option may be useful when Spring controller method accepts custom resolved parameters.
*/
@Parameter
private List<String> springCustomQueryParameterAnnotations;

/**
* Additional annotations which are treated as request body during Spring controllers parsing when Spring's
* RequestBody is not used on a specific method's argument.
* This option may be useful when Spring controller method accepts custom resolved parameters.
*/
@Parameter
private List<String> springCustomRequestBodyAnnotations;

/**
* Deprecated, use {@link #restNamespacing}.
*/
Expand Down Expand Up @@ -800,7 +816,7 @@ public class GenerateMojo extends AbstractMojo {
*/
@Parameter
private String npmBuildScript;

/**
* List of additional NPM <code>dependencies</code>.<br>
* Only applicable when {@link #generateNpmPackageJson} parameter is <code>true</code> and generating implementation file (.ts).<br>
Expand All @@ -809,7 +825,7 @@ public class GenerateMojo extends AbstractMojo {
*/
@Parameter
private List<String> npmDependencies;

/**
* List of additional NPM <code>devDependencies</code>.<br>
* Only applicable when {@link #generateNpmPackageJson} parameter is <code>true</code> and generating implementation file (.ts).<br>
Expand All @@ -818,7 +834,7 @@ public class GenerateMojo extends AbstractMojo {
*/
@Parameter
private List<String> npmDevDependencies;

/**
* List of additional NPM <code>peerDependencies</code>.<br>
* Only applicable when {@link #generateNpmPackageJson} parameter is <code>true</code> and generating implementation file (.ts).<br>
Expand Down Expand Up @@ -973,6 +989,8 @@ private Settings createSettings(URLClassLoader classLoader) {
settings.loadExtensions(classLoader, extensions, extensionsWithConfiguration);
settings.loadIncludePropertyAnnotations(classLoader, includePropertyAnnotations);
settings.loadExcludePropertyAnnotations(classLoader, excludePropertyAnnotations);
settings.loadSpringCustomQueryParameterAnnotations(classLoader, springCustomQueryParameterAnnotations);
settings.loadSpringCustomRequestBodyAnnotations(classLoader, springCustomRequestBodyAnnotations);
settings.loadOptionalAnnotations(classLoader, optionalAnnotations);
settings.loadRequiredAnnotations(classLoader, requiredAnnotations);
settings.loadNullableAnnotations(classLoader, nullableAnnotations);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
Expand Down Expand Up @@ -304,6 +305,7 @@ private void parseControllerMethod(JaxrsApplicationParser.Result result, JaxrsAp
queryParams.add(new RestQueryParam.Single(new MethodParameterModel("size", Long.class), false));
queryParams.add(new RestQueryParam.Single(new MethodParameterModel("sort", String.class), false));
} else {
boolean queryParamAdded = false;
final RequestParam requestParamAnnotation = AnnotationUtils.findAnnotation(parameter, RequestParam.class);
if (requestParamAnnotation != null) {
if (parameter.getType() == MultiValueMap.class) {
Expand All @@ -315,6 +317,7 @@ private void parseControllerMethod(JaxrsApplicationParser.Result result, JaxrsAp
parameter.getName()
), parameter.getParameterizedType()), isRequired));
foundType(result, parameter.getParameterizedType(), controllerClass, method.getName());
queryParamAdded = true;
}
}

Expand All @@ -330,12 +333,26 @@ private void parseControllerMethod(JaxrsApplicationParser.Result result, JaxrsAp
propertyDescriptor.getPropertyType()
), false));
foundType(result, propertyDescriptor.getPropertyType(), controllerClass, method.getName());
queryParamAdded = true;
}
}
} catch (IntrospectionException e) {
TypeScriptGenerator.getLogger().warning(String.format("Cannot introspect '%s' class: " + e.getMessage(), parameter.getAnnotatedType()));
}
}

if(!queryParamAdded){
for(Class<? extends Annotation> customRequestParamAnnotationClass : settings.springCustomQueryParameterAnnotations){
Annotation customRequestParamAnnotation = AnnotationUtils.findAnnotation(parameter, customRequestParamAnnotationClass);
if(customRequestParamAnnotation != null){
queryParams.add(new RestQueryParam.Single(new MethodParameterModel(
parameter.getName(),
parameter.getParameterizedType()), false));
foundType(result, parameter.getParameterizedType(), controllerClass, method.getName());
break;
}
}
}
}
}

Expand Down Expand Up @@ -373,8 +390,16 @@ private MethodParameterModel getEntityParameter(Class<?> controller, Method meth
final List<Type> parameterTypes = settings.getTypeParser().getMethodParameterTypes(method);
final List<Pair<Parameter, Type>> parameters = Utils.zip(Arrays.asList(method.getParameters()), parameterTypes);
for (Pair<Parameter, Type> pair : parameters) {
final RequestBody requestBodyAnnotation = AnnotationUtils.findAnnotation(pair.getValue1(), RequestBody.class);
if (requestBodyAnnotation != null) {
boolean hasRequestBody = AnnotationUtils.findAnnotation(pair.getValue1(), RequestBody.class) != null;
if(!hasRequestBody){
for(Class<? extends Annotation> customRequestBodyAnnotationClass : settings.springCustomRequestBodyAnnotations){
if(AnnotationUtils.findAnnotation(pair.getValue1(), customRequestBodyAnnotationClass) != null){
hasRequestBody = true;
break;
}
}
}
if (hasRequestBody) {
final Type resolvedType = GenericsResolver.resolveType(controller, pair.getValue2(), method.getDeclaringClass());
return new MethodParameterModel(pair.getValue1().getName(), resolvedType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
import cz.habarta.typescript.generator.util.Utils;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
Expand All @@ -35,7 +37,6 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;


public class SpringTest {

@Test
Expand Down Expand Up @@ -169,6 +170,27 @@ public void testInheritance() {
Assertions.assertFalse(output.contains("uriEncoding`test/b`"));
}

@Test
public void testCustomQueryParameters() {
final Settings settings = TestUtils.settings();
settings.outputFileType = TypeScriptFileType.implementationFile;
settings.generateSpringApplicationClient = true;
settings.springCustomQueryParameterAnnotations = Arrays.asList(CustomRequestParam.class);
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(Controller8.class));
Assertions.assertTrue(output.contains("echo(queryParams?: { message?: string; }): RestResponse<string>"));
}

@Test
public void testCustomRequestBody() {
final Settings settings = TestUtils.settings();
settings.outputFileType = TypeScriptFileType.implementationFile;
settings.generateSpringApplicationClient = true;
settings.springCustomRequestBodyAnnotations = Arrays.asList(CustomRequestBody.class);
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(Controller9.class));
Assertions.assertTrue(output.contains("setEntity(data: Data1): RestResponse<void>"));
Assertions.assertTrue(output.contains("interface Data1"));
}

@RestController
@RequestMapping("/owners/{ownerId}")
public static class Controller1 {
Expand Down Expand Up @@ -203,6 +225,23 @@ public String echo(
}
}

@RestController
public static class Controller8 {
@RequestMapping("/echo3")
public String echo(
@CustomRequestParam String message
) {
return message;
}
}

@RestController
public static class Controller9 {
@RequestMapping(path = "/data2", method = RequestMethod.PUT)
public void setEntity(@CustomRequestBody Data1 data) {
}
}

@Test
public void testQueryParametersWithModel() {
final Settings settings = TestUtils.settings();
Expand Down Expand Up @@ -488,4 +527,16 @@ public String shouldBeExcluded() {
}
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface CustomRequestParam {

}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface CustomRequestBody {

}

}

0 comments on commit 8e29e56

Please sign in to comment.