blob: 6c274572a5257fcba7bd250089eebfdd97213c28 [file] [log] [blame]
// 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
}