|
| 1 | +/* |
| 2 | +Copyright 2022 The Kubernetes Authors. |
| 3 | +
|
| 4 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +you may not use this file except in compliance with the License. |
| 6 | +You may obtain a copy of the License at |
| 7 | +
|
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +
|
| 10 | +Unless required by applicable law or agreed to in writing, software |
| 11 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +See the License for the specific language governing permissions and |
| 14 | +limitations under the License. |
| 15 | +*/ |
| 16 | + |
| 17 | +package openapiconv |
| 18 | + |
| 19 | +import ( |
| 20 | + "strings" |
| 21 | + |
| 22 | + klog "k8s.io/klog/v2" |
| 23 | + "k8s.io/kube-openapi/pkg/spec3" |
| 24 | + "k8s.io/kube-openapi/pkg/validation/spec" |
| 25 | +) |
| 26 | + |
| 27 | +var OpenAPIV2DefPrefix = "#/definitions/" |
| 28 | +var OpenAPIV3DefPrefix = "#/components/schemas/" |
| 29 | + |
| 30 | +func ConvertV2ToV3(v2Spec *spec.Swagger) *spec3.OpenAPI { |
| 31 | + v3Spec := &spec3.OpenAPI{ |
| 32 | + Version: "3.0.0", |
| 33 | + Info: v2Spec.Info, |
| 34 | + ExternalDocs: ConvertExternalDocumentation(v2Spec.ExternalDocs), |
| 35 | + Paths: ConvertPaths(v2Spec.Paths), |
| 36 | + Components: ConvertComponents(v2Spec.SecurityDefinitions, v2Spec.Definitions), |
| 37 | + } |
| 38 | + |
| 39 | + return v3Spec |
| 40 | +} |
| 41 | + |
| 42 | +func ConvertExternalDocumentation(v2ED *spec.ExternalDocumentation) *spec3.ExternalDocumentation { |
| 43 | + if v2ED == nil { |
| 44 | + return nil |
| 45 | + } |
| 46 | + return &spec3.ExternalDocumentation{ |
| 47 | + ExternalDocumentationProps: spec3.ExternalDocumentationProps{ |
| 48 | + Description: v2ED.Description, |
| 49 | + URL: v2ED.URL, |
| 50 | + }, |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +func ConvertComponents(v2SecurityDefinitions spec.SecurityDefinitions, v2Definitions spec.Definitions) *spec3.Components { |
| 55 | + components := &spec3.Components{ |
| 56 | + Schemas: make(map[string]*spec.Schema), |
| 57 | + SecuritySchemes: make(spec3.SecuritySchemes), |
| 58 | + } |
| 59 | + for s, schema := range v2Definitions { |
| 60 | + components.Schemas[s] = ConvertSchema(&schema) |
| 61 | + } |
| 62 | + |
| 63 | + for s, securityScheme := range v2SecurityDefinitions { |
| 64 | + components.SecuritySchemes[s] = ConvertSecurityScheme(securityScheme) |
| 65 | + } |
| 66 | + return components |
| 67 | +} |
| 68 | + |
| 69 | +func ConvertSchema(v2Schema *spec.Schema) *spec.Schema { |
| 70 | + if v2Schema == nil { |
| 71 | + return nil |
| 72 | + } |
| 73 | + var v3Schema spec.Schema |
| 74 | + v3Schema = *v2Schema |
| 75 | + |
| 76 | + if refString := v2Schema.Ref.String(); refString != "" { |
| 77 | + if idx := strings.Index(refString, OpenAPIV2DefPrefix); idx != -1 { |
| 78 | + v3Schema.Ref = spec.MustCreateRef(OpenAPIV3DefPrefix + refString[idx+len(OpenAPIV2DefPrefix):]) |
| 79 | + } else { |
| 80 | + klog.Errorf("Error: Swagger V2 Ref %s does not contain #/definitions\n", refString) |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + for key, property := range v2Schema.Properties { |
| 85 | + v3Schema.Properties[key] = *ConvertSchema(&property) |
| 86 | + } |
| 87 | + if v2Schema.Items != nil { |
| 88 | + v3Schema.Items.Schema = ConvertSchema(v2Schema.Items.Schema) |
| 89 | + if len(v2Schema.Items.Schemas) > 0 { |
| 90 | + v3Schema.Items.Schemas = []spec.Schema{} |
| 91 | + for _, s := range v2Schema.Items.Schemas { |
| 92 | + v3Schema.Items.Schemas = append(v3Schema.Items.Schemas, *ConvertSchema(&s)) |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + if v2Schema.AdditionalProperties != nil { |
| 98 | + v3Schema.AdditionalProperties.Schema = ConvertSchema(v2Schema.AdditionalProperties.Schema) |
| 99 | + } |
| 100 | + if v2Schema.AdditionalItems != nil { |
| 101 | + v3Schema.AdditionalProperties.Schema = ConvertSchema(v2Schema.AdditionalProperties.Schema) |
| 102 | + } |
| 103 | + |
| 104 | + return &v3Schema |
| 105 | +} |
| 106 | + |
| 107 | +func ConvertSecurityScheme(securityScheme *spec.SecurityScheme) *spec3.SecurityScheme { |
| 108 | + if securityScheme == nil { |
| 109 | + return nil |
| 110 | + } |
| 111 | + ss := &spec3.SecurityScheme{ |
| 112 | + VendorExtensible: securityScheme.VendorExtensible, |
| 113 | + SecuritySchemeProps: spec3.SecuritySchemeProps{ |
| 114 | + Description: securityScheme.Description, |
| 115 | + Type: securityScheme.Type, |
| 116 | + Name: securityScheme.Name, |
| 117 | + In: securityScheme.In, |
| 118 | + }, |
| 119 | + } |
| 120 | + |
| 121 | + if securityScheme.Flow != "" { |
| 122 | + ss.Flows = make(map[string]*spec3.OAuthFlow) |
| 123 | + ss.Flows[securityScheme.Flow] = &spec3.OAuthFlow{ |
| 124 | + OAuthFlowProps: spec3.OAuthFlowProps{ |
| 125 | + AuthorizationUrl: securityScheme.AuthorizationURL, |
| 126 | + TokenUrl: securityScheme.TokenURL, |
| 127 | + Scopes: securityScheme.Scopes, |
| 128 | + }, |
| 129 | + } |
| 130 | + } |
| 131 | + return ss |
| 132 | +} |
| 133 | + |
| 134 | +func ConvertPaths(v2Paths *spec.Paths) *spec3.Paths { |
| 135 | + if v2Paths == nil { |
| 136 | + return nil |
| 137 | + } |
| 138 | + paths := &spec3.Paths{ |
| 139 | + VendorExtensible: v2Paths.VendorExtensible, |
| 140 | + Paths: make(map[string]*spec3.Path), |
| 141 | + } |
| 142 | + |
| 143 | + for k, v := range v2Paths.Paths { |
| 144 | + paths.Paths[k] = ConvertPathItem(v) |
| 145 | + } |
| 146 | + return paths |
| 147 | +} |
| 148 | + |
| 149 | +func ConvertPathItem(pathItem spec.PathItem) *spec3.Path { |
| 150 | + path := &spec3.Path{ |
| 151 | + Refable: pathItem.Refable, |
| 152 | + PathProps: spec3.PathProps{ |
| 153 | + Get: ConvertOperation(pathItem.Get), |
| 154 | + Put: ConvertOperation(pathItem.Put), |
| 155 | + Post: ConvertOperation(pathItem.Post), |
| 156 | + Delete: ConvertOperation(pathItem.Delete), |
| 157 | + Options: ConvertOperation(pathItem.Options), |
| 158 | + Head: ConvertOperation(pathItem.Head), |
| 159 | + Patch: ConvertOperation(pathItem.Patch), |
| 160 | + Parameters: []*spec3.Parameter{}, |
| 161 | + }, |
| 162 | + VendorExtensible: pathItem.VendorExtensible, |
| 163 | + } |
| 164 | + for _, param := range pathItem.Parameters { |
| 165 | + path.Parameters = append(path.Parameters, ConvertParameter(param)) |
| 166 | + } |
| 167 | + return path |
| 168 | +} |
| 169 | + |
| 170 | +func ConvertOperation(op *spec.Operation) *spec3.Operation { |
| 171 | + if op == nil { |
| 172 | + return nil |
| 173 | + } |
| 174 | + operation := &spec3.Operation{ |
| 175 | + VendorExtensible: op.VendorExtensible, |
| 176 | + OperationProps: spec3.OperationProps{ |
| 177 | + Description: op.Description, |
| 178 | + ExternalDocs: ConvertExternalDocumentation(op.OperationProps.ExternalDocs), |
| 179 | + Tags: op.Tags, |
| 180 | + Summary: op.Summary, |
| 181 | + Deprecated: op.Deprecated, |
| 182 | + OperationId: op.ID, |
| 183 | + Parameters: []*spec3.Parameter{}, |
| 184 | + }, |
| 185 | + } |
| 186 | + |
| 187 | + for _, param := range op.Parameters { |
| 188 | + if param.ParamProps.Name == "body" && param.ParamProps.Schema != nil { |
| 189 | + operation.OperationProps.RequestBody = &spec3.RequestBody{ |
| 190 | + RequestBodyProps: spec3.RequestBodyProps{ |
| 191 | + Content: make(map[string]*spec3.MediaType), |
| 192 | + }, |
| 193 | + } |
| 194 | + for _, consumer := range op.Consumes { |
| 195 | + operation.RequestBody.Content[consumer] = &spec3.MediaType{ |
| 196 | + MediaTypeProps: spec3.MediaTypeProps{ |
| 197 | + Schema: ConvertSchema(param.ParamProps.Schema), |
| 198 | + }, |
| 199 | + } |
| 200 | + } |
| 201 | + } |
| 202 | + operation.Parameters = append(operation.Parameters, ConvertParameter(param)) |
| 203 | + } |
| 204 | + |
| 205 | + operation.Responses = &spec3.Responses{ResponsesProps: spec3.ResponsesProps{ |
| 206 | + Default: ConvertResponse(op.Responses.Default, op.Produces), |
| 207 | + StatusCodeResponses: make(map[int]*spec3.Response), |
| 208 | + }, |
| 209 | + VendorExtensible: op.VendorExtensible, |
| 210 | + } |
| 211 | + |
| 212 | + for k, v := range op.Responses.StatusCodeResponses { |
| 213 | + operation.Responses.StatusCodeResponses[k] = ConvertResponse(&v, op.Produces) |
| 214 | + } |
| 215 | + return operation |
| 216 | +} |
| 217 | + |
| 218 | +func ConvertResponse(res *spec.Response, produces []string) *spec3.Response { |
| 219 | + if res == nil { |
| 220 | + return nil |
| 221 | + } |
| 222 | + response := &spec3.Response{ |
| 223 | + Refable: res.Refable, |
| 224 | + VendorExtensible: res.VendorExtensible, |
| 225 | + ResponseProps: spec3.ResponseProps{ |
| 226 | + Description: res.Description, |
| 227 | + Headers: make(map[string]*spec3.Header), |
| 228 | + Content: make(map[string]*spec3.MediaType), |
| 229 | + }, |
| 230 | + } |
| 231 | + |
| 232 | + for _, producer := range produces { |
| 233 | + response.ResponseProps.Content[producer] = &spec3.MediaType{ |
| 234 | + MediaTypeProps: spec3.MediaTypeProps{ |
| 235 | + Schema: ConvertSchema(res.Schema), |
| 236 | + }, |
| 237 | + } |
| 238 | + |
| 239 | + } |
| 240 | + return response |
| 241 | +} |
| 242 | + |
| 243 | +func ConvertParameter(param spec.Parameter) *spec3.Parameter { |
| 244 | + param3 := &spec3.Parameter{ |
| 245 | + Refable: param.Refable, |
| 246 | + VendorExtensible: param.VendorExtensible, |
| 247 | + ParameterProps: spec3.ParameterProps{ |
| 248 | + Name: param.Name, |
| 249 | + Description: param.Description, |
| 250 | + In: param.In, |
| 251 | + Required: param.Required, |
| 252 | + Schema: ConvertSchema(param.Schema), |
| 253 | + AllowEmptyValue: param.AllowEmptyValue, |
| 254 | + }, |
| 255 | + } |
| 256 | + return param3 |
| 257 | +} |
0 commit comments