This is an automated email from the ASF dual-hosted git repository.
xiaoyu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new 72c43ab02c [type:feature] parse return type when build api doc (#5946)
72c43ab02c is described below
commit 72c43ab02c0413a7566cab58470fe9e43f11abe9
Author: eye-gu <[email protected]>
AuthorDate: Wed Mar 5 13:48:31 2025 +0800
[type:feature] parse return type when build api doc (#5946)
Co-authored-by: xiaoyu <[email protected]>
---
.../AbstractContextRefreshedEventListener.java | 4 +-
.../registrar/AbstractApiDocRegistrar.java | 5 +-
.../register/registrar/ApiDocRegistrarImpl.java | 4 +-
.../shenyu/client/core/utils/OpenApiUtils.java | 219 +++++++++++++++++++++
4 files changed, 229 insertions(+), 3 deletions(-)
diff --git
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/client/AbstractContextRefreshedEventListener.java
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/client/AbstractContextRefreshedEventListener.java
index 707bb850e2..2e55cc4c59 100644
---
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/client/AbstractContextRefreshedEventListener.java
+++
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/client/AbstractContextRefreshedEventListener.java
@@ -263,7 +263,9 @@ public abstract class
AbstractContextRefreshedEventListener<T, A extends Annotat
.put("tags", tags)
.put("operationId", path)
.put("parameters",
OpenApiUtils.generateDocumentParameters(path, method))
- .put("responses",
OpenApiUtils.generateDocumentResponse(path)).build();
+ .put("responses", OpenApiUtils.generateDocumentResponse(path))
+ .put("responseType",
Collections.singletonList(OpenApiUtils.parseReturnType(method)))
+ .build();
return GsonUtils.getInstance().toJson(documentMap);
}
diff --git
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/AbstractApiDocRegistrar.java
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/AbstractApiDocRegistrar.java
index 767054a54c..6d2e5358b3 100644
---
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/AbstractApiDocRegistrar.java
+++
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/AbstractApiDocRegistrar.java
@@ -42,6 +42,7 @@ import org.apache.shenyu.register.common.enums.EventType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -131,7 +132,9 @@ public abstract class AbstractApiDocRegistrar extends
AbstractApiRegistrar<ApiDo
.put("tags", tags)
.put("operationId", path)
.put("parameters",
OpenApiUtils.generateDocumentParameters(path, method))
- .put("responses",
OpenApiUtils.generateDocumentResponse(path)).build();
+ .put("responses", OpenApiUtils.generateDocumentResponse(path))
+ .put("responseType",
Collections.singletonList(OpenApiUtils.parseReturnType(method)))
+ .build();
return GsonUtils.getInstance().toJson(documentMap);
}
diff --git
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/ApiDocRegistrarImpl.java
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/ApiDocRegistrarImpl.java
index 1e0cc2d8fb..91b842f97f 100644
---
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/ApiDocRegistrarImpl.java
+++
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/ApiDocRegistrarImpl.java
@@ -136,7 +136,9 @@ public class ApiDocRegistrarImpl extends
BaseApiRegistrarImpl {
.put("tags", buildTags(api))
.put("operationId", path)
.put("parameters",
OpenApiUtils.generateDocumentParameters(path, api.getApiMethod()))
- .put("responses",
OpenApiUtils.generateDocumentResponse(path)).build();
+ .put("responses", OpenApiUtils.generateDocumentResponse(path))
+ .put("responseType",
Collections.singletonList(OpenApiUtils.parseReturnType(api.getApiMethod())))
+ .build();
return GsonUtils.getInstance().toJson(documentMap);
}
diff --git
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/utils/OpenApiUtils.java
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/utils/OpenApiUtils.java
index 81e8612273..5affbce77a 100644
---
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/utils/OpenApiUtils.java
+++
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/utils/OpenApiUtils.java
@@ -25,10 +25,25 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* openApiUtils.
@@ -150,6 +165,210 @@ public class OpenApiUtils {
.build();
}
+ /**
+ * Perhaps a better way is to generate API documentation through OpenAPI
annotations.
+ *
+ * @param method the method
+ * @return return type
+ */
+ public static ResponseType parseReturnType(final Method method) {
+ Type returnType = method.getGenericReturnType();
+ return parseType("ROOT", returnType, 0, new HashMap<>(16));
+ }
+
+ private static ResponseType parseType(final String name, final Type type,
final int depth, final Map<TypeVariable<?>, Type> typeVariableMap) {
+ ResponseType responseType = new ResponseType();
+ if (StringUtils.isBlank(name)) {
+ responseType.setName(type.getTypeName());
+ } else {
+ responseType.setName(name);
+ }
+ if (depth > 5) {
+ responseType.setType("object");
+ return responseType;
+ }
+ if (type instanceof Class) {
+ return parseClass(responseType, (Class<?>) type, depth,
typeVariableMap);
+ } else if (type instanceof ParameterizedType) {
+ return parseParameterizedType(responseType, (ParameterizedType)
type, depth, typeVariableMap);
+ } else if (type instanceof GenericArrayType) {
+ return parseGenericArrayType(responseType, (GenericArrayType)
type, depth, typeVariableMap);
+ } else if (type instanceof TypeVariable) {
+ Type actualType = typeVariableMap.get(type);
+ if (Objects.nonNull(actualType)) {
+ return parseType(name, actualType, depth, typeVariableMap);
+ } else if (((TypeVariable<?>) type).getBounds().length > 0) {
+ Type upperBound = ((TypeVariable<?>) type).getBounds()[0];
+ return parseType(name, upperBound, depth, typeVariableMap);
+ } else {
+ responseType.setType("object");
+ return responseType;
+ }
+ } else if (type instanceof WildcardType) {
+ responseType.setType("object");
+ return responseType;
+ } else {
+ responseType.setType("object");
+ return responseType;
+ }
+ }
+
+ private static ResponseType parseClass(final ResponseType responseType,
final Class<?> clazz, final int depth, final Map<TypeVariable<?>, Type>
typeVariableMap) {
+ if (clazz.isArray()) {
+ responseType.setType("array");
+ responseType.setRefs(Collections.singletonList(parseType("ITEMS",
clazz.getComponentType(), depth + 1, typeVariableMap)));
+ return responseType;
+ } else if (clazz.isEnum()) {
+ responseType.setType("string");
+ return responseType;
+ } else if (isBooleanType(clazz)) {
+ responseType.setType("boolean");
+ return responseType;
+ } else if (isIntegerType(clazz)) {
+ responseType.setType("integer");
+ return responseType;
+ } else if (isNumberType(clazz)) {
+ responseType.setType("number");
+ return responseType;
+ } else if (isStringType(clazz)) {
+ responseType.setType("string");
+ return responseType;
+ } else if (isDateType(clazz)) {
+ responseType.setType("date");
+ return responseType;
+ } else {
+ List<ResponseType> refs = new ArrayList<>();
+ for (Field field : clazz.getDeclaredFields()) {
+ if (Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+ refs.add(parseType(field.getName(), field.getGenericType(),
depth + 1, typeVariableMap));
+ }
+ responseType.setType("object");
+ responseType.setRefs(refs);
+ return responseType;
+ }
+ }
+
+ private static ResponseType parseParameterizedType(final ResponseType
responseType, final ParameterizedType type, final int depth, final
Map<TypeVariable<?>, Type> typeVariableMap) {
+ Class<?> rawType = (Class<?>) type.getRawType();
+ Type[] actualTypeArguments = type.getActualTypeArguments();
+ TypeVariable<?>[] typeVariables = rawType.getTypeParameters();
+ Map<TypeVariable<?>, Type> newTypeVariableMap = new
HashMap<>(typeVariableMap);
+ for (int i = 0; i < typeVariables.length; i++) {
+ newTypeVariableMap.put(typeVariables[i], actualTypeArguments[i]);
+ }
+ if (Collection.class.isAssignableFrom(rawType)) {
+ Type actualType = actualTypeArguments[0];
+ ResponseType elementParam = parseType("ITEMS", actualType, depth +
1, newTypeVariableMap);
+ responseType.setRefs(Collections.singletonList(elementParam));
+ responseType.setType("array");
+ return responseType;
+ } else if (Map.class.isAssignableFrom(rawType)) {
+ Type keyType = actualTypeArguments[0];
+ Type valueType = actualTypeArguments[1];
+ ResponseType keyParam = parseType("key", keyType, depth + 1,
newTypeVariableMap);
+ ResponseType valueParam = parseType("value", valueType, depth + 1,
newTypeVariableMap);
+ List<ResponseType> children = new ArrayList<>();
+ children.add(keyParam);
+ children.add(valueParam);
+ responseType.setRefs(children);
+ responseType.setType("map");
+ return responseType;
+ } else {
+ List<ResponseType> refs = new ArrayList<>();
+ for (Field field : rawType.getDeclaredFields()) {
+ if (Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+ ResponseType fieldParam = parseType(field.getName(),
field.getGenericType(), depth + 1, newTypeVariableMap);
+ refs.add(fieldParam);
+ }
+ responseType.setType("object");
+ responseType.setRefs(refs);
+ return responseType;
+ }
+ }
+
+ private static ResponseType parseGenericArrayType(final ResponseType
responseType, final GenericArrayType type, final int depth, final
Map<TypeVariable<?>, Type> typeVariableMap) {
+ responseType.setRefs(Collections.singletonList(parseType("ITEMS",
type.getGenericComponentType(), depth + 1, typeVariableMap)));
+ responseType.setType("array");
+ return responseType;
+ }
+
+ private static boolean isDateType(final Class<?> clazz) {
+ return clazz == Date.class || clazz == LocalDate.class
+ || clazz == LocalDateTime.class || clazz == LocalTime.class;
+ }
+
+ private static boolean isStringType(final Class<?> clazz) {
+ return CharSequence.class.isAssignableFrom(clazz) || clazz ==
char.class
+ || clazz == Character.class;
+ }
+
+ private static boolean isBooleanType(final Class<?> clazz) {
+ return clazz == boolean.class || clazz == Boolean.class;
+ }
+
+ private static boolean isIntegerType(final Class<?> clazz) {
+ return clazz == byte.class || clazz == Byte.class
+ || clazz == short.class || clazz == Short.class
+ || clazz == int.class || clazz == Integer.class
+ || clazz == long.class || clazz == Long.class;
+ }
+
+ private static boolean isNumberType(final Class<?> clazz) {
+ return clazz == float.class || clazz == Float.class
+ || clazz == double.class || clazz == Double.class;
+ }
+
+ public static class ResponseType {
+
+ private String name;
+
+ private String description;
+
+ private String type;
+
+ /**
+ * child fields.
+ */
+ private List<ResponseType> refs;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(final String type) {
+ this.type = type;
+ }
+
+ public List<ResponseType> getRefs() {
+ return refs;
+ }
+
+ public void setRefs(final List<ResponseType> refs) {
+ this.refs = refs;
+ }
+ }
+
+
public static class Parameter {
private String name;