Skip to content

Commit 4142adc

Browse files
jaxrs: response types from Swagger annotations
1 parent e8ad284 commit 4142adc

File tree

11 files changed

+272
-8
lines changed

11 files changed

+272
-8
lines changed

typescript-generator-core/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@
6868
<version>${jersey.version}</version>
6969
<scope>test</scope>
7070
</dependency>
71+
<dependency>
72+
<groupId>io.swagger</groupId>
73+
<artifactId>swagger-annotations</artifactId>
74+
<version>1.5.10</version>
75+
<scope>test</scope>
76+
</dependency>
7177
</dependencies>
7278

7379
<build>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class Settings {
4242
public EnumMapping mapEnum; // default is EnumMapping.asUnion
4343
public ClassMapping mapClasses; // default is ClassMapping.asInterfaces
4444
public boolean disableTaggedUnions = false;
45+
public boolean ignoreSwaggerAnnotations = false;
4546
public boolean generateJaxrsApplicationInterface = false;
4647
public boolean generateJaxrsApplicationClient = false;
4748
public String restResponseType = null;

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/JaxrsApplicationParser.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package cz.habarta.typescript.generator.parser;
33

44
import cz.habarta.typescript.generator.JaxrsApplicationScanner;
5+
import cz.habarta.typescript.generator.Settings;
56
import cz.habarta.typescript.generator.util.Parameter;
67
import cz.habarta.typescript.generator.util.Predicate;
78
import cz.habarta.typescript.generator.util.Utils;
@@ -30,12 +31,14 @@
3031

3132
public class JaxrsApplicationParser {
3233

34+
private final Settings settings;
3335
private final Predicate<String> isClassNameExcluded;
3436
private final Set<String> defaultExcludes;
3537
private final JaxrsApplicationModel model;
3638

37-
public JaxrsApplicationParser(Predicate<String> isClassNameExcluded) {
38-
this.isClassNameExcluded = isClassNameExcluded;
39+
public JaxrsApplicationParser(Settings settings) {
40+
this.settings = settings;
41+
this.isClassNameExcluded = settings.getExcludeFilter();
3942
this.defaultExcludes = new LinkedHashSet<>(getDefaultExcludedClassNames());
4043
this.model = new JaxrsApplicationModel();
4144
}
@@ -122,6 +125,18 @@ private void parseResourceMethod(Result result, ResourceContext context, Class<?
122125
// JAX-RS specification - 3.3 Resource Methods
123126
final HttpMethod httpMethod = getHttpMethod(method);
124127
if (httpMethod != null) {
128+
// swagger
129+
final SwaggerOperation swaggerOperation = settings.ignoreSwaggerAnnotations
130+
? new SwaggerOperation()
131+
: Swagger.parseSwaggerAnnotations(method);
132+
if (swaggerOperation.possibleResponses != null) {
133+
for (Type response : swaggerOperation.possibleResponses) {
134+
foundType(result, response, resourceClass, method.getName());
135+
}
136+
}
137+
if (swaggerOperation.hidden) {
138+
return;
139+
}
125140
// path parameters
126141
final List<MethodParameterModel> pathParams = new ArrayList<>();
127142
final PathTemplate pathTemplate = PathTemplate.parse(context.path);
@@ -153,7 +168,12 @@ private void parseResourceMethod(Result result, ResourceContext context, Class<?
153168
if (returnType == void.class) {
154169
modelReturnType = returnType;
155170
} else if (returnType == Response.class) {
156-
modelReturnType = Object.class;
171+
if (swaggerOperation.response != null) {
172+
modelReturnType = swaggerOperation.response;
173+
foundType(result, modelReturnType, resourceClass, method.getName());
174+
} else {
175+
modelReturnType = Object.class;
176+
}
157177
} else if (genericReturnType instanceof ParameterizedType && returnType == GenericEntity.class) {
158178
final ParameterizedType parameterizedReturnType = (ParameterizedType) genericReturnType;
159179
modelReturnType = parameterizedReturnType.getActualTypeArguments()[0];

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/ModelParser.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ public Model parseModel(List<SourceType<Type>> types) {
3535
}
3636

3737
private Model parseQueue() {
38-
final JaxrsApplicationParser jaxrsApplicationParser = new JaxrsApplicationParser(settings.getExcludeFilter());
39-
final Set<Type> parsedTypes = new LinkedHashSet<>();
38+
final JaxrsApplicationParser jaxrsApplicationParser = new JaxrsApplicationParser(settings);
39+
final Collection<Type> parsedTypes = new ArrayList<>(); // do not use hashcodes, we can only count on `equals` since we use custom `ParameterizedType`s
4040
final List<BeanModel> beans = new ArrayList<>();
4141
final List<EnumModel<?>> enums = new ArrayList<>();
4242
SourceType<? extends Type> sourceType;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
package cz.habarta.typescript.generator.parser;
3+
4+
import cz.habarta.typescript.generator.util.Utils;
5+
import java.lang.reflect.Method;
6+
import java.util.*;
7+
8+
9+
public class Swagger {
10+
11+
static SwaggerOperation parseSwaggerAnnotations(Method method) {
12+
final SwaggerOperation swaggerOperation = new SwaggerOperation();
13+
// @ApiOperation
14+
{
15+
final Object apiOperation = Utils.getAnnotation(method, "io.swagger.annotations.ApiOperation");
16+
if (apiOperation != null) {
17+
final Class<?> response = Utils.getAnnotationElementValue(apiOperation, "response", Class.class);
18+
final String responseContainer = Utils.getAnnotationElementValue(apiOperation, "responseContainer", String.class);
19+
if (responseContainer == null || responseContainer.isEmpty()) {
20+
swaggerOperation.response = response;
21+
} else {
22+
switch (responseContainer) {
23+
case "List":
24+
swaggerOperation.response = Utils.createParameterizedType(List.class, response);
25+
break;
26+
case "Set":
27+
swaggerOperation.response = Utils.createParameterizedType(Set.class, response);
28+
break;
29+
case "Map":
30+
swaggerOperation.response = Utils.createParameterizedType(Map.class, String.class, response);
31+
break;
32+
}
33+
}
34+
swaggerOperation.hidden = Utils.getAnnotationElementValue(apiOperation, "hidden", Boolean.class);
35+
}
36+
}
37+
// @ApiResponses
38+
{
39+
final Object[] apiResponses = Utils.getAnnotationElementValue(method, "io.swagger.annotations.ApiResponses", "value", Object[].class);
40+
if (apiResponses != null) {
41+
swaggerOperation.possibleResponses = new ArrayList<>();
42+
for (Object apiResponse : apiResponses) {
43+
swaggerOperation.possibleResponses.add(Utils.getAnnotationElementValue(apiResponse, "response", Class.class));
44+
}
45+
}
46+
}
47+
return swaggerOperation;
48+
}
49+
50+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
package cz.habarta.typescript.generator.parser;
3+
4+
import java.lang.reflect.Type;
5+
import java.util.List;
6+
7+
8+
public class SwaggerOperation {
9+
public Type response;
10+
public List<Type> possibleResponses;
11+
public boolean hidden;
12+
}

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/util/Utils.java

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
import java.io.File;
55
import java.io.InputStream;
6+
import java.lang.annotation.Annotation;
7+
import java.lang.reflect.AnnotatedElement;
8+
import java.lang.reflect.Method;
69
import java.lang.reflect.ParameterizedType;
710
import java.lang.reflect.Type;
811
import java.util.*;
@@ -12,7 +15,7 @@ public class Utils {
1215

1316
private Utils() {
1417
}
15-
18+
1619
public static String join(Iterable<? extends Object> values, String delimiter) {
1720
final StringBuilder sb = new StringBuilder();
1821
boolean first = true;
@@ -62,6 +65,80 @@ public static Class<?> getRawClassOrNull(Type type) {
6265
return null;
6366
}
6467

68+
public static <T> T getAnnotationElementValue(AnnotatedElement annotatedElement, String annotationClassName, String annotationElementName, Class<T> annotationElementType) {
69+
final Object annotation = getAnnotation(annotatedElement, annotationClassName);
70+
return getAnnotationElementValue(annotation, annotationElementName, annotationElementType);
71+
}
72+
73+
public static Object getAnnotation(AnnotatedElement annotatedElement, String annotationClassName) {
74+
for (Annotation annotation : annotatedElement.getAnnotations()) {
75+
if (annotation.annotationType().getName().equals(annotationClassName)) {
76+
return annotation;
77+
}
78+
}
79+
return null;
80+
}
81+
82+
@SuppressWarnings("unchecked")
83+
public static <T> T getAnnotationElementValue(Object annotation, String annotationElementName, Class<T> annotationElementType) {
84+
try {
85+
if (annotation != null) {
86+
for (Method method : annotation.getClass().getMethods()) {
87+
if (method.getName().equals(annotationElementName)) {
88+
final Object value = method.invoke(annotation);
89+
if (annotationElementType.isInstance(value)) {
90+
return (T) value;
91+
}
92+
}
93+
}
94+
}
95+
return null;
96+
} catch (ReflectiveOperationException e) {
97+
throw new RuntimeException(e);
98+
}
99+
}
100+
101+
public static ParameterizedType createParameterizedType(final Type rawType, final Type... actualTypeArguments) {
102+
final Type ownerType = null;
103+
return new ParameterizedType() {
104+
@Override
105+
public Type[] getActualTypeArguments() {
106+
return actualTypeArguments;
107+
}
108+
109+
@Override
110+
public Type getRawType() {
111+
return rawType;
112+
}
113+
114+
@Override
115+
public Type getOwnerType() {
116+
return ownerType;
117+
}
118+
119+
@Override
120+
public boolean equals(Object obj) {
121+
if (this == obj) {
122+
return true;
123+
}
124+
if (obj instanceof ParameterizedType) {
125+
final ParameterizedType that = (ParameterizedType) obj;
126+
return
127+
Objects.equals(ownerType, that.getOwnerType()) &&
128+
Objects.equals(rawType, that.getRawType()) &&
129+
Arrays.equals(actualTypeArguments, that.getActualTypeArguments());
130+
} else {
131+
return false;
132+
}
133+
}
134+
135+
@Override
136+
public int hashCode() {
137+
return Objects.hash(ownerType, rawType, actualTypeArguments);
138+
}
139+
};
140+
}
141+
65142
public static <T> List<T> listFromNullable(T item) {
66143
return item != null ? Arrays.asList(item) : Collections.<T>emptyList();
67144
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public void testReturnedTypesFromApplication() {
3535

3636
@Test
3737
public void testReturnedTypesFromResource() {
38-
final JaxrsApplicationParser.Result result = new JaxrsApplicationParser(null).tryParse(new SourceType<>(TestResource1.class));
38+
final JaxrsApplicationParser.Result result = new JaxrsApplicationParser(TestUtils.settings()).tryParse(new SourceType<>(TestResource1.class));
3939
Assert.assertNotNull(result);
4040
List<Type> types = getTypes(result.discoveredTypes);
4141
final List<Type> expectedTypes = Arrays.asList(
@@ -109,7 +109,7 @@ public void testExcludedType() {
109109
A.class.getName(),
110110
J.class.getName()
111111
), null);
112-
final JaxrsApplicationParser jaxrsApplicationParser = new JaxrsApplicationParser(settings.getExcludeFilter());
112+
final JaxrsApplicationParser jaxrsApplicationParser = new JaxrsApplicationParser(settings);
113113
final JaxrsApplicationParser.Result result = jaxrsApplicationParser.tryParse(new SourceType<>(TestResource1.class));
114114
Assert.assertNotNull(result);
115115
Assert.assertTrue(!getTypes(result.discoveredTypes).contains(A.class));
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
2+
package cz.habarta.typescript.generator;
3+
4+
import io.swagger.annotations.ApiOperation;
5+
import io.swagger.annotations.ApiResponse;
6+
import io.swagger.annotations.ApiResponses;
7+
import java.util.Arrays;
8+
import java.util.LinkedHashSet;
9+
import java.util.Set;
10+
import javax.ws.rs.GET;
11+
import javax.ws.rs.Path;
12+
import javax.ws.rs.core.Application;
13+
import javax.ws.rs.core.Response;
14+
import org.junit.Assert;
15+
import org.junit.Test;
16+
17+
18+
public class SwaggerTest {
19+
20+
@Test
21+
public void test() {
22+
final Settings settings = TestUtils.settings();
23+
settings.generateJaxrsApplicationInterface = true;
24+
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(TestApplication.class));
25+
Assert.assertTrue(output.contains("interface TestResponse"));
26+
Assert.assertTrue(output.contains("interface TestError"));
27+
Assert.assertTrue(output.contains("testOperationError(): RestResponse<any>;"));
28+
Assert.assertTrue(output.contains("testOperation1(): RestResponse<TestResponse>;"));
29+
Assert.assertTrue(output.contains("testOperation2(): RestResponse<TestResponse[]>;"));
30+
Assert.assertTrue(output.contains("testOperation3(): RestResponse<TestResponse[]>;"));
31+
Assert.assertTrue(output.contains("testOperation4(): RestResponse<{ [index: string]: TestResponse }>;"));
32+
Assert.assertTrue(!output.contains("testHiddenOperation"));
33+
}
34+
35+
private static class TestApplication extends Application {
36+
@Override
37+
public Set<Class<?>> getClasses() {
38+
return new LinkedHashSet<>(Arrays.<Class<?>>asList(TestResource.class));
39+
}
40+
}
41+
42+
@Path("test")
43+
private static class TestResource {
44+
45+
@ApiOperation(value = "", response = TestResponse.class)
46+
@GET
47+
public Response testOperation1() {
48+
return Response.ok(new TestResponse()).build();
49+
}
50+
51+
@ApiOperation(value = "", responseContainer = "List", response = TestResponse.class)
52+
@GET
53+
public Response testOperation2() {
54+
return Response.ok(new TestResponse()).build();
55+
}
56+
57+
@ApiOperation(value = "", responseContainer = "Set", response = TestResponse.class)
58+
@GET
59+
public Response testOperation3() {
60+
return Response.ok(new TestResponse()).build();
61+
}
62+
63+
@ApiOperation(value = "", responseContainer = "Map", response = TestResponse.class)
64+
@GET
65+
public Response testOperation4() {
66+
return Response.ok(new TestResponse()).build();
67+
}
68+
69+
@ApiResponses({@ApiResponse(code = 400, message = "", response = TestError.class)})
70+
@GET
71+
public Response testOperationError() {
72+
return Response.status(Response.Status.BAD_REQUEST).build();
73+
}
74+
75+
@ApiOperation(value = "", hidden = true)
76+
@GET
77+
public Response testHiddenOperation() {
78+
return null;
79+
}
80+
81+
}
82+
83+
private static class TestResponse {
84+
}
85+
86+
private static class TestError {
87+
}
88+
89+
}

typescript-generator-gradle-plugin/src/main/java/cz/habarta/typescript/generator/gradle/GenerateTask.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class GenerateTask extends DefaultTask {
4141
public EnumMapping mapEnum;
4242
public ClassMapping mapClasses;
4343
public boolean disableTaggedUnions;
44+
public boolean ignoreSwaggerAnnotations;
4445
public boolean generateJaxrsApplicationInterface;
4546
public boolean generateJaxrsApplicationClient;
4647
public String restResponseType;
@@ -108,6 +109,7 @@ public void generate() throws Exception {
108109
settings.mapEnum = mapEnum;
109110
settings.mapClasses = mapClasses;
110111
settings.disableTaggedUnions = disableTaggedUnions;
112+
settings.ignoreSwaggerAnnotations = ignoreSwaggerAnnotations;
111113
settings.generateJaxrsApplicationInterface = generateJaxrsApplicationInterface;
112114
settings.generateJaxrsApplicationClient = generateJaxrsApplicationClient;
113115
settings.restResponseType = restResponseType;

0 commit comments

Comments
 (0)