1// Copyright 2017 Google Inc. All Rights Reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package main 16 17import ( 18 "log" 19 "net/url" 20 21 discovery "github.com/googleapis/gnostic/discovery" 22 openapi2 "github.com/googleapis/gnostic/OpenAPIv2" 23) 24 25func addOpenAPI2SchemaForSchema(d *openapi2.Document, name string, schema *discovery.Schema) { 26 //log.Printf("SCHEMA %s\n", name) 27 d.Definitions.AdditionalProperties = append(d.Definitions.AdditionalProperties, 28 &openapi2.NamedSchema{ 29 Name: name, 30 Value: buildOpenAPI2SchemaForSchema(schema), 31 }) 32} 33 34func buildOpenAPI2SchemaForSchema(schema *discovery.Schema) *openapi2.Schema { 35 s := &openapi2.Schema{} 36 37 if description := schema.Description; description != "" { 38 s.Description = description 39 } 40 if typeName := schema.Type; typeName != "" { 41 s.Type = &openapi2.TypeItem{[]string{typeName}} 42 } 43 if ref := schema.XRef; ref != "" { 44 s.XRef = "#/definitions/" + ref 45 } 46 if len(schema.Enum) > 0 { 47 for _, e := range schema.Enum { 48 s.Enum = append(s.Enum, &openapi2.Any{Yaml: e}) 49 } 50 } 51 if schema.Items != nil { 52 s2 := buildOpenAPI2SchemaForSchema(schema.Items) 53 s.Items = &openapi2.ItemsItem{} 54 s.Items.Schema = append(s.Items.Schema, s2) 55 } 56 if schema.Properties != nil { 57 if len(schema.Properties.AdditionalProperties) > 0 { 58 s.Properties = &openapi2.Properties{} 59 for _, pair := range schema.Properties.AdditionalProperties { 60 s.Properties.AdditionalProperties = append(s.Properties.AdditionalProperties, 61 &openapi2.NamedSchema{ 62 Name: pair.Name, 63 Value: buildOpenAPI2SchemaForSchema(pair.Value), 64 }, 65 ) 66 } 67 } 68 } 69 // assume that all schemas are closed 70 s.AdditionalProperties = &openapi2.AdditionalPropertiesItem{Oneof: &openapi2.AdditionalPropertiesItem_Boolean{Boolean: false}} 71 return s 72} 73 74func buildOpenAPI2ParameterForParameter(name string, p *discovery.Parameter) *openapi2.Parameter { 75 //log.Printf("- PARAMETER %+v\n", p.Name) 76 typeName := p.Type 77 format := p.Format 78 location := p.Location 79 switch location { 80 case "query": 81 return &openapi2.Parameter{ 82 Oneof: &openapi2.Parameter_NonBodyParameter{ 83 NonBodyParameter: &openapi2.NonBodyParameter{ 84 Oneof: &openapi2.NonBodyParameter_QueryParameterSubSchema{ 85 QueryParameterSubSchema: &openapi2.QueryParameterSubSchema{ 86 Name: name, 87 In: "query", 88 Description: p.Description, 89 Required: p.Required, 90 Type: typeName, 91 Format: format, 92 }, 93 }, 94 }, 95 }, 96 } 97 case "path": 98 return &openapi2.Parameter{ 99 Oneof: &openapi2.Parameter_NonBodyParameter{ 100 NonBodyParameter: &openapi2.NonBodyParameter{ 101 Oneof: &openapi2.NonBodyParameter_PathParameterSubSchema{ 102 PathParameterSubSchema: &openapi2.PathParameterSubSchema{ 103 Name: name, 104 In: "path", 105 Description: p.Description, 106 Required: p.Required, 107 Type: typeName, 108 Format: format, 109 }, 110 }, 111 }, 112 }, 113 } 114 default: 115 return nil 116 } 117} 118 119func buildOpenAPI2ParameterForRequest(p *discovery.Request) *openapi2.Parameter { 120 return &openapi2.Parameter{ 121 Oneof: &openapi2.Parameter_BodyParameter{ 122 BodyParameter: &openapi2.BodyParameter{ 123 Name: "resource", 124 In: "body", 125 Description: "", 126 Schema: &openapi2.Schema{XRef: "#/definitions/" + p.XRef}, 127 }, 128 }, 129 } 130} 131 132func buildOpenAPI2ResponseForResponse(response *discovery.Response) *openapi2.Response { 133 //log.Printf("- RESPONSE %+v\n", schema) 134 if response == nil { 135 return &openapi2.Response{ 136 Description: "Successful operation", 137 } 138 } 139 ref := response.XRef 140 if ref == "" { 141 log.Printf("WARNING: Unhandled response %+v", response) 142 } 143 return &openapi2.Response{ 144 Description: "Successful operation", 145 Schema: &openapi2.SchemaItem{ 146 Oneof: &openapi2.SchemaItem_Schema{ 147 Schema: &openapi2.Schema{ 148 XRef: "#/definitions/" + ref, 149 }, 150 }, 151 }, 152 } 153} 154 155func buildOpenAPI2OperationForMethod(method *discovery.Method) *openapi2.Operation { 156 //log.Printf("METHOD %s %s %s %s\n", method.Name, method.path(), method.HTTPMethod, method.ID) 157 //log.Printf("MAP %+v\n", method.JSONMap) 158 parameters := make([]*openapi2.ParametersItem, 0) 159 if method.Parameters != nil { 160 for _, pair := range method.Parameters.AdditionalProperties { 161 parameters = append(parameters, &openapi2.ParametersItem{ 162 Oneof: &openapi2.ParametersItem_Parameter{ 163 Parameter: buildOpenAPI2ParameterForParameter(pair.Name, pair.Value), 164 }, 165 }) 166 } 167 } 168 responses := &openapi2.Responses{ 169 ResponseCode: []*openapi2.NamedResponseValue{ 170 &openapi2.NamedResponseValue{ 171 Name: "default", 172 Value: &openapi2.ResponseValue{ 173 Oneof: &openapi2.ResponseValue_Response{ 174 Response: buildOpenAPI2ResponseForResponse(method.Response), 175 }, 176 }, 177 }, 178 }, 179 } 180 if method.Request != nil { 181 parameter := buildOpenAPI2ParameterForRequest(method.Request) 182 parameters = append(parameters, &openapi2.ParametersItem{ 183 Oneof: &openapi2.ParametersItem_Parameter{ 184 Parameter: parameter, 185 }, 186 }) 187 } 188 return &openapi2.Operation{ 189 Description: method.Description, 190 OperationId: method.Id, 191 Parameters: parameters, 192 Responses: responses, 193 } 194} 195 196func getOpenAPI2PathItemForPath(d *openapi2.Document, path string) *openapi2.PathItem { 197 // First, try to find a path item with the specified path. If it exists, return it. 198 for _, item := range d.Paths.Path { 199 if item.Name == path { 200 return item.Value 201 } 202 } 203 // Otherwise, create and return a new path item. 204 pathItem := &openapi2.PathItem{} 205 d.Paths.Path = append(d.Paths.Path, 206 &openapi2.NamedPathItem{ 207 Name: path, 208 Value: pathItem, 209 }, 210 ) 211 return pathItem 212} 213 214func addOpenAPI2PathsForMethod(d *openapi2.Document, name string, method *discovery.Method) { 215 operation := buildOpenAPI2OperationForMethod(method) 216 pathItem := getOpenAPI2PathItemForPath(d, pathForMethod(method.Path)) 217 switch method.HttpMethod { 218 case "GET": 219 pathItem.Get = operation 220 case "POST": 221 pathItem.Post = operation 222 case "PUT": 223 pathItem.Put = operation 224 case "DELETE": 225 pathItem.Delete = operation 226 case "PATCH": 227 pathItem.Patch = operation 228 default: 229 log.Printf("WARNING: Unknown HTTP method %s", method.HttpMethod) 230 } 231} 232 233func addOpenAPI2PathsForResource(d *openapi2.Document, name string, resource *discovery.Resource) { 234 //log.Printf("RESOURCE %s (%s)\n", resource.Name, resource.FullName) 235 if resource.Methods != nil { 236 for _, pair := range resource.Methods.AdditionalProperties { 237 addOpenAPI2PathsForMethod(d, pair.Name, pair.Value) 238 } 239 } 240 if resource.Resources != nil { 241 for _, pair := range resource.Resources.AdditionalProperties { 242 addOpenAPI2PathsForResource(d, pair.Name, pair.Value) 243 } 244 } 245} 246 247func removeTrailingSlash(path string) string { 248 if len(path) > 1 && path[len(path)-1] == '/' { 249 return path[0: len(path)-1] 250 } 251 return path 252} 253 254// OpenAPIv2 returns an OpenAPI v2 representation of this Discovery document 255func OpenAPIv2(api *discovery.Document) (*openapi2.Document, error) { 256 d := &openapi2.Document{} 257 d.Swagger = "2.0" 258 d.Info = &openapi2.Info{ 259 Title: api.Title, 260 Version: api.Version, 261 Description: api.Description, 262 } 263 url, _ := url.Parse(api.RootUrl) 264 d.Host = url.Host 265 d.BasePath = removeTrailingSlash(api.BasePath) 266 d.Schemes = []string{url.Scheme} 267 d.Consumes = []string{"application/json"} 268 d.Produces = []string{"application/json"} 269 d.Paths = &openapi2.Paths{} 270 d.Definitions = &openapi2.Definitions{} 271 if api.Schemas != nil { 272 for _, pair := range api.Schemas.AdditionalProperties { 273 addOpenAPI2SchemaForSchema(d, pair.Name, pair.Value) 274 } 275 } 276 if api.Methods != nil { 277 for _, pair := range api.Methods.AdditionalProperties { 278 addOpenAPI2PathsForMethod(d, pair.Name, pair.Value) 279 } 280 } 281 if api.Resources != nil { 282 for _, pair := range api.Resources.AdditionalProperties { 283 addOpenAPI2PathsForResource(d, pair.Name, pair.Value) 284 } 285 } 286 return d, nil 287} 288