1package openapi2conv 2 3import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/url" 8 "sort" 9 "strings" 10 11 "github.com/getkin/kin-openapi/openapi2" 12 "github.com/getkin/kin-openapi/openapi3" 13) 14 15// ToV3 converts an OpenAPIv2 spec to an OpenAPIv3 spec 16func ToV3(doc2 *openapi2.T) (*openapi3.T, error) { 17 stripNonCustomExtensions(doc2.Extensions) 18 19 doc3 := &openapi3.T{ 20 OpenAPI: "3.0.3", 21 Info: &doc2.Info, 22 Components: openapi3.Components{}, 23 Tags: doc2.Tags, 24 ExtensionProps: doc2.ExtensionProps, 25 ExternalDocs: doc2.ExternalDocs, 26 } 27 28 if host := doc2.Host; host != "" { 29 if strings.Contains(host, "/") { 30 err := fmt.Errorf("invalid host %q. This MUST be the host only and does not include the scheme nor sub-paths.", host) 31 return nil, err 32 } 33 schemes := doc2.Schemes 34 if len(schemes) == 0 { 35 schemes = []string{"https"} 36 } 37 basePath := doc2.BasePath 38 if basePath == "" { 39 basePath = "/" 40 } 41 for _, scheme := range schemes { 42 u := url.URL{ 43 Scheme: scheme, 44 Host: host, 45 Path: basePath, 46 } 47 doc3.AddServer(&openapi3.Server{URL: u.String()}) 48 } 49 } 50 51 doc3.Components.Schemas = make(map[string]*openapi3.SchemaRef) 52 if parameters := doc2.Parameters; len(parameters) != 0 { 53 doc3.Components.Parameters = make(map[string]*openapi3.ParameterRef) 54 doc3.Components.RequestBodies = make(map[string]*openapi3.RequestBodyRef) 55 for k, parameter := range parameters { 56 v3Parameter, v3RequestBody, v3SchemaMap, err := ToV3Parameter(&doc3.Components, parameter, doc2.Consumes) 57 switch { 58 case err != nil: 59 return nil, err 60 case v3RequestBody != nil: 61 doc3.Components.RequestBodies[k] = v3RequestBody 62 case v3SchemaMap != nil: 63 for _, v3Schema := range v3SchemaMap { 64 doc3.Components.Schemas[k] = v3Schema 65 } 66 default: 67 doc3.Components.Parameters[k] = v3Parameter 68 } 69 } 70 } 71 72 if paths := doc2.Paths; len(paths) != 0 { 73 doc3Paths := make(map[string]*openapi3.PathItem, len(paths)) 74 for path, pathItem := range paths { 75 r, err := ToV3PathItem(doc2, &doc3.Components, pathItem, doc2.Consumes) 76 if err != nil { 77 return nil, err 78 } 79 doc3Paths[path] = r 80 } 81 doc3.Paths = doc3Paths 82 } 83 84 if responses := doc2.Responses; len(responses) != 0 { 85 doc3.Components.Responses = make(map[string]*openapi3.ResponseRef, len(responses)) 86 for k, response := range responses { 87 r, err := ToV3Response(response) 88 if err != nil { 89 return nil, err 90 } 91 doc3.Components.Responses[k] = r 92 } 93 } 94 95 for key, schema := range ToV3Schemas(doc2.Definitions) { 96 doc3.Components.Schemas[key] = schema 97 } 98 99 if m := doc2.SecurityDefinitions; len(m) != 0 { 100 doc3SecuritySchemes := make(map[string]*openapi3.SecuritySchemeRef) 101 for k, v := range m { 102 r, err := ToV3SecurityScheme(v) 103 if err != nil { 104 return nil, err 105 } 106 doc3SecuritySchemes[k] = r 107 } 108 doc3.Components.SecuritySchemes = doc3SecuritySchemes 109 } 110 111 doc3.Security = ToV3SecurityRequirements(doc2.Security) 112 { 113 sl := openapi3.NewLoader() 114 if err := sl.ResolveRefsIn(doc3, nil); err != nil { 115 return nil, err 116 } 117 } 118 return doc3, nil 119} 120 121func ToV3PathItem(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, consumes []string) (*openapi3.PathItem, error) { 122 stripNonCustomExtensions(pathItem.Extensions) 123 doc3 := &openapi3.PathItem{ 124 ExtensionProps: pathItem.ExtensionProps, 125 } 126 for method, operation := range pathItem.Operations() { 127 doc3Operation, err := ToV3Operation(doc2, components, pathItem, operation, consumes) 128 if err != nil { 129 return nil, err 130 } 131 doc3.SetOperation(method, doc3Operation) 132 } 133 for _, parameter := range pathItem.Parameters { 134 v3Parameter, v3RequestBody, v3Schema, err := ToV3Parameter(components, parameter, consumes) 135 switch { 136 case err != nil: 137 return nil, err 138 case v3RequestBody != nil: 139 return nil, errors.New("pathItem must not have a body parameter") 140 case v3Schema != nil: 141 return nil, errors.New("pathItem must not have a schema parameter") 142 default: 143 doc3.Parameters = append(doc3.Parameters, v3Parameter) 144 } 145 } 146 return doc3, nil 147} 148 149func ToV3Operation(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, operation *openapi2.Operation, consumes []string) (*openapi3.Operation, error) { 150 if operation == nil { 151 return nil, nil 152 } 153 stripNonCustomExtensions(operation.Extensions) 154 doc3 := &openapi3.Operation{ 155 OperationID: operation.OperationID, 156 Summary: operation.Summary, 157 Description: operation.Description, 158 Tags: operation.Tags, 159 ExtensionProps: operation.ExtensionProps, 160 } 161 if v := operation.Security; v != nil { 162 doc3Security := ToV3SecurityRequirements(*v) 163 doc3.Security = &doc3Security 164 } 165 166 if len(operation.Consumes) > 0 { 167 consumes = operation.Consumes 168 } 169 170 var reqBodies []*openapi3.RequestBodyRef 171 formDataSchemas := make(map[string]*openapi3.SchemaRef) 172 for _, parameter := range operation.Parameters { 173 v3Parameter, v3RequestBody, v3SchemaMap, err := ToV3Parameter(components, parameter, consumes) 174 switch { 175 case err != nil: 176 return nil, err 177 case v3RequestBody != nil: 178 reqBodies = append(reqBodies, v3RequestBody) 179 case v3SchemaMap != nil: 180 for key, v3Schema := range v3SchemaMap { 181 formDataSchemas[key] = v3Schema 182 } 183 default: 184 doc3.Parameters = append(doc3.Parameters, v3Parameter) 185 } 186 } 187 var err error 188 if doc3.RequestBody, err = onlyOneReqBodyParam(reqBodies, formDataSchemas, components, consumes); err != nil { 189 return nil, err 190 } 191 192 if responses := operation.Responses; responses != nil { 193 doc3Responses := make(openapi3.Responses, len(responses)) 194 for k, response := range responses { 195 doc3, err := ToV3Response(response) 196 if err != nil { 197 return nil, err 198 } 199 doc3Responses[k] = doc3 200 } 201 doc3.Responses = doc3Responses 202 } 203 return doc3, nil 204} 205 206func getParameterNameFromOldRef(ref string) string { 207 cleanPath := strings.TrimPrefix(ref, "#/parameters/") 208 pathSections := strings.SplitN(cleanPath, "/", 1) 209 210 return pathSections[0] 211} 212 213func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Parameter, consumes []string) (*openapi3.ParameterRef, *openapi3.RequestBodyRef, map[string]*openapi3.SchemaRef, error) { 214 if ref := parameter.Ref; ref != "" { 215 if strings.HasPrefix(ref, "#/parameters/") { 216 name := getParameterNameFromOldRef(ref) 217 if _, ok := components.RequestBodies[name]; ok { 218 v3Ref := strings.Replace(ref, "#/parameters/", "#/components/requestBodies/", 1) 219 return nil, &openapi3.RequestBodyRef{Ref: v3Ref}, nil, nil 220 } else if schema, ok := components.Schemas[name]; ok { 221 schemaRefMap := make(map[string]*openapi3.SchemaRef) 222 if val, ok := schema.Value.Extensions["x-formData-name"]; ok { 223 name = val.(string) 224 } 225 v3Ref := strings.Replace(ref, "#/parameters/", "#/components/schemas/", 1) 226 schemaRefMap[name] = &openapi3.SchemaRef{Ref: v3Ref} 227 return nil, nil, schemaRefMap, nil 228 } 229 } 230 return &openapi3.ParameterRef{Ref: ToV3Ref(ref)}, nil, nil, nil 231 } 232 stripNonCustomExtensions(parameter.Extensions) 233 234 switch parameter.In { 235 case "body": 236 result := &openapi3.RequestBody{ 237 Description: parameter.Description, 238 Required: parameter.Required, 239 ExtensionProps: parameter.ExtensionProps, 240 } 241 if parameter.Name != "" { 242 if result.Extensions == nil { 243 result.Extensions = make(map[string]interface{}) 244 } 245 result.Extensions["x-originalParamName"] = parameter.Name 246 } 247 248 if schemaRef := parameter.Schema; schemaRef != nil { 249 // Assuming JSON 250 result.WithSchemaRef(ToV3SchemaRef(schemaRef), consumes) 251 } 252 return nil, &openapi3.RequestBodyRef{Value: result}, nil, nil 253 254 case "formData": 255 format, typ := parameter.Format, parameter.Type 256 if typ == "file" { 257 format, typ = "binary", "string" 258 } 259 if parameter.ExtensionProps.Extensions == nil { 260 parameter.ExtensionProps.Extensions = make(map[string]interface{}) 261 } 262 parameter.ExtensionProps.Extensions["x-formData-name"] = parameter.Name 263 var required []string 264 if parameter.Required { 265 required = []string{parameter.Name} 266 } 267 schemaRef := &openapi3.SchemaRef{ 268 Value: &openapi3.Schema{ 269 Description: parameter.Description, 270 Type: typ, 271 ExtensionProps: parameter.ExtensionProps, 272 Format: format, 273 Enum: parameter.Enum, 274 Min: parameter.Minimum, 275 Max: parameter.Maximum, 276 ExclusiveMin: parameter.ExclusiveMin, 277 ExclusiveMax: parameter.ExclusiveMax, 278 MinLength: parameter.MinLength, 279 MaxLength: parameter.MaxLength, 280 Default: parameter.Default, 281 Items: parameter.Items, 282 MinItems: parameter.MinItems, 283 MaxItems: parameter.MaxItems, 284 Pattern: parameter.Pattern, 285 AllowEmptyValue: parameter.AllowEmptyValue, 286 UniqueItems: parameter.UniqueItems, 287 MultipleOf: parameter.MultipleOf, 288 Required: required, 289 }, 290 } 291 schemaRefMap := make(map[string]*openapi3.SchemaRef) 292 schemaRefMap[parameter.Name] = schemaRef 293 return nil, nil, schemaRefMap, nil 294 295 default: 296 required := parameter.Required 297 if parameter.In == openapi3.ParameterInPath { 298 required = true 299 } 300 301 var schemaRefRef string 302 if schemaRef := parameter.Schema; schemaRef != nil && schemaRef.Ref != "" { 303 schemaRefRef = schemaRef.Ref 304 } 305 result := &openapi3.Parameter{ 306 In: parameter.In, 307 Name: parameter.Name, 308 Description: parameter.Description, 309 Required: required, 310 ExtensionProps: parameter.ExtensionProps, 311 Schema: ToV3SchemaRef(&openapi3.SchemaRef{Value: &openapi3.Schema{ 312 Type: parameter.Type, 313 Format: parameter.Format, 314 Enum: parameter.Enum, 315 Min: parameter.Minimum, 316 Max: parameter.Maximum, 317 ExclusiveMin: parameter.ExclusiveMin, 318 ExclusiveMax: parameter.ExclusiveMax, 319 MinLength: parameter.MinLength, 320 MaxLength: parameter.MaxLength, 321 Default: parameter.Default, 322 Items: parameter.Items, 323 MinItems: parameter.MinItems, 324 MaxItems: parameter.MaxItems, 325 Pattern: parameter.Pattern, 326 AllowEmptyValue: parameter.AllowEmptyValue, 327 UniqueItems: parameter.UniqueItems, 328 MultipleOf: parameter.MultipleOf, 329 }, 330 Ref: schemaRefRef, 331 }), 332 } 333 return &openapi3.ParameterRef{Value: result}, nil, nil, nil 334 } 335} 336 337func formDataBody(bodies map[string]*openapi3.SchemaRef, reqs map[string]bool, consumes []string) *openapi3.RequestBodyRef { 338 if len(bodies) != len(reqs) { 339 panic(`request bodies and them being required must match`) 340 } 341 requireds := make([]string, 0, len(reqs)) 342 for propName, req := range reqs { 343 if _, ok := bodies[propName]; !ok { 344 panic(`request bodies and them being required must match`) 345 } 346 if req { 347 requireds = append(requireds, propName) 348 } 349 } 350 schema := &openapi3.Schema{ 351 Type: "object", 352 Properties: ToV3Schemas(bodies), 353 Required: requireds, 354 } 355 return &openapi3.RequestBodyRef{ 356 Value: openapi3.NewRequestBody().WithSchema(schema, consumes), 357 } 358} 359 360func getParameterNameFromNewRef(ref string) string { 361 cleanPath := strings.TrimPrefix(ref, "#/components/schemas/") 362 pathSections := strings.SplitN(cleanPath, "/", 1) 363 364 return pathSections[0] 365} 366 367func onlyOneReqBodyParam(bodies []*openapi3.RequestBodyRef, formDataSchemas map[string]*openapi3.SchemaRef, components *openapi3.Components, consumes []string) (*openapi3.RequestBodyRef, error) { 368 if len(bodies) > 1 { 369 return nil, errors.New("multiple body parameters cannot exist for the same operation") 370 } 371 372 if len(bodies) != 0 && len(formDataSchemas) != 0 { 373 return nil, errors.New("body and form parameters cannot exist together for the same operation") 374 } 375 376 for _, requestBodyRef := range bodies { 377 return requestBodyRef, nil 378 } 379 380 if len(formDataSchemas) > 0 { 381 formDataParams := make(map[string]*openapi3.SchemaRef, len(formDataSchemas)) 382 formDataReqs := make(map[string]bool, len(formDataSchemas)) 383 for formDataName, formDataSchema := range formDataSchemas { 384 if formDataSchema.Ref != "" { 385 name := getParameterNameFromNewRef(formDataSchema.Ref) 386 if schema := components.Schemas[name]; schema != nil && schema.Value != nil { 387 if tempName, ok := schema.Value.Extensions["x-formData-name"]; ok { 388 name = tempName.(string) 389 } 390 formDataParams[name] = formDataSchema 391 formDataReqs[name] = false 392 for _, req := range schema.Value.Required { 393 if name == req { 394 formDataReqs[name] = true 395 } 396 } 397 } 398 } else if formDataSchema.Value != nil { 399 formDataParams[formDataName] = formDataSchema 400 formDataReqs[formDataName] = false 401 for _, req := range formDataSchema.Value.Required { 402 if formDataName == req { 403 formDataReqs[formDataName] = true 404 } 405 } 406 } 407 } 408 409 return formDataBody(formDataParams, formDataReqs, consumes), nil 410 } 411 412 return nil, nil 413} 414 415func ToV3Response(response *openapi2.Response) (*openapi3.ResponseRef, error) { 416 if ref := response.Ref; ref != "" { 417 return &openapi3.ResponseRef{Ref: ToV3Ref(ref)}, nil 418 } 419 stripNonCustomExtensions(response.Extensions) 420 result := &openapi3.Response{ 421 Description: &response.Description, 422 ExtensionProps: response.ExtensionProps, 423 } 424 if schemaRef := response.Schema; schemaRef != nil { 425 result.WithJSONSchemaRef(ToV3SchemaRef(schemaRef)) 426 } 427 return &openapi3.ResponseRef{Value: result}, nil 428} 429 430func ToV3Schemas(defs map[string]*openapi3.SchemaRef) map[string]*openapi3.SchemaRef { 431 schemas := make(map[string]*openapi3.SchemaRef, len(defs)) 432 for name, schema := range defs { 433 schemas[name] = ToV3SchemaRef(schema) 434 } 435 return schemas 436} 437 438func ToV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef { 439 if ref := schema.Ref; ref != "" { 440 return &openapi3.SchemaRef{Ref: ToV3Ref(ref)} 441 } 442 if schema.Value == nil { 443 return schema 444 } 445 if schema.Value.Items != nil { 446 schema.Value.Items = ToV3SchemaRef(schema.Value.Items) 447 } 448 for k, v := range schema.Value.Properties { 449 schema.Value.Properties[k] = ToV3SchemaRef(v) 450 } 451 if v := schema.Value.AdditionalProperties; v != nil { 452 schema.Value.AdditionalProperties = ToV3SchemaRef(v) 453 } 454 for i, v := range schema.Value.AllOf { 455 schema.Value.AllOf[i] = ToV3SchemaRef(v) 456 } 457 return schema 458} 459 460var ref2To3 = map[string]string{ 461 "#/definitions/": "#/components/schemas/", 462 "#/responses/": "#/components/responses/", 463 "#/parameters/": "#/components/parameters/", 464} 465 466func ToV3Ref(ref string) string { 467 for old, new := range ref2To3 { 468 if strings.HasPrefix(ref, old) { 469 ref = strings.Replace(ref, old, new, 1) 470 } 471 } 472 return ref 473} 474 475func FromV3Ref(ref string) string { 476 for new, old := range ref2To3 { 477 if strings.HasPrefix(ref, old) { 478 ref = strings.Replace(ref, old, new, 1) 479 } else if strings.HasPrefix(ref, "#/components/requestBodies/") { 480 ref = strings.Replace(ref, "#/components/requestBodies/", "#/parameters/", 1) 481 } 482 } 483 return ref 484} 485 486func ToV3SecurityRequirements(requirements openapi2.SecurityRequirements) openapi3.SecurityRequirements { 487 if requirements == nil { 488 return nil 489 } 490 result := make(openapi3.SecurityRequirements, len(requirements)) 491 for i, item := range requirements { 492 result[i] = item 493 } 494 return result 495} 496 497func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.SecuritySchemeRef, error) { 498 if securityScheme == nil { 499 return nil, nil 500 } 501 stripNonCustomExtensions(securityScheme.Extensions) 502 result := &openapi3.SecurityScheme{ 503 Description: securityScheme.Description, 504 ExtensionProps: securityScheme.ExtensionProps, 505 } 506 switch securityScheme.Type { 507 case "basic": 508 result.Type = "http" 509 result.Scheme = "basic" 510 case "apiKey": 511 result.Type = "apiKey" 512 result.In = securityScheme.In 513 result.Name = securityScheme.Name 514 case "oauth2": 515 result.Type = "oauth2" 516 flows := &openapi3.OAuthFlows{} 517 result.Flows = flows 518 scopesMap := make(map[string]string) 519 for scope, desc := range securityScheme.Scopes { 520 scopesMap[scope] = desc 521 } 522 flow := &openapi3.OAuthFlow{ 523 AuthorizationURL: securityScheme.AuthorizationURL, 524 TokenURL: securityScheme.TokenURL, 525 Scopes: scopesMap, 526 } 527 switch securityScheme.Flow { 528 case "implicit": 529 flows.Implicit = flow 530 case "accessCode": 531 flows.AuthorizationCode = flow 532 case "password": 533 flows.Password = flow 534 case "application": 535 flows.ClientCredentials = flow 536 default: 537 return nil, fmt.Errorf("unsupported flow %q", securityScheme.Flow) 538 } 539 } 540 return &openapi3.SecuritySchemeRef{ 541 Ref: ToV3Ref(securityScheme.Ref), 542 Value: result, 543 }, nil 544} 545 546// FromV3 converts an OpenAPIv3 spec to an OpenAPIv2 spec 547func FromV3(doc3 *openapi3.T) (*openapi2.T, error) { 548 doc2Responses, err := FromV3Responses(doc3.Components.Responses, &doc3.Components) 549 if err != nil { 550 return nil, err 551 } 552 stripNonCustomExtensions(doc3.Extensions) 553 schemas, parameters := FromV3Schemas(doc3.Components.Schemas, &doc3.Components) 554 doc2 := &openapi2.T{ 555 Swagger: "2.0", 556 Info: *doc3.Info, 557 Definitions: schemas, 558 Parameters: parameters, 559 Responses: doc2Responses, 560 Tags: doc3.Tags, 561 ExtensionProps: doc3.ExtensionProps, 562 ExternalDocs: doc3.ExternalDocs, 563 } 564 565 isHTTPS := false 566 isHTTP := false 567 servers := doc3.Servers 568 for i, server := range servers { 569 parsedURL, err := url.Parse(server.URL) 570 if err == nil { 571 // See which schemes seem to be supported 572 if parsedURL.Scheme == "https" { 573 isHTTPS = true 574 } else if parsedURL.Scheme == "http" { 575 isHTTP = true 576 } 577 // The first server is assumed to provide the base path 578 if i == 0 { 579 doc2.Host = parsedURL.Host 580 doc2.BasePath = parsedURL.Path 581 } 582 } 583 } 584 if isHTTPS { 585 doc2.Schemes = append(doc2.Schemes, "https") 586 } 587 if isHTTP { 588 doc2.Schemes = append(doc2.Schemes, "http") 589 } 590 for path, pathItem := range doc3.Paths { 591 if pathItem == nil { 592 continue 593 } 594 doc2.AddOperation(path, "GET", nil) 595 stripNonCustomExtensions(pathItem.Extensions) 596 addPathExtensions(doc2, path, pathItem.ExtensionProps) 597 for method, operation := range pathItem.Operations() { 598 if operation == nil { 599 continue 600 } 601 doc2Operation, err := FromV3Operation(doc3, operation) 602 if err != nil { 603 return nil, err 604 } 605 doc2.AddOperation(path, method, doc2Operation) 606 } 607 params := openapi2.Parameters{} 608 for _, param := range pathItem.Parameters { 609 p, err := FromV3Parameter(param, &doc3.Components) 610 if err != nil { 611 return nil, err 612 } 613 params = append(params, p) 614 } 615 sort.Sort(params) 616 doc2.Paths[path].Parameters = params 617 } 618 619 for name, param := range doc3.Components.Parameters { 620 if doc2.Parameters[name], err = FromV3Parameter(param, &doc3.Components); err != nil { 621 return nil, err 622 } 623 } 624 625 for name, requestBodyRef := range doc3.Components.RequestBodies { 626 bodyOrRefParameters, formDataParameters, consumes, err := fromV3RequestBodies(name, requestBodyRef, &doc3.Components) 627 if err != nil { 628 return nil, err 629 } 630 if len(formDataParameters) != 0 { 631 for _, param := range formDataParameters { 632 doc2.Parameters[param.Name] = param 633 } 634 } else if len(bodyOrRefParameters) != 0 { 635 for _, param := range bodyOrRefParameters { 636 doc2.Parameters[name] = param 637 } 638 } 639 640 if len(consumes) != 0 { 641 doc2.Consumes = consumesToArray(consumes) 642 } 643 } 644 645 if m := doc3.Components.SecuritySchemes; m != nil { 646 doc2SecuritySchemes := make(map[string]*openapi2.SecurityScheme) 647 for id, securityScheme := range m { 648 v, err := FromV3SecurityScheme(doc3, securityScheme) 649 if err != nil { 650 return nil, err 651 } 652 doc2SecuritySchemes[id] = v 653 } 654 doc2.SecurityDefinitions = doc2SecuritySchemes 655 } 656 doc2.Security = FromV3SecurityRequirements(doc3.Security) 657 return doc2, nil 658} 659 660func consumesToArray(consumes map[string]struct{}) []string { 661 consumesArr := make([]string, 0, len(consumes)) 662 for key := range consumes { 663 consumesArr = append(consumesArr, key) 664 } 665 sort.Strings(consumesArr) 666 return consumesArr 667} 668 669func fromV3RequestBodies(name string, requestBodyRef *openapi3.RequestBodyRef, components *openapi3.Components) ( 670 bodyOrRefParameters openapi2.Parameters, 671 formParameters openapi2.Parameters, 672 consumes map[string]struct{}, 673 err error, 674) { 675 if ref := requestBodyRef.Ref; ref != "" { 676 bodyOrRefParameters = append(bodyOrRefParameters, &openapi2.Parameter{Ref: FromV3Ref(ref)}) 677 return 678 } 679 680 //Only select one formData or request body for an individual requesstBody as OpenAPI 2 does not support multiples 681 if requestBodyRef.Value != nil { 682 for contentType, mediaType := range requestBodyRef.Value.Content { 683 if consumes == nil { 684 consumes = make(map[string]struct{}) 685 } 686 consumes[contentType] = struct{}{} 687 if formParams := FromV3RequestBodyFormData(mediaType); len(formParams) != 0 { 688 formParameters = formParams 689 } else { 690 paramName := name 691 if originalName, ok := requestBodyRef.Value.Extensions["x-originalParamName"]; ok { 692 json.Unmarshal(originalName.(json.RawMessage), ¶mName) 693 } 694 695 var r *openapi2.Parameter 696 if r, err = FromV3RequestBody(paramName, requestBodyRef, mediaType, components); err != nil { 697 return 698 } 699 bodyOrRefParameters = append(bodyOrRefParameters, r) 700 } 701 } 702 } 703 return 704} 705 706func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi3.SchemaRef, map[string]*openapi2.Parameter) { 707 v2Defs := make(map[string]*openapi3.SchemaRef) 708 v2Params := make(map[string]*openapi2.Parameter) 709 for name, schema := range schemas { 710 schemaConv, parameterConv := FromV3SchemaRef(schema, components) 711 if schemaConv != nil { 712 v2Defs[name] = schemaConv 713 } else if parameterConv != nil { 714 if parameterConv.Name == "" { 715 parameterConv.Name = name 716 } 717 v2Params[name] = parameterConv 718 } 719 } 720 return v2Defs, v2Params 721} 722 723func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi3.SchemaRef, *openapi2.Parameter) { 724 if ref := schema.Ref; ref != "" { 725 name := getParameterNameFromNewRef(ref) 726 if val, ok := components.Schemas[name]; ok { 727 if val.Value.Format == "binary" { 728 v2Ref := strings.Replace(ref, "#/components/schemas/", "#/parameters/", 1) 729 return nil, &openapi2.Parameter{Ref: v2Ref} 730 } 731 } 732 733 return &openapi3.SchemaRef{Ref: FromV3Ref(ref)}, nil 734 } 735 if schema.Value == nil { 736 return schema, nil 737 } 738 739 if schema.Value != nil { 740 if schema.Value.Type == "string" && schema.Value.Format == "binary" { 741 paramType := "file" 742 required := false 743 744 value, _ := schema.Value.Extensions["x-formData-name"] 745 var originalName string 746 json.Unmarshal(value.(json.RawMessage), &originalName) 747 for _, prop := range schema.Value.Required { 748 if originalName == prop { 749 required = true 750 } 751 } 752 return nil, &openapi2.Parameter{ 753 In: "formData", 754 Name: originalName, 755 Description: schema.Value.Description, 756 Type: paramType, 757 Enum: schema.Value.Enum, 758 Minimum: schema.Value.Min, 759 Maximum: schema.Value.Max, 760 ExclusiveMin: schema.Value.ExclusiveMin, 761 ExclusiveMax: schema.Value.ExclusiveMax, 762 MinLength: schema.Value.MinLength, 763 MaxLength: schema.Value.MaxLength, 764 Default: schema.Value.Default, 765 Items: schema.Value.Items, 766 MinItems: schema.Value.MinItems, 767 MaxItems: schema.Value.MaxItems, 768 AllowEmptyValue: schema.Value.AllowEmptyValue, 769 UniqueItems: schema.Value.UniqueItems, 770 MultipleOf: schema.Value.MultipleOf, 771 ExtensionProps: schema.Value.ExtensionProps, 772 Required: required, 773 } 774 } 775 } 776 if v := schema.Value.Items; v != nil { 777 schema.Value.Items, _ = FromV3SchemaRef(v, components) 778 } 779 keys := make([]string, 0, len(schema.Value.Properties)) 780 for k := range schema.Value.Properties { 781 keys = append(keys, k) 782 } 783 sort.Strings(keys) 784 for _, key := range keys { 785 schema.Value.Properties[key], _ = FromV3SchemaRef(schema.Value.Properties[key], components) 786 } 787 if v := schema.Value.AdditionalProperties; v != nil { 788 schema.Value.AdditionalProperties, _ = FromV3SchemaRef(v, components) 789 } 790 for i, v := range schema.Value.AllOf { 791 schema.Value.AllOf[i], _ = FromV3SchemaRef(v, components) 792 } 793 return schema, nil 794} 795 796func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) openapi2.SecurityRequirements { 797 if requirements == nil { 798 return nil 799 } 800 result := make([]map[string][]string, 0, len(requirements)) 801 for _, item := range requirements { 802 result = append(result, item) 803 } 804 return result 805} 806 807func FromV3PathItem(doc3 *openapi3.T, pathItem *openapi3.PathItem) (*openapi2.PathItem, error) { 808 stripNonCustomExtensions(pathItem.Extensions) 809 result := &openapi2.PathItem{ 810 ExtensionProps: pathItem.ExtensionProps, 811 } 812 for method, operation := range pathItem.Operations() { 813 r, err := FromV3Operation(doc3, operation) 814 if err != nil { 815 return nil, err 816 } 817 result.SetOperation(method, r) 818 } 819 for _, parameter := range pathItem.Parameters { 820 p, err := FromV3Parameter(parameter, &doc3.Components) 821 if err != nil { 822 return nil, err 823 } 824 result.Parameters = append(result.Parameters, p) 825 } 826 return result, nil 827} 828 829func findNameForRequestBody(operation *openapi3.Operation) string { 830nameSearch: 831 for _, name := range attemptedBodyParameterNames { 832 for _, parameterRef := range operation.Parameters { 833 parameter := parameterRef.Value 834 if parameter != nil && parameter.Name == name { 835 continue nameSearch 836 } 837 } 838 return name 839 } 840 return "" 841} 842 843func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameters { 844 parameters := openapi2.Parameters{} 845 for propName, schemaRef := range mediaType.Schema.Value.Properties { 846 if ref := schemaRef.Ref; ref != "" { 847 v2Ref := strings.Replace(ref, "#/components/schemas/", "#/parameters/", 1) 848 parameters = append(parameters, &openapi2.Parameter{Ref: v2Ref}) 849 continue 850 } 851 val := schemaRef.Value 852 typ := val.Type 853 if val.Format == "binary" { 854 typ = "file" 855 } 856 required := false 857 for _, name := range val.Required { 858 if name == propName { 859 required = true 860 break 861 } 862 } 863 parameter := &openapi2.Parameter{ 864 Name: propName, 865 Description: val.Description, 866 Type: typ, 867 In: "formData", 868 ExtensionProps: val.ExtensionProps, 869 Enum: val.Enum, 870 ExclusiveMin: val.ExclusiveMin, 871 ExclusiveMax: val.ExclusiveMax, 872 MinLength: val.MinLength, 873 MaxLength: val.MaxLength, 874 Default: val.Default, 875 Items: val.Items, 876 MinItems: val.MinItems, 877 MaxItems: val.MaxItems, 878 Maximum: val.Max, 879 Minimum: val.Min, 880 Pattern: val.Pattern, 881 // CollectionFormat: val.CollectionFormat, 882 // Format: val.Format, 883 AllowEmptyValue: val.AllowEmptyValue, 884 Required: required, 885 UniqueItems: val.UniqueItems, 886 MultipleOf: val.MultipleOf, 887 } 888 parameters = append(parameters, parameter) 889 } 890 return parameters 891} 892 893func FromV3Operation(doc3 *openapi3.T, operation *openapi3.Operation) (*openapi2.Operation, error) { 894 if operation == nil { 895 return nil, nil 896 } 897 stripNonCustomExtensions(operation.Extensions) 898 result := &openapi2.Operation{ 899 OperationID: operation.OperationID, 900 Summary: operation.Summary, 901 Description: operation.Description, 902 Tags: operation.Tags, 903 ExtensionProps: operation.ExtensionProps, 904 } 905 if v := operation.Security; v != nil { 906 resultSecurity := FromV3SecurityRequirements(*v) 907 result.Security = &resultSecurity 908 } 909 for _, parameter := range operation.Parameters { 910 r, err := FromV3Parameter(parameter, &doc3.Components) 911 if err != nil { 912 return nil, err 913 } 914 result.Parameters = append(result.Parameters, r) 915 } 916 if v := operation.RequestBody; v != nil { 917 // Find parameter name that we can use for the body 918 name := findNameForRequestBody(operation) 919 if name == "" { 920 return nil, errors.New("could not find a name for request body") 921 } 922 923 bodyOrRefParameters, formDataParameters, consumes, err := fromV3RequestBodies(name, v, &doc3.Components) 924 if err != nil { 925 return nil, err 926 } 927 if len(formDataParameters) != 0 { 928 result.Parameters = append(result.Parameters, formDataParameters...) 929 } else if len(bodyOrRefParameters) != 0 { 930 for _, param := range bodyOrRefParameters { 931 result.Parameters = append(result.Parameters, param) 932 break // add a single request body 933 } 934 935 } 936 937 if len(consumes) != 0 { 938 result.Consumes = consumesToArray(consumes) 939 } 940 } 941 sort.Sort(result.Parameters) 942 943 if responses := operation.Responses; responses != nil { 944 resultResponses, err := FromV3Responses(responses, &doc3.Components) 945 if err != nil { 946 return nil, err 947 } 948 result.Responses = resultResponses 949 } 950 return result, nil 951} 952 953func FromV3RequestBody(name string, requestBodyRef *openapi3.RequestBodyRef, mediaType *openapi3.MediaType, components *openapi3.Components) (*openapi2.Parameter, error) { 954 requestBody := requestBodyRef.Value 955 956 stripNonCustomExtensions(requestBody.Extensions) 957 result := &openapi2.Parameter{ 958 In: "body", 959 Name: name, 960 Description: requestBody.Description, 961 Required: requestBody.Required, 962 ExtensionProps: requestBody.ExtensionProps, 963 } 964 965 if mediaType != nil { 966 result.Schema, _ = FromV3SchemaRef(mediaType.Schema, components) 967 } 968 return result, nil 969} 970 971func FromV3Parameter(ref *openapi3.ParameterRef, components *openapi3.Components) (*openapi2.Parameter, error) { 972 if ref := ref.Ref; ref != "" { 973 return &openapi2.Parameter{Ref: FromV3Ref(ref)}, nil 974 } 975 parameter := ref.Value 976 if parameter == nil { 977 return nil, nil 978 } 979 stripNonCustomExtensions(parameter.Extensions) 980 result := &openapi2.Parameter{ 981 Description: parameter.Description, 982 In: parameter.In, 983 Name: parameter.Name, 984 Required: parameter.Required, 985 ExtensionProps: parameter.ExtensionProps, 986 } 987 if schemaRef := parameter.Schema; schemaRef != nil { 988 schemaRef, _ = FromV3SchemaRef(schemaRef, components) 989 if ref := schemaRef.Ref; ref != "" { 990 result.Schema = &openapi3.SchemaRef{Ref: FromV3Ref(ref)} 991 return result, nil 992 } 993 schema := schemaRef.Value 994 result.Type = schema.Type 995 result.Format = schema.Format 996 result.Enum = schema.Enum 997 result.Minimum = schema.Min 998 result.Maximum = schema.Max 999 result.ExclusiveMin = schema.ExclusiveMin 1000 result.ExclusiveMax = schema.ExclusiveMax 1001 result.MinLength = schema.MinLength 1002 result.MaxLength = schema.MaxLength 1003 result.Pattern = schema.Pattern 1004 result.Default = schema.Default 1005 result.Items = schema.Items 1006 result.MinItems = schema.MinItems 1007 result.MaxItems = schema.MaxItems 1008 result.AllowEmptyValue = schema.AllowEmptyValue 1009 // result.CollectionFormat = schema.CollectionFormat 1010 result.UniqueItems = schema.UniqueItems 1011 result.MultipleOf = schema.MultipleOf 1012 } 1013 return result, nil 1014} 1015 1016func FromV3Responses(responses map[string]*openapi3.ResponseRef, components *openapi3.Components) (map[string]*openapi2.Response, error) { 1017 v2Responses := make(map[string]*openapi2.Response, len(responses)) 1018 for k, response := range responses { 1019 r, err := FromV3Response(response, components) 1020 if err != nil { 1021 return nil, err 1022 } 1023 v2Responses[k] = r 1024 } 1025 return v2Responses, nil 1026} 1027 1028func FromV3Response(ref *openapi3.ResponseRef, components *openapi3.Components) (*openapi2.Response, error) { 1029 if ref := ref.Ref; ref != "" { 1030 return &openapi2.Response{Ref: FromV3Ref(ref)}, nil 1031 } 1032 1033 response := ref.Value 1034 if response == nil { 1035 return nil, nil 1036 } 1037 description := "" 1038 if desc := response.Description; desc != nil { 1039 description = *desc 1040 } 1041 stripNonCustomExtensions(response.Extensions) 1042 result := &openapi2.Response{ 1043 Description: description, 1044 ExtensionProps: response.ExtensionProps, 1045 } 1046 if content := response.Content; content != nil { 1047 if ct := content["application/json"]; ct != nil { 1048 result.Schema, _ = FromV3SchemaRef(ct.Schema, components) 1049 } 1050 } 1051 return result, nil 1052} 1053 1054func FromV3SecurityScheme(doc3 *openapi3.T, ref *openapi3.SecuritySchemeRef) (*openapi2.SecurityScheme, error) { 1055 securityScheme := ref.Value 1056 if securityScheme == nil { 1057 return nil, nil 1058 } 1059 stripNonCustomExtensions(securityScheme.Extensions) 1060 result := &openapi2.SecurityScheme{ 1061 Ref: FromV3Ref(ref.Ref), 1062 Description: securityScheme.Description, 1063 ExtensionProps: securityScheme.ExtensionProps, 1064 } 1065 switch securityScheme.Type { 1066 case "http": 1067 switch securityScheme.Scheme { 1068 case "basic": 1069 result.Type = "basic" 1070 default: 1071 result.Type = "apiKey" 1072 result.In = "header" 1073 result.Name = "Authorization" 1074 } 1075 case "apiKey": 1076 result.Type = "apiKey" 1077 result.In = securityScheme.In 1078 result.Name = securityScheme.Name 1079 case "oauth2": 1080 result.Type = "oauth2" 1081 flows := securityScheme.Flows 1082 if flows != nil { 1083 var flow *openapi3.OAuthFlow 1084 // TODO: Is this the right priority? What if multiple defined? 1085 if flow = flows.Implicit; flow != nil { 1086 result.Flow = "implicit" 1087 } else if flow = flows.AuthorizationCode; flow != nil { 1088 result.Flow = "accessCode" 1089 } else if flow = flows.Password; flow != nil { 1090 result.Flow = "password" 1091 } else if flow = flows.ClientCredentials; flow != nil { 1092 result.Flow = "application" 1093 } else { 1094 return nil, nil 1095 } 1096 for scope, desc := range flow.Scopes { 1097 result.Scopes[scope] = desc 1098 } 1099 } 1100 default: 1101 return nil, fmt.Errorf("unsupported security scheme type %q", securityScheme.Type) 1102 } 1103 return result, nil 1104} 1105 1106var attemptedBodyParameterNames = []string{ 1107 "body", 1108 "requestBody", 1109} 1110 1111func stripNonCustomExtensions(extensions map[string]interface{}) { 1112 for extName := range extensions { 1113 if !strings.HasPrefix(extName, "x-") { 1114 delete(extensions, extName) 1115 } 1116 } 1117} 1118 1119func addPathExtensions(doc2 *openapi2.T, path string, extensionProps openapi3.ExtensionProps) { 1120 paths := doc2.Paths 1121 if paths == nil { 1122 paths = make(map[string]*openapi2.PathItem, 8) 1123 doc2.Paths = paths 1124 } 1125 pathItem := paths[path] 1126 if pathItem == nil { 1127 pathItem = &openapi2.PathItem{} 1128 paths[path] = pathItem 1129 } 1130 pathItem.ExtensionProps = extensionProps 1131} 1132