Skip to content

Commit 9950c17

Browse files
Merge pull request #121 from vojtechhabarta/axios-client
JAX-RS application client - Axios implementation
2 parents 0077a05 + 904a5c3 commit 9950c17

File tree

9 files changed

+226
-20
lines changed

9 files changed

+226
-20
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ release.properties
1111
/sample-gradle/.nb-gradle/
1212
/sample-gradle/build/
1313

14+
# npm
15+
node_modules
16+
1417
# NetBeans
1518
nbactions.xml
1619

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/Settings.java

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import cz.habarta.typescript.generator.emitter.Emitter;
55
import cz.habarta.typescript.generator.emitter.EmitterExtension;
6+
import cz.habarta.typescript.generator.emitter.EmitterExtensionFeatures;
67
import cz.habarta.typescript.generator.util.Predicate;
78
import java.io.File;
89
import java.lang.annotation.Annotation;
@@ -60,6 +61,8 @@ public class Settings {
6061
public boolean disableJackson2ModuleDiscovery = false;
6162
public ClassLoader classLoader = null;
6263

64+
private boolean defaultStringEnumsOverriddenByExtension = false;
65+
6366

6467
public void setStringQuotes(StringQuotes quotes) {
6568
this.quotes = quotes == StringQuotes.singleQuotes ? "'" : "\"";
@@ -107,15 +110,15 @@ public void validate() {
107110
if (outputKind == null) {
108111
throw new RuntimeException("Required 'outputKind' parameter is not configured. " + seeLink());
109112
}
113+
if (outputKind == TypeScriptOutputKind.ambientModule && outputFileType == TypeScriptFileType.implementationFile) {
114+
throw new RuntimeException("Ambient modules are not supported in implementation files. " + seeLink());
115+
}
110116
if (outputKind == TypeScriptOutputKind.ambientModule && module == null) {
111117
throw new RuntimeException("'module' parameter must be specified for ambient module. " + seeLink());
112118
}
113119
if (outputKind != TypeScriptOutputKind.ambientModule && module != null) {
114120
throw new RuntimeException("'module' parameter is only applicable to ambient modules. " + seeLink());
115121
}
116-
if (outputKind == TypeScriptOutputKind.ambientModule && outputFileType == TypeScriptFileType.implementationFile) {
117-
throw new RuntimeException("Ambient modules are not supported in implementation files. " + seeLink());
118-
}
119122
if (outputKind != TypeScriptOutputKind.module && umdNamespace != null) {
120123
throw new RuntimeException("'umdNamespace' parameter is only applicable to modules. " + seeLink());
121124
}
@@ -128,17 +131,34 @@ public void validate() {
128131
if (jsonLibrary == null) {
129132
throw new RuntimeException("Required 'jsonLibrary' parameter is not configured.");
130133
}
131-
if (outputFileType != TypeScriptFileType.implementationFile) {
132-
for (EmitterExtension emitterExtension : extensions) {
133-
if (emitterExtension.getFeatures().generatesRuntimeCode) {
134-
throw new RuntimeException(String.format("Extension '%s' generates runtime code but 'outputFileType' parameter is not set to 'implementationFile'.",
135-
emitterExtension.getClass().getSimpleName()));
136-
}
134+
for (EmitterExtension extension : extensions) {
135+
final String extensionName = extension.getClass().getSimpleName();
136+
final EmitterExtensionFeatures features = extension.getFeatures();
137+
if (features.generatesRuntimeCode && outputFileType != TypeScriptFileType.implementationFile) {
138+
throw new RuntimeException(String.format("Extension '%s' generates runtime code but 'outputFileType' parameter is not set to 'implementationFile'.", extensionName));
139+
}
140+
if (features.generatesModuleCode && outputKind != TypeScriptOutputKind.module) {
141+
throw new RuntimeException(String.format("Extension '%s' generates code as module but 'outputKind' parameter is not set to 'module'.", extensionName));
137142
}
138-
if (mapClasses == ClassMapping.asClasses) {
139-
throw new RuntimeException("'mapClasses' parameter is set to 'asClasses' which generates runtime code but 'outputFileType' parameter is not set to 'implementationFile'.");
143+
if (features.generatesJaxrsApplicationClient) {
144+
reportConfigurationChange(extensionName, "generateJaxrsApplicationClient", "true");
145+
generateJaxrsApplicationClient = true;
146+
}
147+
if (features.restResponseType != null) {
148+
reportConfigurationChange(extensionName, "restResponseType", features.restResponseType);
149+
restResponseType = features.restResponseType;
150+
}
151+
if (features.restOptionsType != null) {
152+
reportConfigurationChange(extensionName, "restOptionsType", features.restOptionsType);
153+
restOptionsType = features.restOptionsType;
154+
}
155+
if (features.overridesStringEnums) {
156+
defaultStringEnumsOverriddenByExtension = true;
140157
}
141158
}
159+
if (mapClasses == ClassMapping.asClasses && outputFileType != TypeScriptFileType.implementationFile) {
160+
throw new RuntimeException("'mapClasses' parameter is set to 'asClasses' which generates runtime code but 'outputFileType' parameter is not set to 'implementationFile'.");
161+
}
142162
if (generateJaxrsApplicationClient && outputFileType != TypeScriptFileType.implementationFile) {
143163
throw new RuntimeException("'generateJaxrsApplicationClient' can only be used when generating implementation file ('outputFileType' parameter is 'implementationFile').");
144164
}
@@ -164,6 +184,10 @@ public void validate() {
164184
}
165185
}
166186

187+
private static void reportConfigurationChange(String extensionName, String parameterName, String parameterValue) {
188+
System.out.println(String.format("Configuration: '%s' extension set '%s' parameter to '%s'", extensionName, parameterName, parameterValue));
189+
}
190+
167191
public String getExtension() {
168192
return outputFileType == TypeScriptFileType.implementationFile ? ".ts" : ".d.ts";
169193
}
@@ -204,12 +228,7 @@ public boolean test(String className) {
204228
}
205229

206230
public boolean areDefaultStringEnumsOverriddenByExtension() {
207-
for (EmitterExtension extension : extensions) {
208-
if (extension.getFeatures().overridesStringEnums) {
209-
return true;
210-
}
211-
}
212-
return false;
231+
return defaultStringEnumsOverriddenByExtension;
213232
}
214233

215234
private String seeLink() {

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import cz.habarta.typescript.generator.util.Utils;
88
import java.lang.reflect.*;
99
import java.util.*;
10+
import javax.ws.rs.core.Application;
1011

1112

1213
/**
@@ -298,7 +299,7 @@ private Symbol createJaxrsResponseType(SymbolTable symbolTable, TsModel tsModel)
298299
private TsModel createJaxrsInterface(SymbolTable symbolTable, TsModel tsModel, JaxrsApplicationModel jaxrsApplication, Symbol responseSymbol, TsType optionsType) {
299300
final List<TsMethodModel> methods = processJaxrsMethods(jaxrsApplication, symbolTable, responseSymbol, optionsType, false);
300301
final String applicationName = getApplicationName(jaxrsApplication);
301-
final TsBeanModel interfaceModel = new TsBeanModel(null, false, symbolTable.getSyntheticSymbol(applicationName), null, null, null, null, null, null, methods, null);
302+
final TsBeanModel interfaceModel = new TsBeanModel(Application.class, false, symbolTable.getSyntheticSymbol(applicationName), null, null, null, null, null, null, methods, null);
302303
tsModel.getBeans().add(interfaceModel);
303304
return tsModel;
304305
}
@@ -329,7 +330,7 @@ private TsModel createJaxrsClient(SymbolTable symbolTable, TsModel tsModel, Jaxr
329330
final String applicationName = getApplicationName(jaxrsApplication);
330331
final String applicationClientName = applicationName + "Client";
331332
final TsType interfaceType = settings.generateJaxrsApplicationInterface ? new TsType.ReferenceType(symbolTable.getSyntheticSymbol(applicationName)) : null;
332-
final TsBeanModel clientModel = new TsBeanModel(null, true, symbolTable.getSyntheticSymbol(applicationClientName), null, null, null, Utils.listFromNullable(interfaceType), null, constructor, methods, null);
333+
final TsBeanModel clientModel = new TsBeanModel(Application.class, true, symbolTable.getSyntheticSymbol(applicationClientName), null, null, null, Utils.listFromNullable(interfaceType), null, constructor, methods, null);
333334
tsModel.getBeans().add(clientModel);
334335
return tsModel;
335336
}

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/EmitterExtensionFeatures.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
public class EmitterExtensionFeatures {
66

77
public boolean generatesRuntimeCode = false;
8+
public boolean generatesModuleCode = false;
9+
public boolean generatesJaxrsApplicationClient = false;
10+
public String restResponseType = null;
11+
public String restOptionsType = null;
812
public boolean overridesStringEnums = false;
913

1014
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
2+
package cz.habarta.typescript.generator.ext;
3+
4+
import cz.habarta.typescript.generator.Settings;
5+
import cz.habarta.typescript.generator.emitter.EmitterExtension;
6+
import cz.habarta.typescript.generator.emitter.TsBeanModel;
7+
import cz.habarta.typescript.generator.emitter.TsModel;
8+
import java.io.BufferedReader;
9+
import java.io.IOException;
10+
import java.io.InputStreamReader;
11+
import java.net.URL;
12+
import java.nio.charset.StandardCharsets;
13+
import java.util.*;
14+
import java.util.regex.Pattern;
15+
import javax.ws.rs.core.Application;
16+
17+
18+
public abstract class AbstractClientExtension extends EmitterExtension {
19+
20+
@Override
21+
public void emitElements(Writer writer, Settings settings, boolean exportKeyword, TsModel model) {
22+
final TsBeanModel applicationBean = getJaxrsApplicationClientBean(model);
23+
if (applicationBean != null) {
24+
final String appName = applicationBean.getName().toString();
25+
emitClient(writer, settings, exportKeyword, appName);
26+
}
27+
}
28+
29+
private static TsBeanModel getJaxrsApplicationClientBean(TsModel model) {
30+
for (TsBeanModel bean : model.getBeans()) {
31+
if (bean.getOrigin() != null && bean.getOrigin().equals(Application.class) && bean.isClass()) {
32+
return bean;
33+
}
34+
}
35+
return null;
36+
}
37+
38+
protected abstract void emitClient(Writer writer, Settings settings, boolean exportKeyword, String appName);
39+
40+
protected void emitTemplate(Writer writer, Settings settings, String templateName, Map<String, String> replacements) {
41+
final List<String> template = readAllLines(getClass().getResource(templateName));
42+
for (String line : template) {
43+
for (Map.Entry<String, String> entry : replacements.entrySet()) {
44+
line = line.replaceAll(Pattern.quote(entry.getKey()), entry.getValue());
45+
}
46+
writer.writeIndentedLine(line);
47+
}
48+
}
49+
50+
private static List<String> readAllLines(URL resource) {
51+
try {
52+
final List<String> lines = new ArrayList<>();
53+
final BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8));
54+
String line;
55+
while ((line = reader.readLine()) != null) {
56+
lines.add(line);
57+
}
58+
return lines;
59+
} catch (IOException e) {
60+
throw new RuntimeException(e);
61+
}
62+
}
63+
64+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
package cz.habarta.typescript.generator.ext;
3+
4+
import cz.habarta.typescript.generator.Settings;
5+
import cz.habarta.typescript.generator.emitter.EmitterExtensionFeatures;
6+
import java.util.*;
7+
8+
9+
public class AxiosClientExtension extends AbstractClientExtension {
10+
11+
@Override
12+
public EmitterExtensionFeatures getFeatures() {
13+
final EmitterExtensionFeatures features = new EmitterExtensionFeatures();
14+
features.generatesRuntimeCode = true;
15+
features.generatesModuleCode = true;
16+
features.generatesJaxrsApplicationClient = true;
17+
features.restResponseType = "Axios.Promise<Axios.GenericAxiosResponse<R>>";
18+
features.restOptionsType = "Axios.AxiosRequestConfig";
19+
return features;
20+
}
21+
22+
@Override
23+
protected void emitClient(Writer writer, Settings settings, boolean exportKeyword, String appName) {
24+
final Map<String, String> replacements = new LinkedHashMap<>();
25+
replacements.put("\"", settings.quotes);
26+
replacements.put("/*export*/ ", exportKeyword ? "export " : "");
27+
replacements.put("$$RestApplicationClient$$", appName);
28+
replacements.put("$$AxiosRestApplicationClient$$", "Axios" + appName);
29+
emitTemplate(writer, settings, "AxiosClientExtension.template.ts", replacements);
30+
}
31+
32+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
2+
3+
import axios from "axios";
4+
import * as Axios from "axios";
5+
6+
declare module "axios" {
7+
export interface GenericAxiosResponse<R> extends Axios.AxiosResponse {
8+
data: R;
9+
}
10+
}
11+
12+
class AxiosHttpClient implements HttpClient {
13+
14+
constructor(private axios: Axios.AxiosInstance) {
15+
}
16+
17+
request(requestConfig: { method: string; url: string; queryParams?: any; data?: any; options?: Axios.AxiosRequestConfig; }): RestResponse<any> {
18+
function assign(target: any, source?: any) {
19+
if (source != undefined) {
20+
for (const key in source) {
21+
if (source.hasOwnProperty(key)) {
22+
target[key] = source[key];
23+
}
24+
}
25+
}
26+
return target;
27+
}
28+
29+
const config: Axios.AxiosRequestConfig = {};
30+
config.method = requestConfig.method;
31+
config.url = requestConfig.url;
32+
config.params = requestConfig.queryParams;
33+
config.data = requestConfig.data;
34+
assign(config, requestConfig.options);
35+
36+
const axiosResponse = this.axios.request(config);
37+
return axiosResponse;
38+
}
39+
}
40+
41+
/*export*/ class $$AxiosRestApplicationClient$$ extends $$RestApplicationClient$$ {
42+
43+
constructor(baseURL: string, axiosInstance: Axios.AxiosInstance = axios.create()) {
44+
axiosInstance.defaults.baseURL = baseURL;
45+
super(new AxiosHttpClient(axiosInstance));
46+
}
47+
}

typescript-generator-core/src/test/java/cz/habarta/typescript/generator/JaxrsApplicationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ private static class Organization {
384384
}
385385

386386
@Path("people/{personId}")
387-
private static class PersonResource {
387+
public static class PersonResource {
388388
@PathParam("personId")
389389
protected long personId;
390390
@GET
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
package cz.habarta.typescript.generator.ext;
3+
4+
import cz.habarta.typescript.generator.Input;
5+
import cz.habarta.typescript.generator.JaxrsApplicationTest;
6+
import cz.habarta.typescript.generator.Settings;
7+
import cz.habarta.typescript.generator.TestUtils;
8+
import cz.habarta.typescript.generator.TypeScriptFileType;
9+
import cz.habarta.typescript.generator.TypeScriptGenerator;
10+
import cz.habarta.typescript.generator.TypeScriptOutputKind;
11+
import org.junit.Assert;
12+
import org.junit.Test;
13+
14+
15+
public class AxiosClientExtensionTest {
16+
17+
@Test
18+
public void test() {
19+
final Settings settings = TestUtils.settings();
20+
settings.outputFileType = TypeScriptFileType.implementationFile;
21+
settings.outputKind = TypeScriptOutputKind.module;
22+
settings.generateJaxrsApplicationClient = true;
23+
settings.extensions.add(new AxiosClientExtension());
24+
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(JaxrsApplicationTest.PersonResource.class));
25+
final String errorMessage = "Unexpected output: " + output;
26+
Assert.assertTrue(errorMessage, output.contains("interface Person"));
27+
Assert.assertTrue(errorMessage, output.contains("interface HttpClient"));
28+
Assert.assertTrue(errorMessage, output.contains("class RestApplicationClient"));
29+
Assert.assertTrue(errorMessage, output.contains("type RestResponse<R> = Axios.Promise<Axios.GenericAxiosResponse<R>>"));
30+
Assert.assertTrue(errorMessage, output.contains("class AxiosHttpClient implements HttpClient"));
31+
Assert.assertTrue(errorMessage, output.contains("request(requestConfig: { method: string; url: string; queryParams?: any; data?: any; options?: Axios.AxiosRequestConfig; }): RestResponse<any>"));
32+
Assert.assertTrue(errorMessage, output.contains("class AxiosRestApplicationClient extends RestApplicationClient"));
33+
Assert.assertTrue(errorMessage, output.contains("constructor(baseURL: string, axiosInstance: Axios.AxiosInstance = axios.create())"));
34+
}
35+
36+
}

0 commit comments

Comments
 (0)