Files
grafana/pkg/api/docs/merge/merge_specs.go
ying-jeanne a6f3e0a9dd Swagger: Finish some TODOs and Add consistancy check for definition generation (#50119)
* finish some todo and add consistancy check

* sort parameters

* revert parameter ordering

* fix meaningless changes

* remove go-generate tag also from alerting json

* spec changes

* update spec
2022-06-08 15:27:31 +02:00

148 lines
3.8 KiB
Go

package main
import (
"bytes"
_ "embed"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"os"
"reflect"
"strings"
"github.com/go-openapi/loads"
"github.com/go-openapi/spec"
)
func mergeVectors(a, b []string) []string {
for _, p := range b {
exist := false
for _, op := range a {
if op == p {
exist = true
break
}
}
if !exist {
a = append(a, p)
}
}
return a
}
func compareDefinition(a, b spec.Schema) bool {
return reflect.DeepEqual(a.Type, b.Type) && a.Format == b.Format && reflect.DeepEqual(a.Properties, b.Properties)
}
// mergeSpecs merges OSS API spec with one or more other OpenAPI specs
func mergeSpecs(output string, sources ...string) error {
if len(sources) < 2 {
return fmt.Errorf("no APIs to merge")
}
f, err := os.Open(sources[0])
if err != nil {
return err
}
specData, err := ioutil.ReadAll(f)
if err != nil {
return err
}
var specOSS spec.Swagger
if err := json.Unmarshal(specData, &specOSS); err != nil {
return fmt.Errorf("failed to unmarshal original spec: %w", err)
}
for _, s := range sources[1:] {
additionalSpec, err := loads.JSONSpec(s)
if err != nil {
return fmt.Errorf("failed to load spec from: %s: %w", s, err)
}
// Merge consumes
specOSS.SwaggerProps.Consumes = mergeVectors(specOSS.SwaggerProps.Consumes, additionalSpec.OrigSpec().Consumes)
// Merge produces
specOSS.SwaggerProps.Produces = mergeVectors(specOSS.SwaggerProps.Produces, additionalSpec.OrigSpec().Produces)
// Merge schemes
specOSS.SwaggerProps.Schemes = mergeVectors(specOSS.SwaggerProps.Schemes, additionalSpec.OrigSpec().Schemes)
//TODO: When there are conflict between definitions, we need to error out, but here we need to fix the existing conflict first
// there are false positives, we will have to fix those by regenerate alerting api spec
for k, ad := range additionalSpec.OrigSpec().SwaggerProps.Definitions {
if ossd, exists := specOSS.SwaggerProps.Definitions[k]; exists {
if !compareDefinition(ad, ossd) {
fmt.Printf("the definition of %s differs in specs!\n", k)
}
}
specOSS.SwaggerProps.Definitions[k] = ad
}
for k, ar := range additionalSpec.OrigSpec().SwaggerProps.Responses {
if ossr, exists := specOSS.SwaggerProps.Responses[k]; exists {
if !reflect.DeepEqual(ar, ossr) {
fmt.Printf("the definition of response %s differs in specs!\n", k)
}
}
specOSS.SwaggerProps.Responses[k] = ar
}
for k, p := range additionalSpec.OrigSpec().SwaggerProps.Parameters {
specOSS.SwaggerProps.Parameters[k] = p
}
paths := additionalSpec.OrigSpec().SwaggerProps.Paths
if paths != nil {
for k, pi := range paths.Paths {
kk := strings.TrimPrefix(k, specOSS.BasePath) // remove base path if exists
if specOSS.SwaggerProps.Paths == nil {
specOSS.SwaggerProps.Paths = &spec.Paths{
Paths: make(map[string]spec.PathItem),
}
}
specOSS.SwaggerProps.Paths.Paths[kk] = pi
}
}
specOSS.SwaggerProps.Tags = append(specOSS.SwaggerProps.Tags, additionalSpec.OrigSpec().SwaggerProps.Tags...)
}
// write result to file
newSpec, err := specOSS.MarshalJSON()
if err != nil {
return fmt.Errorf("failed to marshal result spec: %w", err)
}
var prettyJSON bytes.Buffer
err = json.Indent(&prettyJSON, newSpec, "", "\t")
if err != nil {
return fmt.Errorf("failed to intend new spec: %w", err)
}
f, err = os.Create(output)
if err != nil {
return fmt.Errorf("failed to create file for new spec: %w", err)
}
_, err = f.Write(prettyJSON.Bytes())
if err != nil {
return fmt.Errorf("failed to write new spec: %w", err)
}
// validate result
return nil
}
func main() {
output := flag.String("o", "../../../swagger-ui/merged.json", "the output path")
flag.Parse()
err := mergeSpecs(*output, flag.Args()...)
if err != nil {
fmt.Printf("something went wrong: %s\n", err.Error())
}
}