* update code.gitea.io/sdk/gitea v0.13.1 -> v0.13.2 * update github.com/go-swagger/go-swagger v0.25.0 -> v0.26.0 * update github.com/google/uuid v1.1.2 -> v1.2.0 * update github.com/klauspost/compress v1.11.3 -> v1.11.7 * update github.com/lib/pq 083382b7e6fc -> v1.9.0 * update github.com/markbates/goth v1.65.0 -> v1.66.1 * update github.com/mattn/go-sqlite3 v1.14.4 -> v1.14.6 * update github.com/mgechev/revive 246eac737dc7 -> v1.0.3 * update github.com/minio/minio-go/v7 v7.0.6 -> v7.0.7 * update github.com/niklasfasching/go-org v1.3.2 -> v1.4.0 * update github.com/olivere/elastic/v7 v7.0.21 -> v7.0.22 * update github.com/pquerna/otp v1.2.0 -> v1.3.0 * update github.com/xanzy/go-gitlab v0.39.0 -> v0.42.0 * update github.com/yuin/goldmark v1.2.1 -> v1.3.1
248 lines
6.4 KiB
Go
Vendored
248 lines
6.4 KiB
Go
Vendored
package generator
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/go-openapi/analysis"
|
|
swaggererrors "github.com/go-openapi/errors"
|
|
"github.com/go-openapi/loads"
|
|
"github.com/go-openapi/spec"
|
|
"github.com/go-openapi/strfmt"
|
|
"github.com/go-openapi/swag"
|
|
"github.com/go-openapi/validate"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
func (g *GenOpts) validateAndFlattenSpec() (*loads.Document, error) {
|
|
// Load spec document
|
|
specDoc, err := loads.Spec(g.Spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If accepts definitions only, add dummy swagger header to pass validation
|
|
if g.AcceptDefinitionsOnly {
|
|
specDoc, err = applyDefaultSwagger(specDoc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Validate if needed
|
|
if g.ValidateSpec {
|
|
log.Printf("validating spec %v", g.Spec)
|
|
validationErrors := validate.Spec(specDoc, strfmt.Default)
|
|
if validationErrors != nil {
|
|
str := fmt.Sprintf("The swagger spec at %q is invalid against swagger specification %s. see errors :\n",
|
|
g.Spec, specDoc.Version())
|
|
for _, desc := range validationErrors.(*swaggererrors.CompositeError).Errors {
|
|
str += fmt.Sprintf("- %s\n", desc)
|
|
}
|
|
return nil, errors.New(str)
|
|
}
|
|
// TODO(fredbi): due to uncontrolled $ref state in spec, we need to reload the spec atm, or flatten won't
|
|
// work properly (validate expansion alters the $ref cache in go-openapi/spec)
|
|
specDoc, _ = loads.Spec(g.Spec)
|
|
}
|
|
|
|
// Flatten spec
|
|
//
|
|
// Some preprocessing is required before codegen
|
|
//
|
|
// This ensures at least that $ref's in the spec document are canonical,
|
|
// i.e all $ref are local to this file and point to some uniquely named definition.
|
|
//
|
|
// Default option is to ensure minimal flattening of $ref, bundling remote $refs and relocating arbitrary JSON
|
|
// pointers as definitions.
|
|
// This preprocessing may introduce duplicate names (e.g. remote $ref with same name). In this case, a definition
|
|
// suffixed with "OAIGen" is produced.
|
|
//
|
|
// Full flattening option farther transforms the spec by moving every complex object (e.g. with some properties)
|
|
// as a standalone definition.
|
|
//
|
|
// Eventually, an "expand spec" option is available. It is essentially useful for testing purposes.
|
|
//
|
|
// NOTE(fredbi): spec expansion may produce some unsupported constructs and is not yet protected against the
|
|
// following cases:
|
|
// - polymorphic types generation may fail with expansion (expand destructs the reuse intent of the $ref in allOf)
|
|
// - name duplicates may occur and result in compilation failures
|
|
//
|
|
// The right place to fix these shortcomings is go-openapi/analysis.
|
|
|
|
g.FlattenOpts.BasePath = specDoc.SpecFilePath()
|
|
g.FlattenOpts.Spec = analysis.New(specDoc.Spec())
|
|
|
|
g.printFlattenOpts()
|
|
|
|
if err = analysis.Flatten(*g.FlattenOpts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// yields the preprocessed spec document
|
|
return specDoc, nil
|
|
}
|
|
|
|
func (g *GenOpts) analyzeSpec() (*loads.Document, *analysis.Spec, error) {
|
|
// spec preprocessing option
|
|
if g.PropertiesSpecOrder {
|
|
g.Spec = WithAutoXOrder(g.Spec)
|
|
}
|
|
|
|
// load, validate and flatten
|
|
specDoc, err := g.validateAndFlattenSpec()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// analyze the spec
|
|
analyzed := analysis.New(specDoc.Spec())
|
|
|
|
return specDoc, analyzed, nil
|
|
}
|
|
|
|
func (g *GenOpts) printFlattenOpts() {
|
|
var preprocessingOption string
|
|
switch {
|
|
case g.FlattenOpts.Expand:
|
|
preprocessingOption = "expand"
|
|
case g.FlattenOpts.Minimal:
|
|
preprocessingOption = "minimal flattening"
|
|
default:
|
|
preprocessingOption = "full flattening"
|
|
}
|
|
log.Printf("preprocessing spec with option: %s", preprocessingOption)
|
|
}
|
|
|
|
// findSwaggerSpec fetches a default swagger spec if none is provided
|
|
func findSwaggerSpec(nm string) (string, error) {
|
|
specs := []string{"swagger.json", "swagger.yml", "swagger.yaml"}
|
|
if nm != "" {
|
|
specs = []string{nm}
|
|
}
|
|
var name string
|
|
for _, nn := range specs {
|
|
f, err := os.Stat(nn)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
return "", err
|
|
}
|
|
if f.IsDir() {
|
|
return "", fmt.Errorf("%s is a directory", nn)
|
|
}
|
|
name = nn
|
|
break
|
|
}
|
|
if name == "" {
|
|
return "", errors.New("couldn't find a swagger spec")
|
|
}
|
|
return name, nil
|
|
}
|
|
|
|
// WithAutoXOrder amends the spec to specify property order as they appear
|
|
// in the spec (supports yaml documents only).
|
|
func WithAutoXOrder(specPath string) string {
|
|
lookFor := func(ele interface{}, key string) (yaml.MapSlice, bool) {
|
|
if slice, ok := ele.(yaml.MapSlice); ok {
|
|
for _, v := range slice {
|
|
if v.Key == key {
|
|
if slice, ok := v.Value.(yaml.MapSlice); ok {
|
|
return slice, ok
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
var addXOrder func(interface{})
|
|
addXOrder = func(element interface{}) {
|
|
if props, ok := lookFor(element, "properties"); ok {
|
|
for i, prop := range props {
|
|
if pSlice, ok := prop.Value.(yaml.MapSlice); ok {
|
|
isObject := false
|
|
xOrderIndex := -1 // find if x-order already exists
|
|
|
|
for i, v := range pSlice {
|
|
if v.Key == "type" && v.Value == object {
|
|
isObject = true
|
|
}
|
|
if v.Key == xOrder {
|
|
xOrderIndex = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if xOrderIndex > -1 { // override existing x-order
|
|
pSlice[xOrderIndex] = yaml.MapItem{Key: xOrder, Value: i}
|
|
} else { // append new x-order
|
|
pSlice = append(pSlice, yaml.MapItem{Key: xOrder, Value: i})
|
|
}
|
|
prop.Value = pSlice
|
|
props[i] = prop
|
|
|
|
if isObject {
|
|
addXOrder(pSlice)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
yamlDoc, err := swag.YAMLData(specPath)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if defs, ok := lookFor(yamlDoc, "definitions"); ok {
|
|
for _, def := range defs {
|
|
addXOrder(def.Value)
|
|
}
|
|
}
|
|
|
|
addXOrder(yamlDoc)
|
|
|
|
out, err := yaml.Marshal(yamlDoc)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
tmpFile, err := ioutil.TempFile("", filepath.Base(specPath))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if err := ioutil.WriteFile(tmpFile.Name(), out, 0); err != nil {
|
|
panic(err)
|
|
}
|
|
return tmpFile.Name()
|
|
}
|
|
|
|
func applyDefaultSwagger(doc *loads.Document) (*loads.Document, error) {
|
|
// bake a minimal swagger spec to pass validation
|
|
swspec := doc.Spec()
|
|
if swspec.Swagger == "" {
|
|
swspec.Swagger = "2.0"
|
|
}
|
|
if swspec.Info == nil {
|
|
info := new(spec.Info)
|
|
info.Version = "0.0.0"
|
|
info.Title = "minimal"
|
|
swspec.Info = info
|
|
}
|
|
if swspec.Paths == nil {
|
|
swspec.Paths = &spec.Paths{}
|
|
}
|
|
// rewrite the document with the new addition
|
|
jazon, err := json.Marshal(swspec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return loads.Analyzed(jazon, swspec.Swagger)
|
|
}
|