This is an automated email from the ASF dual-hosted git repository.

liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git

commit b1642095ccbc2d53a745db4640735a846efe7512
Author: liubao <[email protected]>
AuthorDate: Mon Aug 21 09:46:24 2023 +0800

    [SCB-2803]add proto-buffer schema codec
---
 .../converter/SwaggerToProtoGenerator.java         |  30 +--
 .../protobuf/schema/SchemaToProtoGenerator.java    | 262 +++++++++++++++++++++
 .../utils/ScopedProtobufSchemaManager.java         |  55 +++++
 .../codec/protobuf/schema/TestSchemaCodec.java     |  87 +++++++
 .../schema/TestSchemaToProtoGenerator.java         | 191 +++++++++++++++
 .../apache/servicecomb/swagger/SwaggerUtils.java   |  83 +++++--
 6 files changed, 665 insertions(+), 43 deletions(-)

diff --git 
a/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/internal/converter/SwaggerToProtoGenerator.java
 
b/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/internal/converter/SwaggerToProtoGenerator.java
index a9333148e..6d00adeda 100644
--- 
a/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/internal/converter/SwaggerToProtoGenerator.java
+++ 
b/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/internal/converter/SwaggerToProtoGenerator.java
@@ -79,22 +79,19 @@ public class SwaggerToProtoGenerator {
   public Proto convert() {
     convertDefinitions();
     convertOperations();
-    for (; ; ) {
+    do {
       List<Runnable> oldPending = pending;
       pending = new ArrayList<>();
       for (Runnable runnable : oldPending) {
         runnable.run();
       }
-      if (pending.isEmpty()) {
-        break;
-      }
-    }
+    } while (!pending.isEmpty());
 
     return createProto();
   }
 
   public static String escapePackageName(String name) {
-    return name.replaceAll("[\\-\\:]", "_");
+    return name.replaceAll("[\\-:]", "_");
   }
 
   public static String escapeMessageName(String name) {
@@ -102,10 +99,7 @@ public class SwaggerToProtoGenerator {
   }
 
   public static boolean isValidEnum(String name) {
-    if (name.contains(".") || name.contains("-")) {
-      return false;
-    }
-    return true;
+    return !name.contains(".") && !name.contains("-");
   }
 
   private void convertDefinitions() {
@@ -272,18 +266,14 @@ public class SwaggerToProtoGenerator {
     String key = swaggerType + ":" + swaggerFmt;
     return switch (key) {
       case "boolean:null" -> "bool";
-      // there is no int8/int16 in protobuf
-      case "integer:null" -> "int64";
-      case "integer:int8", "integer:int16", "integer:int32" -> "int32";
-      case "integer:int64" -> "int64";
-      case "number:null" -> "double";
+      case "integer:null", "integer:int64" -> "int64";
+      case "integer:int32" -> "int32";
+      case "number:null", "number:double" -> "double";
       case "number:float" -> "float";
-      case "number:double" -> "double";
       case "string:null" -> "string";
-      case "string:byte" -> "bytes"; // LocalDate
-      case "string:date", "string:date-time" -> // Date
-          "int64";
-      case "file:null" -> throw new IllegalStateException("not support swagger 
type: " + swaggerType);
+      case "string:byte" -> "bytes";
+      case "string:date", "string:date-time" -> "int64";
+      case "string:binary" -> throw new IllegalArgumentException("proto buffer 
not support file upload/download");
       default -> null;
     };
   }
diff --git 
a/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/schema/SchemaToProtoGenerator.java
 
b/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/schema/SchemaToProtoGenerator.java
new file mode 100644
index 000000000..0e0c4bb6a
--- /dev/null
+++ 
b/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/schema/SchemaToProtoGenerator.java
@@ -0,0 +1,262 @@
+/*
+ * 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.servicecomb.codec.protobuf.schema;
+
+
+import static 
org.apache.servicecomb.foundation.common.utils.StringBuilderUtils.appendLine;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.servicecomb.foundation.protobuf.internal.ProtoConst;
+import org.apache.servicecomb.foundation.protobuf.internal.parser.ProtoParser;
+import org.springframework.util.CollectionUtils;
+
+import com.google.common.hash.Hashing;
+
+import io.protostuff.compiler.model.Message;
+import io.protostuff.compiler.model.Proto;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.media.Schema;
+
+@SuppressWarnings({"rawtypes", "unchecked"})
+public class SchemaToProtoGenerator {
+  private final String protoPackage;
+
+  private final OpenAPI openAPI;
+
+  private final Schema<?> rootSchema;
+
+  private final String rootName;
+
+  private final Set<String> imports = new HashSet<>();
+
+  private final Set<String> messages = new HashSet<>();
+
+  private final StringBuilder msgStringBuilder = new StringBuilder();
+
+  private List<Runnable> pending = new ArrayList<>();
+
+  public SchemaToProtoGenerator(String protoPackage, OpenAPI openAPI, 
Schema<?> rootSchema, String rootName) {
+    this.protoPackage = protoPackage;
+    this.openAPI = openAPI;
+    this.rootSchema = rootSchema;
+    this.rootName = rootName;
+  }
+
+  public Proto convert() {
+    convertSwaggerType(this.rootSchema);
+
+    Map<String, Schema> wrap = new HashMap<>(1);
+    wrap.put("value", this.rootSchema);
+    createMessage(rootName, wrap, ProtoConst.ANNOTATION_WRAP_PROPERTY);
+
+    do {
+      List<Runnable> oldPending = pending;
+      pending = new ArrayList<>();
+      for (Runnable runnable : oldPending) {
+        runnable.run();
+      }
+    } while (!pending.isEmpty());
+
+    return createProto();
+  }
+
+  protected Proto createProto() {
+    StringBuilder sb = new StringBuilder();
+    appendLine(sb, "syntax = \"proto3\";");
+    for (String importMsg : imports) {
+      appendLine(sb, "import \"%s\";", importMsg);
+    }
+    if (StringUtils.isNotEmpty(protoPackage)) {
+      sb.append("package ").append(protoPackage).append(";\n");
+    }
+    sb.append(msgStringBuilder);
+    ProtoParser protoParser = new ProtoParser();
+    return protoParser.parseFromContent(sb.toString());
+  }
+
+  private String convertSwaggerType(Schema<?> swaggerType) {
+    @SuppressWarnings("unchecked")
+    String type = tryFindEnumType((List<String>) swaggerType.getEnum());
+    if (type != null) {
+      return type;
+    }
+
+    type = findBaseType(swaggerType.getType(), swaggerType.getFormat());
+    if (type != null) {
+      return type;
+    }
+
+    Schema<?> itemProperty = swaggerType.getItems();
+    if (itemProperty != null) {
+      return "repeated " + convertArrayOrMapItem(itemProperty);
+    }
+
+    itemProperty = swaggerType.getAdditionalItems();
+    if (itemProperty != null) {
+      return String.format("map<string, %s>", 
convertArrayOrMapItem(itemProperty));
+    }
+
+    type = swaggerType.get$ref();
+    if (type != null) {
+      Schema<?> refSchema = openAPI.getComponents().getSchemas().get(
+          type.substring(Components.COMPONENTS_SCHEMAS_REF.length()));
+      if (refSchema == null) {
+        throw new IllegalArgumentException("not found ref in components " + 
type);
+      }
+      return convertSwaggerType(refSchema);
+    }
+
+    Map<String, Schema> properties = swaggerType.getProperties();
+    if (CollectionUtils.isEmpty(properties)) {
+      addImports(ProtoConst.ANY_PROTO);
+      return ProtoConst.ANY.getCanonicalName();
+    }
+    createMessage(swaggerType.getName(), properties);
+    return swaggerType.getName();
+  }
+
+  private void addImports(Proto proto) {
+    imports.add(proto.getFilename());
+    for (Message message : proto.getMessages()) {
+      messages.add(message.getCanonicalName());
+    }
+  }
+
+  private void createEnum(String enumName, List<String> enums) {
+    if (!messages.add(enumName)) {
+      // already created
+      return;
+    }
+
+    appendLine(msgStringBuilder, "enum %s {", enumName);
+    for (int idx = 0; idx < enums.size(); idx++) {
+      if (isValidEnum(enums.get(idx))) {
+        appendLine(msgStringBuilder, "  %s =%d;", enums.get(idx), idx);
+      } else {
+        throw new IllegalStateException(
+            String.format("enum class [%s] name [%s] not supported by 
protobuffer.", enumName, enums.get(idx)));
+      }
+    }
+    appendLine(msgStringBuilder, "}");
+  }
+
+  public static boolean isValidEnum(String name) {
+    return !name.contains(".") && !name.contains("-");
+  }
+
+  private String tryFindEnumType(List<String> enums) {
+    if (enums != null && !enums.isEmpty()) {
+      String strEnums = enums.toString();
+      String enumName = "Enum_" + Hashing.sha256().hashString(strEnums, 
StandardCharsets.UTF_8);
+      pending.add(() -> createEnum(enumName, enums));
+      return enumName;
+    }
+    return null;
+  }
+
+  private String findBaseType(String swaggerType, String swaggerFmt) {
+    String key = swaggerType + ":" + swaggerFmt;
+    return switch (key) {
+      case "boolean:null" -> "bool";
+      case "integer:null", "integer:int64" -> "int64";
+      case "integer:int32" -> "int32";
+      case "number:null", "number:double" -> "double";
+      case "number:float" -> "float";
+      case "string:null" -> "string";
+      case "string:byte" -> "bytes";
+      case "string:date", "string:date-time" -> "int64";
+      case "string:binary" -> throw new IllegalArgumentException("proto buffer 
not support file upload/download");
+      default -> null;
+    };
+  }
+
+  private String convertArrayOrMapItem(Schema<?> itemProperty) {
+    // List<List<>>, need to wrap
+    if (itemProperty.getItems() != null) {
+      String protoName = generateWrapPropertyName(List.class.getSimpleName(), 
itemProperty.getItems());
+      pending.add(() -> wrapPropertyToMessage(protoName, itemProperty));
+      return protoName;
+    }
+
+    // List<Map<>>, need to wrap
+    if (itemProperty.getAdditionalItems() != null) {
+      String protoName = generateWrapPropertyName(Map.class.getSimpleName(), 
itemProperty.getAdditionalItems());
+      pending.add(() -> wrapPropertyToMessage(protoName, itemProperty));
+      return protoName;
+    }
+
+    return convertSwaggerType(itemProperty);
+  }
+
+
+  private String generateWrapPropertyName(String prefix, Schema<?> property) {
+    // List<List<>>, need to wrap
+    if (property.getItems() != null) {
+      return generateWrapPropertyName(prefix + List.class.getSimpleName(), 
property.getItems());
+    }
+
+    // List<Map<>>, need to wrap
+    if (property.getAdditionalItems() != null) {
+      return generateWrapPropertyName(prefix + Map.class.getSimpleName(), 
property.getAdditionalItems());
+    }
+
+    // message name cannot have . (package separator)
+    return prefix + 
StringUtils.capitalize(escapeMessageName(convertSwaggerType(property)));
+  }
+
+  public static String escapeMessageName(String name) {
+    return name.replaceAll("\\.", "_");
+  }
+
+
+  private void wrapPropertyToMessage(String protoName, Schema<?> property) {
+    createMessage(protoName, Collections.singletonMap("value", property), 
ProtoConst.ANNOTATION_WRAP_PROPERTY);
+  }
+
+  private void createMessage(String protoName, Map<String, Schema> properties, 
String... annotations) {
+    if (!messages.add(protoName)) {
+      // already created
+      return;
+    }
+
+    for (String annotation : annotations) {
+      msgStringBuilder.append("//");
+      appendLine(msgStringBuilder, annotation);
+    }
+    appendLine(msgStringBuilder, "message %s {", protoName);
+    int tag = 1;
+    for (Entry<String, Schema> entry : properties.entrySet()) {
+      Schema property = entry.getValue();
+      String propertyType = convertSwaggerType(property);
+
+      appendLine(msgStringBuilder, "  %s %s = %d;", propertyType, 
entry.getKey(), tag);
+      tag++;
+    }
+    appendLine(msgStringBuilder, "}");
+  }
+}
diff --git 
a/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/utils/ScopedProtobufSchemaManager.java
 
b/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/utils/ScopedProtobufSchemaManager.java
index 0df295d84..7cb40114e 100644
--- 
a/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/utils/ScopedProtobufSchemaManager.java
+++ 
b/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/utils/ScopedProtobufSchemaManager.java
@@ -19,14 +19,18 @@ package org.apache.servicecomb.codec.protobuf.utils;
 
 import java.util.Map;
 
+import org.apache.commons.lang.StringUtils;
 import 
org.apache.servicecomb.codec.protobuf.internal.converter.SwaggerToProtoGenerator;
+import org.apache.servicecomb.codec.protobuf.schema.SchemaToProtoGenerator;
 import org.apache.servicecomb.core.definition.SchemaMeta;
 import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx;
 import org.apache.servicecomb.foundation.protobuf.ProtoMapper;
 import org.apache.servicecomb.foundation.protobuf.ProtoMapperFactory;
+import org.apache.servicecomb.swagger.SwaggerUtils;
 
 import io.protostuff.compiler.model.Proto;
 import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.media.Schema;
 
 /**
  * Manage swagger -> protoBuffer mappings.
@@ -35,9 +39,46 @@ import io.swagger.v3.oas.models.OpenAPI;
  * for each MicroserviceMeta.
  */
 public class ScopedProtobufSchemaManager {
+  static class SchemaKey {
+    String schemaId;
+
+    Schema<?> schema;
+
+    int hashCode = -1;
+
+    SchemaKey(String schemaId, Schema<?> schema) {
+      this.schemaId = schemaId;
+      this.schema = schema;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      SchemaKey other = (SchemaKey) o;
+      return StringUtils.equals(schemaId, other.schemaId)
+          && SwaggerUtils.schemaEquals(schema, other.schema);
+    }
+
+    @Override
+    public int hashCode() {
+      if (hashCode != -1) {
+        return hashCode;
+      }
+      hashCode = schemaId.hashCode() ^ SwaggerUtils.schemaHashCode(schema);
+      return hashCode;
+    }
+  }
+
   // Because this class belongs to each SchemaMeta, the key is the schema id.
   private final Map<String, ProtoMapper> mapperCache = new 
ConcurrentHashMapEx<>();
 
+  private final Map<SchemaKey, ProtoMapper> schemaMapperCache = new 
ConcurrentHashMapEx<>();
+
   public ScopedProtobufSchemaManager() {
 
   }
@@ -55,4 +96,18 @@ public class ScopedProtobufSchemaManager {
       return protoMapperFactory.create(proto);
     });
   }
+
+  /**
+   * get the ProtoMapper from Schema
+   */
+  public ProtoMapper getOrCreateProtoMapper(OpenAPI openAPI, String schemaId, 
String name, Schema<?> schema) {
+    SchemaKey schemaKey = new SchemaKey(schemaId, schema);
+    return schemaMapperCache.computeIfAbsent(schemaKey, key -> {
+      SchemaToProtoGenerator generator = new 
SchemaToProtoGenerator("scb.schema", openAPI,
+          key.schema, name);
+      Proto proto = generator.convert();
+      ProtoMapperFactory protoMapperFactory = new ProtoMapperFactory();
+      return protoMapperFactory.create(proto);
+    });
+  }
 }
diff --git 
a/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/TestSchemaCodec.java
 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/TestSchemaCodec.java
new file mode 100644
index 000000000..a68ac1a1c
--- /dev/null
+++ 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/TestSchemaCodec.java
@@ -0,0 +1,87 @@
+/*
+ * 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.servicecomb.codec.protobuf.schema;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.servicecomb.codec.protobuf.utils.ScopedProtobufSchemaManager;
+import org.apache.servicecomb.foundation.protobuf.ProtoMapper;
+import org.apache.servicecomb.foundation.protobuf.RootDeserializer;
+import org.apache.servicecomb.foundation.protobuf.RootSerializer;
+import 
org.apache.servicecomb.foundation.protobuf.internal.bean.PropertyWrapper;
+import org.junit.jupiter.api.Test;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.media.ObjectSchema;
+import io.swagger.v3.oas.models.media.StringSchema;
+
+public class TestSchemaCodec {
+  ScopedProtobufSchemaManager manager = new ScopedProtobufSchemaManager();
+
+  @Test
+  public void test_string_schema_codec() throws Exception {
+    OpenAPI openAPI = new OpenAPI();
+    StringSchema schema = new StringSchema();
+    ProtoMapper protoMapper = manager.getOrCreateProtoMapper(openAPI, "test", 
"input", schema);
+    RootSerializer serializer = protoMapper.getSerializerSchemaManager()
+        .createRootSerializer(protoMapper.getProto().getMessage("input"),
+            String.class);
+    Map<String, Object> arguments = new HashMap<>();
+    arguments.put("value", "abcdefg");
+    byte[] result = serializer.serialize(arguments);
+    RootDeserializer<PropertyWrapper<String>> deserializer = 
protoMapper.getDeserializerSchemaManager()
+        .createRootDeserializer(protoMapper.getProto().getMessage("input"), 
String.class);
+    PropertyWrapper<String> deserializedResult = 
deserializer.deserialize(result);
+    assertEquals("abcdefg", deserializedResult.getValue());
+  }
+
+  public static class User {
+    public String name;
+  }
+
+  @Test
+  public void test_object_schema_codec() throws Exception {
+    OpenAPI openAPI = new OpenAPI();
+
+    ObjectSchema schema = new ObjectSchema();
+    schema.setName("User");
+    schema.addProperty("name", new StringSchema());
+    openAPI.setComponents(new Components());
+    openAPI.getComponents().addSchemas("User", schema);
+
+    ObjectSchema ref = new ObjectSchema();
+    ref.set$ref(Components.COMPONENTS_SCHEMAS_REF + "User");
+
+    ProtoMapper protoMapper = manager.getOrCreateProtoMapper(openAPI, "test", 
"input", ref);
+    RootSerializer serializer = protoMapper.getSerializerSchemaManager()
+        .createRootSerializer(protoMapper.getProto().getMessage("input"),
+            User.class);
+    Map<String, Object> arguments = new HashMap<>();
+    User user = new User();
+    user.name = "abcdefg";
+    arguments.put("value", user);
+    byte[] result = serializer.serialize(arguments);
+    RootDeserializer<PropertyWrapper<User>> deserializer = 
protoMapper.getDeserializerSchemaManager()
+        .createRootDeserializer(protoMapper.getProto().getMessage("input"), 
User.class);
+    PropertyWrapper<User> deserializedResult = 
deserializer.deserialize(result);
+    assertEquals("abcdefg", deserializedResult.getValue().name);
+  }
+}
diff --git 
a/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/TestSchemaToProtoGenerator.java
 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/TestSchemaToProtoGenerator.java
new file mode 100644
index 000000000..c1b5f029a
--- /dev/null
+++ 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/TestSchemaToProtoGenerator.java
@@ -0,0 +1,191 @@
+/*
+ * 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.servicecomb.codec.protobuf.schema;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import 
org.apache.servicecomb.codec.protobuf.internal.converter.ProtoToStringGenerator;
+import 
org.apache.servicecomb.swagger.generator.springmvc.SpringmvcSwaggerGenerator;
+import org.junit.jupiter.api.Test;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import io.protostuff.compiler.model.Proto;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.media.ObjectSchema;
+import io.swagger.v3.oas.models.media.StringSchema;
+import jakarta.ws.rs.core.MediaType;
+
+@SuppressWarnings("unused")
+public class TestSchemaToProtoGenerator {
+  @Test
+  public void test_string_schema_is_correct() {
+    OpenAPI openAPI = new OpenAPI();
+    StringSchema schema = new StringSchema();
+    SchemaToProtoGenerator generator =
+        new SchemaToProtoGenerator("test.string", openAPI, schema, "input");
+    Proto proto = generator.convert();
+    assertEquals("""
+        syntax = "proto3";
+        package test.string;
+
+        //@WrapProperty
+        message input {
+          string value = 1;
+        }
+
+        """, new ProtoToStringGenerator(proto).protoToString());
+  }
+
+  @Test
+  public void test_object_schema_is_correct() {
+    OpenAPI openAPI = new OpenAPI();
+
+    ObjectSchema schema = new ObjectSchema();
+    schema.setName("User");
+    schema.addProperty("name", new StringSchema());
+    openAPI.setComponents(new Components());
+    openAPI.getComponents().addSchemas("User", schema);
+
+    ObjectSchema ref = new ObjectSchema();
+    ref.set$ref(Components.COMPONENTS_SCHEMAS_REF + "User");
+
+    SchemaToProtoGenerator generator =
+        new SchemaToProtoGenerator("test.object", openAPI, ref, "input");
+    Proto proto = generator.convert();
+    assertEquals("""
+        syntax = "proto3";
+        package test.object;
+
+        message User {
+          string name = 1;
+        }
+
+        //@WrapProperty
+        message input {
+          User value = 1;
+        }
+
+        """, new ProtoToStringGenerator(proto).protoToString());
+  }
+
+  static class Model {
+    public String name;
+
+    public int age;
+  }
+
+  interface SpringMvcSchema {
+    @PostMapping("/testInt")
+    int testInt(@RequestBody int param);
+
+    @PostMapping("/testModel")
+    Model testModel(@RequestBody Model model);
+  }
+
+  @Test
+  public void test_springmvc_int_schema_correct() {
+    SpringmvcSwaggerGenerator generator = new 
SpringmvcSwaggerGenerator(SpringMvcSchema.class);
+    OpenAPI openAPI = generator.generate();
+
+    SchemaToProtoGenerator protoGenerator =
+        new SchemaToProtoGenerator("test.int", openAPI,
+            openAPI.getPaths().get("/testInt").getPost()
+                .getRequestBody().getContent().get(MediaType.APPLICATION_JSON)
+                .getSchema(), "testIntRequest");
+    Proto proto = protoGenerator.convert();
+    assertEquals("""
+        syntax = "proto3";
+        package test.int;
+                
+        //@WrapProperty
+        message testIntRequest {
+          int32 value = 1;
+        }
+
+        """, new ProtoToStringGenerator(proto).protoToString());
+
+    protoGenerator =
+        new SchemaToProtoGenerator("test.int", openAPI,
+            openAPI.getPaths().get("/testInt").getPost()
+                
.getResponses().get("200").getContent().get(MediaType.APPLICATION_JSON)
+                .getSchema(), "testIntResponse");
+    proto = protoGenerator.convert();
+    assertEquals("""
+        syntax = "proto3";
+        package test.int;
+                
+        //@WrapProperty
+        message testIntResponse {
+          int32 value = 1;
+        }
+
+        """, new ProtoToStringGenerator(proto).protoToString());
+  }
+
+
+  @Test
+  public void test_springmvc_model_schema_correct() {
+    SpringmvcSwaggerGenerator generator = new 
SpringmvcSwaggerGenerator(SpringMvcSchema.class);
+    OpenAPI openAPI = generator.generate();
+
+    SchemaToProtoGenerator protoGenerator =
+        new SchemaToProtoGenerator("test.model", openAPI,
+            openAPI.getPaths().get("/testModel").getPost()
+                .getRequestBody().getContent().get(MediaType.APPLICATION_JSON)
+                .getSchema(), "testModelRequest");
+    Proto proto = protoGenerator.convert();
+    assertEquals("""
+        syntax = "proto3";
+        package test.model;
+                
+        message Model {
+          string name = 1;
+          int32 age = 2;
+        }
+                
+        //@WrapProperty
+        message testModelRequest {
+          Model value = 1;
+        }
+
+        """, new ProtoToStringGenerator(proto).protoToString());
+
+    protoGenerator =
+        new SchemaToProtoGenerator("test.model", openAPI,
+            openAPI.getPaths().get("/testModel").getPost()
+                
.getResponses().get("200").getContent().get(MediaType.APPLICATION_JSON)
+                .getSchema(), "testIntResponse");
+    proto = protoGenerator.convert();
+    assertEquals("""
+        syntax = "proto3";
+        package test.model;
+                
+        message Model {
+          string name = 1;
+          int32 age = 2;
+        }
+                
+        //@WrapProperty
+        message testIntResponse {
+          Model value = 1;
+        }
+
+        """, new ProtoToStringGenerator(proto).protoToString());
+  }
+}
diff --git 
a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/SwaggerUtils.java
 
b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/SwaggerUtils.java
index bf9aa56b8..b8da1ed61 100644
--- 
a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/SwaggerUtils.java
+++ 
b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/SwaggerUtils.java
@@ -239,7 +239,7 @@ public final class SwaggerUtils {
           if (!componentSchemas.containsKey(entry.getKey())) {
             componentSchemas.put(entry.getKey(), entry.getValue());
           } else {
-            if 
(!entry.getValue().equals(componentSchemas.get(entry.getKey()))) {
+            if (!schemaEquals(entry.getValue(), 
componentSchemas.get(entry.getKey()))) {
               throw new IllegalArgumentException("duplicate param model: " + 
entry.getKey());
             }
           }
@@ -250,6 +250,65 @@ public final class SwaggerUtils {
     return resolvedSchema.schema;
   }
 
+  // swagger api equals method will compare Map address(extensions)
+  // and is not applicable for usage.
+  public static int schemaHashCode(Schema<?> schema) {
+    int result = schema.getType() != null ? schema.getType().hashCode() : 0;
+    result = result ^ (schema.getFormat() != null ? 
schema.getFormat().hashCode() : 0);
+    result = result ^ (schema.getName() != null ? schema.getName().hashCode() 
: 0);
+    result = result ^ (schema.get$ref() != null ? schema.get$ref().hashCode() 
: 0);
+    result = result ^ (schema.getItems() != null ? 
schemaHashCode(schema.getItems()) : 0);
+    result = result ^ (schema.getAdditionalItems() != null ? 
schemaHashCode(schema.getAdditionalItems()) : 0);
+    result = result ^ (schema.getProperties() != null ? 
propertiesHashCode(schema.getProperties()) : 0);
+    return result;
+  }
+
+  private static int propertiesHashCode(Map<String, Schema> properties) {
+    int result = 0;
+    for (Entry<String, Schema> entry : properties.entrySet()) {
+      result = result ^ (entry.getKey().hashCode() ^ 
schemaHashCode(entry.getValue()));
+    }
+    return result;
+  }
+
+  // swagger api equals method will compare Map address(extensions)
+  // and is not applicable for usage.
+  public static boolean schemaEquals(Schema<?> schema1, Schema<?> schema2) {
+    if (schema1 == null && schema2 == null) {
+      return true;
+    }
+    if (schema1 == null || schema2 == null) {
+      return false;
+    }
+    return StringUtils.equals(schema1.getType(), schema2.getType())
+        && StringUtils.equals(schema1.getFormat(), schema2.getFormat())
+        && StringUtils.equals(schema1.getName(), schema2.getName())
+        && StringUtils.equals(schema1.get$ref(), schema2.get$ref())
+        && schemaEquals(schema1.getItems(), schema2.getItems())
+        && schemaEquals(schema1.getAdditionalItems(), 
schema2.getAdditionalItems())
+        && propertiesEquals(schema1.getProperties(), schema2.getProperties());
+  }
+
+  public static boolean propertiesEquals(Map<String, Schema> properties1, 
Map<String, Schema> properties2) {
+    if (properties1 == null && properties2 == null) {
+      return true;
+    }
+    if (properties1 == null || properties2 == null) {
+      return false;
+    }
+    if (properties1.size() != properties2.size()) {
+      return false;
+    }
+    boolean result = true;
+    for (String key : properties1.keySet()) {
+      if (!schemaEquals(properties1.get(key), properties2.get(key))) {
+        result = false;
+        break;
+      }
+    }
+    return result;
+  }
+
   public static Schema getSchema(OpenAPI swagger, String ref) {
     return 
swagger.getComponents().getSchemas().get(ref.substring(Components.COMPONENTS_SCHEMAS_REF.length()));
   }
@@ -310,11 +369,6 @@ public final class SwaggerUtils {
     return (T) vendorExtensions.get(key);
   }
 
-  public static boolean isBean(RequestBody body) {
-    MediaType type = body.getContent().values().iterator().next();
-    return type.getSchema().get$ref() != null;
-  }
-
   public static boolean isBean(Type type) {
     if (type == null) {
       return false;
@@ -362,23 +416,6 @@ public final class SwaggerUtils {
     }
   }
 
-  public static void updateConsumes(Operation operation, String[] consumes) {
-    if (consumes == null || consumes.length == 0) {
-      return;
-    }
-    if (operation.getRequestBody() == null) {
-      operation.setRequestBody(new RequestBody());
-    }
-    if (operation.getRequestBody().getContent() == null) {
-      operation.getRequestBody().setContent(new Content());
-    }
-    for (String consume : consumes) {
-      if (operation.getRequestBody().getContent().get(consume) == null) {
-        operation.getRequestBody().getContent().addMediaType(consume, new 
MediaType());
-      }
-    }
-  }
-
   public static boolean methodExists(PathItem pathItem, String httpMethod) {
     PathItem.HttpMethod method = PathItem.HttpMethod.valueOf(httpMethod);
     return switch (method) {

Reply via email to