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), &paramName)
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