// Copyright 2020 Google LLC. 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 linter import ( "fmt" rules "github.com/google/gnostic/metrics/rules" pb "github.com/google/gnostic/openapiv2" ) // fillProtoStructure takes a slice of rules and coverts them to a slice of // Message structs. func fillProtoStructure(m []rules.MessageType) []*Message { messages := make([]*Message, 0) for _, message := range m { temp := &Message{ Type: message.Message[0], Message: message.Message[1], Keys: message.Path, } if message.Message[2] != "" { temp.Suggestion = message.Message[2] } messages = append(messages, temp) } return messages } // gatherParametersV2 takes a Document struct as a parameter and calls the // processParamater function on each HTTP request in order to gather the parameters. func gatherParametersV2(document *pb.Document) []rules.Field { p := make([]rules.Field, 0) if document.Paths != nil { for _, pair := range document.Paths.Path { v := pair.Value path := []string{"paths", pair.Name} if v.Get != nil { p = append(p, processParametersV2(v.Get, append(path, "get", "parameters"))...) } if v.Put != nil { p = append(p, processParametersV2(v.Put, append(path, "put", "parameters"))...) } if v.Post != nil { p = append(p, processParametersV2(v.Post, append(path, "post", "parameters"))...) } if v.Delete != nil { p = append(p, processParametersV2(v.Delete, append(path, "delete", "parameters"))...) } if v.Patch != nil { p = append(p, processParametersV2(v.Patch, append(path, "patch", "parameters"))...) } } } return p } // processParametersV2 loops over the parameters of an operation and creates a // Field struct slice which will be used for the linter. func processParametersV2(operation *pb.Operation, path []string) []rules.Field { parameters := make([]rules.Field, 0) for i, item := range operation.Parameters { switch t := item.Oneof.(type) { case *pb.ParametersItem_Parameter: switch t2 := t.Parameter.Oneof.(type) { case *pb.Parameter_BodyParameter: parameters = append(parameters, rules.Field{Name: t2.BodyParameter.Name, Path: append(path, fmt.Sprintf("%d", i), "name")}) case *pb.Parameter_NonBodyParameter: switch t3 := t2.NonBodyParameter.Oneof.(type) { case *pb.NonBodyParameter_FormDataParameterSubSchema: parameters = append(parameters, rules.Field{Name: t3.FormDataParameterSubSchema.Name, Path: append(path, fmt.Sprintf("%d", i), "name")}) case *pb.NonBodyParameter_HeaderParameterSubSchema: parameters = append(parameters, rules.Field{Name: t3.HeaderParameterSubSchema.Name, Path: append(path, fmt.Sprintf("%d", i), "name")}) case *pb.NonBodyParameter_PathParameterSubSchema: parameters = append(parameters, rules.Field{Name: t3.PathParameterSubSchema.Name, Path: append(path, fmt.Sprintf("%d", i), "name")}) case *pb.NonBodyParameter_QueryParameterSubSchema: parameters = append(parameters, rules.Field{Name: t3.QueryParameterSubSchema.Name, Path: append(path, fmt.Sprintf("%d", i), "name")}) } } } } return parameters } //AIPLintV2 accepts an OpenAPI v2 document and will call the individual AIP rules //on the document. func AIPLintV2(document *pb.Document) (*Linter, int) { fields := gatherParametersV2(document) messages := make([]rules.MessageType, 0) for _, field := range fields { messages = append(messages, rules.AIP122Driver(field)...) messages = append(messages, rules.AIP140Driver(field)...) } m := fillProtoStructure(messages) linterResult := &Linter{ Messages: m, } return linterResult, len(messages) }