Skip to content

Commit

Permalink
Implement REST service layer generation technique which unfolds all a…
Browse files Browse the repository at this point in the history
…rguments

Fixes vojtechhabarta#792

Signed-off-by: Oleksandr Porunov <alexandr.porunov@gmail.com>
  • Loading branch information
porunov committed Feb 6, 2022
1 parent 0188fd0 commit 9399f94
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ public class Settings {
public boolean generateJaxrsApplicationClient = false;
public boolean generateSpringApplicationInterface = false;
public boolean generateSpringApplicationClient = false;
public boolean generateClientAsService = false;
public boolean skipNullValuesInOptionalQueryParametersAsService = false;
public boolean scanSpringApplication;
@Deprecated public RestNamespacing jaxrsNamespacing;
@Deprecated public Class<? extends Annotation> jaxrsNamespacingAnnotation = null;
Expand Down Expand Up @@ -414,6 +416,15 @@ public void validate() {
if (generateSpringApplicationClient && outputFileType != TypeScriptFileType.implementationFile) {
throw new RuntimeException("'generateSpringApplicationClient' can only be used when generating implementation file ('outputFileType' parameter is 'implementationFile').");
}

if(generateClientAsService && !(generateSpringApplicationClient || generateJaxrsApplicationClient)){
throw new RuntimeException("'generateClientAsService' can only be used when application client generation is enabled via 'generateSpringApplicationClient' or 'generateJaxrsApplicationClient'.");
}

if(skipNullValuesInOptionalQueryParametersAsService && !generateClientAsService){
throw new RuntimeException("'skipNullValuesInOptionalQueryParametersAsService' can only be used when application client as a service generation is enabled via 'generateClientAsService'.");
}

if (jaxrsNamespacing != null) {
TypeScriptGenerator.getLogger().warning("Parameter 'jaxrsNamespacing' is deprecated. Use 'restNamespacing' parameter.");
if (restNamespacing == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@
import cz.habarta.typescript.generator.emitter.TsAssignmentExpression;
import cz.habarta.typescript.generator.emitter.TsBeanCategory;
import cz.habarta.typescript.generator.emitter.TsBeanModel;
import cz.habarta.typescript.generator.emitter.TsBinaryExpression;
import cz.habarta.typescript.generator.emitter.TsBinaryOperator;
import cz.habarta.typescript.generator.emitter.TsCallExpression;
import cz.habarta.typescript.generator.emitter.TsConstructorModel;
import cz.habarta.typescript.generator.emitter.TsEnumModel;
import cz.habarta.typescript.generator.emitter.TsExpression;
import cz.habarta.typescript.generator.emitter.TsExpressionStatement;
import cz.habarta.typescript.generator.emitter.TsHelper;
import cz.habarta.typescript.generator.emitter.TsIdentifierReference;
import cz.habarta.typescript.generator.emitter.TsIfStatement;
import cz.habarta.typescript.generator.emitter.TsMemberExpression;
import cz.habarta.typescript.generator.emitter.TsMethodModel;
import cz.habarta.typescript.generator.emitter.TsModel;
Expand All @@ -42,6 +45,7 @@
import cz.habarta.typescript.generator.emitter.TsTaggedTemplateLiteral;
import cz.habarta.typescript.generator.emitter.TsTemplateLiteral;
import cz.habarta.typescript.generator.emitter.TsThisExpression;
import cz.habarta.typescript.generator.emitter.TsVariableDeclarationStatement;
import cz.habarta.typescript.generator.parser.BeanModel;
import cz.habarta.typescript.generator.parser.EnumModel;
import cz.habarta.typescript.generator.parser.MethodModel;
Expand Down Expand Up @@ -729,9 +733,11 @@ private TsMethodModel processRestMethod(TsModel tsModel, SymbolTable symbolTable
}
// query params
final List<RestQueryParam> queryParams = method.getQueryParams();
final List<TsProperty> allSingles;
final TsParameterModel queryParameter;
if (queryParams != null && !queryParams.isEmpty()) {
final List<TsType> types = new ArrayList<>();
allSingles = new ArrayList<>(queryParams.size());
if (queryParams.stream().anyMatch(param -> param instanceof RestQueryParam.Map)) {
types.add(new TsType.IndexedArrayType(TsType.String, TsType.Any));
} else {
Expand All @@ -746,7 +752,12 @@ private TsMethodModel processRestMethod(TsModel tsModel, SymbolTable symbolTable
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)));
TsProperty property = new TsProperty(queryParam.getName(), restQueryParam.required ? type : new TsType.OptionalType(type));
currentSingles.add(property);
allSingles.add(property);
if(settings.generateClientAsService) {
parameters.add(new TsParameterModel(property.getName(), property.getTsType()));
}
}
if (restQueryParam instanceof RestQueryParam.Bean) {
final BeanModel queryBean = ((RestQueryParam.Bean) restQueryParam).getBean();
Expand Down Expand Up @@ -776,9 +787,12 @@ private TsMethodModel processRestMethod(TsModel tsModel, SymbolTable symbolTable
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);
parameters.add(queryParameter);
if(!settings.generateClientAsService){
parameters.add(queryParameter);
}
} else {
queryParameter = null;
allSingles = null;
}
if (optionsType != null) {
final TsParameterModel optionsParameter = new TsParameterModel("options", new TsType.OptionalType(optionsType));
Expand All @@ -800,6 +814,9 @@ private TsMethodModel processRestMethod(TsModel tsModel, SymbolTable symbolTable
final List<TsStatement> body;
if (implement) {
body = new ArrayList<>();
if(settings.generateClientAsService && allSingles != null && !allSingles.isEmpty()){
initQueryParameters(body, allSingles);
}
body.add(new TsReturnStatement(
new TsCallExpression(
new TsMemberExpression(new TsMemberExpression(new TsThisExpression(), "httpClient"), "request"),
Expand All @@ -820,6 +837,46 @@ private TsMethodModel processRestMethod(TsModel tsModel, SymbolTable symbolTable
return tsMethodModel;
}

private void initQueryParameters(List<TsStatement> body, List<TsProperty> singleQueryParams){
body.add(new TsVariableDeclarationStatement(
false,
"queryParams",
TsType.Any,
new TsObjectLiteral()
));
for(TsProperty property : singleQueryParams){

TsExpressionStatement assignmentExpressionStatement = new TsExpressionStatement(
new TsAssignmentExpression(
new TsMemberExpression(
new TsIdentifierReference("queryParams"),
property.getName()),
new TsIdentifierReference(property.getName())
)
);

if(property.getTsType() instanceof TsType.OptionalType){

body.add(
new TsIfStatement(
new TsBinaryExpression(
new TsIdentifierReference(property.getName()),
TsBinaryOperator.NEQ,
settings.skipNullValuesInOptionalQueryParametersAsService ?
TsIdentifierReference.Null : TsIdentifierReference.Undefined
),
Collections.singletonList(assignmentExpressionStatement)
)
);

} else {

body.add(assignmentExpressionStatement);

}
}
}

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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

public enum TsBinaryOperator implements Emittable {

BarBar("||");
BarBar("||"),
NEQ("!=");

private final String formatted;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
public class TsIdentifierReference extends TsExpression {

public static final TsIdentifierReference Undefined = new TsIdentifierReference("undefined");
public static final TsIdentifierReference Null = new TsIdentifierReference("null");

private final String identifier;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ public class GenerateTask extends DefaultTask {
public boolean generateJaxrsApplicationClient;
public boolean generateSpringApplicationInterface;
public boolean generateSpringApplicationClient;
public boolean generateClientAsService;
public boolean skipNullValuesInOptionalQueryParametersAsService;
public boolean scanSpringApplication;
@Deprecated public RestNamespacing jaxrsNamespacing;
@Deprecated public String jaxrsNamespacingAnnotation;
Expand Down Expand Up @@ -183,6 +185,8 @@ private Settings createSettings(URLClassLoader classLoader) {
settings.generateJaxrsApplicationClient = generateJaxrsApplicationClient;
settings.generateSpringApplicationInterface = generateSpringApplicationInterface;
settings.generateSpringApplicationClient = generateSpringApplicationClient;
settings.generateClientAsService = generateClientAsService;
settings.skipNullValuesInOptionalQueryParametersAsService = skipNullValuesInOptionalQueryParametersAsService;
settings.scanSpringApplication = scanSpringApplication;
settings.jaxrsNamespacing = jaxrsNamespacing;
settings.setJaxrsNamespacingAnnotation(classLoader, jaxrsNamespacingAnnotation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,25 @@ public class GenerateMojo extends AbstractMojo {
@Parameter
private boolean scanSpringApplication;

/**
* If <code>true</code> it will generate client application methods as service methods, meaning that they will
* accept all arguments as unfolded arguments. Otherwise, REST query parameters will be wrapped into an object <code>queryParams</code>.
* This parameter can be used only when <code>generateSpringApplicationClient</code> or <code>generateJaxrsApplicationClient</code> are set to <code>true</code>.
* Notice, currently only simple (non-bean) parameters will be detected. Currently, beans won't work for this type of client generation.
* If you need for beans to work as well, please, set this option to <code>false</code>. This flow is intended to be fixed in the future releases.
*/
@Parameter
private boolean generateClientAsService;

/**
* If <code>true</code> it will not pass optional parameters to the <code>HttpClient</code> if those parameters
* are set to <code>null</code>. Otherwise, only <code>undefined</code> parameters will be skipped.
* Notice, mandatory parameters which are set to <code>null</code> will still be passed.
* This parameter can be used only when <code>generateClientAsService</code> is set to <code>true</code>.
*/
@Parameter
private boolean skipNullValuesInOptionalQueryParametersAsService;

/**
* Deprecated, use {@link #restNamespacing}.
*/
Expand Down Expand Up @@ -942,6 +961,8 @@ private Settings createSettings(URLClassLoader classLoader) {
settings.generateSpringApplicationInterface = generateSpringApplicationInterface;
settings.generateSpringApplicationClient = generateSpringApplicationClient;
settings.scanSpringApplication = scanSpringApplication;
settings.generateClientAsService = generateClientAsService;
settings.skipNullValuesInOptionalQueryParametersAsService = skipNullValuesInOptionalQueryParametersAsService;
settings.jaxrsNamespacing = jaxrsNamespacing;
settings.setJaxrsNamespacingAnnotation(classLoader, jaxrsNamespacingAnnotation);
settings.restNamespacing = restNamespacing;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,41 @@ public void testQueryParameters() {
Assertions.assertTrue(output.contains("echo(queryParams: { message: string; count?: number; optionalRequestParam?: number; }): RestResponse<string>"));
}

@Test
public void testUnfoldedQueryParameters() {
final Settings settings = TestUtils.settings();
settings.outputFileType = TypeScriptFileType.implementationFile;
settings.generateSpringApplicationClient = true;
settings.generateClientAsService = true;
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(Controller2.class));
Assertions.assertTrue(output.contains("echo(message: string, count?: number, optionalRequestParam?: number): RestResponse<string>"));
Assertions.assertTrue(output.contains("let queryParams: any = {};"));
Assertions.assertTrue(output.contains("queryParams.message = message;"));
Assertions.assertTrue(output.contains("if (count != undefined) {"));
Assertions.assertTrue(output.contains("queryParams.count = count;"));
Assertions.assertTrue(output.contains("if (optionalRequestParam != undefined)"));
Assertions.assertTrue(output.contains("queryParams.optionalRequestParam = optionalRequestParam;"));
Assertions.assertTrue(output.contains("return this.httpClient.request({ method: \"GET\", url: uriEncoding`echo`, queryParams: queryParams });"));
}

@Test
public void testUnfoldedQueryParametersWithSkipOptionalParams() {
final Settings settings = TestUtils.settings();
settings.outputFileType = TypeScriptFileType.implementationFile;
settings.generateSpringApplicationClient = true;
settings.generateClientAsService = true;
settings.skipNullValuesInOptionalQueryParametersAsService = true;
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(Controller2.class));
Assertions.assertTrue(output.contains("echo(message: string, count?: number, optionalRequestParam?: number): RestResponse<string>"));
Assertions.assertTrue(output.contains("let queryParams: any = {};"));
Assertions.assertTrue(output.contains("queryParams.message = message;"));
Assertions.assertTrue(output.contains("if (count != null) {"));
Assertions.assertTrue(output.contains("queryParams.count = count;"));
Assertions.assertTrue(output.contains("if (optionalRequestParam != null)"));
Assertions.assertTrue(output.contains("queryParams.optionalRequestParam = optionalRequestParam;"));
Assertions.assertTrue(output.contains("return this.httpClient.request({ method: \"GET\", url: uriEncoding`echo`, queryParams: queryParams });"));
}

@Test
public void testAllOptionalQueryParameters() {
final Settings settings = TestUtils.settings();
Expand Down

0 comments on commit 9399f94

Please sign in to comment.