grafana/pkg/codegen/generators/go_generator.go
Dave Henderson f446096eb1
chore(deps): Upgrade openapi dep, remove replace directive (#90609)
Signed-off-by: Dave Henderson <dave.henderson@grafana.com>
2024-07-18 14:42:18 -04:00

194 lines
5.1 KiB
Go

package generators
import (
"bytes"
"fmt"
"go/parser"
"go/token"
"path/filepath"
"strings"
"cuelang.org/go/cue"
"cuelang.org/go/pkg/encoding/yaml"
"github.com/dave/dst/decorator"
"github.com/dave/dst/dstutil"
"github.com/getkin/kin-openapi/openapi3"
"github.com/oapi-codegen/oapi-codegen/v2/pkg/codegen"
"golang.org/x/tools/imports"
)
type GoConfig struct {
Config *OpenApiConfig
PackageName string
ApplyFuncs []dstutil.ApplyFunc
}
func GenerateTypesGo(v cue.Value, cfg *GoConfig) ([]byte, error) {
if cfg == nil {
return nil, fmt.Errorf("configuration cannot be nil")
}
applyFuncs := []dstutil.ApplyFunc{depointerizer(), fixRawData(), fixUnderscoreInTypeName(), fixTODOComments()}
applyFuncs = append(applyFuncs, cfg.ApplyFuncs...)
f, err := generateOpenAPI(v, cfg.Config)
if err != nil {
return nil, err
}
str, err := yaml.Marshal(v.Context().BuildFile(f))
if err != nil {
return nil, fmt.Errorf("cue-yaml marshaling failed: %w", err)
}
loader := openapi3.NewLoader()
oT, err := loader.LoadFromData([]byte(str))
if err != nil {
return nil, fmt.Errorf("loading generated openapi failed: %w", err)
}
schemaName, err := getSchemaName(v)
if err != nil {
return nil, err
}
if cfg.PackageName == "" {
cfg.PackageName = schemaName
}
// Hack to fix https://github.com/grafana/thema/pull/127 issue without importing
// to avoid to add the whole vendor in Grafana code
if cfg.PackageName == "dataquery" {
fixDataQuery(oT)
}
ccfg := codegen.Configuration{
PackageName: cfg.PackageName,
Compatibility: codegen.CompatibilityOptions{
AlwaysPrefixEnumValues: true,
},
Generate: codegen.GenerateOptions{
Models: true,
},
OutputOptions: codegen.OutputOptions{
SkipPrune: true,
UserTemplates: map[string]string{
"imports.tmpl": importstmpl,
},
},
}
gostr, err := codegen.Generate(oT, ccfg)
if err != nil {
return nil, fmt.Errorf("openapi generation failed: %w", err)
}
return postprocessGoFile(genGoFile{
path: fmt.Sprintf("%s_type_gen.go", schemaName),
appliers: applyFuncs,
in: []byte(gostr),
})
}
type genGoFile struct {
path string
appliers []dstutil.ApplyFunc
in []byte
}
func postprocessGoFile(cfg genGoFile) ([]byte, error) {
fname := sanitizeLabelString(filepath.Base(cfg.path))
buf := new(bytes.Buffer)
fset := token.NewFileSet()
gf, err := decorator.ParseFile(fset, fname, string(cfg.in), parser.ParseComments)
if err != nil {
return nil, fmt.Errorf("error parsing generated file: %w", err)
}
for _, af := range cfg.appliers {
dstutil.Apply(gf, af, nil)
}
err = decorator.Fprint(buf, gf)
if err != nil {
return nil, fmt.Errorf("error formatting generated file: %w", err)
}
byt, err := imports.Process(fname, buf.Bytes(), nil)
if err != nil {
return nil, fmt.Errorf("goimports processing of generated file failed: %w", err)
}
// Compare imports before and after; warn about performance if some were added
gfa, _ := parser.ParseFile(fset, fname, string(byt), parser.ParseComments)
imap := make(map[string]bool)
for _, im := range gf.Imports {
imap[im.Path.Value] = true
}
var added []string
for _, im := range gfa.Imports {
if !imap[im.Path.Value] {
added = append(added, im.Path.Value)
}
}
if len(added) != 0 {
// TODO improve the guidance in this error if/when we better abstract over imports to generate
return nil, fmt.Errorf("goimports added the following import statements to %s: \n\t%s\nRelying on goimports to find imports significantly slows down code generation. Either add these imports with an AST manipulation in cfg.ApplyFuncs, or set cfg.IgnoreDiscoveredImports to true", cfg.path, strings.Join(added, "\n\t"))
}
return byt, nil
}
// fixDataQuery extends the properties for the AllOf schemas when a DataQuery exists.
// deep/oapi-codegen library ignores the properties of the models and only ones have references.
// It doesn't apply this change https://github.com/grafana/thema/pull/154 since it modifies the
// vendor implementation, and we don't import it.
func fixDataQuery(spec *openapi3.T) *openapi3.T {
for _, sch := range spec.Components.Schemas {
if sch.Value != nil && len(sch.Value.AllOf) > 0 {
for _, allOf := range sch.Value.AllOf {
for n, p := range allOf.Value.Properties {
sch.Value.Properties[n] = p
}
}
sch.Value.AllOf = nil
}
}
return spec
}
// Almost all of the below imports are eliminated by dst transformers and calls
// to goimports - but if they're not present in the template, then the internal
// call to goimports that oapi-codegen makes will trigger a search for them,
// which can slow down codegen by orders of magnitude.
var importstmpl = `package {{ .PackageName }}
import (
"bytes"
"compress/gzip"
"context"
"encoding/base64"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"gopkg.in/yaml.v2"
"io"
"io/ioutil"
"os"
"net/http"
"net/url"
"path"
"strings"
"time"
"github.com/oapi-codegen/runtime"
openapi_types "github.com/oapi-codegen/runtime/types"
"github.com/getkin/kin-openapi/openapi3"
"github.com/go-chi/chi/v5"
"github.com/labstack/echo/v4"
"github.com/gin-gonic/gin"
"github.com/gorilla/mux"
)
`