Copilot commented on code in PR #6339:
URL: https://github.com/apache/shenyu/pull/6339#discussion_r3245194469


##########
shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/utils/OpenApiUtils.java:
##########
@@ -111,14 +171,163 @@ public static List<Parameter> 
generateDocumentParameters(final String path, fina
                     parameter.setIn("path");
                     parameter.setName(name);
                     parameter.setRequired(true);
-                    parameter.setSchema(new Schema("string", null));
+                    parameter.setType("string");
                     list.add(parameter);
                 }
             }
         }
         return list;
     }
 
+    /**
+     * Generate request parameters for RPC methods.
+     * Unlike HTTP methods that use Spring annotations, RPC method parameters
+     * are parsed from Java method parameter types directly.
+
+     * @param method the method
+     * @return request parameters
+     */
+    public static List<Parameter> generateRpcRequestDocParameters(final Method 
method) {
+        List<Parameter> list = new ArrayList<>();
+        java.lang.reflect.Parameter[] methodParams = method.getParameters();
+        for (java.lang.reflect.Parameter methodParam : methodParams) {
+            Class<?> paramType = methodParam.getType();
+            Schema schema = parseSchema(paramType, 0, new HashMap<>(16));
+            Parameter parameter = 
convertSchemaToParameter(methodParam.getName(), schema);

Review Comment:
   RPC request parameter parsing uses `methodParam.getType()` (raw `Class<?>`), 
which discards generic type information. This causes `List<String>` / 
`Map<K,V>` parameters to be treated as plain `object` and prevents accurate 
array/map schema generation. Use `methodParam.getParameterizedType()` (a 
`Type`) and pass that into `parseSchema`, and add handling for raw 
`Collection`/`Map` classes in `parseClassSchema` as a fallback.



##########
shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/utils/OpenApiUtils.java:
##########
@@ -58,16 +61,73 @@ public class OpenApiUtils {
 
     private static final String[] QUERY_CLASSES = new 
String[]{"org.springframework.web.bind.annotation.RequestParam", 
"org.springframework.web.bind.annotation.RequestPart"};
 
+    /**
+     * Check if the given RPC type uses Spring MVC parameter parsing.
+     * HTTP, WebSocket and Spring Cloud types use Spring MVC annotations for 
parameter resolution,
+     * while other RPC types (Dubbo, gRPC, etc.) parse parameters from Java 
method signatures directly.
+     *
+     * @param rpcTypeEnum the RPC type enum
+     * @return true if Spring MVC parameter parsing should be used
+     */
+    public static boolean useSpringMvcParamParsing(final RpcTypeEnum 
rpcTypeEnum) {
+        return rpcTypeEnum == RpcTypeEnum.HTTP
+                || rpcTypeEnum == RpcTypeEnum.WEB_SOCKET
+                || rpcTypeEnum == RpcTypeEnum.SPRING_CLOUD;
+    }
 
     /**
-     * generateDocumentParameters.
+     * Build document JSON string for the given API method.
+     * Dispatches to the appropriate parameter/response generation based on 
RPC type.
+     *
+     * @param tags       the API tags
+     * @param path       the API path
+     * @param method     the Java method
+     * @param rpcTypeEnum the RPC type
+     * @return document JSON string
+     */
+    public static String buildDocumentJson(final List<String> tags, final 
String path,
+                                           final Method method, final 
RpcTypeEnum rpcTypeEnum) {
+        boolean useSpringMvcParamParsing = 
useSpringMvcParamParsing(rpcTypeEnum);
+        Map<String, Object> documentMap;
+        if (useSpringMvcParamParsing) {
+            documentMap = ImmutableMap.<String, Object>builder()
+                    .put("tags", tags)
+                    .put("operationId", path)
+                    .put("requestParameters", 
generateRequestDocParameters(path, method))
+                    .put("responseParameters", 
Collections.singletonList(parseReturnType(method)))
+                    .put("responses", generateDocumentResponse(path))
+                    .build();
+        } else if (rpcTypeEnum == RpcTypeEnum.GRPC) {
+            documentMap = ImmutableMap.<String, Object>builder()
+                    .put("tags", tags)
+                    .put("operationId", path)
+                    .put("requestParameters", 
generateGrpcRequestDocParameters(method))
+                    .put("responseParameters", 
Collections.singletonList(parseGrpcReturnType(method)))
+                    .put("responses", generateGrpcDocumentResponse(path, 
method))
+                    .build();
+        } else {
+            documentMap = ImmutableMap.<String, Object>builder()
+                    .put("tags", tags)
+                    .put("operationId", path)
+                    .put("requestParameters", 
generateRpcRequestDocParameters(method))
+                    .put("responseParameters", 
Collections.singletonList(parseReturnType(method)))
+                    .put("responses", generateRpcDocumentResponse(path, 
method))
+                    .build();
+        }
+        return GsonUtils.getInstance().toJson(documentMap);
+    }
+
+
+    /**
+     * Generate request parameters for HTTP methods.
+     * This produces OpenAPI-style Parameter objects with in and schema fields.

Review Comment:
   The Javadoc says request parameters include "schema" fields, but `Parameter` 
no longer has a `schema` property (it now uses `type`/`refs`). Please update 
the comment to reflect the current JSON shape to avoid misleading future 
changes.
   



##########
shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/utils/OpenApiUtils.java:
##########
@@ -296,6 +749,219 @@ private static ResponseType parseGenericArrayType(final 
ResponseType responseTyp
         return responseType;
     }
 
+    private static Schema parseSchema(final Type type, final int depth, final 
Map<TypeVariable<?>, Type> typeVariableMap) {
+        if (depth > 5) {
+            return new Schema("object", null);
+        }
+        if (type instanceof Class) {
+            return parseClassSchema((Class<?>) type, depth, typeVariableMap);
+        } else if (type instanceof ParameterizedType) {
+            return parseParameterizedTypeSchema((ParameterizedType) type, 
depth, typeVariableMap);
+        } else if (type instanceof GenericArrayType) {
+            Schema elementSchema = parseSchema(((GenericArrayType) 
type).getGenericComponentType(), depth + 1, typeVariableMap);
+            Schema schema = new Schema("array", null);
+            schema.setRefs(Collections.singletonList(elementSchema));
+            return schema;
+        } else if (type instanceof TypeVariable) {
+            Type actualType = typeVariableMap.get(type);
+            if (Objects.nonNull(actualType)) {
+                return parseSchema(actualType, depth, typeVariableMap);
+            } else if (((TypeVariable<?>) type).getBounds().length > 0) {
+                return parseSchema(((TypeVariable<?>) type).getBounds()[0], 
depth, typeVariableMap);
+            } else {
+                return new Schema("object", null);
+            }
+        } else {
+            return new Schema("object", null);
+        }
+    }
+
+    private static Schema parseClassSchema(final Class<?> clazz, final int 
depth, final Map<TypeVariable<?>, Type> typeVariableMap) {
+        if (clazz.isArray()) {
+            Schema elementSchema = parseSchema(clazz.getComponentType(), depth 
+ 1, typeVariableMap);
+            Schema schema = new Schema("array", null);
+            schema.setRefs(Collections.singletonList(elementSchema));
+            return schema;
+        } else if (clazz.isEnum()) {
+            return new Schema("string", null);
+        } else if (isBooleanType(clazz)) {
+            return new Schema("boolean", null);
+        } else if (isIntegerType(clazz)) {
+            return new Schema("integer", null);
+        } else if (isNumberType(clazz)) {
+            return new Schema("number", null);
+        } else if (isStringType(clazz)) {
+            return new Schema("string", null);
+        } else if (isDateType(clazz)) {
+            return new Schema("string", "date");
+        } else if (isProtobufMessage(clazz)) {
+            return parseProtobufClassSchema(clazz, depth, typeVariableMap);
+        } else {
+            List<Schema> refs = new ArrayList<>();
+            for (Field field : clazz.getDeclaredFields()) {
+                if (Modifier.isStatic(field.getModifiers())) {
+                    continue;
+                }
+                Schema fieldSchema = parseSchema(field.getGenericType(), depth 
+ 1, typeVariableMap);
+                fieldSchema.setName(field.getName());
+                refs.add(fieldSchema);
+            }
+            Schema schema = new Schema("object", null);
+            schema.setRefs(refs);
+            return schema;
+        }
+    }
+
+    private static Schema parseParameterizedTypeSchema(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)) {
+            Schema elementSchema = parseSchema(actualTypeArguments[0], depth + 
1, newTypeVariableMap);
+            Schema schema = new Schema("array", null);
+            schema.setRefs(Collections.singletonList(elementSchema));
+            return schema;
+        } else if (Map.class.isAssignableFrom(rawType)) {
+            Schema keySchema = parseSchema(actualTypeArguments[0], depth + 1, 
newTypeVariableMap);
+            Schema valueSchema = parseSchema(actualTypeArguments[1], depth + 
1, newTypeVariableMap);

Review Comment:
   For `Map` schemas, `parseParameterizedTypeSchema` adds key/value schemas to 
`refs` without setting their names. `convertSchemaToParameter(ref.getName(), 
ref)` then produces nested parameters with `name=null`, which is awkward for 
consumers rendering the schema. Consider setting names like "key"/"value" (or 
representing maps via an `additionalProperties`-style structure) and similarly 
naming array element schemas (e.g., "items").
   



##########
shenyu-client/shenyu-client-core/src/test/java/org/apache/shenyu/client/core/utils/OpenApiUtilsTest.java:
##########
@@ -0,0 +1,502 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.client.core.utils;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.protobuf.Empty;
+import org.apache.shenyu.client.core.test.Test.Address;
+import org.apache.shenyu.client.core.test.Test.TestRequest;
+import org.apache.shenyu.client.core.utils.OpenApiUtils.Parameter;
+import org.apache.shenyu.client.core.utils.OpenApiUtils.ResponseType;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.junit.jupiter.api.Test;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+
+public class OpenApiUtilsTest {
+
+    @Test
+    void testGenerateRpcDocumentResponse() throws Exception {
+        Method method = DubboTestService.class.getMethod("findById", 
String.class);
+        Map<String, Object> response = 
OpenApiUtils.generateRpcDocumentResponse("/dubbo/findById", method);
+
+        assertThat(response.containsKey("200"), is(true));
+        assertThat(response.containsKey("404"), is(true));
+        assertThat(response.containsKey("409"), is(false));
+    }
+
+    @Test
+    void testGenerateRpcDocumentResponseVoidReturn() throws Exception {
+        Method method = RpcComplexParamService.class.getMethod("deleteById", 
String.class);
+        Map<String, Object> response = 
OpenApiUtils.generateRpcDocumentResponse("/rpc/deleteById", method);
+        assertThat(response.containsKey("200"), is(true));
+        assertThat(response.containsKey("404"), is(true));
+        assertThat(response.containsKey("409"), is(false));
+        Map<String, Object> successResponse = (Map<String, Object>) 
response.get("200");
+        Map<String, Object> content = (Map<String, Object>) 
successResponse.get("content");
+        Map<String, Object> schema = (Map<String, Object>) ((Map<String, 
Object>) content.get("*/*")).get("schema");
+        assertThat(schema.get("type"), is("void"));
+    }
+
+    @Test
+    void testParseReturnTypeObject() throws Exception {
+        Method method = DubboTestService.class.getMethod("findById", 
String.class);
+        ResponseType returnType = OpenApiUtils.parseReturnType(method);
+
+        assertThat(returnType.getType(), is("object"));
+        assertThat(returnType.getRefs(), notNullValue());
+        assertThat(returnType.getRefs(), hasSize(2));
+        assertThat(returnType.getRefs().get(0).getName(), is("id"));
+        assertThat(returnType.getRefs().get(0).getType(), is("string"));
+        assertThat(returnType.getRefs().get(1).getName(), is("name"));
+        assertThat(returnType.getRefs().get(1).getType(), is("string"));
+    }
+
+    @Test
+    void testParseReturnTypeList() throws Exception {
+        Method method = DubboTestService.class.getMethod("findAll");
+        ResponseType returnType = OpenApiUtils.parseReturnType(method);
+
+        assertThat(returnType.getType(), is("array"));
+        assertThat(returnType.getRefs(), notNullValue());
+        assertThat(returnType.getRefs(), hasSize(1));
+        assertThat(returnType.getRefs().get(0).getType(), is("object"));
+    }
+
+    @Test
+    void testIsProtobufMessageNegative() {
+        assertThat(OpenApiUtils.isProtobufMessage(DubboTest.class), is(false));
+        assertThat(OpenApiUtils.isProtobufMessage(String.class), is(false));
+        assertThat(OpenApiUtils.isProtobufMessage(null), is(false));
+        assertThat(OpenApiUtils.isProtobufMessage(int.class), is(false));
+    }
+
+    @Test
+    void testIsProtobufMessagePositive() {
+        assertThat(OpenApiUtils.isProtobufMessage(TestRequest.class), 
is(true));
+        assertThat(OpenApiUtils.isProtobufMessage(Address.class), is(true));
+        assertThat(OpenApiUtils.isProtobufMessage(Empty.class), is(true));
+    }
+
+    @Test
+    void testParseReturnTypeProtobufWithAllFieldTypes() throws Exception {
+        Method method = ProtobufTestService.class.getMethod("getTestRequest");
+        ResponseType returnType = OpenApiUtils.parseReturnType(method);
+        assertThat(returnType.getType(), is("object"));
+        assertThat(returnType.getRefs(), notNullValue());
+        assertThat(returnType.getRefs(), hasSize(7));
+
+        assertThat(returnType.getRefs().get(0).getName(), is("id"));
+        assertThat(returnType.getRefs().get(0).getType(), is("string"));
+
+        assertThat(returnType.getRefs().get(1).getName(), is("count"));
+        assertThat(returnType.getRefs().get(1).getType(), is("integer"));
+
+        assertThat(returnType.getRefs().get(2).getName(), is("enabled"));
+        assertThat(returnType.getRefs().get(2).getType(), is("boolean"));
+
+        assertThat(returnType.getRefs().get(3).getName(), is("status"));
+        assertThat(returnType.getRefs().get(3).getType(), is("string"));
+
+        assertThat(returnType.getRefs().get(4).getName(), is("address"));
+        assertThat(returnType.getRefs().get(4).getType(), is("object"));
+        assertThat(returnType.getRefs().get(4).getRefs(), notNullValue());
+        assertThat(returnType.getRefs().get(4).getRefs(), hasSize(2));
+        assertThat(returnType.getRefs().get(4).getRefs().get(0).getName(), 
is("street"));
+        assertThat(returnType.getRefs().get(4).getRefs().get(0).getType(), 
is("string"));
+        assertThat(returnType.getRefs().get(4).getRefs().get(1).getName(), 
is("city"));
+        assertThat(returnType.getRefs().get(4).getRefs().get(1).getType(), 
is("string"));
+
+        assertThat(returnType.getRefs().get(5).getName(), is("tags"));
+        assertThat(returnType.getRefs().get(5).getType(), is("array"));
+        assertThat(returnType.getRefs().get(5).getRefs(), notNullValue());
+        assertThat(returnType.getRefs().get(5).getRefs(), hasSize(1));
+        assertThat(returnType.getRefs().get(5).getRefs().get(0).getType(), 
is("string"));
+
+        assertThat(returnType.getRefs().get(6).getName(), is("addresses"));
+        assertThat(returnType.getRefs().get(6).getType(), is("array"));
+        assertThat(returnType.getRefs().get(6).getRefs(), notNullValue());
+        assertThat(returnType.getRefs().get(6).getRefs(), hasSize(1));
+        assertThat(returnType.getRefs().get(6).getRefs().get(0).getType(), 
is("object"));
+    }
+
+    @Test
+    void testParseReturnTypeProtobufEmpty() throws Exception {
+        Method method = ProtobufTestService.class.getMethod("getEmpty");
+        ResponseType returnType = OpenApiUtils.parseReturnType(method);
+        assertThat(returnType.getType(), is("object"));
+        assertThat(returnType.getRefs(), nullValue());
+    }
+
+    @Test
+    void testGenerateRpcRequestDocParametersProtobuf() throws Exception {
+        Method method = ProtobufTestService.class.getMethod("sendTestRequest", 
TestRequest.class);
+        List<Parameter> params = 
OpenApiUtils.generateRpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("request"));
+        assertThat(params.get(0).getType(), is("object"));
+        assertThat(params.get(0).getRefs(), notNullValue());
+        assertThat(params.get(0).getRefs(), hasSize(7));
+        assertThat(params.get(0).getRefs().get(0).getName(), is("id"));
+        assertThat(params.get(0).getRefs().get(0).getType(), is("string"));
+        assertThat(params.get(0).getRefs().get(3).getName(), is("status"));
+        assertThat(params.get(0).getRefs().get(3).getType(), is("string"));
+        assertThat(params.get(0).getRefs().get(5).getName(), is("tags"));
+        assertThat(params.get(0).getRefs().get(5).getType(), is("array"));
+    }
+
+    @Test
+    void testGenerateDocumentResponseExistingBehavior() {
+        Map<String, Object> response = 
OpenApiUtils.generateDocumentResponse("/test/path");
+        assertThat(response.containsKey("200"), is(true));
+        assertThat(response.containsKey("404"), is(true));
+        assertThat(response.containsKey("409"), is(true));
+    }
+
+    @Test
+    void testGenerateRequestDocParametersWithRequestParam() throws Exception {
+        Method method = SpringMvcController.class.getMethod("query", 
String.class);
+        List<Parameter> params = 
OpenApiUtils.generateRequestDocParameters("/test/query", method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("name"));
+        assertThat(params.get(0).getType(), is("string"));
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testGenerateRequestDocParametersWithPathVariable() throws Exception {
+        Method method = SpringMvcController.class.getMethod("getByPath", 
String.class);
+        List<Parameter> params = 
OpenApiUtils.generateRequestDocParameters("/test/{id}", method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("id"));
+        assertThat(params.get(0).getType(), is("string"));
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testGenerateRequestDocParametersNoAnnotations() throws Exception {
+        Method method = DubboTestService.class.getMethod("findById", 
String.class);
+        List<Parameter> params = 
OpenApiUtils.generateRequestDocParameters("/dubbo/findById", method);
+        assertThat(params, hasSize(0));
+    }
+
+    @Test
+    void testGenerateRpcRequestDocParametersSimpleType() throws Exception {
+        Method method = DubboTestService.class.getMethod("findById", 
String.class);
+        List<Parameter> params = 
OpenApiUtils.generateRpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("id"));
+        assertThat(params.get(0).getType(), is("string"));
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testGenerateRpcRequestDocParametersComplexType() throws Exception {
+        Method method = DubboTestService.class.getMethod("insert", 
DubboTest.class);
+        List<Parameter> params = 
OpenApiUtils.generateRpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getType(), is("object"));
+        assertThat(params.get(0).getRefs(), notNullValue());
+        assertThat(params.get(0).getRefs(), hasSize(2));
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testGenerateRpcRequestDocParametersWithListParameter() throws 
Exception {
+        Method method = RpcComplexParamService.class.getMethod("batchInsert", 
List.class);
+        List<Parameter> params = 
OpenApiUtils.generateRpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("ids"));
+        assertThat(params.get(0).getType(), is("object"));
+    }
+
+    @Test
+    void testGenerateRpcRequestDocParametersWithMapParameter() throws 
Exception {
+        Method method = RpcComplexParamService.class.getMethod("searchByMap", 
Map.class);
+        List<Parameter> params = 
OpenApiUtils.generateRpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("params"));
+        assertThat(params.get(0).getType(), is("object"));
+    }
+
+    @Test
+    void testUseSpringMvcParamParsing() {
+        assertThat(OpenApiUtils.useSpringMvcParamParsing(RpcTypeEnum.HTTP), 
is(true));
+        
assertThat(OpenApiUtils.useSpringMvcParamParsing(RpcTypeEnum.WEB_SOCKET), 
is(true));
+        
assertThat(OpenApiUtils.useSpringMvcParamParsing(RpcTypeEnum.SPRING_CLOUD), 
is(true));
+        assertThat(OpenApiUtils.useSpringMvcParamParsing(RpcTypeEnum.DUBBO), 
is(false));
+        assertThat(OpenApiUtils.useSpringMvcParamParsing(RpcTypeEnum.GRPC), 
is(false));
+        assertThat(OpenApiUtils.useSpringMvcParamParsing(RpcTypeEnum.SOFA), 
is(false));
+    }
+
+    // --- gRPC tests ---
+
+    @Test
+    void testGenerateGrpcRequestDocParametersSimpleType() throws Exception {
+        Method method = GrpcTestService.class.getMethod("unaryCall", 
String.class, io.grpc.stub.StreamObserver.class);
+        List<Parameter> params = 
OpenApiUtils.generateGrpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("request"));
+        assertThat(params.get(0).getType(), is("string"));
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testGenerateGrpcRequestDocParametersComplexType() throws Exception {
+        Method method = GrpcTestService.class.getMethod("unaryCallComplex", 
GrpcTestClass.class, io.grpc.stub.StreamObserver.class);
+        List<Parameter> params = 
OpenApiUtils.generateGrpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("request"));
+        assertThat(params.get(0).getType(), is("object"));
+        assertThat(params.get(0).getRefs(), notNullValue());
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testGenerateGrpcRequestDocParametersClientStreaming() throws 
Exception {
+        Method method = 
GrpcClientStreamingService.class.getMethod("clientStreaming", 
io.grpc.stub.StreamObserver.class);
+        List<Parameter> params = 
OpenApiUtils.generateGrpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("request"));
+        assertThat(params.get(0).getType(), is("string"));
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testParseGrpcReturnTypeWithStreamObserver() throws Exception {
+        Method method = GrpcTestService.class.getMethod("unaryCall", 
String.class, io.grpc.stub.StreamObserver.class);
+        ResponseType returnType = OpenApiUtils.parseGrpcReturnType(method);
+        assertThat(returnType.getName(), is("ROOT"));
+        assertThat(returnType.getType(), is("object"));
+        assertThat(returnType.getRefs(), notNullValue());
+        assertThat(returnType.getRefs(), hasSize(2));
+    }
+
+    @Test
+    void testParseGrpcReturnTypeVoid() throws Exception {
+        Method method = GrpcTestService.class.getMethod("noStreamObserver", 
String.class);
+        ResponseType returnType = OpenApiUtils.parseGrpcReturnType(method);
+        assertThat(returnType.getName(), is("ROOT"));
+        assertThat(returnType.getType(), is("void"));
+    }
+
+    @Test
+    void testParseGrpcReturnTypeClientStreaming() throws Exception {
+        Method method = 
GrpcClientStreamingService.class.getMethod("clientStreaming", 
io.grpc.stub.StreamObserver.class);
+        ResponseType returnType = OpenApiUtils.parseGrpcReturnType(method);
+        assertThat(returnType.getName(), is("ROOT"));
+        assertThat(returnType.getType(), is("object"));
+        assertThat(returnType.getRefs(), notNullValue());
+        assertThat(returnType.getRefs(), hasSize(2));
+    }
+
+    @Test
+    void testGenerateGrpcDocumentResponseWithStreamObserver() throws Exception 
{
+        Method method = GrpcTestService.class.getMethod("unaryCall", 
String.class, io.grpc.stub.StreamObserver.class);
+        Map<String, Object> response = 
OpenApiUtils.generateGrpcDocumentResponse("/grpc/unaryCall", method);
+        assertThat(response.containsKey("200"), is(true));
+        assertThat(response.containsKey("404"), is(true));
+        assertThat(response.containsKey("409"), is(false));
+    }
+
+    @Test
+    void testGenerateGrpcDocumentResponseVoid() throws Exception {
+        Method method = GrpcTestService.class.getMethod("noStreamObserver", 
String.class);
+        Map<String, Object> response = 
OpenApiUtils.generateGrpcDocumentResponse("/grpc/noStream", method);
+        assertThat(response.containsKey("200"), is(true));
+        assertThat(response.containsKey("404"), is(true));
+    }
+
+    // --- buildDocumentJson dispatch tests ---
+
+    @Test
+    void testBuildDocumentJsonHttp() throws Exception {
+        Method method = SpringMvcController.class.getMethod("query", 
String.class);
+        String json = OpenApiUtils.buildDocumentJson(Arrays.asList("tag1"), 
"/test/query", method, RpcTypeEnum.HTTP);
+        JsonObject doc = JsonParser.parseString(json).getAsJsonObject();
+        assertThat(doc.has("requestParameters"), is(true));
+        assertThat(doc.has("responseParameters"), is(true));
+        assertThat(doc.has("parameters"), is(false));
+        assertThat(doc.has("responseType"), is(false));
+        JsonObject responses = doc.getAsJsonObject("responses");
+        assertThat(responses.has("200"), is(true));
+        assertThat(responses.has("404"), is(true));
+        assertThat(responses.has("409"), is(true));
+    }
+
+    @Test
+    void testBuildDocumentJsonGrpc() throws Exception {
+        Method method = GrpcTestService.class.getMethod("unaryCall", 
String.class, io.grpc.stub.StreamObserver.class);
+        String json = OpenApiUtils.buildDocumentJson(Arrays.asList("tag1"), 
"/grpc/unaryCall", method, RpcTypeEnum.GRPC);
+        JsonObject doc = JsonParser.parseString(json).getAsJsonObject();
+        assertThat(doc.has("requestParameters"), is(true));
+        assertThat(doc.has("responseParameters"), is(true));
+        assertThat(doc.has("parameters"), is(false));
+        assertThat(doc.has("responseType"), is(false));
+        JsonObject responses = doc.getAsJsonObject("responses");
+        assertThat(responses.has("200"), is(true));
+        assertThat(responses.has("404"), is(true));
+        assertThat(responses.has("409"), is(false));
+    }
+
+    @Test
+    void testBuildDocumentJsonRpc() throws Exception {
+        Method method = DubboTestService.class.getMethod("findById", 
String.class);
+        String json = OpenApiUtils.buildDocumentJson(Arrays.asList("tag1"), 
"/dubbo/findById", method, RpcTypeEnum.DUBBO);
+        JsonObject doc = JsonParser.parseString(json).getAsJsonObject();
+        assertThat(doc.has("requestParameters"), is(true));
+        assertThat(doc.has("responseParameters"), is(true));
+        assertThat(doc.has("parameters"), is(false));
+        assertThat(doc.has("responseType"), is(false));
+        JsonObject responses = doc.getAsJsonObject("responses");
+        assertThat(responses.has("200"), is(true));
+        assertThat(responses.has("404"), is(true));
+        assertThat(responses.has("409"), is(false));
+    }
+
+    // --- Inner types (must be after all methods per checkstyle 
InnerTypeLast) ---
+
+    public static class DubboTest {
+
+        private String id;
+
+        private String name;
+
+        public DubboTest() {
+        }
+
+        public String getId() {
+            return id;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+
+    public static class DubboTestService {
+
+        public DubboTest findById(final String id) {
+            return null;
+        }
+
+        public DubboTest insert(final DubboTest dubboTest) {
+            return null;
+        }
+
+        public List<DubboTest> findAll() {
+            return null;
+        }
+    }
+
+    @RestController
+    @RequestMapping("/test")
+    public static class SpringMvcController {
+
+        @RequestMapping("/query")
+        public String query(@RequestParam("name") final String name) {
+            return "";
+        }
+
+        @RequestMapping("/{id}")
+        public String getByPath(@RequestParam("id") final String id) {
+            return "";

Review Comment:
   This method is intended to validate path-variable parsing, but the parameter 
is annotated with `@RequestParam`. Using `@PathVariable` here would better 
reflect the test name/intent and avoid conflating query parsing with 
path-template parsing.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to