// 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 surface_v1 import ( "fmt" openapiv2 "github.com/googleapis/gnostic/OpenAPIv2" ) // NewModelFromOpenAPI2 builds a model of an API service for use in code generation. func NewModelFromOpenAPI2(document *openapiv2.Document) (*Model, error) { return newOpenAPI2Builder().buildModel(document) } type OpenAPI2Builder struct { model *Model } func newOpenAPI2Builder() *OpenAPI2Builder { return &OpenAPI2Builder{model: &Model{}} } func (b *OpenAPI2Builder) buildModel(document *openapiv2.Document) (*Model, error) { // Set model properties from passed-in document. b.model.Name = document.Info.Title b.model.Types = make([]*Type, 0) b.model.Methods = make([]*Method, 0) err := b.build(document) if err != nil { return nil, err } return b.model, nil } // buildV2 builds an API service description, preprocessing its types and methods for code generation. func (b *OpenAPI2Builder) build(document *openapiv2.Document) (err error) { // Collect service type descriptions from Definitions section. if document.Definitions != nil { for _, pair := range document.Definitions.AdditionalProperties { t, err := b.buildTypeFromDefinition(pair.Name, pair.Value) if err != nil { return err } b.model.addType(t) } } // Collect service method descriptions from Paths section. for _, pair := range document.Paths.Path { v := pair.Value if v.Get != nil { b.buildMethodFromOperation(v.Get, "GET", pair.Name) } if v.Post != nil { b.buildMethodFromOperation(v.Post, "POST", pair.Name) } if v.Put != nil { b.buildMethodFromOperation(v.Put, "PUT", pair.Name) } if v.Delete != nil { b.buildMethodFromOperation(v.Delete, "DELETE", pair.Name) } } return err } func (b *OpenAPI2Builder) buildTypeFromDefinition(name string, schema *openapiv2.Schema) (t *Type, err error) { t = &Type{} t.Name = name t.Description = "implements the service definition of " + name t.Fields = make([]*Field, 0) if schema.Properties != nil { if len(schema.Properties.AdditionalProperties) > 0 { // If the schema has properties, generate a struct. t.Kind = TypeKind_STRUCT } for _, pair2 := range schema.Properties.AdditionalProperties { var f Field f.Name = pair2.Name f.Kind, f.Type, f.Format = b.typeForSchema(pair2.Value) f.Serialize = true t.addField(&f) } } if len(t.Fields) == 0 { if schema.AdditionalProperties != nil { // If the schema has no fixed properties and additional properties of a specified type, // generate a map pointing to objects of that type. t.Kind = TypeKind_OBJECT if schema.AdditionalProperties.GetSchema() != nil { t.ContentType = typeForRef(schema.AdditionalProperties.GetSchema().XRef) } } } return t, err } func (b *OpenAPI2Builder) buildMethodFromOperation(op *openapiv2.Operation, method string, path string) (err error) { var m Method m.Operation = op.OperationId m.Path = path m.Method = method m.Description = op.Description m.Name = sanitizeOperationName(op.OperationId) if m.Name == "" { m.Name = generateOperationName(method, path) } m.ParametersTypeName, err = b.buildTypeFromParameters(m.Name, op.Parameters) m.ResponsesTypeName, err = b.buildTypeFromResponses(&m, m.Name, op.Responses) b.model.addMethod(&m) return err } func (b *OpenAPI2Builder) buildTypeFromParameters(name string, parameters []*openapiv2.ParametersItem) (typeName string, err error) { t := &Type{} t.Name = name + "Parameters" t.Description = t.Name + " holds parameters to " + name t.Kind = TypeKind_STRUCT t.Fields = make([]*Field, 0) for _, parametersItem := range parameters { var f Field f.Type = fmt.Sprintf("%+v", parametersItem) parameter := parametersItem.GetParameter() if parameter != nil { bodyParameter := parameter.GetBodyParameter() if bodyParameter != nil { f.Name = bodyParameter.Name if bodyParameter.Schema != nil { f.Kind, f.Type, f.Format = b.typeForSchema(bodyParameter.Schema) } f.Position = Position_BODY } nonBodyParameter := parameter.GetNonBodyParameter() if nonBodyParameter != nil { headerParameter := nonBodyParameter.GetHeaderParameterSubSchema() if headerParameter != nil { f.Name = headerParameter.Name f.Type = headerParameter.Type f.Position = Position_HEADER } formDataParameter := nonBodyParameter.GetFormDataParameterSubSchema() if formDataParameter != nil { f.Name = formDataParameter.Name f.Type = formDataParameter.Type f.Position = Position_FORMDATA } queryParameter := nonBodyParameter.GetQueryParameterSubSchema() if queryParameter != nil { f.Name = queryParameter.Name f.Type = queryParameter.Type f.Position = Position_QUERY } pathParameter := nonBodyParameter.GetPathParameterSubSchema() if pathParameter != nil { f.Name = pathParameter.Name f.Type = pathParameter.Type f.Format = pathParameter.Format f.Position = Position_PATH } } f.Serialize = true t.addField(&f) } } if len(t.Fields) > 0 { b.model.addType(t) return t.Name, err } return "", err } func (b *OpenAPI2Builder) buildTypeFromResponses(m *Method, name string, responses *openapiv2.Responses) (typeName string, err error) { t := &Type{} t.Name = name + "Responses" t.Description = t.Name + " holds responses of " + name t.Kind = TypeKind_STRUCT t.Fields = make([]*Field, 0) for _, responseCode := range responses.ResponseCode { var f Field f.Name = responseCode.Name f.Serialize = false response := responseCode.Value.GetResponse() if response != nil && response.Schema != nil && response.Schema.GetSchema() != nil { f.Kind, f.Type, f.Format = b.typeForSchema(response.Schema.GetSchema()) f.Kind = FieldKind_REFERENCE t.addField(&f) } } if len(t.Fields) > 0 { b.model.addType(t) return t.Name, err } return "", err } func (b *OpenAPI2Builder) typeForSchema(schema *openapiv2.Schema) (kind FieldKind, typeName, format string) { ref := schema.XRef format = schema.Format if ref != "" { return FieldKind_SCALAR, typeForRef(ref), format } if schema.Type != nil { types := schema.Type.Value if len(types) == 1 && types[0] == "string" { return FieldKind_SCALAR, "string", format } if len(types) == 1 && types[0] == "integer" && format == "int32" { return FieldKind_SCALAR, "integer", format } if len(types) == 1 && types[0] == "integer" { return FieldKind_SCALAR, "integer", format } if len(types) == 1 && types[0] == "number" { return FieldKind_SCALAR, "number", format } if len(types) == 1 && types[0] == "array" && schema.Items != nil { // we have an array.., but of what? items := schema.Items.Schema if len(items) == 1 && items[0].XRef != "" { return FieldKind_ARRAY, typeForRef(items[0].XRef), format } } if len(types) == 1 && types[0] == "object" && schema.AdditionalProperties == nil { return FieldKind_MAP, "object", format } } if schema.AdditionalProperties != nil { additionalProperties := schema.AdditionalProperties if propertySchema := additionalProperties.GetSchema(); propertySchema != nil { if ref := propertySchema.XRef; ref != "" { return FieldKind_MAP, typeForRef(ref), format } } } // this function is incomplete... so return a string representing anything that we don't handle return FieldKind_SCALAR, fmt.Sprintf("%v", schema), format }