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 <734164...@qq.com>
AuthorDate: Wed Mar 5 13:48:31 2025 +0800

    [type:feature] parse return type when build api doc (#5946)
    
    Co-authored-by: xiaoyu <xia...@apache.org>
---
 .../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;

Reply via email to