| // Copyright 2017 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package main |
| |
| import ( |
| "log" |
| "net/url" |
| "strings" |
| |
| openapi3 "github.com/googleapis/gnostic/OpenAPIv3" |
| discovery "github.com/googleapis/gnostic/discovery" |
| ) |
| |
| func pathForMethod(path string) string { |
| return "/" + strings.Replace(path, "{+", "{", -1) |
| } |
| |
| func addOpenAPI3SchemaForSchema(d *openapi3.Document, name string, schema *discovery.Schema) { |
| d.Components.Schemas.AdditionalProperties = append(d.Components.Schemas.AdditionalProperties, |
| &openapi3.NamedSchemaOrReference{ |
| Name: name, |
| Value: buildOpenAPI3SchemaOrReferenceForSchema(schema), |
| }) |
| } |
| |
| func buildOpenAPI3SchemaOrReferenceForSchema(schema *discovery.Schema) *openapi3.SchemaOrReference { |
| if ref := schema.XRef; ref != "" { |
| return &openapi3.SchemaOrReference{ |
| Oneof: &openapi3.SchemaOrReference_Reference{ |
| Reference: &openapi3.Reference{ |
| XRef: "#/definitions/" + ref, |
| }, |
| }, |
| } |
| } |
| |
| s := &openapi3.Schema{} |
| |
| if description := schema.Description; description != "" { |
| s.Description = description |
| } |
| if typeName := schema.Type; typeName != "" { |
| s.Type = typeName |
| } |
| if len(schema.Enum) > 0 { |
| for _, e := range schema.Enum { |
| s.Enum = append(s.Enum, &openapi3.Any{Yaml: e}) |
| } |
| } |
| if schema.Items != nil { |
| s.Items = &openapi3.ItemsItem{ |
| SchemaOrReference: []*openapi3.SchemaOrReference{buildOpenAPI3SchemaOrReferenceForSchema(schema.Items)}, |
| } |
| } |
| if (schema.Properties != nil) && (len(schema.Properties.AdditionalProperties) > 0) { |
| s.Properties = &openapi3.Properties{} |
| for _, pair := range schema.Properties.AdditionalProperties { |
| s.Properties.AdditionalProperties = append(s.Properties.AdditionalProperties, |
| &openapi3.NamedSchemaOrReference{ |
| Name: pair.Name, |
| Value: buildOpenAPI3SchemaOrReferenceForSchema(pair.Value), |
| }, |
| ) |
| } |
| } |
| return &openapi3.SchemaOrReference{ |
| Oneof: &openapi3.SchemaOrReference_Schema{ |
| Schema: s, |
| }, |
| } |
| } |
| |
| func buildOpenAPI3ParameterForParameter(name string, p *discovery.Parameter) *openapi3.Parameter { |
| typeName := p.Type |
| format := p.Format |
| location := p.Location |
| switch location { |
| case "query", "path": |
| return &openapi3.Parameter{ |
| Name: name, |
| In: location, |
| Description: p.Description, |
| Required: p.Required, |
| Schema: &openapi3.SchemaOrReference{ |
| Oneof: &openapi3.SchemaOrReference_Schema{ |
| Schema: &openapi3.Schema{ |
| Type: typeName, |
| Format: format, |
| }, |
| }, |
| }, |
| } |
| default: |
| return nil |
| } |
| } |
| |
| func buildOpenAPI3RequestBodyForRequest(request *discovery.Request) *openapi3.RequestBody { |
| ref := request.XRef |
| if ref == "" { |
| log.Printf("WARNING: Unhandled request schema %+v", request) |
| } |
| return &openapi3.RequestBody{ |
| Content: &openapi3.MediaTypes{ |
| AdditionalProperties: []*openapi3.NamedMediaType{ |
| &openapi3.NamedMediaType{ |
| Name: "application/json", |
| Value: &openapi3.MediaType{ |
| Schema: &openapi3.SchemaOrReference{ |
| Oneof: &openapi3.SchemaOrReference_Reference{ |
| Reference: &openapi3.Reference{ |
| XRef: "#/definitions/" + ref, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| } |
| } |
| |
| func buildOpenAPI3ResponseForResponse(response *discovery.Response, hasDataWrapper bool) *openapi3.Response { |
| if response == nil { |
| return &openapi3.Response{ |
| Description: "Successful operation", |
| } |
| } else { |
| ref := response.XRef |
| if ref == "" { |
| log.Printf("WARNING: Unhandled response %+v", response) |
| } |
| return &openapi3.Response{ |
| Description: "Successful operation", |
| Content: &openapi3.MediaTypes{ |
| AdditionalProperties: []*openapi3.NamedMediaType{ |
| &openapi3.NamedMediaType{ |
| Name: "application/json", |
| Value: &openapi3.MediaType{ |
| Schema: &openapi3.SchemaOrReference{ |
| Oneof: &openapi3.SchemaOrReference_Reference{ |
| Reference: &openapi3.Reference{ |
| XRef: "#/definitions/" + ref, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| } |
| } |
| } |
| |
| func buildOpenAPI3OperationForMethod(method *discovery.Method, hasDataWrapper bool) *openapi3.Operation { |
| if method == nil { |
| return nil |
| } |
| parameters := make([]*openapi3.ParameterOrReference, 0) |
| if method.Parameters != nil { |
| for _, pair := range method.Parameters.AdditionalProperties { |
| parameters = append(parameters, &openapi3.ParameterOrReference{ |
| Oneof: &openapi3.ParameterOrReference_Parameter{ |
| Parameter: buildOpenAPI3ParameterForParameter(pair.Name, pair.Value), |
| }, |
| }) |
| } |
| } |
| responses := &openapi3.Responses{ |
| ResponseOrReference: []*openapi3.NamedResponseOrReference{ |
| &openapi3.NamedResponseOrReference{ |
| Name: "default", |
| Value: &openapi3.ResponseOrReference{ |
| Oneof: &openapi3.ResponseOrReference_Response{ |
| Response: buildOpenAPI3ResponseForResponse(method.Response, hasDataWrapper), |
| }, |
| }, |
| }, |
| }, |
| } |
| var requestBodyOrReference *openapi3.RequestBodyOrReference |
| if method.Request != nil { |
| requestBody := buildOpenAPI3RequestBodyForRequest(method.Request) |
| requestBodyOrReference = &openapi3.RequestBodyOrReference{ |
| Oneof: &openapi3.RequestBodyOrReference_RequestBody{ |
| RequestBody: requestBody, |
| }, |
| } |
| } |
| return &openapi3.Operation{ |
| Description: method.Description, |
| OperationId: method.Id, |
| Parameters: parameters, |
| Responses: responses, |
| RequestBody: requestBodyOrReference, |
| } |
| } |
| |
| func getOpenAPI3PathItemForPath(d *openapi3.Document, path string) *openapi3.PathItem { |
| // First, try to find a path item with the specified path. If it exists, return it. |
| for _, item := range d.Paths.Path { |
| if item.Name == path { |
| return item.Value |
| } |
| } |
| // Otherwise, create and return a new path item. |
| pathItem := &openapi3.PathItem{} |
| d.Paths.Path = append(d.Paths.Path, |
| &openapi3.NamedPathItem{ |
| Name: path, |
| Value: pathItem, |
| }, |
| ) |
| return pathItem |
| } |
| |
| func addOpenAPI3PathsForMethod(d *openapi3.Document, name string, method *discovery.Method, hasDataWrapper bool) { |
| operation := buildOpenAPI3OperationForMethod(method, hasDataWrapper) |
| pathItem := getOpenAPI3PathItemForPath(d, pathForMethod(method.Path)) |
| switch method.HttpMethod { |
| case "GET": |
| pathItem.Get = operation |
| case "POST": |
| pathItem.Post = operation |
| case "PUT": |
| pathItem.Put = operation |
| case "DELETE": |
| pathItem.Delete = operation |
| case "PATCH": |
| pathItem.Patch = operation |
| default: |
| log.Printf("WARNING: Unknown HTTP method %s", method.HttpMethod) |
| } |
| } |
| |
| func addOpenAPI3PathsForResource(d *openapi3.Document, resource *discovery.Resource, hasDataWrapper bool) { |
| if resource.Methods != nil { |
| for _, pair := range resource.Methods.AdditionalProperties { |
| addOpenAPI3PathsForMethod(d, pair.Name, pair.Value, hasDataWrapper) |
| } |
| } |
| if resource.Resources != nil { |
| for _, pair := range resource.Resources.AdditionalProperties { |
| addOpenAPI3PathsForResource(d, pair.Value, hasDataWrapper) |
| } |
| } |
| } |
| |
| // OpenAPIv3 returns an OpenAPI v3 representation of a Discovery document |
| func OpenAPIv3(api *discovery.Document) (*openapi3.Document, error) { |
| d := &openapi3.Document{} |
| d.Openapi = "3.0" |
| d.Info = &openapi3.Info{ |
| Title: api.Title, |
| Version: api.Version, |
| Description: api.Description, |
| } |
| d.Servers = make([]*openapi3.Server, 0) |
| |
| url, _ := url.Parse(api.RootUrl) |
| host := url.Host |
| basePath := api.BasePath |
| if basePath == "" { |
| basePath = "/" |
| } |
| d.Servers = append(d.Servers, &openapi3.Server{Url: "https://" + host + basePath}) |
| |
| hasDataWrapper := false |
| for _, feature := range api.Features { |
| if feature == "dataWrapper" { |
| hasDataWrapper = true |
| } |
| } |
| |
| d.Components = &openapi3.Components{} |
| d.Components.Schemas = &openapi3.SchemasOrReferences{} |
| for _, pair := range api.Schemas.AdditionalProperties { |
| addOpenAPI3SchemaForSchema(d, pair.Name, pair.Value) |
| } |
| |
| d.Paths = &openapi3.Paths{} |
| if api.Methods != nil { |
| for _, pair := range api.Methods.AdditionalProperties { |
| addOpenAPI3PathsForMethod(d, pair.Name, pair.Value, hasDataWrapper) |
| } |
| } |
| for _, pair := range api.Resources.AdditionalProperties { |
| addOpenAPI3PathsForResource(d, pair.Value, hasDataWrapper) |
| } |
| |
| return d, nil |
| } |