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"