From 6e2662a9ba675ae923cbde3a5d980ab10708a023 Mon Sep 17 00:00:00 2001 From: Oleksandr Porunov Date: Sun, 6 Feb 2022 14:36:29 +0200 Subject: [PATCH] Add possibility to pass request headers to HttpClient Fixes #790 Signed-off-by: Oleksandr Porunov --- .../typescript/generator/Settings.java | 5 + .../generator/compiler/ModelCompiler.java | 112 ++++++++++-------- .../parser/JaxrsApplicationParser.java | 60 +++++++++- .../generator/parser/RestMethodModel.java | 16 ++- .../{RestQueryParam.java => RestParam.java} | 20 ++-- .../AxiosClientExtension-shared.template.ts | 3 +- .../generator/JaxrsApplicationTest.java | 29 ++++- .../ext/AxiosClientExtensionTest.java | 2 +- .../generator/gradle/GenerateTask.java | 2 + .../generator/maven/GenerateMojo.java | 7 ++ .../spring/SpringApplicationParser.java | 39 ++++-- .../generator/spring/SpringTest.java | 39 ++++++ 12 files changed, 251 insertions(+), 83 deletions(-) rename typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/{RestQueryParam.java => RestParam.java} (51%) diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/Settings.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/Settings.java index 6d42a3f5c..1738eb94a 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/Settings.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/Settings.java @@ -108,6 +108,7 @@ public class Settings { public String restResponseType = null; public String restOptionsType = null; public boolean restOptionsTypeIsGeneric; + public boolean restHeaderArgumentsParsed; private List restApplicationParserFactories; public TypeProcessor customTypeProcessor = null; public boolean sortDeclarations = false; @@ -759,6 +760,10 @@ public void setRestOptionsType(String restOptionsType) { } } + public void setRestHeaderArgumentsParsed(boolean restHeaderArgumentsParsed) { + this.restHeaderArgumentsParsed = restHeaderArgumentsParsed; + } + public List getRestApplicationParserFactories() { if (restApplicationParserFactories == null) { final List factories = new ArrayList<>(); diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java index 5c91e7482..10a49bbcd 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java @@ -52,7 +52,7 @@ import cz.habarta.typescript.generator.parser.PropertyModel; import cz.habarta.typescript.generator.parser.RestApplicationModel; import cz.habarta.typescript.generator.parser.RestMethodModel; -import cz.habarta.typescript.generator.parser.RestQueryParam; +import cz.habarta.typescript.generator.parser.RestParam; import cz.habarta.typescript.generator.type.JTypeWithNullability; import cz.habarta.typescript.generator.util.GenericsResolver; import cz.habarta.typescript.generator.util.Pair; @@ -622,6 +622,7 @@ private void createRestClients(TsModel tsModel, SymbolTable symbolTable, List queryParams = method.getQueryParams(); - final TsParameterModel queryParameter; - if (queryParams != null && !queryParams.isEmpty()) { - final List types = new ArrayList<>(); - if (queryParams.stream().anyMatch(param -> param instanceof RestQueryParam.Map)) { - types.add(new TsType.IndexedArrayType(TsType.String, TsType.Any)); - } else { - final List currentSingles = new ArrayList<>(); - final Runnable flushSingles = () -> { - if (!currentSingles.isEmpty()) { - types.add(new TsType.ObjectType(currentSingles)); - currentSingles.clear(); - } - }; - for (RestQueryParam restQueryParam : queryParams) { - if (restQueryParam instanceof RestQueryParam.Single) { - final MethodParameterModel queryParam = ((RestQueryParam.Single) restQueryParam).getQueryParam(); - final TsType type = typeFromJava(symbolTable, queryParam.getType(), method.getName(), method.getOriginClass()); - currentSingles.add(new TsProperty(queryParam.getName(), restQueryParam.required ? type : new TsType.OptionalType(type))); - } - if (restQueryParam instanceof RestQueryParam.Bean) { - final BeanModel queryBean = ((RestQueryParam.Bean) restQueryParam).getBean(); - flushSingles.run(); - final Symbol queryParamsSymbol = symbolTable.getSymbol(queryBean.getOrigin(), "QueryParams"); - if (tsModel.getBean(queryParamsSymbol) == null) { - tsModel.getBeans().add(new TsBeanModel( - queryBean.getOrigin(), - TsBeanCategory.Data, - /*isClass*/false, - queryParamsSymbol, - /*typeParameters*/null, - /*parent*/null, - /*extendsList*/null, - /*implementsList*/null, - processProperties(symbolTable, null, queryBean), - /*constructor*/null, - /*methods*/null, - /*comments*/null - )); - } - types.add(new TsType.ReferenceType(queryParamsSymbol)); - } - } - flushSingles.run(); - } - boolean allQueryParamsOptional = queryParams.stream().noneMatch(queryParam -> queryParam.required); - TsType.IntersectionType queryParamType = new TsType.IntersectionType(types); - queryParameter = new TsParameterModel("queryParams", allQueryParamsOptional ? new TsType.OptionalType(queryParamType) : queryParamType); + final TsParameterModel queryParameter = convertRestParams(method.getQueryParams(), symbolTable, method, tsModel, "queryParams", "QueryParams"); + if(queryParameter != null){ parameters.add(queryParameter); - } else { - queryParameter = null; + } + // body params + final TsParameterModel headersParameter = convertRestParams(method.getHeaders(), symbolTable, method, tsModel, "headers", "Headers"); + if(headersParameter != null){ + parameters.add(headersParameter); } if (optionsType != null) { final TsParameterModel optionsParameter = new TsParameterModel("options", new TsType.OptionalType(optionsType)); @@ -808,6 +766,7 @@ private TsMethodModel processRestMethod(TsModel tsModel, SymbolTable symbolTable new TsPropertyDefinition("url", processPathTemplate(pathTemplate)), queryParameter != null ? new TsPropertyDefinition("queryParams", new TsIdentifierReference("queryParams")) : null, method.getEntityParam() != null ? new TsPropertyDefinition("data", new TsIdentifierReference(method.getEntityParam().getName())) : null, + headersParameter != null ? new TsPropertyDefinition("headers", new TsIdentifierReference("headers")) : null, optionsType != null ? new TsPropertyDefinition("options", new TsIdentifierReference("options")) : null ) ) @@ -820,6 +779,57 @@ private TsMethodModel processRestMethod(TsModel tsModel, SymbolTable symbolTable return tsMethodModel; } + private TsParameterModel convertRestParams(List restParams, SymbolTable symbolTable, RestMethodModel method, TsModel tsModel, String parameterName, String beanSuffix){ + if (restParams == null || restParams.isEmpty()){ + return null; + } + final List types = new ArrayList<>(); + if (restParams.stream().anyMatch(param -> param instanceof RestParam.Map)) { + types.add(new TsType.IndexedArrayType(TsType.String, TsType.Any)); + } else { + final List currentSingles = new ArrayList<>(); + final Runnable flushSingles = () -> { + if (!currentSingles.isEmpty()) { + types.add(new TsType.ObjectType(currentSingles)); + currentSingles.clear(); + } + }; + for (RestParam restParam : restParams) { + if (restParam instanceof RestParam.Single) { + final MethodParameterModel restParamMethodParameterModel = ((RestParam.Single) restParam).getRestParam(); + final TsType type = typeFromJava(symbolTable, restParamMethodParameterModel.getType(), method.getName(), method.getOriginClass()); + currentSingles.add(new TsProperty(restParamMethodParameterModel.getName(), restParam.required ? type : new TsType.OptionalType(type))); + } + if (restParam instanceof RestParam.Bean) { + final BeanModel paramBean = ((RestParam.Bean) restParam).getBean(); + flushSingles.run(); + final Symbol paramsSymbol = symbolTable.getSymbol(paramBean.getOrigin(), beanSuffix); + if (tsModel.getBean(paramsSymbol) == null) { + tsModel.getBeans().add(new TsBeanModel( + paramBean.getOrigin(), + TsBeanCategory.Data, + /*isClass*/false, + paramsSymbol, + /*typeParameters*/null, + /*parent*/null, + /*extendsList*/null, + /*implementsList*/null, + processProperties(symbolTable, null, paramBean), + /*constructor*/null, + /*methods*/null, + /*comments*/null + )); + } + types.add(new TsType.ReferenceType(paramsSymbol)); + } + } + flushSingles.run(); + } + boolean allParamsOptional = restParams.stream().noneMatch(param -> param.required); + TsType.IntersectionType paramType = new TsType.IntersectionType(types); + return new TsParameterModel(parameterName, allParamsOptional ? new TsType.OptionalType(paramType) : paramType); + } + private TsParameterModel processParameter(SymbolTable symbolTable, MethodModel method, MethodParameterModel parameter) { final String parameterName = parameter.getName(); final TsType parameterType = typeFromJava(symbolTable, parameter.getType(), method.getName(), method.getOriginClass()); diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/JaxrsApplicationParser.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/JaxrsApplicationParser.java index c0c8c6ec3..f5f130e66 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/JaxrsApplicationParser.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/JaxrsApplicationParser.java @@ -174,11 +174,11 @@ private void parseResourceMethod(Result result, ResourceContext context, Class queryParams = new ArrayList<>(); + final List queryParams = new ArrayList<>(); for (Parameter param : method.getParameters()) { final QueryParam queryParamAnnotation = getRsAnnotation(param, QueryParam.class); if (queryParamAnnotation != null) { - queryParams.add(new RestQueryParam.Single(new MethodParameterModel(queryParamAnnotation.value(), param.getParameterizedType()), false)); + queryParams.add(new RestParam.Single(new MethodParameterModel(queryParamAnnotation.value(), param.getParameterizedType()), false)); foundType(result, param.getParameterizedType(), resourceClass, method.getName()); } final BeanParam beanParamAnnotation = getRsAnnotation(param, BeanParam.class); @@ -186,13 +186,35 @@ private void parseResourceMethod(Result result, ResourceContext context, Class beanParamClass = param.getType(); final BeanModel paramBean = getQueryParameters(beanParamClass); if (paramBean != null) { - queryParams.add(new RestQueryParam.Bean(paramBean)); + queryParams.add(new RestParam.Bean(paramBean)); for (PropertyModel property : paramBean.getProperties()) { foundType(result, property.getType(), beanParamClass, property.getName()); } } } } + // header parameters + final List headers = new ArrayList<>(); + if(settings.restHeaderArgumentsParsed){ + for (Parameter param : method.getParameters()) { + final HeaderParam headerParamAnnotation = getRsAnnotation(param, HeaderParam.class); + if (headerParamAnnotation != null) { + headers.add(new RestParam.Single(new MethodParameterModel(headerParamAnnotation.value(), param.getParameterizedType()), false)); + foundType(result, param.getParameterizedType(), resourceClass, method.getName()); + } + final BeanParam beanParamAnnotation = getRsAnnotation(param, BeanParam.class); + if (beanParamAnnotation != null) { + final Class beanParamClass = param.getType(); + final BeanModel paramBean = getHeaderParameters(beanParamClass); + if (paramBean != null) { + headers.add(new RestParam.Bean(paramBean)); + for (PropertyModel property : paramBean.getProperties()) { + foundType(result, property.getType(), beanParamClass, property.getName()); + } + } + } + } + } // JAX-RS specification - 3.3.2.1 Entity Parameters final List parameterTypes = settings.getTypeParser().getMethodParameterTypes(method); final List> parameters = Utils.zip(Arrays.asList(method.getParameters()), parameterTypes); @@ -234,7 +256,7 @@ private void parseResourceMethod(Result result, ResourceContext context, Class comments = Swagger.getOperationComments(swaggerOperation); // create method model.getMethods().add(new RestMethodModel(resourceClass, method.getName(), resolvedModelReturnType, method, - context.rootResource, httpMethod.value(), context.path, pathParams, queryParams, entityParameter, comments)); + context.rootResource, httpMethod.value(), context.path, pathParams, queryParams, entityParameter, comments, headers)); } // JAX-RS specification - 3.4.1 Sub Resources if (pathAnnotation != null && httpMethod == null) { @@ -282,6 +304,36 @@ private static BeanModel getQueryParameters(Class paramBean) { } } + private static BeanModel getHeaderParameters(Class paramBean) { + final List properties = new ArrayList<>(); + final List fields = Utils.getAllFields(paramBean); + for (Field field : fields) { + final HeaderParam annotation = getRsAnnotation(field, HeaderParam.class); + if (annotation != null) { + properties.add(new PropertyModel(annotation.value(), field.getGenericType(), /*optional*/true, null, field, null, null, null)); + } + } + try { + final BeanInfo beanInfo = Introspector.getBeanInfo(paramBean); + for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) { + final Method writeMethod = propertyDescriptor.getWriteMethod(); + if (writeMethod != null) { + final HeaderParam annotation = getRsAnnotation(writeMethod, HeaderParam.class); + if (annotation != null) { + properties.add(new PropertyModel(annotation.value(), propertyDescriptor.getPropertyType(), /*optional*/true, null, writeMethod, null, null, null)); + } + } + } + } catch (IntrospectionException e) { + TypeScriptGenerator.getLogger().warning(String.format("Cannot introspect '%s' class: " + e.getMessage(), paramBean)); + } + if (properties.isEmpty()) { + return null; + } else { + return new BeanModel(paramBean, null, null, null, null, null, properties, null); + } + } + private MethodParameterModel getEntityParameter(Class resourceClass, Method method, List> parameters) { for (Pair pair : parameters) { if (!Utils.hasAnyAnnotation(annotationClass -> pair.getValue1().getAnnotation(annotationClass), Arrays.asList( diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/RestMethodModel.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/RestMethodModel.java index b5b7716b7..f2c1a59bc 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/RestMethodModel.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/RestMethodModel.java @@ -12,12 +12,13 @@ public class RestMethodModel extends MethodModel { private final String httpMethod; private final String path; private final List pathParams; - private final List queryParams; + private final List queryParams; private final MethodParameterModel entityParam; + private final List headers; public RestMethodModel(Class originClass, String name, Type returnType, Method originalMethod, - Class rootResource, String httpMethod, String path, List pathParams, List queryParams, MethodParameterModel entityParam, - List comments) { + Class rootResource, String httpMethod, String path, List pathParams, List queryParams, MethodParameterModel entityParam, + List comments, List headers) { super(originClass, name, null, returnType, originalMethod, comments); this.rootResource = rootResource; this.httpMethod = httpMethod; @@ -25,6 +26,7 @@ public RestMethodModel(Class originClass, String name, Type returnType, Metho this.pathParams = pathParams; this.queryParams = queryParams; this.entityParam = entityParam; + this.headers = headers; } public Class getRootResource() { @@ -43,7 +45,7 @@ public List getPathParams() { return pathParams; } - public List getQueryParams() { + public List getQueryParams() { return queryParams; } @@ -51,9 +53,13 @@ public MethodParameterModel getEntityParam() { return entityParam; } + public List getHeaders() { + return headers; + } + @Override public RestMethodModel withComments(List comments) { - return new RestMethodModel(originClass, name, returnType, originalMethod, rootResource, httpMethod, path, pathParams, queryParams, entityParam, comments); + return new RestMethodModel(originClass, name, returnType, originalMethod, rootResource, httpMethod, path, pathParams, queryParams, entityParam, comments, headers); } } diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/RestQueryParam.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/RestParam.java similarity index 51% rename from typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/RestQueryParam.java rename to typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/RestParam.java index 24146dcb9..7fae40223 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/RestQueryParam.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/RestParam.java @@ -2,28 +2,28 @@ package cz.habarta.typescript.generator.parser; -public abstract class RestQueryParam { +public abstract class RestParam { public boolean required; - RestQueryParam(boolean required) { + RestParam(boolean required) { this.required = required; } - public static class Single extends RestQueryParam { - private final MethodParameterModel queryParam; + public static class Single extends RestParam { + private final MethodParameterModel restParam; - public Single(MethodParameterModel queryParam, boolean required) { + public Single(MethodParameterModel restParam, boolean required) { super(required); - this.queryParam = queryParam; + this.restParam = restParam; } - public MethodParameterModel getQueryParam() { - return queryParam; + public MethodParameterModel getRestParam() { + return restParam; } } - public static class Bean extends RestQueryParam { + public static class Bean extends RestParam { private final BeanModel bean; // Only used in JAX-Rs, so optional @@ -37,7 +37,7 @@ public BeanModel getBean() { } } - public static class Map extends RestQueryParam { + public static class Map extends RestParam { public Map(boolean required) { super(required); } diff --git a/typescript-generator-core/src/main/resources/cz/habarta/typescript/generator/ext/AxiosClientExtension-shared.template.ts b/typescript-generator-core/src/main/resources/cz/habarta/typescript/generator/ext/AxiosClientExtension-shared.template.ts index 4b7cad192..77a91e248 100644 --- a/typescript-generator-core/src/main/resources/cz/habarta/typescript/generator/ext/AxiosClientExtension-shared.template.ts +++ b/typescript-generator-core/src/main/resources/cz/habarta/typescript/generator/ext/AxiosClientExtension-shared.template.ts @@ -13,7 +13,7 @@ class AxiosHttpClient implements HttpClient { constructor(private axios: Axios.AxiosInstance) { } - request(requestConfig: { method: string; url: string; queryParams?: any; data?: any; copyFn?: (data: R) => R; options?: Axios.AxiosRequestConfig; }): RestResponse { + request(requestConfig: { method: string; url: string; queryParams?: any; data?: any; copyFn?: (data: R) => R; headers?: any; options?: Axios.AxiosRequestConfig; }): RestResponse { function assign(target: any, source?: any) { if (source != undefined) { for (const key in source) { @@ -30,6 +30,7 @@ class AxiosHttpClient implements HttpClient { config.url = requestConfig.url; config.params = requestConfig.queryParams; config.data = requestConfig.data; + assign(config, requestConfig.headers); assign(config, requestConfig.options); const copyFn = requestConfig.copyFn; diff --git a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/JaxrsApplicationTest.java b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/JaxrsApplicationTest.java index 9709b1580..f676467aa 100644 --- a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/JaxrsApplicationTest.java +++ b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/JaxrsApplicationTest.java @@ -379,7 +379,7 @@ public void basicClientTest() { final String errorMessage = "Unexpected output: " + output; // HttpClient Assertions.assertTrue(output.contains("interface HttpClient"), errorMessage); - Assertions.assertTrue(output.contains("request(requestConfig: { method: string; url: string; queryParams?: any; data?: any; copyFn?: (data: R) => R; }): RestResponse;"), errorMessage); + Assertions.assertTrue(output.contains("request(requestConfig: { method: string; url: string; queryParams?: any; data?: any; copyFn?: (data: R) => R; headers?: any; }): RestResponse;"), errorMessage); // application client Assertions.assertTrue(output.contains("class OrganizationApplicationClient"), errorMessage); Assertions.assertTrue(output.contains("getPerson(personId: number): RestResponse"), errorMessage); @@ -401,7 +401,7 @@ public void clientCustomizationTest() { final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(OrganizationApplication.class)); final String errorMessage = "Unexpected output: " + output; // HttpClient - Assertions.assertTrue(output.contains("request(requestConfig: { method: string; url: string; queryParams?: any; data?: any; copyFn?: (data: R) => R; options?: AxiosRequestConfig; }): RestResponse;"), errorMessage); + Assertions.assertTrue(output.contains("request(requestConfig: { method: string; url: string; queryParams?: any; data?: any; copyFn?: (data: R) => R; headers?: any; options?: AxiosRequestConfig; }): RestResponse;"), errorMessage); // application client Assertions.assertTrue(output.contains("class OrganizationApplicationClient"), errorMessage); Assertions.assertTrue(output.contains("getPerson(personId: number, options?: AxiosRequestConfig): RestResponse"), errorMessage); @@ -722,6 +722,31 @@ public static interface AccountResource extends AbstractCrudResource")); + } + + @Path("bean-param") + @Produces(MediaType.APPLICATION_JSON) + public static class HeaderParamResource { + + @POST + public List sendHeadersWithQueryParam( + @HeaderParam(value = "header1") String params1, + @HeaderParam(value = "header2") Long params2, + @QueryParam("message") String message + ) { + return Collections.emptyList(); + } + } + public static void main(String[] args) { final ResourceConfig config = new ResourceConfig(BeanParamResource.class, JacksonFeature.class); JdkHttpServerFactory.createHttpServer(URI.create("http://localhost:9998/"), config); diff --git a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/ext/AxiosClientExtensionTest.java b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/ext/AxiosClientExtensionTest.java index 3b515a279..6fe3245fc 100644 --- a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/ext/AxiosClientExtensionTest.java +++ b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/ext/AxiosClientExtensionTest.java @@ -36,7 +36,7 @@ public void test() { Assertions.assertTrue(output.contains("type RestResponse = Promise>"), errorMessage); Assertions.assertTrue(output.contains("class AxiosHttpClient implements HttpClient"), errorMessage); - Assertions.assertTrue(output.contains("request(requestConfig: { method: string; url: string; queryParams?: any; data?: any; copyFn?: (data: R) => R; options?: Axios.AxiosRequestConfig; }): RestResponse"), errorMessage); + Assertions.assertTrue(output.contains("request(requestConfig: { method: string; url: string; queryParams?: any; data?: any; copyFn?: (data: R) => R; headers?: any; options?: Axios.AxiosRequestConfig; }): RestResponse"), errorMessage); Assertions.assertTrue(output.contains("class AxiosOrganizationsResourceClient extends OrganizationsResourceClient"), errorMessage); Assertions.assertTrue(output.contains("class AxiosPersonResourceClient extends PersonResourceClient"), errorMessage); Assertions.assertTrue(output.contains("constructor(baseURL: string, axiosInstance: Axios.AxiosInstance = axios.create())"), errorMessage); diff --git a/typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/GenerateTask.java b/typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/GenerateTask.java index a8fb841a1..ff97827bd 100644 --- a/typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/GenerateTask.java +++ b/typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/GenerateTask.java @@ -101,6 +101,7 @@ public class GenerateTask extends DefaultTask { public String restNamespacingAnnotation; public String restResponseType; public String restOptionsType; + public boolean restHeaderArgumentsParsed; public String customTypeProcessor; public boolean sortDeclarations; public boolean sortTypeDeclarations; @@ -190,6 +191,7 @@ private Settings createSettings(URLClassLoader classLoader) { settings.setRestNamespacingAnnotation(classLoader, restNamespacingAnnotation); settings.restResponseType = restResponseType; settings.setRestOptionsType(restOptionsType); + settings.setRestHeaderArgumentsParsed(restHeaderArgumentsParsed); settings.loadCustomTypeProcessor(classLoader, customTypeProcessor); settings.sortDeclarations = sortDeclarations; settings.sortTypeDeclarations = sortTypeDeclarations; diff --git a/typescript-generator-maven-plugin/src/main/java/cz/habarta/typescript/generator/maven/GenerateMojo.java b/typescript-generator-maven-plugin/src/main/java/cz/habarta/typescript/generator/maven/GenerateMojo.java index 8c6ad8f49..9da478723 100644 --- a/typescript-generator-maven-plugin/src/main/java/cz/habarta/typescript/generator/maven/GenerateMojo.java +++ b/typescript-generator-maven-plugin/src/main/java/cz/habarta/typescript/generator/maven/GenerateMojo.java @@ -616,6 +616,12 @@ public class GenerateMojo extends AbstractMojo { @Parameter private String restOptionsType; + /** + * If true REST method parameters marked as headers will be parsed. Otherwise, they will be ignored. + */ + @Parameter + private boolean restHeaderArgumentsParsed; + /** * Specifies custom class implementing {@link cz.habarta.typescript.generator.TypeProcessor}. * This allows to customize how Java types are mapped to TypeScript. @@ -948,6 +954,7 @@ private Settings createSettings(URLClassLoader classLoader) { settings.setRestNamespacingAnnotation(classLoader, restNamespacingAnnotation); settings.restResponseType = restResponseType; settings.setRestOptionsType(restOptionsType); + settings.setRestHeaderArgumentsParsed(restHeaderArgumentsParsed); settings.loadCustomTypeProcessor(classLoader, customTypeProcessor); settings.sortDeclarations = sortDeclarations; settings.sortTypeDeclarations = sortTypeDeclarations; diff --git a/typescript-generator-spring/src/main/java/cz/habarta/typescript/generator/spring/SpringApplicationParser.java b/typescript-generator-spring/src/main/java/cz/habarta/typescript/generator/spring/SpringApplicationParser.java index 99b36662f..ec8afbb84 100644 --- a/typescript-generator-spring/src/main/java/cz/habarta/typescript/generator/spring/SpringApplicationParser.java +++ b/typescript-generator-spring/src/main/java/cz/habarta/typescript/generator/spring/SpringApplicationParser.java @@ -12,7 +12,7 @@ import cz.habarta.typescript.generator.parser.RestApplicationParser; import cz.habarta.typescript.generator.parser.RestApplicationType; import cz.habarta.typescript.generator.parser.RestMethodModel; -import cz.habarta.typescript.generator.parser.RestQueryParam; +import cz.habarta.typescript.generator.parser.RestParam; import cz.habarta.typescript.generator.parser.SourceType; import cz.habarta.typescript.generator.parser.Swagger; import cz.habarta.typescript.generator.parser.SwaggerOperation; @@ -51,6 +51,7 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @@ -297,20 +298,20 @@ private void parseControllerMethod(JaxrsApplicationParser.Result result, JaxrsAp .collect(Collectors.toList()); // query parameters - final List queryParams = new ArrayList<>(); + final List queryParams = new ArrayList<>(); for (Parameter parameter : method.getParameters()) { if (parameter.getType() == Pageable.class) { - queryParams.add(new RestQueryParam.Single(new MethodParameterModel("page", Long.class), false)); - queryParams.add(new RestQueryParam.Single(new MethodParameterModel("size", Long.class), false)); - queryParams.add(new RestQueryParam.Single(new MethodParameterModel("sort", String.class), false)); + queryParams.add(new RestParam.Single(new MethodParameterModel("page", Long.class), false)); + queryParams.add(new RestParam.Single(new MethodParameterModel("size", Long.class), false)); + queryParams.add(new RestParam.Single(new MethodParameterModel("sort", String.class), false)); } else { final RequestParam requestParamAnnotation = AnnotationUtils.findAnnotation(parameter, RequestParam.class); if (requestParamAnnotation != null) { if (parameter.getType() == MultiValueMap.class) { - queryParams.add(new RestQueryParam.Map(false)); + queryParams.add(new RestParam.Map(false)); } else { final boolean isRequired = requestParamAnnotation.required() && requestParamAnnotation.defaultValue().equals(ValueConstants.DEFAULT_NONE); - queryParams.add(new RestQueryParam.Single(new MethodParameterModel(firstOf( + queryParams.add(new RestParam.Single(new MethodParameterModel(firstOf( requestParamAnnotation.value(), parameter.getName() ), parameter.getParameterizedType()), isRequired)); @@ -325,7 +326,7 @@ private void parseControllerMethod(JaxrsApplicationParser.Result result, JaxrsAp for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) { final Method writeMethod = propertyDescriptor.getWriteMethod(); if (writeMethod != null) { - queryParams.add(new RestQueryParam.Single(new MethodParameterModel( + queryParams.add(new RestParam.Single(new MethodParameterModel( propertyDescriptor.getName(), propertyDescriptor.getPropertyType() ), false)); @@ -345,11 +346,31 @@ private void parseControllerMethod(JaxrsApplicationParser.Result result, JaxrsAp foundType(result, entityParameter.getType(), controllerClass, method.getName()); } + // header parameters + final List headers = new ArrayList<>(); + if(settings.restHeaderArgumentsParsed){ + for (Parameter parameter : method.getParameters()) { + final RequestHeader requestHeaderAnnotation = AnnotationUtils.findAnnotation(parameter, RequestHeader.class); + if (requestHeaderAnnotation != null) { + if (parameter.getType() == MultiValueMap.class) { + headers.add(new RestParam.Map(false)); + } else { + final boolean isRequired = requestHeaderAnnotation.required() && requestHeaderAnnotation.defaultValue().equals(ValueConstants.DEFAULT_NONE); + headers.add(new RestParam.Single(new MethodParameterModel(firstOf( + requestHeaderAnnotation.value(), + parameter.getName() + ), parameter.getParameterizedType()), isRequired)); + foundType(result, parameter.getParameterizedType(), controllerClass, method.getName()); + } + } + } + } + final Type modelReturnType = parseReturnType(controllerClass, method); foundType(result, modelReturnType, controllerClass, method.getName()); model.getMethods().add(new RestMethodModel(controllerClass, method.getName(), modelReturnType, method, - controllerClass, httpMethod.name(), context.path, pathParams, queryParams, entityParameter, null)); + controllerClass, httpMethod.name(), context.path, pathParams, queryParams, entityParameter, null, headers)); } } diff --git a/typescript-generator-spring/src/test/java/cz/habarta/typescript/generator/spring/SpringTest.java b/typescript-generator-spring/src/test/java/cz/habarta/typescript/generator/spring/SpringTest.java index a2a932bef..076ca341c 100644 --- a/typescript-generator-spring/src/test/java/cz/habarta/typescript/generator/spring/SpringTest.java +++ b/typescript-generator-spring/src/test/java/cz/habarta/typescript/generator/spring/SpringTest.java @@ -30,6 +30,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @@ -169,6 +170,26 @@ public void testInheritance() { Assertions.assertFalse(output.contains("uriEncoding`test/b`")); } + @Test + public void testHeadersParameter() { + final Settings settings = TestUtils.settings(); + settings.outputFileType = TypeScriptFileType.implementationFile; + settings.generateSpringApplicationClient = true; + settings.restHeaderArgumentsParsed = true; + final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(Controller8.class)); + Assertions.assertTrue(output.contains("sendHeaders(headers: { ownerId: number; petId?: string; }): RestResponse")); + } + + @Test + public void testHeadersParameterWithQueryParameter() { + final Settings settings = TestUtils.settings(); + settings.outputFileType = TypeScriptFileType.implementationFile; + settings.generateSpringApplicationClient = true; + settings.restHeaderArgumentsParsed = true; + final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(Controller9.class)); + Assertions.assertTrue(output.contains("sendHeadersAndQueryParams(queryParams: { petId: string; }, headers: { ownerId: number; }): RestResponse")); + } + @RestController @RequestMapping("/owners/{ownerId}") public static class Controller1 { @@ -291,6 +312,24 @@ int doSomethingElseAgain() { } } + @RestController + public static class Controller8 { + @GetMapping("/headers1") + public void sendHeaders( + @RequestHeader Long ownerId, + @RequestHeader(required = false) String petId + ) {} + } + + @RestController + public static class Controller9 { + @GetMapping("/headersWithQueryParams1") + public void sendHeadersAndQueryParams( + @RequestHeader Long ownerId, + @RequestParam String petId + ) {} + } + public static class Pet { }