From ec2463f2f8f67f4cc436d3604270409cb938987f Mon Sep 17 00:00:00 2001 From: kinggo <1963359402@qq.com> Date: Wed, 16 Oct 2024 12:39:34 +0800 Subject: [PATCH] Revert "feat: swagger2idl plugin (#9)" This reverts commit afce6c58ea3f94c16fbc6ab2e6d588ec08238596. --- README.md | 21 +- README_CN.md | 23 +- common/consts/consts.go | 12 - common/utils/utils.go | 315 ----- go.mod | 1 - go.sum | 2 - licenses/LICENSE-cli.txt | 21 - licenses/LICENSE-kin-openapi.txt | 21 - swagger2idl/LICENSE | 201 ---- swagger2idl/README.md | 84 -- swagger2idl/README_CN.md | 82 -- swagger2idl/converter/converter.go | 42 - swagger2idl/converter/proto_converter.go | 1327 --------------------- swagger2idl/converter/thrift_converter.go | 1297 -------------------- swagger2idl/example/openapi.yaml | 224 ---- swagger2idl/generate/generate.go | 22 - swagger2idl/generate/proto_generate.go | 321 ----- swagger2idl/generate/thrift_generate.go | 293 ----- swagger2idl/go.mod | 26 - swagger2idl/go.sum | 38 - swagger2idl/main.go | 177 --- swagger2idl/parser/parser.go | 46 - swagger2idl/protobuf/protobuf.go | 91 -- swagger2idl/thrift/thrift.go | 90 -- swagger2idl/utils/utils.go | 102 -- 25 files changed, 13 insertions(+), 4866 deletions(-) delete mode 100644 licenses/LICENSE-cli.txt delete mode 100644 licenses/LICENSE-kin-openapi.txt delete mode 100644 swagger2idl/LICENSE delete mode 100644 swagger2idl/README.md delete mode 100644 swagger2idl/README_CN.md delete mode 100644 swagger2idl/converter/converter.go delete mode 100644 swagger2idl/converter/proto_converter.go delete mode 100644 swagger2idl/converter/thrift_converter.go delete mode 100644 swagger2idl/example/openapi.yaml delete mode 100644 swagger2idl/generate/generate.go delete mode 100644 swagger2idl/generate/proto_generate.go delete mode 100644 swagger2idl/generate/thrift_generate.go delete mode 100644 swagger2idl/go.mod delete mode 100644 swagger2idl/go.sum delete mode 100644 swagger2idl/main.go delete mode 100644 swagger2idl/parser/parser.go delete mode 100644 swagger2idl/protobuf/protobuf.go delete mode 100644 swagger2idl/thrift/thrift.go delete mode 100644 swagger2idl/utils/utils.go diff --git a/README.md b/README.md index 8a6ea6a..a9d5768 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,14 @@ English | [中文](README_CN.md) -**Swagger Generate** is a set of plugin tools designed for HTTP and RPC services, supporting the automatic generation of Swagger documentation and integration with Swagger-UI for debugging. Additionally, it provides the ability to convert Swagger documents into Protobuf or Thrift IDL files, simplifying the API development and maintenance process. - -This project is compatible with the [CloudWeGo](https://www.cloudwego.io) ecosystem frameworks such as [Cwgo](https://github.com/cloudwego/cwgo), [Hertz](https://github.com/cloudwego/hertz), and [Kitex](https://github.com/cloudwego/kitex). It offers a convenient toolset for developers to automatically generate Swagger documentation, simplifying the API documentation and debugging process. +**Swagger Generate** is a collection of plugins that generate Swagger documentation and provide Swagger-UI access for debugging HTTP and RPC services. This project is compatible with the [CloudWeGo](https://www.cloudwego.io) ecosystem frameworks such as [Cwgo](https://github.com/cloudwego/cwgo), [Hertz](https://github.com/cloudwego/hertz), and [Kitex](https://github.com/cloudwego/kitex). It offers a convenient toolset for developers to automatically generate Swagger documentation, simplifying the API documentation and debugging process. ## Included Plugins -- **[protoc-gen-http-swagger](https://github.com/hertz-contrib/swagger-generate/tree/main/thrift-gen-rpc-swagger)**: Generates Swagger documentation and provides Swagger UI debugging for HTTP services based on Protobuf. -- **[thrift-gen-http-swagger](https://github.com/hertz-contrib/swagger-generate/tree/main/thrift-gen-http-swagger)**: Generates Swagger documentation and provides Swagger UI debugging for HTTP services based on Thrift. -- **[protoc-gen-rpc-swagger](https://github.com/hertz-contrib/swagger-generate/tree/main/protoc-gen-rpc-swagger)**: Generates Swagger documentation and provides Swagger UI debugging for RPC services based on Protobuf. -- **[thrift-gen-rpc-swagger](https://github.com/hertz-contrib/swagger-generate/tree/main/thrift-gen-rpc-swagger)**: Generates Swagger documentation and provides Swagger UI debugging for RPC services based on Thrift. -- **[swagger2idl](https://github.com/hertz-contrib/swagger-generate/tree/main/swagger2idl)**: Converts Swagger documents into Protobuf or Thrift IDL files. +- **protoc-gen-http-swagger**: Generates Swagger documentation and provides Swagger UI debugging for HTTP services based on Protobuf. +- **thrift-gen-http-swagger**: Generates Swagger documentation and provides Swagger UI debugging for HTTP services based on Thrift. +- **protoc-gen-rpc-swagger**: Generates Swagger documentation and provides Swagger UI debugging for RPC services based on Protobuf. +- **thrift-gen-rpc-swagger**: Generates Swagger documentation and provides Swagger UI debugging for RPC services based on Thrift. ## Key Advantages @@ -20,7 +17,6 @@ This project is compatible with the [CloudWeGo](https://www.cloudwego.io) ecosys - **Integrated Debugging**: The generated Swagger UI can be used directly for service debugging, supporting both HTTP and RPC modes. - **Hertz and Kitex Integration**: Provides seamless documentation generation and debugging support for [Hertz](https://github.com/cloudwego/hertz) and [Kitex](https://github.com/cloudwego/kitex). - **Flexible Annotation Support**: Allows extending the generated Swagger documentation through annotations, supporting OpenAPI annotations such as `openapi.operation`, `openapi.schema`, etc. -- **IDL Conversion**: Supports converting Swagger documents into Protobuf or Thrift IDL files, making it easier for developers to switch between different frameworks. ## Installation @@ -103,13 +99,8 @@ func main() { } } ``` -For more examples, please refer to [kitex_swagger_gen](https://github.com/cloudwego/kitex-examples/tree/main/bizdemo/kitex_swagger_gen) and [hertz_swagger_gen](https://github.com/cloudwego/hertz-examples/tree/main/bizdemo/hertz_swagger_gen). - -### Converting Swagger Documents to IDL Files -```sh -swagger2idl -o my_output.proto -oa -a openapi.yaml -``` +For more examples, please refer to [kitex_swagger_gen](https://github.com/cloudwego/kitex-examples/tree/main/bizdemo/kitex_swagger_gen) and [hertz_swagger_gen](https://github.com/cloudwego/hertz-examples/tree/main/bizdemo/hertz_swagger_gen). ## More Information diff --git a/README_CN.md b/README_CN.md index bacdbc4..00b1fb5 100644 --- a/README_CN.md +++ b/README_CN.md @@ -2,17 +2,14 @@ [English](README.md) | 中文 -**Swagger Generate** 是一组插件工具,专为 HTTP 和 RPC 服务设计,支持自动生成 Swagger 文档,并集成 Swagger-UI 进行调试。此外,它还提供将 Swagger 文档转换为 Protobuf 或 Thrift IDL 文件的功能,简化了 API 开发与维护的流程。 - -该项目适用于 [CloudWeGo](https://www.cloudwego.io) 生态下的 [Cwgo](https://github.com/cloudwego/cwgo)、 [Hertz](https://github.com/cloudwego/hertz) 和 [Kitex](https://github.com/cloudwego/kitex) 框架。它提供了一套便捷的工具来帮助开发者自动生成 Swagger 文档,从而简化 API 文档编写及调试过程。 +**Swagger Generate** 是一个为 HTTP 和 RPC 服务生成 Swagger 文档及 Swagger-UI 访问调试的插件集合。该项目适用于 [CloudWeGo](https://www.cloudwego.io) 生态下的 [Cwgo](https://github.com/cloudwego/cwgo)、 [Hertz](https://github.com/cloudwego/hertz) 和 [Kitex](https://github.com/cloudwego/kitex) 框架。它提供了一套便捷的工具来帮助开发者自动生成 Swagger 文档,从而简化 API 文档编写及调试过程。 ## 包含的插件 -- **[protoc-gen-http-swagger](https://github.com/hertz-contrib/swagger-generate/tree/main/thrift-gen-rpc-swagger)**:为基于 Protobuf 的 HTTP 服务生成 Swagger 文档和 Swagger UI 进行调试。 -- **[thrift-gen-http-swagger](https://github.com/hertz-contrib/swagger-generate/tree/main/thrift-gen-http-swagger)**:为基于 Thrift 的 HTTP 服务生成 Swagger 文档和 Swagger UI 进行调试。 -- **[protoc-gen-rpc-swagger](https://github.com/hertz-contrib/swagger-generate/tree/main/protoc-gen-rpc-swagger)**:为基于 Protobuf 的 RPC 服务生成 Swagger 文档和 Swagger UI 进行调试。 -- **[thrift-gen-rpc-swagger](https://github.com/hertz-contrib/swagger-generate/tree/main/thrift-gen-rpc-swagger)**:为基于 Thrift 的 RPC 服务生成 Swagger 文档和 Swagger UI 进行调试。 -- **[swagger2idl](https://github.com/hertz-contrib/swagger-generate/tree/main/swagger2idl)**:将 Swagger 文档转换为 Protobuf 或 Thrift IDL 文件。 +- **protoc-gen-http-swagger**:为基于 Protobuf 的 HTTP 服务生成 Swagger 文档和 Swagger UI 进行调试。 +- **thrift-gen-http-swagger**:为基于 Thrift 的 HTTP 服务生成 Swagger 文档和 Swagger UI 进行调试。 +- **protoc-gen-rpc-swagger**:为基于 Protobuf 的 RPC 服务生成 Swagger 文档和 Swagger UI 进行调试。 +- **thrift-gen-rpc-swagger**:为基于 Thrift 的 RPC 服务生成 Swagger 文档和 Swagger UI 进行调试。 ## 项目优势 @@ -20,7 +17,6 @@ - **集成调试**:生成的 Swagger UI 能直接用于调试服务,支持 HTTP 和 RPC 两种模式。 - **Hertz 和 Kitex 集成**:为 [Hertz](https://github.com/cloudwego/hertz) 和 [Kitex](https://github.com/cloudwego/kitex) 提供了无缝的文档生成和调试支持。 - **灵活的注解支持**:允许通过注解扩展生成的 Swagger 文档内容,支持 `openapi.operation`、`openapi.schema` 等 OpenAPI 注解。 -- **IDL 转换**:支持将 Swagger 文档转换为 Protobuf 或 Thrift IDL 文件,方便开发者在不同框架间切换。 ## 安装 @@ -102,14 +98,9 @@ func main() { } } ``` -请参考 [kitex_swagger_gen](https://github.com/cloudwego/kitex-examples/tree/main/bizdemo/kitex_swagger_gen) 和 [hertz_swagger_gen](https://github.com/cloudwego/hertz-examples/tree/main/bizdemo/hertz_swagger_gen) 获取更多使用场景示例。 - -### 将 Swagger 文档转换为 IDL 文件 -```sh -swagger2idl -o my_output.proto -oa -a openapi.yaml -``` +请参考 [kitex_swagger_gen](https://github.com/cloudwego/kitex-examples/tree/main/bizdemo/kitex_swagger_gen) 和 [hertz_swagger_gen](https://github.com/cloudwego/hertz-examples/tree/main/bizdemo/hertz_swagger_gen) 获取更多使用场景示例。 ## 更多信息 -请参考各个插件的 README 文档获取更多使用细节。 \ No newline at end of file +请参考各个插件的 readme 文档获取更多使用细节。 \ No newline at end of file diff --git a/common/consts/consts.go b/common/consts/consts.go index d82ffd5..0357119 100644 --- a/common/consts/consts.go +++ b/common/consts/consts.go @@ -74,9 +74,6 @@ const ( DefaultInfoDesc = "API description" DefaultInfoVersion = "0.0.1" - IDLProto = "proto" - IDLThrift = "thrift" - DocumentOptionServiceType = "service" DocumentOptionStructType = "struct" @@ -103,13 +100,6 @@ const ( DefaultOutputDir = "swagger" DefaultOutputYamlFile = "openapi.yaml" DefaultOutputSwaggerFile = "swagger.go" - DefaultProtoFilename = "output.proto" - DefaultThriftFilename = "output.thrift" - OpenapiThriftFile = "openapi.thrift" - ApiProtoFile = "api.proto" - OpenapiProtoFile = "openapi/annotations.proto" - EmptyProtoFile = "google/protobuf/empty.proto" - TimestampProtoFile = "google/protobuf/timestamp.proto" DefaultServerURL = "http://127.0.0.1:8888" DefaultKitexAddr = "127.0.0.1:8888" @@ -122,6 +112,4 @@ const ( ProtobufValueName = "GoogleProtobufValue" ProtobufAnyName = "GoogleProtobufAny" - EmptyMessage = "google.protobuf.Empty" - TimestampMessage = "google.protobuf.Timestamp" ) diff --git a/common/utils/utils.go b/common/utils/utils.go index 32652b8..e38512a 100644 --- a/common/utils/utils.go +++ b/common/utils/utils.go @@ -21,12 +21,8 @@ import ( "fmt" "os" "reflect" - "regexp" "strconv" "strings" - "unicode" - - "github.com/iancoleman/strcase" ) // Contains returns true if an array Contains a specified string. @@ -166,314 +162,3 @@ func FileExists(filePath string) bool { _, err := os.Stat(filePath) return err == nil } - -// Stringify converts a value to a string -func Stringify(value interface{}) string { - switch v := value.(type) { - case string: - return fmt.Sprintf("%q", v) // Add quotes around strings - case int, int64, float64: - return fmt.Sprintf("%v", v) // Output numbers directly - case *uint64: - return fmt.Sprintf("%d", *v) // Handle *uint64 pointer type - case []string: - return fmt.Sprintf("[%s]", strings.Join(v, ", ")) // Output string arrays as a list - case []interface{}: - // Handle arrays of arbitrary types - var strValues []string - for _, item := range v { - strValues = append(strValues, Stringify(item)) - } - return fmt.Sprintf("[%s]", strings.Join(strValues, ", ")) - default: - return fmt.Sprintf("%v", v) // Convert other types directly to string - } -} - -// StructToOption converts a struct to an option string -func StructToOption(value interface{}, indent string) string { - var sb strings.Builder - v := reflect.ValueOf(value) - t := reflect.TypeOf(value) - - // If it's a pointer, get the actual value - if v.Kind() == reflect.Ptr { - if v.IsNil() { - return "" // Skip nil pointers - } - v = v.Elem() - t = t.Elem() - } - - // Handle slice types - if v.Kind() == reflect.Slice { - if v.Len() == 0 { - return "" // Skip empty slices - } - sb.WriteString("[\n") - for i := 0; i < v.Len(); i++ { - sb.WriteString(fmt.Sprintf("%s ", indent)) - sb.WriteString(StructToOption(v.Index(i).Interface(), indent+" ")) - if i < v.Len()-1 { - sb.WriteString(",\n") - } - } - sb.WriteString(fmt.Sprintf("\n%s]", indent)) - return sb.String() - } - - // Handle map types - if v.Kind() == reflect.Map { - if v.Len() == 0 { - return "" // Skip empty maps - } - sb.WriteString("{\n") - for _, key := range v.MapKeys() { - if isZeroValue(v.MapIndex(key)) { - continue - } - sb.WriteString(fmt.Sprintf("%s %v: ", indent, reflect.ValueOf(ToSnakeCase(key.String())))) - sb.WriteString(StructToOption(v.MapIndex(key).Interface(), indent+" ")) - sb.WriteString(",\n") - } - sb.WriteString(fmt.Sprintf("%s}", indent)) - return sb.String() - } - - // Handle struct types - if v.Kind() == reflect.Struct { - sb.WriteString("{\n") - for i := 0; i < v.NumField(); i++ { - field := v.Field(i) - fieldType := t.Field(i) - - // Skip unexported fields - if !field.CanInterface() { - continue - } - - // Skip fields with zero values - if isZeroValue(field) { - continue - } - - fieldName := fieldType.Tag.Get("json") - if fieldName == "" { - fieldName = fieldType.Name // If no json tag, use field name - } - fieldName = strings.Split(fieldName, ",")[0] // Remove options from json tag, e.g., "omitempty" - - // Skip specific fields (Parameters, RequestBody, Responses) - if fieldName == "parameters" || fieldName == "requestBody" || fieldName == "responses" || - fieldName == "schemas" || fieldName == "requestBodies" || fieldName == "items" || - fieldName == "paths" || fieldName == "properties" || fieldName == "content" || - fieldName == "schema" || fieldName == "oneOf" || fieldName == "allOf" || fieldName == "anyOf" || - fieldName == "additionalProperties" || fieldName == "-" || - fieldName == "components" { - continue - } - - fieldName = ToSnakeCase(fieldName) // Convert field name to snake_case - - // Use the field name as the Protobuf key - sb.WriteString(fmt.Sprintf("%s %s: ", indent, fieldName)) - - // Recursively handle the field - sb.WriteString(StructToOption(field.Interface(), indent+" ")) - sb.WriteString(";\n") - } - sb.WriteString(fmt.Sprintf("%s}", indent)) - return sb.String() - } - - // Handle other basic types - switch v.Kind() { - case reflect.String: - if v.String() == "" { - return "" // Skip empty strings - } - - // Process multi-line strings by replacing actual newlines with "\n" - multiLineStr := strings.ReplaceAll(v.String(), "\n", "\\n") - return fmt.Sprintf("\"%s\"", multiLineStr) - case reflect.Int, reflect.Int64, reflect.Int32: - if v.Int() == 0 { - return "" // Skip 0 values - } - return fmt.Sprintf("%d", v.Int()) - case reflect.Float64: - if v.Float() == 0 { - return "" // Skip 0.0 - } - return fmt.Sprintf("%f", v.Float()) - case reflect.Bool: - if !v.Bool() { - return "" // Skip false - } - return fmt.Sprintf("%t", v.Bool()) - case reflect.Ptr: - if !v.IsNil() { - return StructToOption(v.Interface(), indent) - } - return "" - default: - // Skip zero values - if !v.IsValid() || v.IsZero() { - return "" - } - return fmt.Sprintf("\"%v\"", v.Interface()) - } -} - -// isZeroValue checks if a value is the zero value for its type -func isZeroValue(v reflect.Value) bool { - switch v.Kind() { - case reflect.String: - return v.String() == "" - case reflect.Bool: - return !v.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Complex64, reflect.Complex128: - return v.Complex() == 0 - case reflect.Slice, reflect.Array: - return v.Len() == 0 // Check if slice or array is empty - case reflect.Map: - if v.Len() == 0 { - return true - } - for _, key := range v.MapKeys() { - value := v.MapIndex(key) - if !isZeroValue(value) { - return false - } - } - return true - case reflect.Struct: - for i := 0; i < v.NumField(); i++ { - if !isZeroValue(v.Field(i)) { - return false - } - } - return true - case reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: - return v.IsNil() - default: - return !v.IsValid() - } -} - -// ToUpperCase converts the first letter of a string to uppercase -func ToUpperCase(s string) string { - if len(s) == 0 { - return s - } - - firstChar := unicode.ToUpper(rune(s[0])) - - if len(s) == 1 { - return string(firstChar) - } - - return string(firstChar) + s[1:] -} - -// FormatStr formats a string to remove special characters -func FormatStr(str string) string { - str = strings.ReplaceAll(str, " ", "_") - str = strings.ReplaceAll(str, "/", "_") - str = strings.ReplaceAll(str, "-", "_") - reg, _ := regexp.Compile(`[^a-zA-Z0-9_]`) - str = reg.ReplaceAllString(str, "") - return str -} - -// ToPascaleCase converts a string to PascalCase -func ToPascaleCase(name string) string { - name = strcase.ToCamel(name) - name = ToUpperCase(name) - return name -} - -// ToSnakeCase converts a string to snake_case -func ToSnakeCase(name string) string { - name = FormatStr(name) - name = ToSnake(name) - return name -} - -// ToSnake converts a string to snake_case -func ToSnake(s string) string { - return ToDelimited(s, '_') -} - -// ToDelimited converts a string to delimited.snake.case -// (in this case `delimiter = '.'`) -func ToDelimited(s string, delimiter uint8) string { - return ToScreamingDelimited(s, delimiter, "", false) -} - -// ToScreamingDelimited converts a string to SCREAMING.DELIMITED.SNAKE.CASE -// (in this case `delimiter = '.'; screaming = true`) -// or delimited.snake.case -// (in this case `delimiter = '.'; screaming = false`) -func ToScreamingDelimited(s string, delimiter uint8, ignore string, screaming bool) string { - s = strings.TrimSpace(s) - n := strings.Builder{} - n.Grow(len(s) + 2) // nominal 2 bytes of extra space for inserted delimiters - for i, v := range []byte(s) { - vIsCap := v >= 'A' && v <= 'Z' - vIsLow := v >= 'a' && v <= 'z' - if vIsLow && screaming { - v += 'A' - v -= 'a' - } else if vIsCap && !screaming { - v += 'a' - v -= 'A' - } - - // treat acronyms as words, eg for JSONData -> JSON is a whole word - if i+1 < len(s) { - next := s[i+1] - vIsNum := v >= '0' && v <= '9' - nextIsCap := next >= 'A' && next <= 'Z' - nextIsLow := next >= 'a' && next <= 'z' - nextIsNum := next >= '0' && next <= '9' - - // add delimiter if the next character is of a different type - // but do not insert delimiter between a letter and a number - if (vIsCap && (nextIsLow || nextIsNum)) || (vIsLow && (nextIsCap || nextIsNum)) || (vIsNum && (nextIsCap || nextIsLow)) { - prevIgnore := ignore != "" && i > 0 && strings.ContainsAny(string(s[i-1]), ignore) - if !prevIgnore { - if vIsCap && nextIsLow { - if prevIsCap := i > 0 && s[i-1] >= 'A' && s[i-1] <= 'Z'; prevIsCap { - n.WriteByte(delimiter) - } - } - - // Skip adding delimiter if current character is a letter followed by a number - if !(vIsLow && nextIsNum) && !(vIsCap && nextIsNum) { - n.WriteByte(v) - if vIsLow || vIsNum || nextIsNum { - n.WriteByte(delimiter) - } - continue - } - } - } - } - - if (v == ' ' || v == '_' || v == '-' || v == '.') && !strings.ContainsAny(string(v), ignore) { - // replace space/underscore/hyphen/dot with delimiter - n.WriteByte(delimiter) - } else { - n.WriteByte(v) - } - } - - return n.String() -} diff --git a/go.mod b/go.mod index 1743fa6..3a3e045 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.18 require ( github.com/apache/thrift v0.13.0 github.com/google/gnostic-models v0.6.8 - github.com/iancoleman/strcase v0.3.0 google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 4b49dc0..31e1cfc 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,6 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= -github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= diff --git a/licenses/LICENSE-cli.txt b/licenses/LICENSE-cli.txt deleted file mode 100644 index 2c84c78..0000000 --- a/licenses/LICENSE-cli.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 urfave/cli maintainers - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/licenses/LICENSE-kin-openapi.txt b/licenses/LICENSE-kin-openapi.txt deleted file mode 100644 index 992b983..0000000 --- a/licenses/LICENSE-kin-openapi.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017-2018 the project authors. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/swagger2idl/LICENSE b/swagger2idl/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/swagger2idl/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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. diff --git a/swagger2idl/README.md b/swagger2idl/README.md deleted file mode 100644 index 59fc40f..0000000 --- a/swagger2idl/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# swagger2idl - -ENGLISH | [中文](README_CN.md) - -`swagger2idl` is a tool designed to convert Swagger documentation into Thrift or Proto files. It supports relevant annotations from [swagger-generate](https://github.com/hertz-contrib/swagger-generate), [cloudwego/cwgo](https://github.com/cloudwego/cwgo), [hertz](https://github.com/cloudwego/hertz), and [kitex](https://github.com/cloudwego/kitex). - -## Installation - -```sh -# Install from the official repository - -git clone https://github.com/hertz-contrib/swagger-generate -cd swagger2idl -go install - -# Direct installation -go install github.com/hertz-contrib/swagger-generate/swagger2idl@latest -``` - -## Usage - -### Parameter Description - -| Parameter | Abbreviation | Default Value | Description | -|-----------------|--------------|--------------------------------|--------------------------------------------------------------------------------------------------------------------| -| `--type` | `-t` | Inferred from the output file extension | Specify the output type, either `'proto'` or `'thrift'`. If not provided, it is inferred from the output file extension. | -| `--output` | `-o` | `filename.proto` or `filename.thrift` | Specify the output file path. If not provided, it defaults to `output.proto` or `output.thrift`, depending on the output type. | -| `--openapi` | `-oa` | `false` | Includes OpenAPI-specific annotations and adds references. The related reference files can be found in [idl](https://github.com/hertz-contrib/swagger-generate/idl). | -| `--api` | `-a` | `false` | Adds annotations for compatibility with Cwgo/Hertz and adds references. The related reference files are in [idl](https://github.com/hertz-contrib/swagger-generate/idl). | -| `--naming` | `-n` | `true` | Use naming conventions in the output IDL file. | - -### Usage Examples - -1. Convert to Protobuf format and specify the output path: -```bash - swagger2idl --output my_output.proto --openapi --api --naming=false openapi.yaml -``` -or -```bash - swagger2idl -o my_output.proto -oa -a -n=false openapi.yaml -``` - -### Extensions -You can add extensions like `x-options` to parameters in the `openapi.yaml` file. More extensions will be supported in the future. - -For Proto files: -```yaml -x-options: - go_package: myawesomepackage -``` -Generates: -```protobuf -option go_package = "myawesomepackage"; -``` - -For Thrift files: -```yaml -x-options: - go: myawesomepackage -``` -Generates: -```thrift -namespace go myawesomepackage -``` - -### Naming Conventions - -| **Category** | **Thrift/Proto Naming Rules** | -|------------------------------------|-------------------------------------------------------------------------------| -| **Struct/Message** | - Use **PascalCase**.
Example: `UserInfo` | -| **Field** | - Use **snake_case**.
Example: `user_id`. If a field name contains a number, the number should follow a letter, not an underscore. | -| **Enum**, **Service**, **Union** | - Use **PascalCase**.
Example: `UserType` | -| **Enum Values** | - Use **UPPER_SNAKE_CASE**.
Example: `ADMIN_USER` | -| **RPC Methods** | - Use **PascalCase**.
Example: `GetUserInfo` | -| **Package/Namespace** | - Use **snake_case**, typically based on the project structure.
Example: `com.project.service` | - -#### Naming Conventions Explained: -- **PascalCase**: Capitalize the first letter of each word, such as `UserInfo`. -- **snake_case**: All lowercase with underscores separating words, such as `user_info`. -- **UPPER_SNAKE_CASE**: All uppercase letters with underscores separating words, such as `ADMIN_USER`. - -## More Information - -For more usage details, refer to the [Examples](example). \ No newline at end of file diff --git a/swagger2idl/README_CN.md b/swagger2idl/README_CN.md deleted file mode 100644 index aaac445..0000000 --- a/swagger2idl/README_CN.md +++ /dev/null @@ -1,82 +0,0 @@ -# swagger2idl - -[English](README.md) | 中文 - -swagger2idl 是一个用于将 Swagger 文档转换为 Thrift 或 Proto 文件的工具。 -适配了[swagger-generate](https://github.com/hertz-contrib/swagger-generate)、[cloudwego/cwgo](https://github.com/cloudwego/cwgo)、[hertz](https://github.com/cloudwego/hertz)及[kitex](https://github.com/cloudwego/kitex)中的相关注解。 - -## 安装 - -```sh -# 官方仓库安装 - -git clone https://github.com/hertz-contrib/swagger-generate -cd swagger2idl -go install - -# 直接安装 -go install github.com/hertz-contrib/swagger-generate/swagger2idl@latest -``` - -## 使用 -### 参数说明 - -| 参数名称 | 缩写 | 默认值 | 说明 | -|-------------|-------|----------------------------|-------------------------------------------------------------------------------------------------------| -| `--type` | `-t` | 自动根据输出文件扩展名推断 | 指定输出类型,可选值为 `'proto'` 或 `'thrift'`。如果未提供,则从输出文件扩展名推断。 | -| `--output` | `-o` | `文件名.proto` 或 `文件名.thrift` | 指定输出文件的路径。如果未提供,默认为 `output.proto` 或 `output.thrift`,具体取决于输出类型。 | -| `--openapi` | `-oa` | `false` | 会生成相应的openapi注解,并添加引用,相关引用文件可以在[idl](https://github.com/hertz-contrib/swagger-generate/idl)中找到。 | -| `--api` | `-a` | `false` | 会生成相应的适配Cwgo/Hertz的注解,并添加引用,相关引用文件可以在[idl](https://github.com/hertz-contrib/swagger-generate/idl)中找到。 | -| `--naming` | `-n` | `true` | 在输出的 IDL 文件中使用命名约定。 | - -### 使用示例 - -1. 指定输出为 Protobuf 格式,并输出到指定路径: -```bash - swagger2idl --output my_output.proto --openapi --api --naming=false openapi.yaml -``` -or -```bash - swagger2idl -o my_output.proto -oa -a -n=false openapi.yaml -``` - -### 扩展 -支持向openapi.yaml中的参数添加扩展,如`x-options`,后面会增加更多扩展。 - -如果是proto文件 -```yaml -x-options: - go_package: myawesomepackage -``` -会生成 -```protobuf -option go_package = "myawesomepackage"; -``` -如果是thrift文件 -```yaml -x-options: - go: myawesomepackage -``` -会生成 -```thrift -namespace go myawesomepackage -``` -### 命名约定 - -| **类别** | **Thrift/Proto 命名规范** | -|----------------------------------|-------------------------------------------------------------------------------| -| **Struct/Message** | - 使用 **PascalCase** 命名。
- 例:`UserInfo` | -| **Field** | - 使用 **snake_case** 命名。
- 例:`user_id`, 如果你的字段名包含一个数字,数字应该出现在字母后面,而不是下划线后面 | -| **Enum**, **Service**, **Union** | - 使用 **PascalCase**。
- 例:`UserType` | -| **Enum 值** | - 使用 **UPPER_SNAKE_CASE** 命名。
- 例:`ADMIN_USER` | -| **RPC 方法** | - 使用 **PascalCase** 命名。
- 例:`GetUserInfo` | -| **Package/Namespace** | - 使用 **snake_case**,通常基于项目结构命名。
例:`com.project.service` | - -#### 详细说明: -- **PascalCase**: 首字母大写,每个单词的首字母都大写,例如 `UserInfo`。 -- **snake_case**: 全部小写,单词之间使用下划线分隔,例如 `user_info`。 -- **UPPER_SNAKE_CASE**: 全部字母大写,单词之间用下划线分隔,例如 `ADMIN_USER`。 - -## 更多信息 - -更多的使用方法请参考 [示例](example) \ No newline at end of file diff --git a/swagger2idl/converter/converter.go b/swagger2idl/converter/converter.go deleted file mode 100644 index 888d10b..0000000 --- a/swagger2idl/converter/converter.go +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2024 CloudWeGo Authors - * - * Licensed 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/hertz-contrib/swagger-generate/common/consts" - -// Converter is an interface for converting files -type Converter interface { - Convert() error - GetIdl() interface{} -} - -// ConvertOption adds a struct for conversion options -type ConvertOption struct { - OpenapiOption bool - ApiOption bool - NamingOption bool -} - -var MethodToOption = map[string]string{ - consts.HttpMethodGet: consts.ApiGet, - consts.HttpMethodPost: consts.ApiPost, - consts.HttpMethodPut: consts.ApiPut, - consts.HttpMethodPatch: consts.ApiPatch, - consts.HttpMethodDelete: consts.ApiDelete, - consts.HttpMethodOptions: consts.ApiOptions, - consts.HttpMethodHead: consts.ApiHEAD, -} diff --git a/swagger2idl/converter/proto_converter.go b/swagger2idl/converter/proto_converter.go deleted file mode 100644 index 413680d..0000000 --- a/swagger2idl/converter/proto_converter.go +++ /dev/null @@ -1,1327 +0,0 @@ -/* - * Copyright 2024 CloudWeGo Authors - * - * Licensed 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 ( - "errors" - "fmt" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/hertz-contrib/swagger-generate/common/consts" - common "github.com/hertz-contrib/swagger-generate/common/utils" - "github.com/hertz-contrib/swagger-generate/swagger2idl/protobuf" - "github.com/hertz-contrib/swagger-generate/swagger2idl/utils" -) - -// ProtoConverter struct, used to convert OpenAPI specifications into Proto files -type ProtoConverter struct { - spec *openapi3.T - ProtoFile *protobuf.ProtoFile - converterOption *ConvertOption -} - -// NewProtoConverter creates and initializes a ProtoConverter -func NewProtoConverter(spec *openapi3.T, option *ConvertOption) *ProtoConverter { - return &ProtoConverter{ - spec: spec, - ProtoFile: &protobuf.ProtoFile{ - PackageName: utils.GetPackageName(spec), - Messages: []*protobuf.ProtoMessage{}, - Services: []*protobuf.ProtoService{}, - Enums: []*protobuf.ProtoEnum{}, - Imports: []string{}, - Options: []*protobuf.Option{}, - }, - converterOption: option, - } -} - -// Convert converts the OpenAPI specification to a Proto file -func (c *ProtoConverter) Convert() error { - // Convert the go Option to Proto - err := c.addExtensionsToProtoOptions() - if err != nil { - return fmt.Errorf("error parsing extensions to proto options: %w", err) - } - - // Convert tags into Proto services - c.convertTagsToProtoServices() - - // Convert components into Proto messages - err = c.convertComponentsToProtoMessages() - if err != nil { - return fmt.Errorf("error converting components to proto messages: %w", err) - } - - // Convert paths into Proto services - err = c.convertPathsToProtoServices() - if err != nil { - return fmt.Errorf("error converting paths to proto services: %w", err) - } - - if c.converterOption.OpenapiOption { - c.addOptionsToProto() - } - - return nil -} - -func (c *ProtoConverter) GetIdl() interface{} { - return c.ProtoFile -} - -// convertTagsToProtoServices converts OpenAPI tags into Proto services and stores them in the ProtoFile -func (c *ProtoConverter) convertTagsToProtoServices() { - tags := c.spec.Tags - for _, tag := range tags { - serviceName := common.ToPascaleCase(tag.Name) - service := &protobuf.ProtoService{ - Name: serviceName, - Description: tag.Description, - } - c.ProtoFile.Services = append(c.ProtoFile.Services, service) - } -} - -// convertComponentsToProtoMessages converts OpenAPI components into Proto messages and stores them in the ProtoFile -func (c *ProtoConverter) convertComponentsToProtoMessages() error { - components := c.spec.Components - if components == nil { - return nil - } - - if components.Schemas == nil { - return nil - } - - for name, schemaRef := range components.Schemas { - schema := schemaRef - - if c.converterOption.NamingOption { - name = common.ToPascaleCase(name) - } - - protoType, err := c.ConvertSchemaToProtoType(schema, name, nil) - if err != nil { - return fmt.Errorf("error converting schema %s: %w", name, err) - } - - switch v := protoType.(type) { - case *protobuf.ProtoField: - message := &protobuf.ProtoMessage{ - Name: name, - Fields: []*protobuf.ProtoField{v}, - } - - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiSchema, - Value: optionStr, - } - message.Options = append(message.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - c.addMessageToProto(message) - case *protobuf.ProtoMessage: - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiSchema, - Value: optionStr, - } - v.Options = append(v.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - c.addMessageToProto(v) - case *protobuf.ProtoEnum: - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiSchema, - Value: optionStr, - } - v.Options = append(v.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - c.addEnumToProto(v) - case *protobuf.ProtoOneOf: - message := &protobuf.ProtoMessage{ - Name: name, - OneOfs: []*protobuf.ProtoOneOf{v}, - } - - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiSchema, - Value: optionStr, - } - message.Options = append(message.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - c.addMessageToProto(message) - } - } - return nil -} - -// convertPathsToProtoServices converts OpenAPI path items into Proto services and stores them in the ProtoFile -func (c *ProtoConverter) convertPathsToProtoServices() error { - paths := c.spec.Paths - services, err := c.ConvertPathsToProtoServices(paths) - if err != nil { - return fmt.Errorf("error converting paths to proto services: %w", err) - } - - c.ProtoFile.Services = append(c.ProtoFile.Services, services...) - return nil -} - -// ConvertPathsToProtoServices converts OpenAPI path items into Proto services -func (c *ProtoConverter) ConvertPathsToProtoServices(paths *openapi3.Paths) ([]*protobuf.ProtoService, error) { - var services []*protobuf.ProtoService - - for path, pathItem := range paths.Map() { - for method, operation := range pathItem.Operations() { - serviceName := utils.GetServiceName(operation) - methodName := utils.GetMethodName(operation, path, method) - - if c.converterOption.NamingOption { - serviceName = common.ToPascaleCase(serviceName) - methodName = common.ToPascaleCase(methodName) - } - - inputMessage, err := c.generateRequestMessage(operation, methodName) - if err != nil { - return nil, fmt.Errorf("error generating request message for %s: %w", methodName, err) - } - - outputMessage, err := c.generateResponseMessage(operation, methodName) - if err != nil { - return nil, fmt.Errorf("error generating response message for %s: %w", methodName, err) - } - - service := c.findOrCreateService(serviceName) - - if !c.methodExistsInService(service, methodName) { - protoMethod := &protobuf.ProtoMethod{ - Name: methodName, - Input: inputMessage, - Output: outputMessage, - } - - if c.converterOption.ApiOption { - if optionName, ok := MethodToOption[method]; ok { - option := &protobuf.Option{ - Name: optionName, - Value: fmt.Sprintf("%q", utils.ConvertPath(path)), - } - protoMethod.Options = append(protoMethod.Options, option) - c.AddProtoImport(consts.ApiProtoFile) - } - } - - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(operation, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiOperation, - Value: optionStr, - } - protoMethod.Options = append(protoMethod.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - - } - service.Methods = append(service.Methods, protoMethod) - } - } - } - - return services, nil -} - -// generateRequestMessage generates a request message for an operation -func (c *ProtoConverter) generateRequestMessage(operation *openapi3.Operation, methodName string) (string, error) { - messageName := utils.GetMessageName(operation, methodName, "Request") - - if c.converterOption.NamingOption { - messageName = common.ToPascaleCase(messageName) - } - - message := &protobuf.ProtoMessage{Name: messageName} - - if operation.RequestBody == nil && len(operation.Parameters) == 0 { - c.AddProtoImport(consts.EmptyProtoFile) - return consts.EmptyMessage, nil - } - - if operation.RequestBody != nil { - if operation.RequestBody.Ref != "" { - return common.ToPascaleCase(utils.ExtractMessageNameFromRef(operation.RequestBody.Ref)), nil - } - - if operation.RequestBody.Value != nil && len(operation.RequestBody.Value.Content) > 0 { - for mediaTypeStr, mediaType := range operation.RequestBody.Value.Content { - schema := mediaType.Schema - if schema != nil { - protoType, err := c.ConvertSchemaToProtoType(schema, common.FormatStr(mediaTypeStr), message) - if err != nil { - return "", err - } - - switch v := protoType.(type) { - case *protobuf.ProtoField: - if c.converterOption.ApiOption { - var optionName string - if mediaTypeStr == "application/json" { - optionName = "api.body" - } else if mediaTypeStr == "application/x-www-form-urlencoded" || mediaTypeStr == "multipart/form-data" { - optionName = "api.form" - } - if optionName != "" { - v.Options = append(v.Options, &protobuf.Option{ - Name: optionName, - Value: fmt.Sprintf("%q", v.Name), - }) - c.AddProtoImport(consts.ApiProtoFile) - } - } - c.addFieldIfNotExists(&message.Fields, v) - case *protobuf.ProtoMessage: - for _, field := range v.Fields { - if c.converterOption.ApiOption { - var optionName string - if mediaTypeStr == "application/json" { - optionName = "api.body" - } else if mediaTypeStr == "application/x-www-form-urlencoded" || mediaTypeStr == "multipart/form-data" { - optionName = "api.form" - } - if optionName != "" { - field.Options = append(field.Options, &protobuf.Option{ - Name: optionName, - Value: fmt.Sprintf("%q", field.Name), - }) - c.AddProtoImport(consts.ApiProtoFile) - } - } - c.addFieldIfNotExists(&message.Fields, field) - } - - message.Enums = append(message.Enums, v.Enums...) - - message.OneOfs = append(message.OneOfs, v.OneOfs...) - - for _, nestedMessage := range v.Messages { - c.addMessageIfNotExists(&message.Messages, nestedMessage) - } - case *protobuf.ProtoEnum: - name := mediaTypeStr - if c.converterOption.NamingOption { - name = common.ToSnakeCase(name) - } else { - name = common.FormatStr(name) - } - newField := &protobuf.ProtoField{ - Name: name + "_field", - Type: v.Name, - } - if c.converterOption.ApiOption { - var optionName string - if mediaTypeStr == "application/json" { - optionName = "api.body" - } else if mediaTypeStr == "application/x-www-form-urlencoded" || mediaTypeStr == "multipart/form-data" { - optionName = "api.form" - } - if optionName != "" { - newField.Options = append(newField.Options, &protobuf.Option{ - Name: optionName, - Value: fmt.Sprintf("%q", v.Name), - }) - c.AddProtoImport(consts.ApiProtoFile) - } - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - message.Enums = append(message.Enums, v) - message.Fields = append(message.Fields, newField) - case *protobuf.ProtoOneOf: - message.OneOfs = append(message.OneOfs, v) - } - } - } - } - } - - if len(operation.Parameters) > 0 { - for _, param := range operation.Parameters { - if param.Value.Schema != nil { - fieldOrMessage, err := c.ConvertSchemaToProtoType(param.Value.Schema, param.Value.Name, message) - if err != nil { - return "", err - } - description := param.Value.Description - switch v := fieldOrMessage.(type) { - case *protobuf.ProtoField: - if c.converterOption.ApiOption { - v.Options = append(v.Options, &protobuf.Option{ - Name: "api." + param.Value.In, - Value: fmt.Sprintf("%q", param.Value.Name), - }) - c.AddProtoImport(consts.ApiProtoFile) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(param.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiParameter, - Value: optionStr, - } - v.Options = append(v.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - v.Description = description - c.addFieldIfNotExists(&message.Fields, v) - case *protobuf.ProtoMessage: - for _, field := range v.Fields { - if c.converterOption.ApiOption { - field.Options = append(field.Options, &protobuf.Option{ - Name: "api." + param.Value.In, - Value: fmt.Sprintf("%q", param.Value.Name), - }) - c.AddProtoImport(consts.ApiProtoFile) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(param.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiParameter, - Value: optionStr, - } - field.Options = append(field.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - c.addFieldIfNotExists(&message.Fields, field) - } - message.Enums = append(message.Enums, v.Enums...) - - message.OneOfs = append(message.OneOfs, v.OneOfs...) - - for _, nestedMessage := range v.Messages { - c.addMessageIfNotExists(&message.Messages, nestedMessage) - } - case *protobuf.ProtoEnum: - name := param.Value.Name - if c.converterOption.NamingOption { - name = common.ToPascaleCase(name) - } - newField := &protobuf.ProtoField{ - Name: name + "_field", - Type: v.Name, - } - if c.converterOption.ApiOption { - newField.Options = append(newField.Options, &protobuf.Option{ - Name: "api." + param.Value.In, - Value: fmt.Sprintf("%q", param.Value.Name), - }) - c.AddProtoImport(consts.ApiProtoFile) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(param.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiParameter, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - message.Enums = append(message.Enums, v) - message.Fields = append(message.Fields, newField) - case *protobuf.ProtoOneOf: - message.OneOfs = append(message.OneOfs, v) - } - } - } - } - - // if there are no fields or messages, return an empty message - if len(message.Fields) > 0 || len(message.Messages) > 0 || len(message.Enums) > 0 { - c.addMessageToProto(message) - return message.Name, nil - } - - return "", nil -} - -// generateResponseMessage generates a response message for an operation -func (c *ProtoConverter) generateResponseMessage(operation *openapi3.Operation, methodName string) (string, error) { - if operation.Responses == nil { - return "", nil - } - - responses := operation.Responses.Map() - responseCount := 0 - for _, responseRef := range responses { - if responseRef.Ref == "" && (responseRef.Value == nil || (len(responseRef.Value.Content) == 0 && len(responseRef.Value.Headers) == 0)) { - continue - } - responseCount++ - } - - if responseCount == 1 { - for _, responseRef := range responses { - if responseRef.Ref == "" && (responseRef.Value == nil || (len(responseRef.Value.Content) == 0 && len(responseRef.Value.Headers) == 0)) { - continue - } - return c.processSingleResponse("", responseRef, operation, methodName) - } - } - - if responseCount == 0 { - c.AddProtoImport(consts.EmptyProtoFile) - return consts.EmptyMessage, nil - } - - // create a wrapper message for multiple responses - wrapperMessageName := utils.GetMessageName(operation, methodName, "Response") - if c.converterOption.NamingOption { - wrapperMessageName = common.ToPascaleCase(wrapperMessageName) - } - - wrapperMessage := &protobuf.ProtoMessage{Name: wrapperMessageName} - - emptyFlag := true - - for statusCode, responseRef := range responses { - if responseRef.Ref == "" && (responseRef.Value == nil || len(responseRef.Value.Content) == 0) { - break - } - emptyFlag = false - messageName, err := c.processSingleResponse(statusCode, responseRef, operation, methodName) - if err != nil { - return "", err - } - - name := "Response_" + statusCode - if c.converterOption.NamingOption { - name = common.ToSnakeCase(name) - } - field := &protobuf.ProtoField{ - Name: name, - Type: messageName, - } - wrapperMessage.Fields = append(wrapperMessage.Fields, field) - } - - if emptyFlag { - c.AddProtoImport(consts.EmptyProtoFile) - return consts.EmptyMessage, nil - } - - c.addMessageToProto(wrapperMessage) - - return wrapperMessageName, nil -} - -// processSingleResponse deals with a single response in an operation -func (c *ProtoConverter) processSingleResponse(statusCode string, responseRef *openapi3.ResponseRef, operation *openapi3.Operation, methodName string) (string, error) { - if responseRef.Ref != "" { - return common.ToPascaleCase(utils.ExtractMessageNameFromRef(responseRef.Ref)), nil - } - - response := responseRef.Value - messageName := utils.GetMessageName(operation, methodName, "Response") + common.ToUpperCase(statusCode) - - if c.converterOption.NamingOption { - messageName = common.ToPascaleCase(messageName) - } - - message := &protobuf.ProtoMessage{Name: messageName} - - if len(response.Headers) > 0 { - for headerName, headerRef := range response.Headers { - if headerRef != nil { - - fieldOrMessage, err := c.ConvertSchemaToProtoType(headerRef.Value.Schema, headerName, message) - if err != nil { - return "", err - } - - switch v := fieldOrMessage.(type) { - case *protobuf.ProtoField: - if c.converterOption.ApiOption { - option := &protobuf.Option{ - Name: "api.header", - Value: fmt.Sprintf("%q", headerName), - } - v.Options = append(v.Options, option) - c.AddProtoImport(consts.ApiProtoFile) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(headerRef.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - v.Options = append(v.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - c.addFieldIfNotExists(&message.Fields, v) - case *protobuf.ProtoMessage: - for _, field := range v.Fields { - if c.converterOption.ApiOption { - option := &protobuf.Option{ - Name: "api.header", - Value: fmt.Sprintf("%q", field.Name), - } - field.Options = append(field.Options, option) - c.AddProtoImport(consts.ApiProtoFile) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(headerRef.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - field.Options = append(field.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - c.addFieldIfNotExists(&message.Fields, field) - } - message.Enums = append(message.Enums, v.Enums...) - - message.OneOfs = append(message.OneOfs, v.OneOfs...) - - for _, nestedMessage := range v.Messages { - c.addMessageIfNotExists(&message.Messages, nestedMessage) - } - case *protobuf.ProtoEnum: - name := headerName - if c.converterOption.NamingOption { - name = common.ToSnakeCase(name) - } - newField := &protobuf.ProtoField{ - Name: name + "_field", - Type: v.Name, - } - if c.converterOption.ApiOption { - option := &protobuf.Option{ - Name: "api.header", - Value: fmt.Sprintf("%q", headerName), - } - newField.Options = append(newField.Options, option) - c.AddProtoImport(consts.ApiProtoFile) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(headerRef.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - message.Enums = append(message.Enums, v) - message.Fields = append(message.Fields, newField) - case *protobuf.ProtoOneOf: - message.OneOfs = append(message.OneOfs, v) - } - } - } - } - - for mediaTypeStr, mediaType := range response.Content { - schema := mediaType.Schema - if schema != nil { - - protoType, err := c.ConvertSchemaToProtoType(schema, common.FormatStr(mediaTypeStr), message) - if err != nil { - return "", err - } - - switch v := protoType.(type) { - case *protobuf.ProtoField: - if c.converterOption.ApiOption && mediaTypeStr == "application/json" { - option := &protobuf.Option{ - Name: "api.body", - Value: fmt.Sprintf("%q", v.Name), - } - v.Options = append(v.Options, option) - c.AddProtoImport(consts.ApiProtoFile) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - v.Options = append(v.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - c.addFieldIfNotExists(&message.Fields, v) - case *protobuf.ProtoMessage: - for _, field := range v.Fields { - if c.converterOption.ApiOption && mediaTypeStr == "application/json" { - option := &protobuf.Option{ - Name: "api.body", - Value: fmt.Sprintf("%q", field.Name), - } - field.Options = append(field.Options, option) - c.AddProtoImport(consts.ApiProtoFile) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - field.Options = append(field.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - c.addFieldIfNotExists(&message.Fields, field) - } - message.Enums = append(message.Enums, v.Enums...) - - message.OneOfs = append(message.OneOfs, v.OneOfs...) - - for _, nestedMessage := range v.Messages { - c.addMessageIfNotExists(&message.Messages, nestedMessage) - } - case *protobuf.ProtoEnum: - name := mediaTypeStr - if c.converterOption.NamingOption { - name = common.ToSnakeCase(mediaTypeStr) - } else { - name = common.ToUpperCase(name) - } - newField := &protobuf.ProtoField{ - Name: name + "_field", - Type: v.Name, - } - if c.converterOption.ApiOption && mediaTypeStr == "application/json" { - option := &protobuf.Option{ - Name: "api.body", - Value: fmt.Sprintf("%q", v.Name), - } - newField.Options = append(newField.Options, option) - c.AddProtoImport(consts.ApiProtoFile) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - message.Enums = append(message.Enums, v) - message.Fields = append(message.Fields, newField) - case *protobuf.ProtoOneOf: - message.OneOfs = append(message.OneOfs, v) - } - } - } - - if len(message.Fields) > 0 || len(message.Messages) > 0 || len(message.Enums) > 0 { - c.addMessageToProto(message) - return message.Name, nil - } - return "", nil -} - -// ConvertSchemaToProtoType converts an OpenAPI schema to a Proto field or message -func (c *ProtoConverter) ConvertSchemaToProtoType( - schemaRef *openapi3.SchemaRef, - protoName string, - parentMessage *protobuf.ProtoMessage, -) (interface{}, error) { - var protoType string - var result interface{} - - // Handle referenced schema - if schemaRef.Ref != "" { - name := c.applySnakeCaseNamingOption(utils.ExtractMessageNameFromRef(schemaRef.Ref)) - return &protobuf.ProtoField{ - Name: name, - Type: common.ToPascaleCase(utils.ExtractMessageNameFromRef(schemaRef.Ref)), - }, nil - } - - // Ensure schema value is valid - if schemaRef.Value == nil { - return nil, errors.New("schema type is required") - } - - schema := schemaRef.Value - description := schema.Description - - // Handle oneOf, allOf, anyOf even if schema.Type is nil - if len(schema.OneOf) > 0 { - protoUnion, err := c.handleOneOf(schema.OneOf, protoName, parentMessage) - if err != nil { - return nil, err - } - return protoUnion, nil - } else if len(schema.AllOf) > 0 { - protoMessage, err := c.handleAllOf(schema.AllOf, protoName, parentMessage) - if err != nil { - return nil, err - } - return protoMessage, nil - } else if len(schema.AnyOf) > 0 { - protoMessage, err := c.handleAnyOf(schema.AnyOf, protoName, parentMessage) - if err != nil { - return nil, err - } - return protoMessage, nil - } - - // Process schema type - switch { - case schema.Type.Includes("string"): - if schema.Format == "date" || schema.Format == "date-time" { - protoType = consts.TimestampMessage - c.AddProtoImport(consts.TimestampProtoFile) - } else if len(schema.Enum) != 0 { - var name string - if parentMessage == nil { - name = protoName - } else { - name = c.applyPascaleCaseNamingOption(common.ToUpperCase(protoName)) - } - protoEnum := &protobuf.ProtoEnum{ - Name: name, - Description: description, - } - for i, enumValue := range schema.Enum { - protoEnum.Values = append(protoEnum.Values, &protobuf.ProtoEnumValue{ - Index: i, - Value: enumValue, - }) - } - result = protoEnum - } else { - protoType = "string" - } - - case schema.Type.Includes("integer"): - if len(schema.Enum) != 0 { - var name string - if parentMessage == nil { - name = protoName - } else { - name = c.applyPascaleCaseNamingOption(common.ToUpperCase(protoName)) - } - protoEnum := &protobuf.ProtoEnum{ - Name: name, - Description: description, - } - for i, enumValue := range schema.Enum { - protoEnum.Values = append(protoEnum.Values, &protobuf.ProtoEnumValue{ - Index: i, - Value: enumValue, - }) - } - result = protoEnum - } else if schema.Format == "int32" { - protoType = "int32" - } else { - protoType = "int64" - } - - case schema.Type.Includes("number"): - if len(schema.Enum) != 0 { - var name string - if parentMessage == nil { - name = protoName - } else { - name = c.applyPascaleCaseNamingOption(common.ToUpperCase(protoName)) - } - protoEnum := &protobuf.ProtoEnum{ - Name: name, - Description: description, - } - for i, enumValue := range schema.Enum { - protoEnum.Values = append(protoEnum.Values, &protobuf.ProtoEnumValue{ - Index: i, - Value: enumValue, - }) - } - result = protoEnum - } else if schema.Format == "float" { - protoType = "float" - } else { - protoType = "double" - } - - case schema.Type.Includes("boolean"): - protoType = "bool" - - case schema.Type.Includes("array"): - if schema.Items != nil { - fieldOrMessage, err := c.ConvertSchemaToProtoType(schema.Items, protoName+"Item", parentMessage) - if err != nil { - return nil, err - } - - fieldType := "" - if field, ok := fieldOrMessage.(*protobuf.ProtoField); ok { - fieldType = field.Type - } else if nestedMessage, ok := fieldOrMessage.(*protobuf.ProtoMessage); ok { - fieldType = nestedMessage.Name - c.addNestedMessageToParent(parentMessage, nestedMessage) - } else if enum, ok := fieldOrMessage.(*protobuf.ProtoEnum); ok { - fieldType = enum.Name - c.addNestedEnumToParent(parentMessage, enum) - } - - result = &protobuf.ProtoField{ - Name: c.applySnakeCaseNamingOption(protoName), - Type: fieldType, - Repeated: true, - Description: description, - } - } - - case schema.Type.Includes("object"): - var message *protobuf.ProtoMessage - if parentMessage == nil { - message = &protobuf.ProtoMessage{Name: protoName} - } else { - message = &protobuf.ProtoMessage{Name: c.applyPascaleCaseNamingOption(common.ToUpperCase(protoName))} - } - for propName, propSchema := range schema.Properties { - protoType, err := c.ConvertSchemaToProtoType(propSchema, propName, message) - if err != nil { - return nil, err - } - - if field, ok := protoType.(*protobuf.ProtoField); ok { - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(propSchema.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - field.Options = append(field.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - message.Fields = append(message.Fields, field) - } else if nestedMessage, ok := protoType.(*protobuf.ProtoMessage); ok { - var name string - if c.converterOption.NamingOption { - name = common.ToSnakeCase(nestedMessage.Name) - } - newField := &protobuf.ProtoField{ - Name: name + "_field", - Type: nestedMessage.Name, - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(propSchema.Value, " ") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) - } - c.addNestedMessageToParent(message, nestedMessage) - message.Fields = append(message.Fields, newField) - } else if enum, ok := protoType.(*protobuf.ProtoEnum); ok { - c.addNestedEnumToParent(message, enum) - message.Fields = append(message.Fields, &protobuf.ProtoField{ - Name: c.applySnakeCaseNamingOption(propName + "_field"), - Type: enum.Name, - }) - } else if oneOf, ok := protoType.(*protobuf.ProtoOneOf); ok { - c.addNestedOneOfToParent(message, oneOf) - } - } - - if schema.AdditionalProperties.Schema != nil { - mapValueType := "string" - additionalPropMessage, err := c.ConvertSchemaToProtoType(schema.AdditionalProperties.Schema, protoName+"AdditionalProperties", parentMessage) - if err != nil { - return nil, err - } - if msg, ok := additionalPropMessage.(*protobuf.ProtoMessage); ok { - mapValueType = msg.Name - } else if enum, ok := additionalPropMessage.(*protobuf.ProtoEnum); ok { - mapValueType = enum.Name - } - - message.Fields = append(message.Fields, &protobuf.ProtoField{ - Name: "additional_properties", - Type: "map", - }) - } - - message.Description = description - result = message - } - - // If result is still nil, construct a default ProtoField - if result == nil { - result = &protobuf.ProtoField{ - Name: c.applySnakeCaseNamingOption(protoName), - Type: protoType, - Description: description, - } - } - - return result, nil -} - -// handleOneOf processes oneOf schemas -func (c *ProtoConverter) handleOneOf(oneOfSchemas []*openapi3.SchemaRef, protoName string, parentMessage *protobuf.ProtoMessage) (*protobuf.ProtoOneOf, error) { - oneOf := &protobuf.ProtoOneOf{ - Name: c.applyPascaleCaseNamingOption(protoName + "OneOf"), - } - - for i, schemaRef := range oneOfSchemas { - fieldName := fmt.Sprintf("%sOption%d", protoName, i+1) - protoType, err := c.ConvertSchemaToProtoType(schemaRef, fieldName, parentMessage) - if err != nil { - return nil, err - } - switch v := protoType.(type) { - case *protobuf.ProtoField: - oneOf.Fields = append(oneOf.Fields, v) - case *protobuf.ProtoMessage: - newField := &protobuf.ProtoField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addNestedMessageToParent(parentMessage, v) - oneOf.Fields = append(oneOf.Fields, newField) - case *protobuf.ProtoEnum: - newField := &protobuf.ProtoField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addNestedEnumToParent(parentMessage, v) - oneOf.Fields = append(oneOf.Fields, newField) - case *protobuf.ProtoOneOf: - c.addNestedOneOfToParent(parentMessage, v) - } - } - return oneOf, nil -} - -// handleAllOf processes allOf schemas -func (c *ProtoConverter) handleAllOf(allOfSchemas []*openapi3.SchemaRef, protoName string, parentMessage *protobuf.ProtoMessage) (*protobuf.ProtoMessage, error) { - allOfMessage := &protobuf.ProtoMessage{ - Name: c.applyPascaleCaseNamingOption(protoName + "AllOf"), - } - - for i, schemaRef := range allOfSchemas { - fieldName := fmt.Sprintf("%sPart%d", protoName, i+1) - protoType, err := c.ConvertSchemaToProtoType(schemaRef, fieldName, parentMessage) - if err != nil { - return nil, err - } - - switch v := protoType.(type) { - case *protobuf.ProtoField: - allOfMessage.Fields = append(allOfMessage.Fields, v) - case *protobuf.ProtoMessage: - newField := &protobuf.ProtoField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addNestedMessageToParent(allOfMessage, v) - allOfMessage.Fields = append(allOfMessage.Fields, newField) - case *protobuf.ProtoEnum: - newField := &protobuf.ProtoField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addNestedEnumToParent(allOfMessage, v) - allOfMessage.Fields = append(allOfMessage.Fields, newField) - case *protobuf.ProtoOneOf: - c.addNestedOneOfToParent(allOfMessage, v) - } - } - - return allOfMessage, nil -} - -// handleAnyOf processes anyOf schemas -func (c *ProtoConverter) handleAnyOf(anyOfSchemas []*openapi3.SchemaRef, protoName string, parentMessage *protobuf.ProtoMessage) (*protobuf.ProtoMessage, error) { - anyOfMessage := &protobuf.ProtoMessage{ - Name: c.applyPascaleCaseNamingOption(protoName + "AnyOf"), - } - - for i, schemaRef := range anyOfSchemas { - fieldName := fmt.Sprintf("%sOption%d", protoName, i+1) - protoType, err := c.ConvertSchemaToProtoType(schemaRef, fieldName, parentMessage) - if err != nil { - return nil, err - } - - switch v := protoType.(type) { - case *protobuf.ProtoField: - anyOfMessage.Fields = append(anyOfMessage.Fields, v) - case *protobuf.ProtoMessage: - newField := &protobuf.ProtoField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addNestedMessageToParent(anyOfMessage, v) - anyOfMessage.Fields = append(anyOfMessage.Fields, newField) - case *protobuf.ProtoEnum: - newField := &protobuf.ProtoField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addNestedEnumToParent(anyOfMessage, v) - anyOfMessage.Fields = append(anyOfMessage.Fields, newField) - case *protobuf.ProtoOneOf: - c.addNestedOneOfToParent(anyOfMessage, v) - } - } - - return anyOfMessage, nil -} - -// applyPascaleCaseNamingOption applies naming convention based on the converter's naming option -func (c *ProtoConverter) applyPascaleCaseNamingOption(name string) string { - if c.converterOption.NamingOption { - return common.ToPascaleCase(name) - } - return name -} - -// applySnakeCaseNamingOption applies naming convention based on the converter's naming option -func (c *ProtoConverter) applySnakeCaseNamingOption(name string) string { - if c.converterOption.NamingOption { - return common.ToSnakeCase(name) - } - return name -} - -// addOptionsToProto adds options to the ProtoFile -func (c *ProtoConverter) addOptionsToProto() { - optionStr := common.StructToOption(c.spec, "") - - schemaOption := &protobuf.Option{ - Name: consts.OpenapiDocument, - Value: optionStr, - } - c.ProtoFile.Options = append(c.ProtoFile.Options, schemaOption) - c.AddProtoImport(consts.OpenapiProtoFile) -} - -// Add a new method to handle structured extensions -func (c *ProtoConverter) addExtensionsToProtoOptions() error { - // Check for x-option in spec extensions - if xOption, ok := c.spec.Extensions["x-options"]; ok { - if optionMap, ok := xOption.(map[string]interface{}); ok { - for key, value := range optionMap { - option := &protobuf.Option{ - Name: key, - Value: fmt.Sprintf("%q", value), - } - c.ProtoFile.Options = append(c.ProtoFile.Options, option) - } - } - } - - // Check for x-option in spec.info.extensions - if c.spec.Info != nil { - if xOption, ok := c.spec.Info.Extensions["x-options"]; ok { - if optionMap, ok := xOption.(map[string]interface{}); ok { - for key, value := range optionMap { - option := &protobuf.Option{ - Name: key, - Value: fmt.Sprintf("%q", value), - } - c.ProtoFile.Options = append(c.ProtoFile.Options, option) - } - } - } - } - - return nil -} - -// addNestedMessageToParent adds a nested message to a parent message -func (c *ProtoConverter) addNestedMessageToParent(parentMessage, nestedMessage *protobuf.ProtoMessage) { - if parentMessage != nil && nestedMessage != nil { - parentMessage.Messages = append(parentMessage.Messages, nestedMessage) - } -} - -// addNestedEnum adds a nested Enum to a parent message -func (c *ProtoConverter) addNestedEnumToParent(parentMessage *protobuf.ProtoMessage, nestedEnum *protobuf.ProtoEnum) { - if parentMessage != nil && nestedEnum != nil { - parentMessage.Enums = append(parentMessage.Enums, nestedEnum) - } -} - -// addNestedOneOfToParent adds a nested oneOf to a parent message -func (c *ProtoConverter) addNestedOneOfToParent(parentMessage *protobuf.ProtoMessage, nestedOneOf *protobuf.ProtoOneOf) { - if parentMessage != nil && nestedOneOf != nil { - parentMessage.OneOfs = append(parentMessage.OneOfs, nestedOneOf) - } -} - -// mergeProtoMessage merges a ProtoMessage into the ProtoFile -func (c *ProtoConverter) addMessageToProto(message *protobuf.ProtoMessage) error { - var existingMessage *protobuf.ProtoMessage - for _, msg := range c.ProtoFile.Messages { - if msg.Name == message.Name { - existingMessage = msg - break - } - } - - // merge message - if existingMessage != nil { - // merge Fields - fieldNames := make(map[string]struct{}) - for _, field := range existingMessage.Fields { - fieldNames[field.Name] = struct{}{} - } - for _, newField := range message.Fields { - if _, exists := fieldNames[newField.Name]; !exists { - existingMessage.Fields = append(existingMessage.Fields, newField) - } - } - - // merge Messages - messageNames := make(map[string]struct{}) - for _, nestedMsg := range existingMessage.Messages { - messageNames[nestedMsg.Name] = struct{}{} - } - for _, newMessage := range message.Messages { - if _, exists := messageNames[newMessage.Name]; !exists { - existingMessage.Messages = append(existingMessage.Messages, newMessage) - } - } - - // merge Enums - enumNames := make(map[string]struct{}) - for _, enum := range existingMessage.Enums { - enumNames[enum.Name] = struct{}{} - } - for _, newEnum := range message.Enums { - if _, exists := enumNames[newEnum.Name]; !exists { - existingMessage.Enums = append(existingMessage.Enums, newEnum) - } - } - - // merge Options - optionNames := make(map[string]struct{}) - for _, option := range existingMessage.Options { - optionNames[option.Name] = struct{}{} - } - for _, newOption := range message.Options { - if _, exists := optionNames[newOption.Name]; !exists { - existingMessage.Options = append(existingMessage.Options, newOption) - } - } - } else { - c.ProtoFile.Messages = append(c.ProtoFile.Messages, message) - } - - return nil -} - -// addEnumToProto adds an enum to the ProtoFile -func (c *ProtoConverter) addEnumToProto(enum *protobuf.ProtoEnum) { - c.ProtoFile.Enums = append(c.ProtoFile.Enums, enum) -} - -// AddProtoImport adds an import to the ProtoFile -func (c *ProtoConverter) AddProtoImport(importFile string) { - if c.ProtoFile != nil { - for _, existingImport := range c.ProtoFile.Imports { - if existingImport == importFile { - return - } - } - c.ProtoFile.Imports = append(c.ProtoFile.Imports, importFile) - } -} - -// addFieldIfNotExists adds a field to Fields if it does not already exist -func (c *ProtoConverter) addFieldIfNotExists(fields *[]*protobuf.ProtoField, field *protobuf.ProtoField) { - for _, existingField := range *fields { - if existingField.Name == field.Name { - return - } - } - *fields = append(*fields, field) -} - -// addMessageIfNotExists adds a message to Messages if it does not already exist -func (c *ProtoConverter) addMessageIfNotExists(messages *[]*protobuf.ProtoMessage, nestedMessage *protobuf.ProtoMessage) { - for _, existingMessage := range *messages { - if existingMessage.Name == nestedMessage.Name { - return - } - } - *messages = append(*messages, nestedMessage) -} - -// methodExistsInService checks if a method exists in a service -func (c *ProtoConverter) methodExistsInService(service *protobuf.ProtoService, methodName string) bool { - for _, method := range service.Methods { - if method.Name == methodName { - return true - } - } - return false -} - -// findOrCreateService finds an existing service by name or creates a new one if it doesn't exist -func (c *ProtoConverter) findOrCreateService(serviceName string) *protobuf.ProtoService { - // Iterate over existing services to find a match - for i := range c.ProtoFile.Services { - if c.ProtoFile.Services[i].Name == serviceName { - return c.ProtoFile.Services[i] - } - } - - // If no existing service is found, create a new one - newService := &protobuf.ProtoService{Name: serviceName} - c.ProtoFile.Services = append(c.ProtoFile.Services, newService) - return newService -} diff --git a/swagger2idl/converter/thrift_converter.go b/swagger2idl/converter/thrift_converter.go deleted file mode 100644 index e24532c..0000000 --- a/swagger2idl/converter/thrift_converter.go +++ /dev/null @@ -1,1297 +0,0 @@ -/* - * Copyright 2024 CloudWeGo Authors - * - * Licensed 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 ( - "errors" - "fmt" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/hertz-contrib/swagger-generate/common/consts" - common "github.com/hertz-contrib/swagger-generate/common/utils" - "github.com/hertz-contrib/swagger-generate/swagger2idl/thrift" - "github.com/hertz-contrib/swagger-generate/swagger2idl/utils" -) - -// ThriftConverter struct, used to convert OpenAPI specifications into Thrift files -type ThriftConverter struct { - spec *openapi3.T - ThriftFile *thrift.ThriftFile - converterOption *ConvertOption -} - -// NewThriftConverter creates and initializes a ThriftConverter -func NewThriftConverter(spec *openapi3.T, option *ConvertOption) *ThriftConverter { - return &ThriftConverter{ - spec: spec, - ThriftFile: &thrift.ThriftFile{ - Namespace: map[string]string{}, - Includes: []string{}, - Structs: []*thrift.ThriftStruct{}, - Enums: []*thrift.ThriftEnum{}, - Services: []*thrift.ThriftService{}, - }, - converterOption: option, - } -} - -// Convert converts the OpenAPI specification to a Thrift file -func (c *ThriftConverter) Convert() error { - // Convert the go Option to Thrift - err := c.addExtensionsToProtoOptions() - if err != nil { - return fmt.Errorf("error parsing extensions to proto options: %w", err) - } - - // Convert tags into Thrift services - c.convertTagsToThriftServices() - - // Convert components into Thrift messages - err = c.convertComponentsToThriftMessages() - if err != nil { - return fmt.Errorf("error converting components to thrift messages: %w", err) - } - - // Convert paths into Thrift services - err = c.convertPathsToThriftServices() - if err != nil { - return fmt.Errorf("error converting paths to thrift services: %w", err) - } - - if c.converterOption.OpenapiOption { - c.addOptionsToThrift() - } - - return nil -} - -func (c *ThriftConverter) GetIdl() interface{} { - return c.ThriftFile -} - -// convertTagsToThriftServices converts OpenAPI tags into Thrift services and stores them in the ThriftFile -func (c *ThriftConverter) convertTagsToThriftServices() { - tags := c.spec.Tags - for _, tag := range tags { - serviceName := common.ToPascaleCase(tag.Name) - service := &thrift.ThriftService{ - Name: serviceName, - Description: tag.Description, - } - c.ThriftFile.Services = append(c.ThriftFile.Services, service) - } -} - -// convertComponentsToThriftMessages converts OpenAPI components into Thrift messages and stores them in the ThriftFile -func (c *ThriftConverter) convertComponentsToThriftMessages() error { - components := c.spec.Components - if components == nil { - return nil - } - - if components.Schemas == nil { - return nil - } - - for name, schemaRef := range components.Schemas { - schema := schemaRef - - if c.converterOption.NamingOption { - name = common.ToPascaleCase(name) - } - - thriftType, err := c.ConvertSchemaToThriftType(schema, name, nil) - if err != nil { - return fmt.Errorf("error converting schema %s: %w", name, err) - } - - switch v := thriftType.(type) { - case *thrift.ThriftField: - message := &thrift.ThriftStruct{ - Name: name, - Fields: []*thrift.ThriftField{v}, - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiSchema, - Value: optionStr, - } - message.Options = append(message.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - c.addMessageToThrift(message) - case *thrift.ThriftStruct: - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiSchema, - Value: optionStr, - } - v.Options = append(v.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - c.addMessageToThrift(v) - case *thrift.ThriftEnum: - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiSchema, - Value: optionStr, - } - v.Options = append(v.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - c.addEnumToThrift(v) - case *thrift.ThriftUnion: - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiSchema, - Value: optionStr, - } - v.Options = append(v.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - c.addUnionToThrift(v) - } - } - return nil -} - -// convertPathsToThriftServices converts OpenAPI path items into Thrift services and stores them in the ThriftFile -func (c *ThriftConverter) convertPathsToThriftServices() error { - paths := c.spec.Paths - services, err := c.ConvertPathsToThriftServices(paths) - if err != nil { - return fmt.Errorf("error converting paths to thrift services: %w", err) - } - - c.ThriftFile.Services = append(c.ThriftFile.Services, services...) - return nil -} - -// ConvertPathsToThriftServices converts OpenAPI path items into Thrift services -func (c *ThriftConverter) ConvertPathsToThriftServices(paths *openapi3.Paths) ([]*thrift.ThriftService, error) { - var services []*thrift.ThriftService - - for path, pathItem := range paths.Map() { - for method, operation := range pathItem.Operations() { - serviceName := utils.GetServiceName(operation) - methodName := utils.GetMethodName(operation, path, method) - - if c.converterOption.NamingOption { - serviceName = common.ToPascaleCase(serviceName) - methodName = common.ToPascaleCase(methodName) - } - - inputMessage, err := c.generateRequestMessage(operation, methodName) - if err != nil { - return nil, fmt.Errorf("error generating request message for %s: %w", methodName, err) - } - - outputMessage, err := c.generateResponseMessage(operation, methodName) - if err != nil { - return nil, fmt.Errorf("error generating response message for %s: %w", methodName, err) - } - - service := c.findOrCreateService(serviceName) - - if !c.methodExistsInService(service, methodName) { - thriftMethod := &thrift.ThriftMethod{ - Name: methodName, - Input: inputMessage, - Output: outputMessage, - } - - if c.converterOption.ApiOption { - if optionName, ok := MethodToOption[method]; ok { - option := &thrift.Option{ - Name: optionName, - Value: fmt.Sprintf("%q", utils.ConvertPath(path)), - } - thriftMethod.Options = append(thriftMethod.Options, option) - } - } - - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(operation, " ") - - schemaOption := &thrift.Option{ - Name: "openapi.operation", - Value: optionStr, - } - thriftMethod.Options = append(thriftMethod.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - service.Methods = append(service.Methods, thriftMethod) - } - } - } - - return services, nil -} - -// generateRequestMessage generates a request message for an operation -func (c *ThriftConverter) generateRequestMessage(operation *openapi3.Operation, methodName string) ([]string, error) { - messageName := utils.GetMessageName(operation, methodName, "Request") - - if c.converterOption.NamingOption { - messageName = common.ToPascaleCase(messageName) - } - - message := &thrift.ThriftStruct{Name: messageName} - - if operation.RequestBody == nil && len(operation.Parameters) == 0 { - return []string{""}, nil - } - - if operation.RequestBody != nil { - if operation.RequestBody.Ref != "" { - return []string{common.ToPascaleCase(utils.ExtractMessageNameFromRef(operation.RequestBody.Ref))}, nil - } - - if operation.RequestBody.Value != nil && len(operation.RequestBody.Value.Content) > 0 { - for mediaTypeStr, mediaType := range operation.RequestBody.Value.Content { - schema := mediaType.Schema - if schema != nil { - thriftType, err := c.ConvertSchemaToThriftType(schema, common.FormatStr(mediaTypeStr), message) - if err != nil { - return []string{""}, err - } - - switch v := thriftType.(type) { - case *thrift.ThriftField: - if c.converterOption.ApiOption { - var optionName string - if mediaTypeStr == "application/json" { - optionName = "api.body" - } else if mediaTypeStr == "application/x-www-form-urlencoded" || mediaTypeStr == "multipart/form-data" { - optionName = "api.form" - } - if optionName != "" { - v.Options = append(v.Options, &thrift.Option{ - Name: optionName, - Value: fmt.Sprintf("%q", v.Name), - }) - } - } - c.addFieldIfNotExists(&message.Fields, v) - case *thrift.ThriftStruct: - for _, field := range v.Fields { - if c.converterOption.ApiOption { - var optionName string - if mediaTypeStr == "application/json" { - optionName = "api.body" - } else if mediaTypeStr == "application/x-www-form-urlencoded" || mediaTypeStr == "multipart/form-data" { - optionName = "api.form" - } - if optionName != "" { - field.Options = append(field.Options, &thrift.Option{ - Name: optionName, - Value: fmt.Sprintf("%q", field.Name), - }) - } - } - c.addFieldIfNotExists(&message.Fields, field) - } - case *thrift.ThriftEnum: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(mediaTypeStr + "_field"), - Type: v.Name, - } - if c.converterOption.ApiOption { - var optionName string - if mediaTypeStr == "application/json" { - optionName = "api.body" - } else if mediaTypeStr == "application/x-www-form-urlencoded" || mediaTypeStr == "multipart/form-data" { - optionName = "api.form" - } - if optionName != "" { - newField.Options = append(newField.Options, &thrift.Option{ - Name: optionName, - Value: fmt.Sprintf("%q", v.Name), - }) - } - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(operation.RequestBody.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - message.Fields = append(message.Fields, newField) - c.addEnumToThrift(v) - case *thrift.ThriftUnion: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(mediaTypeStr + "_field"), - Type: v.Name, - } - if c.converterOption.ApiOption { - var optionName string - if mediaTypeStr == "application/json" { - optionName = "api.body" - } else if mediaTypeStr == "application/x-www-form-urlencoded" || mediaTypeStr == "multipart/form-data" { - optionName = "api.form" - } - if optionName != "" { - newField.Options = append(newField.Options, &thrift.Option{ - Name: optionName, - Value: fmt.Sprintf("%q", v.Name), - }) - } - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(operation.RequestBody.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - message.Fields = append(message.Fields, newField) - c.addUnionToThrift(v) - } - } - } - } - } - - if len(operation.Parameters) > 0 { - for _, param := range operation.Parameters { - if param.Value.Schema != nil { - fieldOrMessage, err := c.ConvertSchemaToThriftType(param.Value.Schema, param.Value.Name, message) - if err != nil { - return []string{""}, err - } - - switch v := fieldOrMessage.(type) { - case *thrift.ThriftField: - if c.converterOption.ApiOption { - v.Options = append(v.Options, &thrift.Option{ - Name: "api." + param.Value.In, - Value: fmt.Sprintf("%q", param.Value.Name), - }) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(param.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiParameter, - Value: optionStr, - } - v.Options = append(v.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - v.Description = param.Value.Description - c.addFieldIfNotExists(&message.Fields, v) - case *thrift.ThriftStruct: - for _, field := range v.Fields { - if c.converterOption.ApiOption { - field.Options = append(field.Options, &thrift.Option{ - Name: "api." + param.Value.In, - Value: fmt.Sprintf("%q", param.Value.Name), - }) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(param.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiParameter, - Value: optionStr, - } - field.Options = append(field.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - c.addFieldIfNotExists(&message.Fields, field) - } - case *thrift.ThriftEnum: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(param.Value.Name + "_field"), - Type: v.Name, - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(param.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiParameter, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - message.Fields = append(message.Fields, newField) - c.addEnumToThrift(v) - case *thrift.ThriftUnion: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(param.Value.Name + "_field"), - Type: v.Name, - } - if c.converterOption.ApiOption { - newField.Options = append(newField.Options, &thrift.Option{ - Name: "api." + param.Value.In, - Value: fmt.Sprintf("%q", param.Value.Name), - }) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(param.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiParameter, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - message.Fields = append(message.Fields, newField) - c.addUnionToThrift(v) - } - } - } - } - - // if there are no fields or messages, return an empty message - if len(message.Fields) > 0 { - c.addMessageToThrift(message) - return []string{message.Name}, nil - } - - return []string{""}, nil -} - -// generateResponseMessage generates a response message for an operation -func (c *ThriftConverter) generateResponseMessage(operation *openapi3.Operation, methodName string) (string, error) { - if operation.Responses == nil { - return "", nil - } - - responses := operation.Responses.Map() - responseCount := 0 - for _, responseRef := range responses { - if responseRef.Ref == "" && (responseRef.Value == nil || (len(responseRef.Value.Content) == 0 && len(responseRef.Value.Headers) == 0)) { - continue - } - responseCount++ - } - - if responseCount == 1 { - for _, responseRef := range responses { - if responseRef.Ref == "" && (responseRef.Value == nil || (len(responseRef.Value.Content) == 0 && len(responseRef.Value.Headers) == 0)) { - continue - } - return c.processSingleResponse("", responseRef, operation, methodName) - } - } - - if responseCount == 0 { - return "void", nil - } - - // create a wrapper message for multiple responses - wrapperMessageName := utils.GetMessageName(operation, methodName, "Response") - if c.converterOption.NamingOption { - wrapperMessageName = common.ToPascaleCase(wrapperMessageName) - } - - wrapperMessage := &thrift.ThriftStruct{Name: wrapperMessageName} - - emptyFlag := true - - for statusCode, responseRef := range responses { - if responseRef.Ref == "" && (responseRef.Value == nil || len(responseRef.Value.Content) == 0) { - break - } - emptyFlag = false - messageName, err := c.processSingleResponse(statusCode, responseRef, operation, methodName) - if err != nil { - return "", err - } - - name := "Response_" + statusCode - if c.converterOption.NamingOption { - name = common.ToSnakeCase(name) - } - field := &thrift.ThriftField{ - Name: name, - Type: messageName, - } - wrapperMessage.Fields = append(wrapperMessage.Fields, field) - } - - if emptyFlag { - // c.AddThriftInclude(emptyThriftFile) - return "void", nil - } - - c.addMessageToThrift(wrapperMessage) - - return wrapperMessage.Name, nil -} - -// processSingleResponse deals with a single response in an operation -func (c *ThriftConverter) processSingleResponse(statusCode string, responseRef *openapi3.ResponseRef, operation *openapi3.Operation, methodName string) (string, error) { - if responseRef.Ref != "" { - return common.ToPascaleCase(utils.ExtractMessageNameFromRef(responseRef.Ref)), nil - } - - response := responseRef.Value - messageName := utils.GetMessageName(operation, methodName, "Response") + common.ToUpperCase(statusCode) - - if c.converterOption.NamingOption { - messageName = common.ToPascaleCase(messageName) - } - - message := &thrift.ThriftStruct{Name: messageName} - - if len(response.Headers) > 0 { - for headerName, headerRef := range response.Headers { - if headerRef != nil { - - fieldOrMessage, err := c.ConvertSchemaToThriftType(headerRef.Value.Schema, headerName, message) - if err != nil { - return "", err - } - - switch v := fieldOrMessage.(type) { - case *thrift.ThriftField: - if c.converterOption.ApiOption { - option := &thrift.Option{ - Name: "api.header", - Value: fmt.Sprintf("%q", headerName), - } - v.Options = append(v.Options, option) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(headerRef.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - v.Options = append(v.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - c.addFieldIfNotExists(&message.Fields, v) - case *thrift.ThriftStruct: - for _, field := range v.Fields { - if c.converterOption.ApiOption { - option := &thrift.Option{ - Name: "api.header", - Value: fmt.Sprintf("%q", field.Name), - } - field.Options = append(field.Options, option) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(headerRef.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - field.Options = append(field.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - c.addFieldIfNotExists(&message.Fields, field) - } - case *thrift.ThriftEnum: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(headerName + "_field"), - Type: v.Name, - } - if c.converterOption.ApiOption { - option := &thrift.Option{ - Name: "api.header", - Value: fmt.Sprintf("%q", headerName), - } - newField.Options = append(newField.Options, option) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(headerRef.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - message.Fields = append(message.Fields, newField) - c.addEnumToThrift(v) - case *thrift.ThriftUnion: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(headerName + "_field"), - Type: v.Name, - } - if c.converterOption.ApiOption { - option := &thrift.Option{ - Name: "api.header", - Value: fmt.Sprintf("%q", headerName), - } - newField.Options = append(newField.Options, option) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(headerRef.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - message.Fields = append(message.Fields, newField) - c.addUnionToThrift(v) - } - } - } - } - - for mediaTypeStr, mediaType := range response.Content { - schema := mediaType.Schema - if schema != nil { - - thriftType, err := c.ConvertSchemaToThriftType(schema, common.FormatStr(mediaTypeStr), message) - if err != nil { - return "", err - } - - switch v := thriftType.(type) { - case *thrift.ThriftField: - if c.converterOption.ApiOption && mediaTypeStr == "application/json" { - option := &thrift.Option{ - Name: "api.body", - Value: fmt.Sprintf("%q", v.Name), - } - v.Options = append(v.Options, option) - } - c.addFieldIfNotExists(&message.Fields, v) - case *thrift.ThriftStruct: - for _, field := range v.Fields { - if c.converterOption.ApiOption && mediaTypeStr == "application/json" { - option := &thrift.Option{ - Name: "api.body", - Value: fmt.Sprintf("%q", field.Name), - } - field.Options = append(field.Options, option) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - field.Options = append(field.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - c.addFieldIfNotExists(&message.Fields, field) - } - case *thrift.ThriftEnum: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(mediaTypeStr + "_field"), - Type: v.Name, - } - if c.converterOption.ApiOption && mediaTypeStr == "application/json" { - option := &thrift.Option{ - Name: "api.body", - Value: fmt.Sprintf("%q", v.Name), - } - newField.Options = append(newField.Options, option) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - message.Fields = append(message.Fields, newField) - c.addEnumToThrift(v) - case *thrift.ThriftUnion: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(mediaTypeStr + "_field"), - Type: v.Name, - } - if c.converterOption.ApiOption && mediaTypeStr == "application/json" { - option := &thrift.Option{ - Name: "api.body", - Value: fmt.Sprintf("%q", v.Name), - } - newField.Options = append(newField.Options, option) - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(schema, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - message.Fields = append(message.Fields, newField) - c.addUnionToThrift(v) - } - } - } - - if len(message.Fields) > 0 { - c.addMessageToThrift(message) - return message.Name, nil - } - return "", nil -} - -// ConvertSchemaToThriftType converts an OpenAPI schema to a Thrift field or message -func (c *ThriftConverter) ConvertSchemaToThriftType( - schemaRef *openapi3.SchemaRef, - thriftName string, - parentMessage *thrift.ThriftStruct, -) (interface{}, error) { - var thriftType string - var result interface{} - - // Handle referenced schema - if schemaRef.Ref != "" { - name := c.applySnakeCaseNamingOption(utils.ExtractMessageNameFromRef(schemaRef.Ref)) - return &thrift.ThriftField{ - Name: name, - Type: common.ToPascaleCase(utils.ExtractMessageNameFromRef(schemaRef.Ref)), - }, nil - } - - // Ensure schema value is valid - if schemaRef.Value == nil { - return nil, errors.New("schema type is required") - } - - schema := schemaRef.Value - description := schema.Description - - // Handle oneOf, allOf, anyOf even if schema.Type is nil - if len(schema.OneOf) > 0 { - thriftStruct, err := c.handleOneOf(schema.OneOf, thriftName, parentMessage) - if err != nil { - return nil, err - } - return thriftStruct, nil - } else if len(schema.AllOf) > 0 { - thriftStruct, err := c.handleAllOf(schema.AllOf, thriftName, parentMessage) - if err != nil { - return nil, err - } - return thriftStruct, nil - } else if len(schema.AnyOf) > 0 { - thriftStruct, err := c.handleAnyOf(schema.AnyOf, thriftName, parentMessage) - if err != nil { - return nil, err - } - return thriftStruct, nil - } - - // Process schema type - switch { - case schema.Type.Includes("string"): - if schema.Format == "date" || schema.Format == "date-time" { - thriftType = "string" - } else if schema.Format == "byte" || schema.Format == "binary" { - thriftType = "binary" - } else if len(schema.Enum) != 0 { - var name string - if parentMessage == nil { - name = thriftName - } else { - name = c.applyPascalseCaseNamingOption(common.ToUpperCase(thriftName)) - } - thriftEnum := &thrift.ThriftEnum{ - Name: name, - Description: description, - } - for i, enumValue := range schema.Enum { - thriftEnum.Values = append(thriftEnum.Values, &thrift.ThriftEnumValue{ - Index: i, - Value: enumValue, - }) - } - result = thriftEnum - } else { - thriftType = "string" - } - - case schema.Type.Includes("integer"): - if len(schema.Enum) != 0 { - var name string - if parentMessage == nil { - name = thriftName - } else { - name = c.applyPascalseCaseNamingOption(common.ToUpperCase(thriftName)) - } - thriftEnum := &thrift.ThriftEnum{ - Name: name, - Description: description, - } - for i, enumValue := range schema.Enum { - thriftEnum.Values = append(thriftEnum.Values, &thrift.ThriftEnumValue{ - Index: i, - Value: enumValue, - }) - } - result = thriftEnum - } else if schema.Format == "int32" { - thriftType = "i32" - } else { - thriftType = "i64" - } - - case schema.Type.Includes("number"): - if len(schema.Enum) != 0 { - var name string - if parentMessage == nil { - name = thriftName - } else { - name = c.applyPascalseCaseNamingOption(common.ToUpperCase(thriftName)) - } - thriftEnum := &thrift.ThriftEnum{ - Name: name, - Description: description, - } - for i, enumValue := range schema.Enum { - thriftEnum.Values = append(thriftEnum.Values, &thrift.ThriftEnumValue{ - Index: i, - Value: enumValue, - }) - } - result = thriftEnum - } else if schema.Format == "float" { - thriftType = "float" - } else { - thriftType = "double" - } - - case schema.Type.Includes("boolean"): - thriftType = "bool" - - case schema.Type.Includes("array"): - if schema.Items != nil { - fieldOrMessage, err := c.ConvertSchemaToThriftType(schema.Items, thriftName+"Item", parentMessage) - if err != nil { - return nil, err - } - - fieldType := "" - if field, ok := fieldOrMessage.(*thrift.ThriftField); ok { - fieldType = field.Type - } else if nestedMessage, ok := fieldOrMessage.(*thrift.ThriftStruct); ok { - fieldType = nestedMessage.Name - c.addMessageToThrift(nestedMessage) - } else if enum, ok := fieldOrMessage.(*thrift.ThriftEnum); ok { - fieldType = enum.Name - c.addEnumToThrift(enum) - } else if union, ok := fieldOrMessage.(*thrift.ThriftUnion); ok { - fieldType = union.Name - c.addUnionToThrift(union) - } - - result = &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(thriftName), - Type: fieldType, - Repeated: true, - Description: description, - } - } - - case schema.Type.Includes("object"): - - // Regular object handling - var message *thrift.ThriftStruct - if parentMessage == nil { - message = &thrift.ThriftStruct{Name: thriftName} - } else { - message = &thrift.ThriftStruct{Name: c.applyPascalseCaseNamingOption(common.ToUpperCase(thriftName))} - } - - // Process each property in the object - for propName, propSchema := range schema.Properties { - thriftType, err := c.ConvertSchemaToThriftType(propSchema, propName, message) - if err != nil { - return nil, err - } - - // Add the converted fields to the message - if field, ok := thriftType.(*thrift.ThriftField); ok { - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(propSchema, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - field.Options = append(field.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - message.Fields = append(message.Fields, field) - } else if nestedMessage, ok := thriftType.(*thrift.ThriftStruct); ok { - var name string - if c.converterOption.NamingOption { - name = common.ToSnakeCase(nestedMessage.Name) - } - newField := &thrift.ThriftField{ - Name: name + "_field", - Type: nestedMessage.Name, - } - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(propSchema.Value, " ") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiProperty, - Value: optionStr, - } - newField.Options = append(newField.Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - c.addMessageToThrift(nestedMessage) - message.Fields = append(message.Fields, newField) - } else if enum, ok := thriftType.(*thrift.ThriftEnum); ok { - c.addEnumToThrift(enum) - message.Fields = append(message.Fields, &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(propName + "_field"), - Type: enum.Name, - }) - } else if union, ok := thriftType.(*thrift.ThriftUnion); ok { - c.addUnionToThrift(union) - message.Fields = append(message.Fields, &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(propName + "_field"), - Type: union.Name, - }) - } - } - - // Handle additionalProperties if present - if schema.AdditionalProperties.Schema != nil { - mapValueType := "string" - additionalPropMessage, err := c.ConvertSchemaToThriftType(schema.AdditionalProperties.Schema, thriftName+"AdditionalProperties", parentMessage) - if err != nil { - return nil, err - } - if msg, ok := additionalPropMessage.(*thrift.ThriftStruct); ok { - mapValueType = msg.Name - } else if enum, ok := additionalPropMessage.(*thrift.ThriftEnum); ok { - mapValueType = enum.Name - } - - message.Fields = append(message.Fields, &thrift.ThriftField{ - Name: "additionalProperties", - Type: "map", - }) - } - - // Set the result as the final message - message.Description = description - result = message - } - - // If result is still nil, construct a default ThriftField - if result == nil { - result = &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(thriftName), - Type: thriftType, - Description: description, - } - } - - return result, nil -} - -func (c *ThriftConverter) handleOneOf(oneOfSchemas []*openapi3.SchemaRef, thriftName string, parentMessage *thrift.ThriftStruct) (*thrift.ThriftUnion, error) { - oneOfUnion := &thrift.ThriftUnion{ - Name: c.applyPascalseCaseNamingOption(thriftName + "OneOf"), - } - - for i, schemaRef := range oneOfSchemas { - fieldName := fmt.Sprintf("%sOption%d", thriftName, i+1) - thriftType, err := c.ConvertSchemaToThriftType(schemaRef, fieldName, parentMessage) - if err != nil { - return nil, err - } - - switch v := thriftType.(type) { - case *thrift.ThriftField: - oneOfUnion.Fields = append(oneOfUnion.Fields, v) - case *thrift.ThriftStruct: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addMessageToThrift(v) - oneOfUnion.Fields = append(oneOfUnion.Fields, newField) - case *thrift.ThriftEnum: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addEnumToThrift(v) - oneOfUnion.Fields = append(oneOfUnion.Fields, newField) - case *thrift.ThriftUnion: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addUnionToThrift(v) - oneOfUnion.Fields = append(oneOfUnion.Fields, newField) - } - } - - return oneOfUnion, nil -} - -func (c *ThriftConverter) handleAllOf(allOfSchemas []*openapi3.SchemaRef, thriftName string, parentMessage *thrift.ThriftStruct) (*thrift.ThriftStruct, error) { - allOfStruct := &thrift.ThriftStruct{ - Name: c.applyPascalseCaseNamingOption(thriftName + "AllOf"), - } - - for i, schemaRef := range allOfSchemas { - fieldName := fmt.Sprintf("%sPart%d", thriftName, i+1) - thriftType, err := c.ConvertSchemaToThriftType(schemaRef, fieldName, parentMessage) - if err != nil { - return nil, err - } - - switch v := thriftType.(type) { - case *thrift.ThriftField: - allOfStruct.Fields = append(allOfStruct.Fields, v) - case *thrift.ThriftStruct: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addMessageToThrift(v) - allOfStruct.Fields = append(allOfStruct.Fields, newField) - case *thrift.ThriftEnum: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addEnumToThrift(v) - allOfStruct.Fields = append(allOfStruct.Fields, newField) - case *thrift.ThriftUnion: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addUnionToThrift(v) - allOfStruct.Fields = append(allOfStruct.Fields, newField) - } - } - - return allOfStruct, nil -} - -func (c *ThriftConverter) handleAnyOf(anyOfSchemas []*openapi3.SchemaRef, thriftName string, parentMessage *thrift.ThriftStruct) (*thrift.ThriftStruct, error) { - anyOfStruct := &thrift.ThriftStruct{ - Name: c.applyPascalseCaseNamingOption(thriftName + "AnyOf"), - } - - for i, schemaRef := range anyOfSchemas { - fieldName := fmt.Sprintf("%sOption%d", thriftName, i+1) - thriftType, err := c.ConvertSchemaToThriftType(schemaRef, fieldName, parentMessage) - if err != nil { - return nil, err - } - - switch v := thriftType.(type) { - case *thrift.ThriftField: - anyOfStruct.Fields = append(anyOfStruct.Fields, v) - case *thrift.ThriftStruct: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addMessageToThrift(v) - anyOfStruct.Fields = append(anyOfStruct.Fields, newField) - case *thrift.ThriftEnum: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addEnumToThrift(v) - anyOfStruct.Fields = append(anyOfStruct.Fields, newField) - case *thrift.ThriftUnion: - newField := &thrift.ThriftField{ - Name: c.applySnakeCaseNamingOption(v.Name + "_field"), - Type: v.Name, - } - c.addUnionToThrift(v) - anyOfStruct.Fields = append(anyOfStruct.Fields, newField) - } - } - - return anyOfStruct, nil -} - -// applyPascalseCaseNamingOption applies naming convention based on the converter's naming option -func (c *ThriftConverter) applyPascalseCaseNamingOption(name string) string { - if c.converterOption.NamingOption { - return common.ToPascaleCase(name) - } - return name -} - -// applySnakeCaseNamingOption applies naming convention based on the converter's naming option -func (c *ThriftConverter) applySnakeCaseNamingOption(name string) string { - if c.converterOption.NamingOption { - return common.ToSnakeCase(name) - } - return name -} - -// Add a new method to handle structured extensions -func (c *ThriftConverter) addExtensionsToProtoOptions() error { - // Check for x-option in spec extensions - if xOption, ok := c.spec.Extensions["x-options"]; ok { - if optionMap, ok := xOption.(map[string]interface{}); ok { - for key, value := range optionMap { - c.ThriftFile.Namespace[key] = fmt.Sprintf("%q", value) - } - } - } - - // Check for x-option in spec.info.extensions - if c.spec.Info != nil { - if xOption, ok := c.spec.Info.Extensions["x-options"]; ok { - if optionMap, ok := xOption.(map[string]interface{}); ok { - for key, value := range optionMap { - c.ThriftFile.Namespace[key] = fmt.Sprintf("%q", value) - } - } - } - } - - return nil -} - -// addMessageToThrift adds a ThriftStruct to the ThriftFile globally -func (c *ThriftConverter) addMessageToThrift(message *thrift.ThriftStruct) error { - if message == nil { - return errors.New("message is nil") - } - - // Check if the message already exists in the ThriftFile - for _, existingMessage := range c.ThriftFile.Structs { - if existingMessage.Name == message.Name { - // Merge fields if the message already exists - fieldNames := make(map[string]struct{}) - for _, field := range existingMessage.Fields { - fieldNames[field.Name] = struct{}{} - } - for _, newField := range message.Fields { - if _, exists := fieldNames[newField.Name]; !exists { - existingMessage.Fields = append(existingMessage.Fields, newField) - } - } - return nil - } - } - - // Add the message globally - c.ThriftFile.Structs = append(c.ThriftFile.Structs, message) - return nil -} - -// addEnumToThrift adds an enum to the ThriftFile -func (c *ThriftConverter) addEnumToThrift(enum *thrift.ThriftEnum) { - c.ThriftFile.Enums = append(c.ThriftFile.Enums, enum) -} - -// addUnionToThrift adds a union to the ThriftFile -func (c *ThriftConverter) addUnionToThrift(union *thrift.ThriftUnion) { - c.ThriftFile.Unions = append(c.ThriftFile.Unions, union) -} - -// AddThriftInclude adds an include to the ThriftFile -func (c *ThriftConverter) AddThriftInclude(includeFile string) { - if c.ThriftFile != nil { - for _, existingInclude := range c.ThriftFile.Includes { - if existingInclude == includeFile { - return - } - } - c.ThriftFile.Includes = append(c.ThriftFile.Includes, includeFile) - } -} - -// addOptionsToThrift adds options to the Thrift file -func (c *ThriftConverter) addOptionsToThrift() { - if len(c.ThriftFile.Services) > 0 { - if c.converterOption.OpenapiOption { - optionStr := common.StructToOption(c.spec, "") - - schemaOption := &thrift.Option{ - Name: consts.OpenapiDocument, - Value: optionStr, - } - c.ThriftFile.Services[0].Options = append(c.ThriftFile.Services[0].Options, schemaOption) - c.AddThriftInclude(consts.OpenapiThriftFile) - } - } -} - -// addFieldIfNotExists adds a field to Fields if it does not already exist -func (c *ThriftConverter) addFieldIfNotExists(fields *[]*thrift.ThriftField, field *thrift.ThriftField) { - for _, existingField := range *fields { - if existingField.Name == field.Name { - return - } - } - *fields = append(*fields, field) -} - -// methodExistsInService checks if a method exists in a service -func (c *ThriftConverter) methodExistsInService(service *thrift.ThriftService, methodName string) bool { - for _, method := range service.Methods { - if method.Name == methodName { - return true - } - } - return false -} - -// findOrCreateService finds or creates a service -func (c *ThriftConverter) findOrCreateService(serviceName string) *thrift.ThriftService { - for i := range c.ThriftFile.Services { - if c.ThriftFile.Services[i].Name == serviceName { - return c.ThriftFile.Services[i] - } - } - - // If no existing service is found, create a new one - newService := &thrift.ThriftService{Name: serviceName} - c.ThriftFile.Services = append(c.ThriftFile.Services, newService) - return newService -} diff --git a/swagger2idl/example/openapi.yaml b/swagger2idl/example/openapi.yaml deleted file mode 100644 index 65019ca..0000000 --- a/swagger2idl/example/openapi.yaml +++ /dev/null @@ -1,224 +0,0 @@ -# Generated with protoc-gen-http-swagger -# https://github.com/hertz-contrib/swagger-generate/protoc-gen-http-swagger - -openapi: 3.0.3 -info: - title: example swagger doc - version: Version from annotation -servers: - - url: http://127.0.0.1:8888 - - url: http://127.0.0.1:8889 -paths: - /body: - post: - tags: - - HelloService1 - operationId: HelloService1_BodyMethod - parameters: - - name: query2 - in: query - description: 'field: query描述' - schema: - type: string - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/BodyReqBody' - responses: - "200": - description: HelloResp描述 - headers: - token: - schema: - type: string - content: - application/json: - schema: - $ref: '#/components/schemas/HelloRespBody' - servers: - - url: http://127.0.0.1:8888 - /form: - post: - tags: - - HelloService1 - operationId: HelloService1_FormMethod - requestBody: - content: - multipart/form-data: - schema: - $ref: '#/components/schemas/FormReqForm' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/FormReqForm' - responses: - "200": - description: HelloResp描述 - headers: - token: - schema: - type: string - content: - application/json: - schema: - $ref: '#/components/schemas/HelloRespBody' - servers: - - url: http://127.0.0.1:8888 - /hello1: - get: - tags: - - HelloService1 - operationId: HelloService1_QueryMethod1 - parameters: - - name: query1 - in: query - schema: - type: object - additionalProperties: - type: string - - name: items - in: query - schema: - type: array - items: - type: string - - name: query2 - in: query - description: QueryValue描述 - required: true - schema: - title: Name - maxLength: 50 - minLength: 1 - type: string - description: Name - responses: - "200": - description: HelloResp描述 - headers: - token: - schema: - type: string - content: - application/json: - schema: - $ref: '#/components/schemas/HelloRespBody' - servers: - - url: http://127.0.0.1:8888 - /hello2: - get: - tags: - - HelloService2 - summary: Hello - Get - description: Hello - Get - operationId: HelloService2_QueryMethod2 - parameters: - - name: query1 - in: query - schema: - type: object - additionalProperties: - type: string - - name: items - in: query - schema: - type: array - items: - type: string - - name: query2 - in: query - description: QueryValue描述 - required: true - schema: - title: Name - maxLength: 50 - minLength: 1 - type: string - description: Name - responses: - "200": - description: HelloResp描述 - headers: - token: - schema: - type: string - content: - application/json: - schema: - $ref: '#/components/schemas/HelloRespBody' - servers: - - url: http://127.0.0.1:8889 - /path{path1}: - get: - tags: - - HelloService1 - operationId: HelloService1_PathMethod - parameters: - - name: path1 - in: path - description: 'field: path描述' - required: true - schema: - type: string - responses: - "200": - description: HelloResp描述 - headers: - token: - schema: - type: string - content: - application/json: - schema: - $ref: '#/components/schemas/HelloRespBody' - servers: - - url: http://127.0.0.1:8888 -components: - schemas: - BodyReqBody: - type: object - properties: - body: - type: string - description: 'field: body描述' - body1: - type: string - description: 'field: body1描述' - FormReqForm: - title: Hello - request - required: - - form1 - type: object - properties: - form1: - title: this is an override field schema title - maxLength: 255 - type: string - form2: - $ref: '#/components/schemas/FormReq_InnerForm' - description: Hello - request - FormReq_InnerForm: - type: object - properties: - form3: - type: string - description: 内嵌message描述 - HelloRespBody: - title: Hello - response - required: - - body - type: object - properties: - body: - title: response content - maxLength: 80 - minLength: 1 - type: string - description: response content - description: Hello - response -tags: - - name: HelloService1 - description: HelloService1描述 - - name: HelloService2 -x-options: - go_package: example \ No newline at end of file diff --git a/swagger2idl/generate/generate.go b/swagger2idl/generate/generate.go deleted file mode 100644 index 748bf27..0000000 --- a/swagger2idl/generate/generate.go +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2024 CloudWeGo Authors - * - * Licensed 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 generate - -// Generator is an interface for generating files -type Generator interface { - Generate(fileContent interface{}) (string, error) -} diff --git a/swagger2idl/generate/proto_generate.go b/swagger2idl/generate/proto_generate.go deleted file mode 100644 index 2feac1a..0000000 --- a/swagger2idl/generate/proto_generate.go +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright 2024 CloudWeGo Authors - * - * Licensed 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 generate - -import ( - "fmt" - "sort" - "strconv" - "strings" - - common "github.com/hertz-contrib/swagger-generate/common/utils" - "github.com/hertz-contrib/swagger-generate/swagger2idl/protobuf" -) - -// ProtoGenerate is used to handle the encoding context -type ProtoGenerate struct { - dst *strings.Builder // The target for output -} - -// NewProtoGenerate creates a new ProtoGenerate instance -func NewProtoGenerate() *ProtoGenerate { - return &ProtoGenerate{dst: &strings.Builder{}} -} - -// Generate converts the ProtoFile structure into Proto file content -func (e *ProtoGenerate) Generate(fileContent interface{}) (string, error) { - protoFile, ok := fileContent.(*protobuf.ProtoFile) - if !ok { - return "", fmt.Errorf("invalid type: expected *protobuf.ProtoFile") - } - - e.dst.WriteString("syntax = \"proto3\";\n\n") - e.dst.WriteString(fmt.Sprintf("package %s;\n\n", protoFile.PackageName)) - - // Generate imports - if len(protoFile.Imports) > 0 { - for _, importFile := range protoFile.Imports { - e.dst.WriteString(fmt.Sprintf("import \"%s\";\n", importFile)) - } - e.dst.WriteString("\n") - } - - // Generate file-level options - if len(protoFile.Options) > 0 { - for _, value := range protoFile.Options { - e.dst.WriteString(fmt.Sprintf("option %s = %s;\n", value.Name, value.Value)) - } - e.dst.WriteString("\n") - } - - // Sort enums by name - sort.Slice(protoFile.Enums, func(i, j int) bool { - return protoFile.Enums[i].Name < protoFile.Enums[j].Name - }) - - // Generate enums - for _, enum := range protoFile.Enums { - if err := e.encodeEnum(enum, 0); err != nil { - return "", fmt.Errorf("failed to encode enum %s: %w", enum.Name, err) - } - } - - // Sort messages by name - sort.Slice(protoFile.Messages, func(i, j int) bool { - return protoFile.Messages[i].Name < protoFile.Messages[j].Name - }) - - if len(protoFile.Messages) > 0 { - for _, message := range protoFile.Messages { - if err := e.encodeMessage(message, 0); err != nil { - return "", fmt.Errorf("failed to encode message %s: %w", message.Name, err) - } - } - } - - // Sort services by name - sort.Slice(protoFile.Services, func(i, j int) bool { - return protoFile.Services[i].Name < protoFile.Services[j].Name - }) - - // Generate services - for _, service := range protoFile.Services { - if err := e.encodeService(service); err != nil { - return "", fmt.Errorf("failed to encode service %s: %w", service.Name, err) - } - } - - return e.dst.String(), nil -} - -// encodeService encodes service types -func (e *ProtoGenerate) encodeService(service *protobuf.ProtoService) error { - if service.Description != "" { - e.dst.WriteString(fmt.Sprintf("// %s\n", service.Description)) - } - e.dst.WriteString(fmt.Sprintf("service %s {\n", service.Name)) - - // Sort methods by name - sort.Slice(service.Methods, func(i, j int) bool { - return service.Methods[i].Name < service.Methods[j].Name - }) - - for _, method := range service.Methods { - if method.Description != "" { - e.dst.WriteString(fmt.Sprintf(" // %s\n", method.Description)) - } - e.dst.WriteString(fmt.Sprintf(" rpc %s(%s) returns (%s)", method.Name, method.Input, method.Output)) - if len(method.Options) > 0 { - sort.Slice(method.Options, func(i, j int) bool { - return method.Options[i].Name < method.Options[j].Name - }) - e.dst.WriteString(" {\n") - for _, option := range method.Options { - e.dst.WriteString(" option ") - if err := e.encodeFieldOption(option); err != nil { - return fmt.Errorf("failed to encode option for method %s: %w", method.Name, err) - } - e.dst.WriteString(";\n") - } - e.dst.WriteString(" }\n") - } else { - e.dst.WriteString(";\n") - } - } - e.dst.WriteString("}\n\n") - return nil -} - -// encodeMessage recursively encodes messages, including nested messages, enums, and oneofs -func (e *ProtoGenerate) encodeMessage(message *protobuf.ProtoMessage, indentLevel int) error { - if indentLevel > 0 { - e.dst.WriteString("\n") - } - indent := strings.Repeat(" ", indentLevel) - if message.Description != "" { - e.dst.WriteString(fmt.Sprintf("%s// %s\n", indent, message.Description)) - } - e.dst.WriteString(fmt.Sprintf("%smessage %s {\n", indent, message.Name)) - - // Generate message-level options - if len(message.Options) > 0 { - sort.Slice(message.Options, func(i, j int) bool { - return message.Options[i].Name < message.Options[j].Name - }) - e.dst.WriteString(fmt.Sprintf("%s option", indent)) - for _, option := range message.Options { - if err := e.encodeFieldOption(option); err != nil { - return fmt.Errorf("failed to encode option for message %s: %w", message.Name, err) - } - e.dst.WriteString(";\n") - } - } - - // Sort fields by name - sort.Slice(message.Fields, func(i, j int) bool { - return message.Fields[i].Name < message.Fields[j].Name - }) - - // Generate fields - for i, field := range message.Fields { - err := e.encodeField(field, i+1, indentLevel) - if err != nil { - return fmt.Errorf("failed to encode field %s: %w", field.Name, err) - } - } - - // Generate oneofs - for _, oneOf := range message.OneOfs { - err := e.encodeOneOf(oneOf, indentLevel+1, len(message.Fields)+1) - if err != nil { - return fmt.Errorf("failed to encode oneof %s: %w", oneOf.Name, err) - } - } - - // Generate nested enums - if len(message.Enums) > 0 { - e.dst.WriteString("\n") - for _, nestedEnum := range message.Enums { - if err := e.encodeEnum(nestedEnum, indentLevel+1); err != nil { - return fmt.Errorf("failed to encode nested enum %s: %w", nestedEnum.Name, err) - } - } - } - - // Generate nested messages - for _, nestedMessage := range message.Messages { - if err := e.encodeMessage(nestedMessage, indentLevel+1); err != nil { - return fmt.Errorf("failed to encode nested message %s: %w", nestedMessage.Name, err) - } - } - - e.dst.WriteString(fmt.Sprintf("%s}\n\n", indent)) - return nil -} - -// encodeEnum encodes enum types -func (e *ProtoGenerate) encodeEnum(enum *protobuf.ProtoEnum, indentLevel int) error { - indent := strings.Repeat(" ", indentLevel) - e.dst.WriteString(fmt.Sprintf("%senum %s {\n", indent, enum.Name)) - - // Generate enum values - for _, value := range enum.Values { - valueStr := fmt.Sprintf("%v", value.Value) - enumValueName := valueStr - if _, err := strconv.Atoi(valueStr); err == nil { - enumValueName = fmt.Sprintf("%s%s", enum.Name, valueStr) - } - enumValueName = strings.ToUpper(common.FormatStr(enumValueName)) - e.dst.WriteString(fmt.Sprintf("%s %s = %d;\n", indent, enumValueName, value.Index)) - } - - e.dst.WriteString(fmt.Sprintf("%s}\n\n", indent)) - return nil -} - -// encodeField encodes a single field in the message. -func (e *ProtoGenerate) encodeField(field *protobuf.ProtoField, fieldNumber, indentLevel int) error { - indent := strings.Repeat(" ", indentLevel) - - // Add field description if present - if field.Description != "" { - e.dst.WriteString(fmt.Sprintf("%s // %s\n", indent, field.Description)) - } - - // Determine if the field is repeated - repeated := "" - if field.Repeated { - repeated = "repeated " - } - - // Write the field definition - e.dst.WriteString(fmt.Sprintf("%s %s%s %s = %d", indent, repeated, field.Type, common.FormatStr(field.Name), fieldNumber)) - - // Generate field-level options if present - if len(field.Options) > 0 { - sort.Slice(field.Options, func(i, j int) bool { - return field.Options[i].Name < field.Options[j].Name - }) - e.dst.WriteString(" [\n ") - for j, option := range field.Options { - if err := e.encodeFieldOption(option); err != nil { - return fmt.Errorf("failed to encode option for field %s: %w", field.Name, err) - } - if j < len(field.Options)-1 { - e.dst.WriteString(",\n ") - } - } - e.dst.WriteString("\n ]") - } - - e.dst.WriteString(";\n") - return nil -} - -// encodeOneOf encodes oneof types -func (e *ProtoGenerate) encodeOneOf(oneOf *protobuf.ProtoOneOf, indentLevel, fieldNumber int) error { - indent := strings.Repeat(" ", indentLevel) - e.dst.WriteString(fmt.Sprintf("%soneof %s {\n", indent, oneOf.Name)) - // Generate oneof fields - for _, field := range oneOf.Fields { - e.dst.WriteString(fmt.Sprintf("%s %s %s = %d;\n", indent, field.Type, field.Name, fieldNumber)) - fieldNumber++ - } - - e.dst.WriteString(fmt.Sprintf("%s}\n", indent)) - return nil -} - -// encodeFieldOption encodes an option for a single field -func (e *ProtoGenerate) encodeFieldOption(opt *protobuf.Option) error { - // Output the option name - fmt.Fprintf(e.dst, "(%s) = ", opt.Name) // Add indentation for consistency - - // Check if the option value is a complex structure - switch value := opt.Value.(type) { - case map[string]interface{}: - // If it's a map type, it needs to output as a nested structure - fmt.Fprintf(e.dst, "{\n") // Newline after { - e.encodeFieldOptionMap(value, 6) // Output map content, passing the current indentation level - fmt.Fprintf(e.dst, " }") // Indent and output the closing }, with the appropriate indentation level - default: - fmt.Fprintf(e.dst, "%s", value) // For simple types, output directly - } - - return nil -} - -// encodeFieldOptionMap encodes a complex map type option value -func (e *ProtoGenerate) encodeFieldOptionMap(optionMap map[string]interface{}, indent int) error { - keys := make([]string, 0, len(optionMap)) - for k := range optionMap { - keys = append(keys, k) - } - sort.Strings(keys) // Sort keys to ensure consistent output order - - indentSpace := strings.Repeat(" ", indent) // Dynamically generate indent spaces - - for _, key := range keys { - value := optionMap[key] - // Output key-value pairs with appropriate indentation - fmt.Fprintf(e.dst, "%s%s: %s", indentSpace, key, common.Stringify(value)) // Add deeper indentation - // Don't add a semicolon after the last item, maintain correct format - fmt.Fprintf(e.dst, ";\n") - } - - return nil -} diff --git a/swagger2idl/generate/thrift_generate.go b/swagger2idl/generate/thrift_generate.go deleted file mode 100644 index bfaa0d6..0000000 --- a/swagger2idl/generate/thrift_generate.go +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright 2024 CloudWeGo Authors - * - * Licensed 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 generate - -import ( - "fmt" - "sort" - "strconv" - "strings" - - common "github.com/hertz-contrib/swagger-generate/common/utils" - "github.com/hertz-contrib/swagger-generate/swagger2idl/thrift" -) - -// ThriftGenerate handles the encoding context for Thrift files -type ThriftGenerate struct { - dst *strings.Builder // Output destination -} - -// NewThriftGenerate creates a new instance of ThriftGenerate -func NewThriftGenerate() *ThriftGenerate { - return &ThriftGenerate{dst: &strings.Builder{}} -} - -// Generate converts a ThriftFile structure into Thrift file content -func (e *ThriftGenerate) Generate(fileContent interface{}) (string, error) { - thriftFile, ok := fileContent.(*thrift.ThriftFile) - if !ok { - return "", fmt.Errorf("invalid type: expected *ThriftFile") - } - - if len(thriftFile.Namespace) == 0 { - e.dst.WriteString("namespace go example\n\n") - } else { - for language, ns := range thriftFile.Namespace { - e.dst.WriteString(fmt.Sprintf("namespace %s %s\n", language, ns)) - } - e.dst.WriteString("\n") - } - - // Generate includes - if len(thriftFile.Includes) > 0 { - for _, include := range thriftFile.Includes { - e.dst.WriteString(fmt.Sprintf("include \"%s\"\n", include)) - } - - e.dst.WriteString("\n") - } - - // sort the enums - sort.Slice(thriftFile.Enums, func(i, j int) bool { - return thriftFile.Enums[i].Name < thriftFile.Enums[j].Name - }) - - // Generate enums - for _, enum := range thriftFile.Enums { - e.encodeEnum(enum, 0) - } - - // sort the structs - sort.Slice(thriftFile.Structs, func(i, j int) bool { - return thriftFile.Structs[i].Name < thriftFile.Structs[j].Name - }) - - // Generate structs - for _, message := range thriftFile.Structs { - e.encodeMessage(message, 0) - } - - // sort the unions - sort.Slice(thriftFile.Unions, func(i, j int) bool { - return thriftFile.Unions[i].Name < thriftFile.Unions[j].Name - }) - - // Generate unions - for _, union := range thriftFile.Unions { - e.encodeUnion(union, 0) - } - - // sort the services - sort.Slice(thriftFile.Services, func(i, j int) bool { - return thriftFile.Services[i].Name < thriftFile.Services[j].Name - }) - - // Generate services - for _, service := range thriftFile.Services { - e.encodeService(service) - } - - return e.dst.String(), nil -} - -// encodeService encodes service definitions -func (e *ThriftGenerate) encodeService(service *thrift.ThriftService) { - if service.Description != "" { - e.dst.WriteString(fmt.Sprintf("// %s\n", service.Description)) - } - - e.dst.WriteString(fmt.Sprintf("service %s {\n", service.Name)) // Service declaration - - // Methods - for _, method := range service.Methods { - e.encodeMethod(method) - } - - e.dst.WriteString("}") - - // Service options - if len(service.Options) > 0 { - e.dst.WriteString("(") - for i, option := range service.Options { - if i > 0 { - e.dst.WriteString(", ") - } - e.encodeOption(option) - } - e.dst.WriteString(")\n") - } else { - e.dst.WriteString("\n") - } - - e.dst.WriteString("\n") -} - -// encodeMethod encodes methods within a service -func (e *ThriftGenerate) encodeMethod(method *thrift.ThriftMethod) { - if method.Description != "" { - e.dst.WriteString(fmt.Sprintf(" // %s\n", method.Description)) - } - - e.dst.WriteString(fmt.Sprintf(" %s %s (", method.Output, method.Name)) // Method signature - - // Input parameters - for i, input := range method.Input { - if i > 0 { - e.dst.WriteString(", ") - } - e.dst.WriteString(fmt.Sprintf("%d: %s req", i+1, input)) - } - - e.dst.WriteString(")") - - // Method options - if len(method.Options) > 0 { - e.dst.WriteString(" (\n") - for i, option := range method.Options { - if i > 0 { - e.dst.WriteString(",\n") - } - e.dst.WriteString(" ") - e.encodeOption(option) - } - e.dst.WriteString("\n )") - } - - e.dst.WriteString("\n") -} - -// encodeMessage recursively encodes structs, including nested structs and enums -func (e *ThriftGenerate) encodeMessage(message *thrift.ThriftStruct, indentLevel int) { - indent := strings.Repeat(" ", indentLevel) - - if message.Description != "" { - e.dst.WriteString(fmt.Sprintf("%s// %s\n", indent, message.Description)) - } - - e.dst.WriteString(fmt.Sprintf("%sstruct %s {\n", indent, message.Name)) - - // Fields: Traverse the fields and assign indexes - for i, field := range message.Fields { - e.encodeField(field, i+1, indentLevel+1) // Use 1-based indexing with `i+1` - } - - e.dst.WriteString(fmt.Sprintf("%s}", indent)) - - // Struct options - if len(message.Options) > 0 { - e.dst.WriteString(indent + "(\n") - for i, option := range message.Options { - if i > 0 { - e.dst.WriteString(",\n") - } - e.dst.WriteString(indent + " ") // Increase indentation - e.encodeOption(option) - } - e.dst.WriteString("\n" + indent + ")\n") - } else { - e.dst.WriteString("\n") - } - - e.dst.WriteString("\n") -} - -// encodeUnion encodes a Thrift union -func (e *ThriftGenerate) encodeUnion(union *thrift.ThriftUnion, indentLevel int) { - indent := strings.Repeat(" ", indentLevel) - - e.dst.WriteString(fmt.Sprintf("%sunion %s {\n", indent, union.Name)) - - // Traverse union fields - for i, field := range union.Fields { - e.encodeField(field, i+1, indentLevel+1) // Use 1-based indexing with `i+1` - } - - e.dst.WriteString(fmt.Sprintf("%s}\n\n", indent)) -} - -// encodeEnum encodes enum types -func (e *ThriftGenerate) encodeEnum(enum *thrift.ThriftEnum, indentLevel int) { - indent := strings.Repeat(" ", indentLevel) - - e.dst.WriteString(fmt.Sprintf("%senum %s {\n", indent, enum.Name)) - - for _, value := range enum.Values { - valueStr := fmt.Sprintf("%v", value.Value) // Convert the value to a string - - // Check if the value is numeric and generate a name if needed - enumValueName := valueStr - if _, err := strconv.Atoi(valueStr); err == nil { - enumValueName = fmt.Sprintf("%s%s", enum.Name, valueStr) - } - - enumValueName = strings.ToUpper(common.FormatStr(enumValueName)) - - e.dst.WriteString(fmt.Sprintf("%s %s = %d;\n", indent, enumValueName, value.Index)) - } - - e.dst.WriteString(fmt.Sprintf("%s}\n\n", indent)) -} - -// encodeField encodes a single field within a struct -func (e *ThriftGenerate) encodeField(field *thrift.ThriftField, index, indentLevel int) { - indent := strings.Repeat(" ", indentLevel) - - if field.Description != "" { - e.dst.WriteString(fmt.Sprintf("%s// %s\n", indent, field.Description)) - } - - // Field index and type - fieldType := field.Type - if field.Repeated { - fieldType = fmt.Sprintf("list<%s>", field.Type) - } - - // Handle optional fields - optionalFlag := "" - if field.Optional { - optionalFlag = "optional " - } - - // Assign the provided index to the field - e.dst.WriteString(fmt.Sprintf("%s%d: %s%s %s", indent, index, optionalFlag, fieldType, common.FormatStr(field.Name))) - - // Field options - if len(field.Options) > 0 { - e.dst.WriteString(" (") - for i, option := range field.Options { - if i > 0 { - e.dst.WriteString(",\n" + indent) - } - e.encodeOption(option) - } - e.dst.WriteString(")\n") // Close parentheses aligned with the field - } else { - e.dst.WriteString("\n") - } -} - -// encodeOption handles the encoding of options for methods, structs, and fields -func (e *ThriftGenerate) encodeOption(option *thrift.Option) { - // If the option key starts with "api", use double quotes for the value - if strings.HasPrefix(option.Name, "api.") { - e.dst.WriteString(fmt.Sprintf("%s = %s", option.Name, option.Value)) - } else if strings.HasPrefix(option.Name, "openapi.") { - e.dst.WriteString(fmt.Sprintf("%s = '%s'", option.Name, option.Value)) - } else { - e.dst.WriteString(fmt.Sprintf("%s = %s", option.Name, option.Value)) - } -} diff --git a/swagger2idl/go.mod b/swagger2idl/go.mod deleted file mode 100644 index bff41fd..0000000 --- a/swagger2idl/go.mod +++ /dev/null @@ -1,26 +0,0 @@ -module github.com/hertz-contrib/swagger-generate/swagger2idl - -go 1.18 - -require ( - github.com/getkin/kin-openapi v0.127.0 - github.com/hertz-contrib/swagger-generate v0.0.0-00010101000000-000000000000 - github.com/urfave/cli/v2 v2.27.4 -) - -require ( - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect - github.com/iancoleman/strcase v0.3.0 // indirect - github.com/invopop/yaml v0.3.1 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) - -replace github.com/hertz-contrib/swagger-generate => ../../swagger-generate diff --git a/swagger2idl/go.sum b/swagger2idl/go.sum deleted file mode 100644 index ca53ed7..0000000 --- a/swagger2idl/go.sum +++ /dev/null @@ -1,38 +0,0 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY= -github.com/getkin/kin-openapi v0.127.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= -github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= -github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= -github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= -github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8= -github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -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/swagger2idl/main.go b/swagger2idl/main.go deleted file mode 100644 index 5b60b9d..0000000 --- a/swagger2idl/main.go +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2024 CloudWeGo Authors - * - * Licensed 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 ( - "log" - "os" - "path/filepath" - - "github.com/hertz-contrib/swagger-generate/common/consts" - "github.com/hertz-contrib/swagger-generate/swagger2idl/converter" - "github.com/hertz-contrib/swagger-generate/swagger2idl/generate" - "github.com/hertz-contrib/swagger-generate/swagger2idl/parser" - "github.com/urfave/cli/v2" -) - -var ( - outputType string - outputFile string - openapiOption bool - apiOption bool - namingOption bool -) - -func main() { - app := &cli.App{ - Name: "swagger2idl", - Usage: "Convert OpenAPI specs to Protobuf or Thrift IDL", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "type", - Aliases: []string{"t"}, - Usage: "Specify output type: 'proto' or 'thrift'. If not provided, inferred from output file extension.", - Destination: &outputType, - }, - &cli.StringFlag{ - Name: "output", - Aliases: []string{"o"}, - Usage: "Specify output file path. If not provided, defaults to output.proto or output.thrift based on the output type.", - Destination: &outputFile, - }, - &cli.BoolFlag{ - Name: "openapi", - Aliases: []string{"oa"}, - Usage: "Include OpenAPI specific options in the output", - Destination: &openapiOption, - }, - &cli.BoolFlag{ - Name: "api", - Aliases: []string{"a"}, - Usage: "Include API specific options in the output", - Destination: &apiOption, - }, - &cli.BoolFlag{ - Name: "naming", - Aliases: []string{"n"}, - Usage: "use naming conventions for the output IDL file", - Value: true, - Destination: &namingOption, - }, - }, - Action: func(c *cli.Context) error { - args := c.Args().Slice() - - if len(args) < 1 { - log.Fatal("Please provide the path to the OpenAPI file.") - } - - openapiFile := args[0] - - // Automatically determine output type based on file extension if not provided - if outputType == "" && outputFile != "" { - ext := filepath.Ext(outputFile) - switch ext { - case ".proto": - outputType = consts.IDLProto - case ".thrift": - outputType = consts.IDLThrift - default: - log.Fatalf("Cannot determine output type from file extension: %s. Use --type to specify explicitly.", ext) - } - } - - if outputFile == "" { - if outputType == consts.IDLProto { - outputFile = consts.DefaultProtoFilename - } else if outputType == consts.IDLThrift { - outputFile = consts.DefaultThriftFilename - } else { - log.Fatal("Output file must be specified if output type is not provided.") - } - } - - spec, err := parser.LoadOpenAPISpec(openapiFile) - if err != nil { - log.Fatalf("Failed to load OpenAPI file: %v", err) - } - - converterOption := &converter.ConvertOption{ - OpenapiOption: openapiOption, - ApiOption: apiOption, - NamingOption: namingOption, - } - - var idlContent string - var file *os.File - var errFile error - - switch outputType { - case consts.IDLProto: - protoConv := converter.NewProtoConverter(spec, converterOption) - - if err = protoConv.Convert(); err != nil { - log.Fatalf("Error during conversion: %v", err) - } - protoEngine := generate.NewProtoGenerate() - - idlContent, err = protoEngine.Generate(protoConv.GetIdl()) - if err != nil { - log.Fatalf("Error generating proto docs: %v", err) - } - - file, errFile = os.Create(outputFile) - case consts.IDLThrift: - thriftConv := converter.NewThriftConverter(spec, converterOption) - - if err = thriftConv.Convert(); err != nil { - log.Fatalf("Error during conversion: %v", err) - } - thriftEngine := generate.NewThriftGenerate() - - idlContent, err = thriftEngine.Generate(thriftConv.GetIdl()) - if err != nil { - log.Fatalf("Error generating thrift docs: %v", err) - } - - file, errFile = os.Create(outputFile) - default: - log.Fatalf("Invalid output type: %s", outputType) - } - - if errFile != nil { - log.Fatalf("Failed to create file: %v", errFile) - } - defer func() { - if err := file.Close(); err != nil { - log.Printf("Error closing file: %v", err) - } - }() - - if _, err = file.WriteString(idlContent); err != nil { - log.Fatalf("Error writing to file: %v", err) - } - - return nil - }, - } - - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} diff --git a/swagger2idl/parser/parser.go b/swagger2idl/parser/parser.go deleted file mode 100644 index 7712ca4..0000000 --- a/swagger2idl/parser/parser.go +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2024 CloudWeGo Authors - * - * Licensed 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 parser - -import ( - "fmt" - "os" - - "github.com/getkin/kin-openapi/openapi3" -) - -// LoadOpenAPISpec parses an OpenAPI spec from a file and returns it. -func LoadOpenAPISpec(filePath string) (*openapi3.T, error) { - loader := openapi3.NewLoader() - var err error - var spec *openapi3.T - - if _, err = os.Stat(filePath); os.IsNotExist(err) { - return nil, fmt.Errorf("file %s does not exist", filePath) - } - - spec, err = loader.LoadFromFile(filePath) - if err != nil { - return nil, fmt.Errorf("failed to load OpenAPI spec: %v", err) - } - - if err = spec.Validate(loader.Context); err != nil { - return nil, fmt.Errorf("failed to validate OpenAPI spec: %v", err) - } - - return spec, nil -} diff --git a/swagger2idl/protobuf/protobuf.go b/swagger2idl/protobuf/protobuf.go deleted file mode 100644 index eb52a14..0000000 --- a/swagger2idl/protobuf/protobuf.go +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2024 CloudWeGo Authors - * - * Licensed 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 protobuf - -// ProtoFile represents a complete Proto file -type ProtoFile struct { - PackageName string // The package name of the Proto file - Messages []*ProtoMessage // List of Proto messages - Services []*ProtoService // List of Proto services - Enums []*ProtoEnum // List of Proto enums - Imports []string // List of imported Proto files - Options []*Option // File-level options -} - -// ProtoService represents a Proto service -type ProtoService struct { - Name string // Name of the service - Description string // Description for the service - Methods []*ProtoMethod // List of methods in the service - Options []*Option // Service-level options -} - -// ProtoMethod represents a method in a Proto service -type ProtoMethod struct { - Name string // Name of the method - Description string // Description for the method - Input string // Input message type - Output string // Output message type - Options []*Option // Options for the method -} - -// ProtoMessage represents a Proto message -type ProtoMessage struct { - Name string - Description string // Description for the Proto message - Fields []*ProtoField // List of fields in the Proto message - Messages []*ProtoMessage // Nested Proto messages - Enums []*ProtoEnum // Enums within the Proto message - OneOfs []*ProtoOneOf // OneOfs within the Proto message - Options []*Option // Options specific to this Proto message -} - -// ProtoField represents a field in a Proto message -type ProtoField struct { - Name string // Name of the field - Type string // Type of the field - Description string // Description for the field - Repeated bool // Indicates if the field is repeated (array) - Options []*Option // Additional options for this field -} - -// Option represents an option in a Proto field or message -type Option struct { - Name string // Name of the option - Value interface{} // Value of the option -} - -// ProtoEnum represents a Proto enum -type ProtoEnum struct { - Name string // Name of the enum - Description string // Description for the enum - Values []*ProtoEnumValue // Values within the enum - Options []*Option // Enum-level options -} - -// ProtoEnumValue represents a value in a Proto enum -type ProtoEnumValue struct { - Index int // index of the enum value - Value any // Corresponding integer value for the enum -} - -// ProtoOneOf represents a oneof in a Proto message -type ProtoOneOf struct { - Name string // Name of the oneof - Fields []*ProtoField // List of fields in the oneof - Options []*Option // Options specific to this oneof -} diff --git a/swagger2idl/thrift/thrift.go b/swagger2idl/thrift/thrift.go deleted file mode 100644 index 45947d4..0000000 --- a/swagger2idl/thrift/thrift.go +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2024 CloudWeGo Authors - * - * Licensed 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 thrift - -// ThriftFile represents a complete Thrift file -type ThriftFile struct { - Namespace map[string]string // Namespace for the Thrift file - Includes []string // List of included Thrift files - Structs []*ThriftStruct // List of Thrift structs - Unions []*ThriftUnion // List of Thrift unions - Enums []*ThriftEnum // List of Thrift enums - Services []*ThriftService // List of Thrift services -} - -// ThriftService represents a Thrift service -type ThriftService struct { - Name string // Name of the service - Description string // Description of the service - Methods []*ThriftMethod // List of methods in the service - Options []*Option // Service-level options -} - -// ThriftMethod represents a method in a Thrift service -type ThriftMethod struct { - Name string // Name of the method - Description string // Description of the method - Input []string // List of input fields for the method - Output string // Output field for the method - Options []*Option // Options for the method -} - -// ThriftStruct represents a Thrift struct -type ThriftStruct struct { - Name string // Name of the struct - Description string // Description of the struct - Fields []*ThriftField // List of fields in the struct - Options []*Option // Options specific to this struct -} - -// ThriftField represents a field in a Thrift struct or union -type ThriftField struct { - ID int // Field ID for Thrift - Name string // Name of the field - Description string // Description of the field - Type string // Type of the field (Thrift types) - Optional bool // Indicates if the field is optional - Repeated bool // Indicates if the field is repeated (list) - Options []*Option // Additional options for this field -} - -// ThriftUnion represents a Thrift union (similar to a struct but only one field can be set at a time) -type ThriftUnion struct { - Name string // Name of the union - Fields []*ThriftField // List of fields in the union - Options []*Option // Options specific to this union -} - -// ThriftEnum represents a Thrift enum -type ThriftEnum struct { - Name string // Name of the enum - Description string // Description of the enum - Values []*ThriftEnumValue // Values within the enum - Options []*Option // Enum-level options -} - -// ThriftEnumValue represents a value in a Thrift enum -type ThriftEnumValue struct { - Index int // Index of the enum value - Value any // Enum values are integers in Thrift -} - -// Option represents an option in a Thrift field or struct -type Option struct { - Name string // Name of the option - Value interface{} // Value of the option -} diff --git a/swagger2idl/utils/utils.go b/swagger2idl/utils/utils.go deleted file mode 100644 index b347840..0000000 --- a/swagger2idl/utils/utils.go +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2024 CloudWeGo Authors - * - * Licensed 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 utils - -import ( - "regexp" - "strings" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/hertz-contrib/swagger-generate/common/utils" -) - -// GetMethodName generates a method name from the OpenAPI spec -func GetMethodName(operation *openapi3.Operation, path, method string) string { - if operation.OperationID != "" { - return operation.OperationID - } - if operation.Tags != nil { - return operation.Tags[0] - } - if path != "" { - // Convert path to PascalCase, replacing placeholders with a suitable format - convertedPath := ConvertPathToPascalCase(path) - return convertedPath + strings.Title(strings.ToLower(method)) - } - // If no OperationID, generate using HTTP method - return strings.Title(strings.ToLower(method)) + "Method" -} - -// GetServiceName generates a service name from the OpenAPI spec -func GetServiceName(operation *openapi3.Operation) string { - if len(operation.Tags) > 0 { - return operation.Tags[0] - } - return "DefaultService" -} - -// GetMessageName generates a message name from the OpenAPI spec -func GetMessageName(operation *openapi3.Operation, methodName, suffix string) string { - if operation.OperationID != "" { - return operation.OperationID + suffix - } - return methodName + suffix -} - -// GetPackageName generates a package name from the OpenAPI spec -func GetPackageName(spec *openapi3.T) string { - if spec.Info.Title != "" { - return utils.FormatStr(utils.ToSnakeCase(spec.Info.Title)) - } - if spec.Info.Description != "" { - return utils.FormatStr(utils.ToSnakeCase(spec.Info.Description)) - } - return "DefaultPackage" -} - -// ConvertPathToPascalCase converts a path with placeholders to PascalCase -func ConvertPathToPascalCase(path string) string { - // Replace placeholders like {orderId} with OrderId - re := regexp.MustCompile(`\{(\w+)\}`) - path = re.ReplaceAllStringFunc(path, func(s string) string { - return utils.ToPascaleCase(strings.Trim(s, "{}")) - }) - - // Split the path by '/' and convert each segment to PascalCase - segments := strings.Split(path, "/") - for i, segment := range segments { - segments[i] = utils.ToPascaleCase(segment) - } - - // Join the segments back together - return strings.Join(segments, "") -} - -// ExtractMessageNameFromRef extracts the name of a message from a reference -func ExtractMessageNameFromRef(ref string) string { - parts := strings.Split(ref, "/") - return parts[len(parts)-1] // Return the last part, usually the name of the reference -} - -// ConvertPath converts a path with placeholders to a format that can be used in a URL -func ConvertPath(path string) string { - // Regular expression to match content inside {} - re := regexp.MustCompile(`\{(\w+)\}`) - // Replace {param} with :param - result := re.ReplaceAllString(path, ":$1") - return result -}