doc-generate/internal/swagger/openapi3.go

433 lines
11 KiB
Go

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 = &param
} else {
newOp.Parameters = append(newOp.Parameters, convertParameter(&param))
}
}
// 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)
for _, contentType := range produces {
mediaType := &openapi3.MediaType{
Schema: convertSchemaToSchemaRef(response.Schema),
}
newResp.Content[contentType] = mediaType
}
}
return &openapi3.ResponseRef{Value: newResp}
}
// 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))
}