package swagger import ( "github.com/getkin/kin-openapi/openapi3" "github.com/go-openapi/spec" ) // convertSwagger2ToOpenAPI3 converts a Swagger 2.0 spec to OpenAPI 3.0 func convertSwagger2ToOpenAPI3(swagger2 *spec.Swagger) *openapi3.T { openapi3Doc := &openapi3.T{ OpenAPI: "3.0.3", Info: &openapi3.Info{ Title: swagger2.Info.Title, Description: swagger2.Info.Description, TermsOfService: swagger2.Info.TermsOfService, Version: swagger2.Info.Version, }, Servers: openapi3.Servers{}, Paths: &openapi3.Paths{}, } // Convert extensions if swagger2.Info.VendorExtensible.Extensions != nil { openapi3Doc.Extensions = make(map[string]interface{}) for k, v := range swagger2.Info.VendorExtensible.Extensions { openapi3Doc.Extensions[k] = v } } // Add extensions from swagger root if swagger2.Extensions != nil { if openapi3Doc.Extensions == nil { openapi3Doc.Extensions = make(map[string]interface{}) } for k, v := range swagger2.Extensions { openapi3Doc.Extensions[k] = v } } // Convert Contact if swagger2.Info.Contact != nil { openapi3Doc.Info.Contact = &openapi3.Contact{ Name: swagger2.Info.Contact.Name, URL: swagger2.Info.Contact.URL, Email: swagger2.Info.Contact.Email, } } // Convert License if swagger2.Info.License != nil { openapi3Doc.Info.License = &openapi3.License{ Name: swagger2.Info.License.Name, URL: swagger2.Info.License.URL, } } // Convert Servers from host, basePath, and schemes if len(swagger2.Host) > 0 || len(swagger2.BasePath) > 0 { schemes := swagger2.Schemes if len(schemes) == 0 { schemes = []string{"https"} } for _, scheme := range schemes { serverURL := scheme + "://" if len(swagger2.Host) > 0 { serverURL += swagger2.Host } else { serverURL += "localhost" } if len(swagger2.BasePath) > 0 && swagger2.BasePath != "/" { serverURL += swagger2.BasePath } openapi3Doc.Servers = append(openapi3Doc.Servers, &openapi3.Server{ URL: serverURL, }) } } // Convert Paths openapi3Doc.Paths = openapi3.NewPaths() for path, pathItem := range swagger2.Paths.Paths { newPathItem := &openapi3.PathItem{} if pathItem.Get != nil { newPathItem.Get = convertOperation(pathItem.Get) } if pathItem.Post != nil { newPathItem.Post = convertOperation(pathItem.Post) } if pathItem.Put != nil { newPathItem.Put = convertOperation(pathItem.Put) } if pathItem.Delete != nil { newPathItem.Delete = convertOperation(pathItem.Delete) } if pathItem.Patch != nil { newPathItem.Patch = convertOperation(pathItem.Patch) } if pathItem.Head != nil { newPathItem.Head = convertOperation(pathItem.Head) } if pathItem.Options != nil { newPathItem.Options = convertOperation(pathItem.Options) } openapi3Doc.Paths.Set(path, newPathItem) } // Convert Definitions to Components/Schemas if len(swagger2.Definitions) > 0 { openapi3Doc.Components = &openapi3.Components{ Schemas: make(openapi3.Schemas), } for name, schema := range swagger2.Definitions { openapi3Doc.Components.Schemas[name] = convertSchemaToSchemaRef(&schema) } } // Convert SecurityDefinitions to Components/SecuritySchemes if len(swagger2.SecurityDefinitions) > 0 { if openapi3Doc.Components == nil { openapi3Doc.Components = &openapi3.Components{} } openapi3Doc.Components.SecuritySchemes = make(openapi3.SecuritySchemes) for name, secDef := range swagger2.SecurityDefinitions { openapi3Doc.Components.SecuritySchemes[name] = convertSecurityScheme(secDef) } } return openapi3Doc } // convertOperation converts a Swagger 2.0 operation to OpenAPI 3.0 func convertOperation(op *spec.Operation) *openapi3.Operation { newOp := &openapi3.Operation{ Tags: op.Tags, Summary: op.Summary, Description: op.Description, OperationID: op.ID, Parameters: openapi3.Parameters{}, Responses: &openapi3.Responses{}, Deprecated: op.Deprecated, } // Convert ExternalDocs if op.ExternalDocs != nil { newOp.ExternalDocs = &openapi3.ExternalDocs{ Description: op.ExternalDocs.Description, URL: op.ExternalDocs.URL, } } // Convert Parameters and RequestBody var bodyParam *spec.Parameter for _, param := range op.Parameters { if param.In == "body" { bodyParam = ¶m } else { newOp.Parameters = append(newOp.Parameters, convertParameter(¶m)) } } // Convert body parameter to requestBody if bodyParam != nil { newOp.RequestBody = &openapi3.RequestBodyRef{ Value: convertBodyParameter(bodyParam, op.Consumes), } } // Convert Responses for code, response := range op.Responses.StatusCodeResponses { newOp.Responses.Set(intToString(code), convertResponse(&response, op.Produces)) } // Convert Security if len(op.Security) > 0 { newOp.Security = &openapi3.SecurityRequirements{} for _, sec := range op.Security { secReq := openapi3.SecurityRequirement{} for key, scopes := range sec { secReq[key] = scopes } *newOp.Security = append(*newOp.Security, secReq) } } return newOp } // convertParameter converts a Swagger 2.0 parameter to OpenAPI 3.0 func convertParameter(param *spec.Parameter) *openapi3.ParameterRef { schema := &openapi3.Schema{ Format: param.Format, Default: param.Default, } if param.Type != "" { schema.Type = &openapi3.Types{param.Type} } newParam := &openapi3.Parameter{ Name: param.Name, In: param.In, Description: param.Description, Required: param.Required, Schema: &openapi3.SchemaRef{Value: schema}, } // Convert validation properties if param.Maximum != nil { max := *param.Maximum newParam.Schema.Value.Max = &max } if param.Minimum != nil { min := *param.Minimum newParam.Schema.Value.Min = &min } if param.ExclusiveMaximum { newParam.Schema.Value.ExclusiveMax = true } if param.ExclusiveMinimum { newParam.Schema.Value.ExclusiveMin = true } if len(param.Enum) > 0 { newParam.Schema.Value.Enum = param.Enum } // Handle array items if param.Items != nil { newParam.Schema.Value.Items = convertItemsToSchemaRef(param.Items) } return &openapi3.ParameterRef{Value: newParam} } // convertBodyParameter converts a body parameter to RequestBody func convertBodyParameter(param *spec.Parameter, consumes []string) *openapi3.RequestBody { if len(consumes) == 0 { consumes = []string{"application/json"} } content := make(openapi3.Content) for _, contentType := range consumes { mediaType := &openapi3.MediaType{} if param.Schema != nil { mediaType.Schema = convertSchemaToSchemaRef(param.Schema) } content[contentType] = mediaType } return &openapi3.RequestBody{ Description: param.Description, Required: param.Required, Content: content, } } // convertResponse converts a Swagger 2.0 response to OpenAPI 3.0 func convertResponse(response *spec.Response, produces []string) *openapi3.ResponseRef { if len(produces) == 0 { produces = []string{"application/json"} } newResp := &openapi3.Response{ Description: &response.Description, } if response.Schema != nil { newResp.Content = make(openapi3.Content) // 檢查是否有業務錯誤碼(x-biz-codes) bizCodes, hasBizCodes := response.Extensions["x-biz-codes"] if hasBizCodes { // 使用 oneOf 來表示多個可能的回應類型 for _, contentType := range produces { mediaType := &openapi3.MediaType{ Schema: createOneOfSchemaFromBizCodes(bizCodes), } newResp.Content[contentType] = mediaType } } else { // 普通回應 for _, contentType := range produces { mediaType := &openapi3.MediaType{ Schema: convertSchemaToSchemaRef(response.Schema), } newResp.Content[contentType] = mediaType } } } return &openapi3.ResponseRef{Value: newResp} } // createOneOfSchemaFromBizCodes 從業務錯誤碼創建 oneOf schema func createOneOfSchemaFromBizCodes(bizCodesInterface interface{}) *openapi3.SchemaRef { bizCodes, ok := bizCodesInterface.(map[string]BizCode) if !ok { // 嘗試轉換為 map[string]interface{} bizCodesMap, ok := bizCodesInterface.(map[string]interface{}) if !ok { return nil } // 轉換為 BizCode bizCodes = make(map[string]BizCode) for code, val := range bizCodesMap { if valMap, ok := val.(map[string]interface{}); ok { bizCode := BizCode{ Code: code, } if schema, ok := valMap["Schema"].(string); ok { bizCode.Schema = schema } if desc, ok := valMap["Description"].(string); ok { bizCode.Description = desc } bizCodes[code] = bizCode } } } if len(bizCodes) == 0 { return nil } // 創建 oneOf schema oneOfSchemas := make([]*openapi3.SchemaRef, 0, len(bizCodes)) for code, bizCode := range bizCodes { // 創建每個業務錯誤碼的 schema 引用 ref := "#/components/schemas/" + bizCode.Schema schemaRef := &openapi3.SchemaRef{ Ref: ref, } // 添加描述作為擴展 if bizCode.Description != "" { schemaRef.Value = &openapi3.Schema{ Description: "業務錯誤碼 " + code + ": " + bizCode.Description, } } oneOfSchemas = append(oneOfSchemas, schemaRef) } // 返回包含 oneOf 的 schema return &openapi3.SchemaRef{ Value: &openapi3.Schema{ OneOf: oneOfSchemas, }, } } // convertSchemaToSchemaRef converts a Swagger 2.0 schema to OpenAPI 3.0 SchemaRef func convertSchemaToSchemaRef(schema *spec.Schema) *openapi3.SchemaRef { if schema == nil { return nil } // Handle $ref if schema.Ref.String() != "" { ref := schema.Ref.String() // Convert #/definitions/ to #/components/schemas/ if len(ref) > 14 && ref[:14] == "#/definitions/" { ref = "#/components/schemas/" + ref[14:] } return &openapi3.SchemaRef{Ref: ref} } newSchema := &openapi3.Schema{ Format: schema.Format, Description: schema.Description, Default: schema.Default, Example: schema.Example, Required: schema.Required, } // Convert Type from StringOrArray to *Types if len(schema.Type) > 0 { types := openapi3.Types(schema.Type) newSchema.Type = &types } // Convert validation properties if schema.Maximum != nil { max := *schema.Maximum newSchema.Max = &max } if schema.Minimum != nil { min := *schema.Minimum newSchema.Min = &min } if schema.ExclusiveMaximum { newSchema.ExclusiveMax = true } if schema.ExclusiveMinimum { newSchema.ExclusiveMin = true } if len(schema.Enum) > 0 { newSchema.Enum = schema.Enum } // Convert properties if len(schema.Properties) > 0 { newSchema.Properties = make(openapi3.Schemas) for name, prop := range schema.Properties { newSchema.Properties[name] = convertSchemaToSchemaRef(&prop) } } // Convert items if schema.Items != nil && schema.Items.Schema != nil { newSchema.Items = convertSchemaToSchemaRef(schema.Items.Schema) } // Convert additionalProperties if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil { newSchema.AdditionalProperties = openapi3.AdditionalProperties{ Schema: convertSchemaToSchemaRef(schema.AdditionalProperties.Schema), } } return &openapi3.SchemaRef{Value: newSchema} } // convertItemsToSchemaRef converts Swagger 2.0 items to OpenAPI 3.0 SchemaRef func convertItemsToSchemaRef(items *spec.Items) *openapi3.SchemaRef { if items == nil { return nil } schema := &openapi3.Schema{ Format: items.Format, } if items.Type != "" { schema.Type = &openapi3.Types{items.Type} } if items.Items != nil { schema.Items = convertItemsToSchemaRef(items.Items) } return &openapi3.SchemaRef{Value: schema} } // convertSecurityScheme converts a Swagger 2.0 security definition to OpenAPI 3.0 func convertSecurityScheme(secDef *spec.SecurityScheme) *openapi3.SecuritySchemeRef { newScheme := &openapi3.SecurityScheme{ Type: secDef.Type, Description: secDef.Description, Name: secDef.Name, } // Convert In to openapi3 format switch secDef.In { case "header": newScheme.In = "header" case "query": newScheme.In = "query" case "cookie": newScheme.In = "cookie" } // Handle OAuth2 if secDef.Type == "oauth2" { newScheme.Flows = &openapi3.OAuthFlows{} switch secDef.Flow { case "implicit": newScheme.Flows.Implicit = &openapi3.OAuthFlow{ AuthorizationURL: secDef.AuthorizationURL, Scopes: secDef.Scopes, } case "password": newScheme.Flows.Password = &openapi3.OAuthFlow{ TokenURL: secDef.TokenURL, Scopes: secDef.Scopes, } case "application": newScheme.Flows.ClientCredentials = &openapi3.OAuthFlow{ TokenURL: secDef.TokenURL, Scopes: secDef.Scopes, } case "accessCode": newScheme.Flows.AuthorizationCode = &openapi3.OAuthFlow{ AuthorizationURL: secDef.AuthorizationURL, TokenURL: secDef.TokenURL, Scopes: secDef.Scopes, } } } return &openapi3.SecuritySchemeRef{Value: newScheme} } // intToString converts int to string for response codes func intToString(code int) string { if code < 0 || code > 999 { return "200" // fallback to 200 } hundreds := code / 100 tens := (code / 10) % 10 ones := code % 10 return string(rune('0'+hundreds)) + string(rune('0'+tens)) + string(rune('0'+ones)) }