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

alexstocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git


The following commit(s) were added to refs/heads/develop by this push:
     new cd737b375 feat(tools): help triple protocol generate OpenAPI docs 
(#2951)
cd737b375 is described below

commit cd737b37561090039e6c5b5314a1f5a22201aa58
Author: marsevilspirit <[email protected]>
AuthorDate: Sat Aug 2 18:22:17 2025 +0800

    feat(tools): help triple protocol generate OpenAPI docs (#2951)
    
    * feat(openapi): init commit
    
    * feat(openapi): init commit
    
    * feat(openapi): add triple error components
    
    * feat(openapi): add google.protobuf.any schema
    
    * add service field components
    
    * perf(openapi): remove useless json schema function
    
    * style(converter): change to libopenapiutil
    
    * feat(converter): add response body
    
    * feat(yaml): set indent 2 space
    
    * refactor(openapi): same with dubbo openapi
    
    * feat(openapi): add servers
    
    * feat(openapi): add requestbody requested field
    
    * fix(openapi): sort messages
    
    * docs(converter): add comments
    
    * feat(openapi): add more status code
    
    * refactor(openapi): same with dubbo openapi doc
    
    * perf(converter): delete useless field
    
    * feat(openapi): add .gitignore file
    
    * feat(openapi): add Makefile
    
    * feat(openapi): add display version
    
    * docs(readme): update readme file
    
    * import format
    
    * fix(readme): delete useless fields.
    
    * perf(converter): delete some useless fuctions.
    
    * refactor(openapi): Optimize code structure and reduce redundant code.
    
    * feat(openapi): add json format
    
    * style(openapi): rename to returnToProtoc
    
    * fix(openapi): change to right go package
    
    * style(converter): rename
    
    * docs(converter): add some comments
    
    * fix(options): change wrong grammar
    
    * fix(makefile): fix wrong makefile
    
    * style(converter): improve readability
    
    * style(constant): use constant
    
    * perf(converter): delete useless code
    
    * fix(license): fix license header check ci error
    
    * chore(version): update version constant
---
 .licenserc.yaml                                    |   1 +
 tools/protoc-gen-triple-openapi/.gitignore         |   1 +
 tools/protoc-gen-triple-openapi/Makefile           |  53 +++++
 tools/protoc-gen-triple-openapi/README.md          |  49 +++++
 tools/protoc-gen-triple-openapi/constant/code.go   |  36 ++++
 tools/protoc-gen-triple-openapi/constant/format.go |  30 +++
 .../protoc-gen-triple-openapi/constant/openapi.go  |  41 ++++
 .../protoc-gen-triple-openapi/example/greet.proto  |  44 ++++
 .../example/greet.triple.openapi.json              | 181 ++++++++++++++++
 .../example/greet.triple.openapi.yaml              | 113 ++++++++++
 tools/protoc-gen-triple-openapi/go.mod             |  20 ++
 tools/protoc-gen-triple-openapi/go.sum             |  55 +++++
 .../internal/converter/components.go               |  55 +++++
 .../internal/converter/convert.go                  | 237 +++++++++++++++++++++
 .../internal/converter/schema/schema.go            | 100 +++++++++
 .../internal/converter/schema/util.go              | 158 ++++++++++++++
 .../internal/converter/util.go                     |  30 +++
 .../internal/options/options.go                    |  59 +++++
 tools/protoc-gen-triple-openapi/main.go            |  68 ++++++
 tools/protoc-gen-triple-openapi/version.go         |  20 ++
 20 files changed, 1351 insertions(+)

diff --git a/.licenserc.yaml b/.licenserc.yaml
index 4b5a69f3f..f7ef89379 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -47,6 +47,7 @@ header:
     - 'LICENSE'
     - 'NOTICE'
     - '**/*.hessian2.go'
+    - '**/*.triple.openapi.yaml'
     - 'protocol/triple/triple_protocol'
     - 'protocol/triple/reflection/serverreflection.go'
 
diff --git a/tools/protoc-gen-triple-openapi/.gitignore 
b/tools/protoc-gen-triple-openapi/.gitignore
new file mode 100644
index 000000000..d8567884b
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/.gitignore
@@ -0,0 +1 @@
+protoc-gen-triple-openapi
diff --git a/tools/protoc-gen-triple-openapi/Makefile 
b/tools/protoc-gen-triple-openapi/Makefile
new file mode 100644
index 000000000..1c9ff532b
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/Makefile
@@ -0,0 +1,53 @@
+#
+# 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.
+#
+
+SHELL := bash
+.DELETE_ON_ERROR:
+.SHELLFLAGS := -eu -o pipefail -c
+MAKEFLAGS += --warn-undefined-variables
+MAKEFLAGS += --no-builtin-rules
+
+# Variables
+BINARY_NAME=protoc-gen-triple-openapi
+PROTO_FILE=./example/greet.proto
+OUTPUT_DIR=./example
+
+.PHONY: help
+help:
+       @echo "Usage: make [target]"
+       @echo ""
+       @echo "Targets:"
+       @echo "  build     Build the binary"
+       @echo "  generate  Generate OpenAPI spec from the example proto file"
+       @echo "  clean     Remove generated files"
+       @echo "  help      Show this help message"
+
+.PHONY: build
+build:
+       @echo "Building $(BINARY_NAME)..."
+       go build -o $(BINARY_NAME) .
+
+.PHONY: generate
+generate: build
+       @echo "Generating OpenAPI spec from $(PROTO_FILE)..."
+       protoc --plugin=$(BINARY_NAME)=./$(BINARY_NAME) --triple-openapi_out=. 
$(PROTO_FILE)
+       protoc --plugin=$(BINARY_NAME)=./$(BINARY_NAME) --triple-openapi_out=. 
--triple-openapi_opt=format=json $(PROTO_FILE)
+
+.PHONY: clean
+clean:
+       @echo "Cleaning up..."
+       rm -f $(BINARY_NAME) $(OUTPUT_DIR)/greet.triple.openapi.yaml 
$(OUTPUT_DIR)/greet.triple.openapi.json
diff --git a/tools/protoc-gen-triple-openapi/README.md 
b/tools/protoc-gen-triple-openapi/README.md
new file mode 100644
index 000000000..29d59e044
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/README.md
@@ -0,0 +1,49 @@
+# protoc-gen-triple-openapi
+
+`protoc-gen-triple-openapi` is a protoc plugin to generate OpenAPI v3 
documentation from protobuf files that use the Triple protocol.
+
+## Getting Started
+
+### Installation
+
+To install `protoc-gen-triple-openapi`, you need to have Go installed and 
configured. Then, you can use the following command to install the plugin:
+
+```shell
+go install github.com/apache/dubbo-go/tools/protoc-gen-triple-openapi
+```
+
+## Usage
+
+To use `protoc-gen-triple-openapi`, you need to have a `.proto` file that 
defines your service. For example, you can have a file named `greet.proto` with 
the following content:
+
+```proto
+syntax = "proto3";
+
+package org.apache.dubbo.samples.greet;
+
+option go_package = "github.com/apache/dubbo-go-samples/api";
+
+// The greeting service definition.
+service GreetService {
+  // Sends a greeting
+  rpc Greet(GreetRequest) returns (GreetResponse) {}
+}
+
+// The request message containing the user's name.
+message GreetRequest {
+  string name = 1;
+}
+
+// The response message containing the greetings
+message GreetResponse {
+  string greeting = 1;
+}
+```
+
+Then, you can use the following command to generate the OpenAPI documentation:
+
+```shell
+protoc --triple-openapi_out=. greet.proto
+```
+
+This will generate a file named `greet.openapi.yaml` with the OpenAPI 
documentation.
diff --git a/tools/protoc-gen-triple-openapi/constant/code.go 
b/tools/protoc-gen-triple-openapi/constant/code.go
new file mode 100644
index 000000000..6a05791cf
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/constant/code.go
@@ -0,0 +1,36 @@
+/*
+ * 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 constant
+
+// status code 200
+const (
+       StatusCode200            = "200"
+       StatusCode200Description = "OK"
+)
+
+// status code 400
+const (
+       StatusCode400            = "400"
+       StatusCode400Description = "Bad Request"
+)
+
+// status code 500
+const (
+       StatusCode500            = "500"
+       StatusCode500Description = "Internal Server Error"
+)
diff --git a/tools/protoc-gen-triple-openapi/constant/format.go 
b/tools/protoc-gen-triple-openapi/constant/format.go
new file mode 100644
index 000000000..53ca92875
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/constant/format.go
@@ -0,0 +1,30 @@
+/*
+ * 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 constant
+
+// yaml format
+const (
+       YAMLFormat       = "yaml"
+       YAMLFormatSuffix = ".triple.openapi.yaml"
+)
+
+// json format
+const (
+       JSONFormat = "json"
+       JSONFormatSuffix = ".triple.openapi.json"
+)
diff --git a/tools/protoc-gen-triple-openapi/constant/openapi.go 
b/tools/protoc-gen-triple-openapi/constant/openapi.go
new file mode 100644
index 000000000..9f49aee5c
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/constant/openapi.go
@@ -0,0 +1,41 @@
+/*
+ * 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 constant
+
+// OpenAPI Document Version
+const (
+       OpenAPIDocVersion = "3.0.1"
+)
+
+// OpenAPI Document Info
+const (
+       OpenAPIDocInfoTitle       = "Dubbo-go OpenAPI"
+       OpenAPIDocInfoVersion     = "v1"
+       OpenAPIDocInfoDescription = "dubbo-go generate OpenAPI docs."
+)
+
+// OpenAPI Document Server
+const (
+       OpenAPIDocServerURL        = "http://0.0.0.0:20000";
+       OpenAPIDocServerDesription = "Dubbo-go Default Server"
+)
+
+// OpenAPI Document Components
+const (
+       OpenAPIDocComponentsSchemaSuffix = "#/components/schemas/"
+)
diff --git a/tools/protoc-gen-triple-openapi/example/greet.proto 
b/tools/protoc-gen-triple-openapi/example/greet.proto
new file mode 100644
index 000000000..f333b7edb
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/example/greet.proto
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package greet;
+
+option go_package = 
"github.com/apache/dubbo-go/tools/protoc-gen-triple-openapi/example;greet";
+
+message GreetRequest {
+  string name = 1;
+}
+
+message GreetResponse {
+  string greeting = 1;
+}
+
+message GreetUserRequest {
+  string name = 1;
+}
+
+message GreetUserResponse {
+  string greeting = 1;
+}
+
+service GreetService {
+  rpc Greet(GreetRequest) returns (GreetResponse) {}
+
+  rpc GreetUser(GreetUserRequest) returns (GreetUserResponse) {}
+}
diff --git a/tools/protoc-gen-triple-openapi/example/greet.triple.openapi.json 
b/tools/protoc-gen-triple-openapi/example/greet.triple.openapi.json
new file mode 100644
index 000000000..b0f578fe3
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/example/greet.triple.openapi.json
@@ -0,0 +1,181 @@
+{
+  "openapi": "3.0.1",
+  "info": {
+    "title": "Dubbo-go OpenAPI",
+    "description": "dubbo-go generate OpenAPI docs.",
+    "version": "v1"
+  },
+  "servers": [
+    {
+      "url": "http://0.0.0.0:20000";,
+      "description": "Dubbo-go Default Server"
+    }
+  ],
+  "paths": {
+    "/greet.GreetService/Greet": {
+      "post": {
+        "tags": [
+          "greet.GreetService"
+        ],
+        "operationId": "Greet",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/greet.GreetRequest"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/greet.GreetResponse"
+                }
+              }
+            }
+          },
+          "400": {
+            "description": "Bad Request",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ErrorResponse"
+                }
+              }
+            }
+          },
+          "500": {
+            "description": "Internal Server Error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ErrorResponse"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/greet.GreetService/GreetUser": {
+      "post": {
+        "tags": [
+          "greet.GreetService"
+        ],
+        "operationId": "GreetUser",
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/greet.GreetUserRequest"
+              }
+            }
+          },
+          "required": true
+        },
+        "responses": {
+          "200": {
+            "description": "OK",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/greet.GreetUserResponse"
+                }
+              }
+            }
+          },
+          "400": {
+            "description": "Bad Request",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ErrorResponse"
+                }
+              }
+            }
+          },
+          "500": {
+            "description": "Internal Server Error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/ErrorResponse"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "greet.GreetRequest": {
+        "type": "object",
+        "properties": {
+          "name": {
+            "type": "string",
+            "title": "name"
+          }
+        },
+        "title": "GreetRequest"
+      },
+      "greet.GreetResponse": {
+        "type": "object",
+        "properties": {
+          "greeting": {
+            "type": "string",
+            "title": "greeting"
+          }
+        },
+        "title": "GreetResponse"
+      },
+      "greet.GreetUserRequest": {
+        "type": "object",
+        "properties": {
+          "name": {
+            "type": "string",
+            "title": "name"
+          }
+        },
+        "title": "GreetUserRequest"
+      },
+      "greet.GreetUserResponse": {
+        "type": "object",
+        "properties": {
+          "greeting": {
+            "type": "string",
+            "title": "greeting"
+          }
+        },
+        "title": "GreetUserResponse"
+      },
+      "ErrorResponse": {
+        "type": "object",
+        "properties": {
+          "status": {
+            "type": "string",
+            "description": "The status code."
+          },
+          "message": {
+            "type": "string",
+            "description": "A developer-facing error message."
+          }
+        },
+        "title": "ErrorResponse",
+        "description": "Error type returned by triple: 
https://cn.dubbo.apache.org/zh-cn/overview/reference/protocols/triple-spec/";
+      }
+    }
+  },
+  "security": [],
+  "tags": [
+    {
+      "name": "greet.GreetService"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tools/protoc-gen-triple-openapi/example/greet.triple.openapi.yaml 
b/tools/protoc-gen-triple-openapi/example/greet.triple.openapi.yaml
new file mode 100644
index 000000000..7f6f7979d
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/example/greet.triple.openapi.yaml
@@ -0,0 +1,113 @@
+openapi: 3.0.1
+info:
+  title: Dubbo-go OpenAPI
+  description: dubbo-go generate OpenAPI docs.
+  version: v1
+servers:
+  - url: http://0.0.0.0:20000
+    description: Dubbo-go Default Server
+paths:
+  /greet.GreetService/Greet:
+    post:
+      tags:
+        - greet.GreetService
+      operationId: Greet
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/greet.GreetRequest'
+        required: true
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/greet.GreetResponse'
+        "400":
+          description: Bad Request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+  /greet.GreetService/GreetUser:
+    post:
+      tags:
+        - greet.GreetService
+      operationId: GreetUser
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/greet.GreetUserRequest'
+        required: true
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/greet.GreetUserResponse'
+        "400":
+          description: Bad Request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
+components:
+  schemas:
+    greet.GreetRequest:
+      type: object
+      properties:
+        name:
+          type: string
+          title: name
+      title: GreetRequest
+    greet.GreetResponse:
+      type: object
+      properties:
+        greeting:
+          type: string
+          title: greeting
+      title: GreetResponse
+    greet.GreetUserRequest:
+      type: object
+      properties:
+        name:
+          type: string
+          title: name
+      title: GreetUserRequest
+    greet.GreetUserResponse:
+      type: object
+      properties:
+        greeting:
+          type: string
+          title: greeting
+      title: GreetUserResponse
+    ErrorResponse:
+      type: object
+      properties:
+        status:
+          type: string
+          description: The status code.
+        message:
+          type: string
+          description: A developer-facing error message.
+      title: ErrorResponse
+      description: 'Error type returned by triple: 
https://cn.dubbo.apache.org/zh-cn/overview/reference/protocols/triple-spec/'
+security: []
+tags:
+  - name: greet.GreetService
diff --git a/tools/protoc-gen-triple-openapi/go.mod 
b/tools/protoc-gen-triple-openapi/go.mod
new file mode 100644
index 000000000..5d9bf1ccb
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/go.mod
@@ -0,0 +1,20 @@
+module dubbo.apache.org/dubbo-go/v3/tools/protoc-gen-triple-openapi
+
+go 1.24.5
+
+require (
+       github.com/golang/protobuf v1.5.0
+       github.com/pb33f/libopenapi v0.23.0
+       github.com/swaggest/jsonschema-go v0.3.78
+       google.golang.org/protobuf v1.36.6
+       gopkg.in/yaml.v3 v3.0.1
+)
+
+require (
+       github.com/bahlo/generic-list-go v0.2.0 // indirect
+       github.com/buger/jsonparser v1.1.1 // indirect
+       github.com/mailru/easyjson v0.7.7 // indirect
+       github.com/speakeasy-api/jsonpath v0.6.2 // indirect
+       github.com/swaggest/refl v1.4.0 // indirect
+       github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd 
// indirect
+)
diff --git a/tools/protoc-gen-triple-openapi/go.sum 
b/tools/protoc-gen-triple-openapi/go.sum
new file mode 100644
index 000000000..f0df01c56
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/go.sum
@@ -0,0 +1,55 @@
+github.com/bahlo/generic-list-go v0.2.0 
h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
+github.com/bahlo/generic-list-go v0.2.0/go.mod 
h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
+github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE=
+github.com/bool64/dev v0.2.39/go.mod 
h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
+github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
+github.com/bool64/shared v0.1.5/go.mod 
h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs=
+github.com/buger/jsonparser v1.1.1 
h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
+github.com/buger/jsonparser v1.1.1/go.mod 
h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
+github.com/davecgh/go-spew v1.1.1 
h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang/protobuf v1.5.0 
h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
+github.com/golang/protobuf v1.5.0/go.mod 
h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod 
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/iancoleman/orderedmap v0.3.0 
h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
+github.com/iancoleman/orderedmap v0.3.0/go.mod 
h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
+github.com/josharian/intern v1.0.0/go.mod 
h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod 
h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod 
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mailru/easyjson v0.7.7 
h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod 
h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/pb33f/libopenapi v0.23.0 
h1:gZP1zrtvMwk7spGDTZf4OufKgpOH8M9gHAZ77rf39Oo=
+github.com/pb33f/libopenapi v0.23.0/go.mod 
h1:utT5sD2/mnN7YK68FfZT5yEPbI1wwRBpSS4Hi0oOrBU=
+github.com/pmezard/go-difflib v1.0.0 
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
+github.com/sergi/go-diff v1.3.1/go.mod 
h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
+github.com/speakeasy-api/jsonpath v0.6.2 
h1:Mys71yd6u8kuowNCR0gCVPlVAHCmKtoGXYoAtcEbqXQ=
+github.com/speakeasy-api/jsonpath v0.6.2/go.mod 
h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw=
+github.com/stretchr/testify v1.10.0 
h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod 
h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/swaggest/assertjson v1.9.0 
h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ=
+github.com/swaggest/assertjson v1.9.0/go.mod 
h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU=
+github.com/swaggest/jsonschema-go v0.3.78 
h1:5+YFQrLxOR8z6CHvgtZc42WRy/Q9zRQQ4HoAxlinlHw=
+github.com/swaggest/jsonschema-go v0.3.78/go.mod 
h1:4nniXBuE+FIGkOGuidjOINMH7OEqZK3HCSbfDuLRI0g=
+github.com/swaggest/refl v1.4.0 h1:CftOSdTqRqs100xpFOT/Rifss5xBV/CT0S/FN60Xe9k=
+github.com/swaggest/refl v1.4.0/go.mod 
h1:4uUVFVfPJ0NSX9FPwMPspeHos9wPFlCMGoPRllUbpvA=
+github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd 
h1:dLuIF2kX9c+KknGJUdJi1Il1SDiTSK158/BB9kdgAew=
+github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd/go.mod 
h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo=
+github.com/yudai/gojsondiff v1.0.0 
h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
+github.com/yudai/gojsondiff v1.0.0/go.mod 
h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
+github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 
h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
+github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod 
h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 
h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod 
h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.36.6 
h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod 
h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 
h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/tools/protoc-gen-triple-openapi/internal/converter/components.go 
b/tools/protoc-gen-triple-openapi/internal/converter/components.go
new file mode 100644
index 000000000..2a2bca07c
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/internal/converter/components.go
@@ -0,0 +1,55 @@
+/*
+ * 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 converter
+
+import (
+       "github.com/pb33f/libopenapi/datamodel/high/base"
+       openapimodel "github.com/pb33f/libopenapi/datamodel/high/v3"
+       "github.com/pb33f/libopenapi/orderedmap"
+
+       "google.golang.org/protobuf/reflect/protoreflect"
+)
+
+import (
+       
"dubbo.apache.org/dubbo-go/v3/tools/protoc-gen-triple-openapi/internal/converter/schema"
+)
+
+func generateComponents(fd protoreflect.FileDescriptor) 
(*openapimodel.Components, error) {
+       components := &openapimodel.Components{
+               Schemas: schema.GenerateFileSchemas(fd),
+       }
+
+       // triple error component
+       errorResponseProps := orderedmap.New[string, *base.SchemaProxy]()
+       errorResponseProps.Set("status", base.CreateSchemaProxy(&base.Schema{
+               Description: "The status code.",
+               Type:        []string{"string"},
+       }))
+       errorResponseProps.Set("message", base.CreateSchemaProxy(&base.Schema{
+               Description: "A developer-facing error message.",
+               Type:        []string{"string"},
+       }))
+       components.Schemas.Set("ErrorResponse", 
base.CreateSchemaProxy(&base.Schema{
+               Title:       "ErrorResponse",
+               Description: `Error type returned by triple: 
https://cn.dubbo.apache.org/zh-cn/overview/reference/protocols/triple-spec/`,
+               Properties:  errorResponseProps,
+               Type:        []string{"object"},
+       }))
+
+       return components, nil
+}
diff --git a/tools/protoc-gen-triple-openapi/internal/converter/convert.go 
b/tools/protoc-gen-triple-openapi/internal/converter/convert.go
new file mode 100644
index 000000000..53886d3d3
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/internal/converter/convert.go
@@ -0,0 +1,237 @@
+/*
+ * 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 converter
+
+import (
+       "fmt"
+       "io"
+       "path/filepath"
+       "strings"
+)
+
+import (
+       plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
+
+       "github.com/pb33f/libopenapi/datamodel/high/base"
+       openapimodel "github.com/pb33f/libopenapi/datamodel/high/v3"
+       "github.com/pb33f/libopenapi/index"
+       "github.com/pb33f/libopenapi/orderedmap"
+
+       "google.golang.org/protobuf/proto"
+
+       "google.golang.org/protobuf/reflect/protodesc"
+
+       "google.golang.org/protobuf/types/descriptorpb"
+       "google.golang.org/protobuf/types/pluginpb"
+
+       "gopkg.in/yaml.v3"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/tools/protoc-gen-triple-openapi/constant"
+       
"dubbo.apache.org/dubbo-go/v3/tools/protoc-gen-triple-openapi/internal/options"
+)
+
+func ConvertFrom(r io.Reader) (*pluginpb.CodeGeneratorResponse, error) {
+       in, err := io.ReadAll(r)
+       if err != nil {
+               return nil, fmt.Errorf("failed to read request: %w", err)
+       }
+
+       req := &pluginpb.CodeGeneratorRequest{}
+       if err = proto.Unmarshal(in, req); err != nil {
+               return nil, fmt.Errorf("can't unmarshal input: %w", err)
+       }
+
+       return convert(req)
+}
+
+func convert(req *pluginpb.CodeGeneratorRequest) 
(*pluginpb.CodeGeneratorResponse, error) {
+       opts, err := options.Generate(req.GetParameter())
+
+       genFiles := make(map[string]struct{}, len(req.FileToGenerate))
+       for _, file := range req.FileToGenerate {
+               genFiles[file] = struct{}{}
+       }
+
+       resolver, err := protodesc.NewFiles(&descriptorpb.FileDescriptorSet{
+               File: req.GetProtoFile(),
+       })
+       if err != nil {
+               return nil, err
+       }
+
+       // TODO: consider basic OpenAPI file
+
+       doc := &openapimodel.Document{
+               Version: constant.OpenAPIDocVersion,
+               Info:    &base.Info{},
+               Paths: &openapimodel.Paths{
+                       PathItems:  orderedmap.New[string, 
*openapimodel.PathItem](),
+                       Extensions: orderedmap.New[string, *yaml.Node](),
+               },
+               Security:   []*base.SecurityRequirement{},
+               Extensions: orderedmap.New[string, *yaml.Node](),
+               Webhooks:   orderedmap.New[string, *openapimodel.PathItem](),
+               Index:      &index.SpecIndex{},
+               Rolodex:    &index.Rolodex{},
+               Components: &openapimodel.Components{},
+       }
+
+       files := []*pluginpb.CodeGeneratorResponse_File{}
+       for _, fileDesc := range req.GetProtoFile() {
+               if _, ok := genFiles[fileDesc.GetName()]; !ok {
+                       continue
+               }
+
+               fd, err := resolver.FindFileByPath(fileDesc.GetName())
+               if err != nil {
+                       return nil, err
+               }
+
+               // handle openapi info
+               doc.Info = &base.Info{
+                       Title:       constant.OpenAPIDocInfoTitle,
+                       Version:     constant.OpenAPIDocInfoVersion,
+                       Description: constant.OpenAPIDocInfoDescription,
+               }
+
+               // handle openapi servers
+               doc.Servers = append(doc.Servers, &openapimodel.Server{
+                       URL:         constant.OpenAPIDocServerURL,
+                       Description: constant.OpenAPIDocServerDesription,
+               })
+
+               // handle openapi components
+               doc.Components, err = generateComponents(fd)
+               if err != nil {
+                       return nil, err
+               }
+
+               items := orderedmap.New[string, *openapimodel.PathItem]()
+               tags := []*base.Tag{}
+
+               services := fd.Services()
+               for i := 0; i < services.Len(); i++ {
+                       service := services.Get(i)
+
+                       tags = append(tags, &base.Tag{
+                               Name: string(service.FullName()),
+                               // TODO: add serivce description
+                       })
+
+                       methods := service.Methods()
+                       for j := 0; j < methods.Len(); j++ {
+                               md := methods.Get(j)
+
+                               // operation
+                               operation := &openapimodel.Operation{
+                                       OperationId: string(md.Name()),
+                                       Tags:        
[]string{string(service.FullName())},
+                                       // TODO: add operation description
+                               }
+
+                               // RequestBody
+                               isRequired := true
+                               requestSchema := 
base.CreateSchemaProxyRef(constant.OpenAPIDocComponentsSchemaSuffix +
+                                       string(md.Input().FullName()))
+                               operation.RequestBody = 
&openapimodel.RequestBody{
+                                       // TODO: description
+                                       Content:  makeMediaTypes(requestSchema),
+                                       Required: &isRequired,
+                               }
+
+                               // Responses
+                               codeMap := orderedmap.New[string, 
*openapimodel.Response]()
+
+                               // status code 200
+                               response200Schema := 
base.CreateSchemaProxyRef(constant.OpenAPIDocComponentsSchemaSuffix +
+                                       string(md.Output().FullName()))
+                               codeMap.Set(constant.StatusCode200, 
&openapimodel.Response{
+                                       Description: 
constant.StatusCode200Description,
+                                       Content:     
makeMediaTypes(response200Schema),
+                               })
+
+                               // status code 400
+                               codeMap.Set(constant.StatusCode400, 
newErrorResponse(constant.StatusCode400Description))
+
+                               // status code 500
+                               codeMap.Set(constant.StatusCode500, 
newErrorResponse(constant.StatusCode500Description))
+
+                               operation.Responses = &openapimodel.Responses{
+                                       Codes: codeMap,
+                               }
+
+                               item := &openapimodel.PathItem{}
+                               item.Post = operation
+
+                               
items.Set("/"+string(service.FullName())+"/"+string(md.Name()), item)
+                       }
+               }
+               doc.Paths.PathItems = items
+               doc.Tags = tags
+
+               content, err := formatOpenapiDoc(opts, doc)
+               if err != nil {
+                       return nil, err
+               }
+
+               var filename string
+
+               name := *fileDesc.Name
+               switch opts.Format {
+               case constant.YAMLFormat:
+                       filename = strings.TrimSuffix(name, filepath.Ext(name)) 
+ constant.YAMLFormatSuffix
+               case constant.JSONFormat:
+                       filename = strings.TrimSuffix(name, filepath.Ext(name)) 
+ constant.JSONFormatSuffix
+               }
+               files = append(files, &pluginpb.CodeGeneratorResponse_File{
+                       Name:              &filename,
+                       Content:           &content,
+                       GeneratedCodeInfo: &descriptorpb.GeneratedCodeInfo{},
+               })
+       }
+
+       return &plugin.CodeGeneratorResponse{
+               File: files,
+       }, nil
+}
+
+func formatOpenapiDoc(opts options.Options, doc *openapimodel.Document) 
(string, error) {
+       switch opts.Format {
+       case constant.YAMLFormat:
+               return string(doc.RenderWithIndention(2)), nil
+       case constant.JSONFormat:
+               b, err := doc.RenderJSON("  ")
+               if err != nil {
+                       return "", err
+               }
+               return string(b), nil
+       default:
+               return "", fmt.Errorf("unknown format: %s", opts.Format)
+       }
+}
+
+func newErrorResponse(description string) *openapimodel.Response {
+       responseSchema := 
base.CreateSchemaProxyRef(constant.OpenAPIDocComponentsSchemaSuffix + 
"ErrorResponse")
+       responseMediaType := makeMediaTypes(responseSchema)
+       return &openapimodel.Response{
+               Description: description,
+               Content:     responseMediaType,
+       }
+}
diff --git 
a/tools/protoc-gen-triple-openapi/internal/converter/schema/schema.go 
b/tools/protoc-gen-triple-openapi/internal/converter/schema/schema.go
new file mode 100644
index 000000000..c7b5f9aee
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/internal/converter/schema/schema.go
@@ -0,0 +1,100 @@
+/*
+ * 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 schema
+
+import (
+       "sort"
+)
+
+import (
+       "github.com/pb33f/libopenapi/datamodel/high/base"
+       "github.com/pb33f/libopenapi/orderedmap"
+
+       "google.golang.org/protobuf/reflect/protoreflect"
+)
+
+func GenerateFileSchemas(tt protoreflect.FileDescriptor) 
*orderedmap.Map[string, *base.SchemaProxy] {
+       messages := make(map[protoreflect.MessageDescriptor]struct{})
+       collectMessages(tt, messages)
+
+       schemas := orderedmap.New[string, *base.SchemaProxy]()
+
+       // It is necessary to sort multiple messages here,
+       // otherwise the components will be out of order.
+       sortedMessages := make([]protoreflect.MessageDescriptor, 0, 
len(messages))
+       for message := range messages {
+               sortedMessages = append(sortedMessages, message)
+       }
+       sort.Slice(sortedMessages, func(i, j int) bool {
+               return sortedMessages[i].FullName() < 
sortedMessages[j].FullName()
+       })
+
+       for _, message := range sortedMessages {
+               id, schema := messageToSchema(message)
+               if schema != nil {
+                       schemas.Set(id, base.CreateSchemaProxy(schema))
+               }
+       }
+
+       return schemas
+}
+
+func collectMessages(fd protoreflect.FileDescriptor, messages 
map[protoreflect.MessageDescriptor]struct{}) {
+       services := fd.Services()
+       for i := 0; i < services.Len(); i++ {
+               service := services.Get(i)
+               methods := service.Methods()
+               for j := 0; j < methods.Len(); j++ {
+                       method := methods.Get(j)
+                       collectMethodMessages(method, messages)
+               }
+       }
+}
+
+func collectMethodMessages(md protoreflect.MethodDescriptor, messages 
map[protoreflect.MessageDescriptor]struct{}) {
+       collectMessage(md.Input(), messages)
+       collectMessage(md.Output(), messages)
+}
+
+func collectMessage(md protoreflect.MessageDescriptor, messages 
map[protoreflect.MessageDescriptor]struct{}) {
+       if md == nil {
+               return
+       }
+
+       if _, ok := messages[md]; ok {
+               return
+       }
+       messages[md] = struct{}{}
+
+       fields := md.Fields()
+       for i := 0; i < fields.Len(); i++ {
+               collectField(fields.Get(i), messages)
+       }
+
+       nestedMessages := md.Messages()
+       for i := 0; i < nestedMessages.Len(); i++ {
+               collectMessage(nestedMessages.Get(i), messages)
+       }
+}
+
+func collectField(fd protoreflect.FieldDescriptor, messages 
map[protoreflect.MessageDescriptor]struct{}) {
+       if fd == nil {
+               return
+       }
+       collectMessage(fd.Message(), messages)
+}
diff --git a/tools/protoc-gen-triple-openapi/internal/converter/schema/util.go 
b/tools/protoc-gen-triple-openapi/internal/converter/schema/util.go
new file mode 100644
index 000000000..4dbca29a7
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/internal/converter/schema/util.go
@@ -0,0 +1,158 @@
+/*
+ * 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 schema
+
+import (
+       "fmt"
+)
+
+import (
+       "github.com/pb33f/libopenapi/datamodel/high/base"
+       "github.com/pb33f/libopenapi/orderedmap"
+
+       "google.golang.org/protobuf/reflect/protoreflect"
+
+       "gopkg.in/yaml.v3"
+)
+
+func messageToSchema(tt protoreflect.MessageDescriptor) (string, *base.Schema) 
{
+       s := &base.Schema{
+               Title: string(tt.Name()),
+               // TODO: add Description
+               Description: "",
+               Type:        []string{"object"},
+       }
+
+       props := orderedmap.New[string, *base.SchemaProxy]()
+
+       fields := tt.Fields()
+       for i := 0; i < fields.Len(); i++ {
+               field := fields.Get(i)
+               // TODO: handle oneof
+               prop := fieldToSchema(base.CreateSchemaProxy(s), field)
+               props.Set(field.JSONName(), prop)
+       }
+
+       s.Properties = props
+
+       return string(tt.FullName()), s
+}
+
+func fieldToSchema(parent *base.SchemaProxy, tt protoreflect.FieldDescriptor) 
*base.SchemaProxy {
+       if tt.IsMap() {
+               // Handle maps
+               root := ScalarFieldToSchema(parent, tt, false)
+               root.Title = string(tt.Name())
+               root.Type = []string{"object"}
+               // TODO: todo
+               root.Description = ""
+               return base.CreateSchemaProxy(root)
+       } else if tt.IsList() {
+               var itemSchema *base.SchemaProxy
+               switch tt.Kind() {
+               case protoreflect.MessageKind:
+                       itemSchema = ReferenceFieldToSchema(parent, tt)
+               case protoreflect.EnumKind:
+                       itemSchema = ReferenceFieldToSchema(parent, tt)
+               default:
+                       itemSchema = 
base.CreateSchemaProxy(ScalarFieldToSchema(parent, tt, true))
+               }
+               s := &base.Schema{
+                       Title:       string(tt.Name()),
+                       ParentProxy: parent,
+                       // TODO: todo
+                       Description: "",
+                       Type:        []string{"array"},
+                       Items:       &base.DynamicValue[*base.SchemaProxy, 
bool]{A: itemSchema},
+               }
+               return base.CreateSchemaProxy(s)
+       } else {
+               switch tt.Kind() {
+               case protoreflect.MessageKind, protoreflect.EnumKind:
+                       msg := ScalarFieldToSchema(parent, tt, false)
+                       ref := ReferenceFieldToSchema(parent, tt)
+                       extensions := orderedmap.New[string, *yaml.Node]()
+                       extensions.Set("$ref", 
CreateStringNode(ref.GetReference()))
+                       msg.Extensions = extensions
+                       return base.CreateSchemaProxy(msg)
+               }
+
+               s := ScalarFieldToSchema(parent, tt, false)
+               return base.CreateSchemaProxy(s)
+       }
+}
+
+func ScalarFieldToSchema(parent *base.SchemaProxy, tt 
protoreflect.FieldDescriptor, inContainer bool) *base.Schema {
+       s := &base.Schema{
+               ParentProxy: parent,
+       }
+       if !inContainer {
+               s.Title = string(tt.Name())
+               // TODO: add description
+       }
+
+       switch tt.Kind() {
+       case protoreflect.BoolKind:
+               s.Type = []string{"boolean"}
+       case protoreflect.Int32Kind, protoreflect.Sfixed32Kind, 
protoreflect.Sint32Kind: // int32 types
+               s.Type = []string{"integer"}
+               s.Format = "int32"
+       case protoreflect.Fixed32Kind, protoreflect.Uint32Kind: // uint32 types
+               s.Type = []string{"integer"}
+       case protoreflect.Int64Kind, protoreflect.Sint64Kind, 
protoreflect.Sfixed64Kind: // int64 types
+               // NOTE: 64-bit integer types can be strings or numbers because 
they sometimes
+               //       cannot fit into a JSON number type
+               s.Type = []string{"integer", "string"}
+               s.Format = "int64"
+       case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: // uint64 types
+               s.Type = []string{"integer", "string"}
+               s.Format = "int64"
+       case protoreflect.DoubleKind:
+               s.Type = []string{"number"}
+               s.Format = "double"
+       case protoreflect.FloatKind:
+               s.Type = []string{"number"}
+               s.Format = "float"
+       case protoreflect.StringKind:
+               s.Type = []string{"string"}
+       case protoreflect.BytesKind:
+               s.Type = []string{"string"}
+               s.Format = "byte"
+       }
+       return s
+}
+
+func ReferenceFieldToSchema(parent *base.SchemaProxy, tt 
protoreflect.FieldDescriptor) *base.SchemaProxy {
+       switch tt.Kind() {
+       case protoreflect.MessageKind:
+               return base.CreateSchemaProxyRef("#/components/schemas/" + 
string(tt.Message().FullName()))
+       case protoreflect.EnumKind:
+               return base.CreateSchemaProxyRef("#/components/schemas/" + 
string(tt.Enum().FullName()))
+       default:
+               panic(fmt.Errorf("ReferenceFieldToSchema called with unknown 
kind: %T", tt.Kind()))
+       }
+}
+
+func CreateStringNode(str string) *yaml.Node {
+       n := &yaml.Node{
+               Kind:  yaml.ScalarNode,
+               Tag:   "!!str",
+               Value: str,
+       }
+       return n
+}
diff --git a/tools/protoc-gen-triple-openapi/internal/converter/util.go 
b/tools/protoc-gen-triple-openapi/internal/converter/util.go
new file mode 100644
index 000000000..5525fc482
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/internal/converter/util.go
@@ -0,0 +1,30 @@
+/*
+ * 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 converter
+
+import (
+       "github.com/pb33f/libopenapi/datamodel/high/base"
+       openapimodel "github.com/pb33f/libopenapi/datamodel/high/v3"
+       "github.com/pb33f/libopenapi/orderedmap"
+)
+
+func makeMediaTypes(schemaProxy *base.SchemaProxy) *orderedmap.Map[string, 
*openapimodel.MediaType] {
+       mediaTypes := orderedmap.New[string, *openapimodel.MediaType]()
+       mediaTypes.Set("application/json", &openapimodel.MediaType{Schema: 
schemaProxy})
+       return mediaTypes
+}
diff --git a/tools/protoc-gen-triple-openapi/internal/options/options.go 
b/tools/protoc-gen-triple-openapi/internal/options/options.go
new file mode 100644
index 000000000..f85677195
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/internal/options/options.go
@@ -0,0 +1,59 @@
+/*
+ * 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 options
+
+import (
+       "fmt"
+       "strings"
+)
+
+type Options struct {
+       // yaml or json
+       Format string
+}
+
+func defaultOptions() Options {
+       return Options{
+               // default yaml format
+               Format: "yaml",
+       }
+}
+
+func Generate(s string) (Options, error) {
+       opts := defaultOptions()
+
+       for _, param := range strings.Split(s, ",") {
+               switch {
+               case param == "":
+               case strings.HasPrefix(param, "format="):
+                       format := param[7:]
+                       switch format {
+                       case "yaml":
+                               opts.Format = "yaml"
+                       case "json":
+                               opts.Format = "json"
+                       default:
+                               return opts, fmt.Errorf("format '%s' is not 
supported", format)
+                       }
+               default:
+                       return opts, fmt.Errorf("invalid parameter: %s", param)
+               }
+       }
+
+       return opts, nil
+}
diff --git a/tools/protoc-gen-triple-openapi/main.go 
b/tools/protoc-gen-triple-openapi/main.go
new file mode 100644
index 000000000..3a9392c73
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/main.go
@@ -0,0 +1,68 @@
+/*
+ * 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 main
+
+import (
+       "flag"
+       "fmt"
+       "log"
+       "os"
+)
+
+import (
+       plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
+
+       "google.golang.org/protobuf/proto"
+)
+
+import (
+       
"dubbo.apache.org/dubbo-go/v3/tools/protoc-gen-triple-openapi/internal/converter"
+)
+
+func main() {
+       showVersion := flag.Bool("version", false, "print the version and exit")
+
+       flag.Parse()
+
+       if *showVersion {
+               fmt.Printf("protoc-gen-triple-openapi %s\n", version)
+               return
+       }
+
+       resp, err := converter.ConvertFrom(os.Stdin)
+       if err != nil {
+               log.Fatalf("Failed to convert from stdin: %v", err)
+       }
+
+       if err := returnToProtoc(resp); err != nil {
+               log.Fatalf("Failed to render response: %v", err)
+       }
+}
+
+func returnToProtoc(resp *plugin.CodeGeneratorResponse) error {
+       data, err := proto.Marshal(resp)
+       if err != nil {
+               return fmt.Errorf("failed to marshal response: %w", err)
+       }
+
+       _, err = os.Stdout.Write(data)
+       if err != nil {
+               return fmt.Errorf("failed to write response: %w", err)
+       }
+       return nil
+}
diff --git a/tools/protoc-gen-triple-openapi/version.go 
b/tools/protoc-gen-triple-openapi/version.go
new file mode 100644
index 000000000..70fd6b76d
--- /dev/null
+++ b/tools/protoc-gen-triple-openapi/version.go
@@ -0,0 +1,20 @@
+/*
+ * 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 main
+
+const version = " 3.3.1-dev"

Reply via email to