mirror of https://github.com/grafana/grafana
Codegen: Generate Golang code using cog (#98812)
* Use cog for Go types * Delete old generation code * Fix plugins generation * workspaces update * Update datasources with new generated code * More fixes * Update swagger and openapi specs * Fixes * More files... * Update workspace * More fixes... * Remove unused functionspull/98970/head
parent
b96a752db3
commit
7151ea6abc
@ -1,282 +0,0 @@ |
||||
package generators |
||||
|
||||
import ( |
||||
"fmt" |
||||
"regexp" |
||||
"strings" |
||||
"unicode" |
||||
|
||||
"github.com/dave/dst" |
||||
"github.com/dave/dst/dstutil" |
||||
) |
||||
|
||||
// depointerizer returns an AST manipulator that removes redundant
|
||||
// pointer indirection from the defined types.
|
||||
func depointerizer() dstutil.ApplyFunc { |
||||
return func(c *dstutil.Cursor) bool { |
||||
switch x := c.Node().(type) { |
||||
case *dst.Field: |
||||
if s, is := x.Type.(*dst.StarExpr); is { |
||||
switch deref := depoint(s).(type) { |
||||
case *dst.ArrayType, *dst.MapType: |
||||
x.Type = deref |
||||
} |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
} |
||||
|
||||
func depoint(e dst.Expr) dst.Expr { |
||||
if star, is := e.(*dst.StarExpr); is { |
||||
return star.X |
||||
} |
||||
return e |
||||
} |
||||
|
||||
func setStar(e dst.Expr) string { |
||||
if _, is := e.(*dst.StarExpr); is { |
||||
return "*" |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
func fixTODOComments() dstutil.ApplyFunc { |
||||
return func(cursor *dstutil.Cursor) bool { |
||||
switch f := cursor.Node().(type) { |
||||
case *dst.File: |
||||
for _, d := range f.Decls { |
||||
if isTypeSpec(d) { |
||||
removeGoFieldComment(d.Decorations().Start.All()) |
||||
} |
||||
fixTODOComment(d.Decorations().Start.All()) |
||||
} |
||||
case *dst.Field: |
||||
if len(f.Names) > 0 { |
||||
removeGoFieldComment(f.Decorations().Start.All()) |
||||
} |
||||
} |
||||
|
||||
return true |
||||
} |
||||
} |
||||
|
||||
func fixTODOComment(comments []string) { |
||||
todoRegex := regexp.MustCompile("(//) (.*) (TODO.*)") |
||||
if len(comments) > 0 { |
||||
comments[0] = todoRegex.ReplaceAllString(comments[0], "$1 $3") |
||||
} |
||||
} |
||||
|
||||
func removeGoFieldComment(comments []string) { |
||||
todoRegex := regexp.MustCompile("(//) ([A-Z].*?) ([A-Z]?.*?) (.*)") |
||||
if len(comments) > 0 { |
||||
matches := todoRegex.FindAllStringSubmatch(comments[0], -1) |
||||
if len(matches) > 0 { |
||||
if strings.EqualFold(matches[0][3], matches[0][2]) { |
||||
comments[0] = fmt.Sprintf("%s %s %s", matches[0][1], matches[0][3], matches[0][4]) |
||||
} else { |
||||
r := []rune(matches[0][3]) |
||||
if !unicode.IsLower(r[0]) { |
||||
comments[0] = fmt.Sprintf("%s %s %s", matches[0][1], matches[0][3], matches[0][4]) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func isTypeSpec(d dst.Decl) bool { |
||||
gd, ok := d.(*dst.GenDecl) |
||||
if !ok { |
||||
return false |
||||
} |
||||
|
||||
_, is := gd.Specs[0].(*dst.TypeSpec) |
||||
return is |
||||
} |
||||
|
||||
// It fixes the "generic" fields. It happens when a value in cue could be different structs.
|
||||
// For Go it generates a struct with a json.RawMessage field inside and multiple functions to map it between the different possibilities.
|
||||
func fixRawData() dstutil.ApplyFunc { |
||||
return func(c *dstutil.Cursor) bool { |
||||
f, is := c.Node().(*dst.File) |
||||
if !is { |
||||
return false |
||||
} |
||||
|
||||
rawFields := make(map[string]bool) |
||||
existingRawFields := make(map[string]bool) |
||||
for _, decl := range f.Decls { |
||||
switch x := decl.(type) { |
||||
// Find the structs that only contains one json.RawMessage inside
|
||||
case *dst.GenDecl: |
||||
for _, t := range x.Specs { |
||||
if ts, ok := t.(*dst.TypeSpec); ok { |
||||
if tp, ok := ts.Type.(*dst.StructType); ok && len(tp.Fields.List) == 1 { |
||||
if fn, ok := tp.Fields.List[0].Type.(*dst.SelectorExpr); ok { |
||||
if fmt.Sprintf("%s.%s", fn.X, fn.Sel.Name) == "json.RawMessage" { |
||||
rawFields[ts.Name.Name] = true |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
// Find the functions of the previous structs to verify that are the ones that we are looking for.
|
||||
case *dst.FuncDecl: |
||||
for _, recv := range x.Recv.List { |
||||
fnType := depoint(recv.Type).(*dst.Ident).Name |
||||
if rawFields[fnType] { |
||||
existingRawFields[fnType] = true |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
dstutil.Apply(f, func(c *dstutil.Cursor) bool { |
||||
switch x := c.Node().(type) { |
||||
// Delete the functions
|
||||
case *dst.FuncDecl: |
||||
c.Delete() |
||||
case *dst.GenDecl: |
||||
// Deletes all "generics" generated for these json.RawMessage structs
|
||||
comments := x.Decorations().Start.All() |
||||
if len(comments) > 0 { |
||||
if strings.HasSuffix(comments[0], "defines model for .") { |
||||
c.Delete() |
||||
} |
||||
} |
||||
for _, spec := range x.Specs { |
||||
if tp, ok := spec.(*dst.TypeSpec); ok { |
||||
// Delete structs with only json.RawMessage
|
||||
if existingRawFields[tp.Name.Name] && tp.Name.Name != "MetricAggregation2" { |
||||
c.Delete() |
||||
continue |
||||
} |
||||
// Set types that was using these structs as interface{}
|
||||
if st, ok := tp.Type.(*dst.StructType); ok { |
||||
iterateStruct(st, withoutRawData(existingRawFields)) |
||||
} |
||||
if mt, ok := tp.Type.(*dst.MapType); ok { |
||||
iterateMap(mt, withoutRawData(existingRawFields)) |
||||
} |
||||
if at, ok := tp.Type.(*dst.ArrayType); ok { |
||||
iterateArray(at, withoutRawData(existingRawFields)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return true |
||||
}, nil) |
||||
|
||||
return true |
||||
} |
||||
} |
||||
|
||||
// Fixes type name containing underscores in the generated Go files
|
||||
func fixUnderscoreInTypeName() dstutil.ApplyFunc { |
||||
return func(c *dstutil.Cursor) bool { |
||||
switch x := c.Node().(type) { |
||||
case *dst.GenDecl: |
||||
if specs, isType := x.Specs[0].(*dst.TypeSpec); isType { |
||||
if strings.Contains(specs.Name.Name, "_") { |
||||
oldName := specs.Name.Name |
||||
specs.Name.Name = strings.ReplaceAll(specs.Name.Name, "_", "") |
||||
x.Decs.Start[0] = strings.ReplaceAll(x.Decs.Start[0], oldName, specs.Name.Name) |
||||
} |
||||
if st, ok := specs.Type.(*dst.StructType); ok { |
||||
iterateStruct(st, withoutUnderscore) |
||||
} |
||||
if mt, ok := specs.Type.(*dst.MapType); ok { |
||||
iterateMap(mt, withoutUnderscore) |
||||
} |
||||
if at, ok := specs.Type.(*dst.ArrayType); ok { |
||||
iterateArray(at, withoutUnderscore) |
||||
} |
||||
} |
||||
case *dst.Field: |
||||
findFieldsWithUnderscores(x) |
||||
} |
||||
return true |
||||
} |
||||
} |
||||
|
||||
func findFieldsWithUnderscores(x *dst.Field) { |
||||
switch t := x.Type.(type) { |
||||
case *dst.Ident: |
||||
withoutUnderscore(t) |
||||
case *dst.StarExpr: |
||||
if i, is := t.X.(*dst.Ident); is { |
||||
withoutUnderscore(i) |
||||
} |
||||
case *dst.ArrayType: |
||||
iterateArray(t, withoutUnderscore) |
||||
case *dst.MapType: |
||||
iterateMap(t, withoutUnderscore) |
||||
} |
||||
} |
||||
|
||||
func withoutUnderscore(i *dst.Ident) { |
||||
if strings.Contains(i.Name, "_") { |
||||
i.Name = strings.ReplaceAll(i.Name, "_", "") |
||||
} |
||||
} |
||||
|
||||
func withoutRawData(existingFields map[string]bool) func(ident *dst.Ident) { |
||||
return func(i *dst.Ident) { |
||||
if existingFields[i.Name] { |
||||
i.Name = setStar(i) + "any" |
||||
} |
||||
} |
||||
} |
||||
|
||||
func iterateStruct(s *dst.StructType, fn func(i *dst.Ident)) { |
||||
for i, f := range s.Fields.List { |
||||
switch mx := depoint(f.Type).(type) { |
||||
case *dst.Ident: |
||||
fn(mx) |
||||
case *dst.ArrayType: |
||||
iterateArray(mx, fn) |
||||
case *dst.MapType: |
||||
iterateMap(mx, fn) |
||||
case *dst.StructType: |
||||
iterateStruct(mx, fn) |
||||
case *dst.InterfaceType: |
||||
s.Fields.List[i].Type = interfaceToAny(f.Type) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func iterateMap(s *dst.MapType, fn func(i *dst.Ident)) { |
||||
switch mx := s.Value.(type) { |
||||
case *dst.Ident: |
||||
fn(mx) |
||||
case *dst.ArrayType: |
||||
iterateArray(mx, fn) |
||||
case *dst.MapType: |
||||
iterateMap(mx, fn) |
||||
case *dst.InterfaceType: |
||||
s.Value = interfaceToAny(s.Value) |
||||
} |
||||
} |
||||
|
||||
func iterateArray(a *dst.ArrayType, fn func(i *dst.Ident)) { |
||||
switch mx := a.Elt.(type) { |
||||
case *dst.Ident: |
||||
fn(mx) |
||||
case *dst.ArrayType: |
||||
iterateArray(mx, fn) |
||||
case *dst.StructType: |
||||
iterateStruct(mx, fn) |
||||
case *dst.InterfaceType: |
||||
a.Elt = interfaceToAny(a.Elt) |
||||
} |
||||
} |
||||
|
||||
func interfaceToAny(i dst.Expr) dst.Expr { |
||||
star := "" |
||||
if _, is := i.(*dst.StarExpr); is { |
||||
star = "*" |
||||
} |
||||
|
||||
return &dst.Ident{Name: star + "any"} |
||||
} |
||||
@ -1,193 +0,0 @@ |
||||
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" |
||||
) |
||||
` |
||||
@ -1,199 +0,0 @@ |
||||
package generators |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strings" |
||||
|
||||
"cuelang.org/go/cue" |
||||
"cuelang.org/go/cue/ast" |
||||
"cuelang.org/go/encoding/openapi" |
||||
) |
||||
|
||||
type OpenApiConfig struct { |
||||
Config *openapi.Config |
||||
IsGroup bool |
||||
RootName string |
||||
SubPath cue.Path |
||||
} |
||||
|
||||
func generateOpenAPI(v cue.Value, cfg *OpenApiConfig) (*ast.File, error) { |
||||
if cfg == nil { |
||||
return nil, fmt.Errorf("missing openapi configuration") |
||||
} |
||||
|
||||
if cfg.Config == nil { |
||||
cfg.Config = &openapi.Config{} |
||||
} |
||||
|
||||
name, err := getSchemaName(v) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
gen := &oapiGen{ |
||||
cfg: cfg, |
||||
name: name, |
||||
val: v.LookupPath(cue.ParsePath("lineage.schemas[0].schema")), |
||||
subpath: cfg.SubPath, |
||||
bpath: v.LookupPath(cue.ParsePath("lineage.schemas[0]")).Path(), |
||||
} |
||||
|
||||
declFunc := genSchema |
||||
if cfg.IsGroup { |
||||
declFunc = genGroup |
||||
} |
||||
|
||||
decls, err := declFunc(gen) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// TODO recursively sort output to improve stability of output
|
||||
return &ast.File{ |
||||
Decls: []ast.Decl{ |
||||
ast.NewStruct( |
||||
"openapi", ast.NewString("3.0.0"), |
||||
"paths", ast.NewStruct(), |
||||
"components", ast.NewStruct( |
||||
"schemas", &ast.StructLit{Elts: decls}, |
||||
), |
||||
), |
||||
}, |
||||
}, nil |
||||
} |
||||
|
||||
type oapiGen struct { |
||||
cfg *OpenApiConfig |
||||
val cue.Value |
||||
subpath cue.Path |
||||
|
||||
// overall name for the generated oapi doc
|
||||
name string |
||||
|
||||
// original NameFunc
|
||||
onf func(cue.Value, cue.Path) string |
||||
|
||||
// full prefix path that leads up to the #SchemaDef, e.g. lin._sortedSchemas[0]
|
||||
bpath cue.Path |
||||
} |
||||
|
||||
func genGroup(gen *oapiGen) ([]ast.Decl, error) { |
||||
ctx := gen.val.Context() |
||||
iter, err := gen.val.Fields(cue.Definitions(true), cue.Optional(true)) |
||||
if err != nil { |
||||
panic(fmt.Errorf("unreachable - should always be able to get iter for struct kinds: %w", err)) |
||||
} |
||||
|
||||
var decls []ast.Decl |
||||
for iter.Next() { |
||||
val, sel := iter.Value(), iter.Selector() |
||||
name := strings.Trim(sel.String(), "?#") |
||||
|
||||
v := ctx.CompileString(fmt.Sprintf("#%s: _", name)) |
||||
defpath := cue.MakePath(cue.Def(name)) |
||||
defsch := v.FillPath(defpath, val) |
||||
|
||||
cfgi := *gen.cfg.Config |
||||
cfgi.NameFunc = func(val cue.Value, path cue.Path) string { |
||||
return gen.nfSingle(val, path, defpath, name) |
||||
} |
||||
|
||||
part, err := openapi.Generate(defsch, &cfgi) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed generation for grouped field %s: %w", sel, err) |
||||
} |
||||
|
||||
decls = append(decls, getSchemas(part)...) |
||||
} |
||||
|
||||
return decls, nil |
||||
} |
||||
|
||||
func genSchema(gen *oapiGen) ([]ast.Decl, error) { |
||||
hasSubpath := len(gen.cfg.SubPath.Selectors()) > 0 |
||||
name := sanitizeLabelString(gen.name) |
||||
if gen.cfg.RootName != "" { |
||||
name = gen.cfg.RootName |
||||
} else if hasSubpath { |
||||
sel := gen.cfg.SubPath.Selectors() |
||||
name = sel[len(sel)-1].String() |
||||
} |
||||
|
||||
val := gen.val |
||||
if hasSubpath { |
||||
for i, sel := range gen.cfg.SubPath.Selectors() { |
||||
if !gen.val.Allows(sel) { |
||||
return nil, fmt.Errorf("subpath %q not present in schema", cue.MakePath(gen.cfg.SubPath.Selectors()[:i+1]...)) |
||||
} |
||||
} |
||||
val = val.LookupPath(gen.cfg.SubPath) |
||||
} |
||||
|
||||
v := gen.val.Context().CompileString(fmt.Sprintf("#%s: _", name)) |
||||
defpath := cue.MakePath(cue.Def(name)) |
||||
defsch := v.FillPath(defpath, val) |
||||
|
||||
gen.cfg.Config.NameFunc = func(val cue.Value, path cue.Path) string { |
||||
return gen.nfSingle(val, path, defpath, name) |
||||
} |
||||
|
||||
f, err := openapi.Generate(defsch.Eval(), gen.cfg.Config) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return getSchemas(f), nil |
||||
} |
||||
|
||||
// For generating a single, our NameFunc must:
|
||||
// - Eliminate any path prefixes on the element, both internal lineage and wrapping
|
||||
// - Replace the name "_#schema" with the desired name
|
||||
// - Call the user-provided NameFunc, if any
|
||||
// - Remove CUE markers like #, !, ?
|
||||
func (gen *oapiGen) nfSingle(val cue.Value, path, defpath cue.Path, name string) string { |
||||
tpath := trimPathPrefix(trimThemaPathPrefix(path, gen.bpath), defpath) |
||||
|
||||
if path.String() == "" || tpath.String() == defpath.String() { |
||||
return name |
||||
} |
||||
|
||||
if val == gen.val { |
||||
return "" |
||||
} |
||||
|
||||
if gen.onf != nil { |
||||
return gen.onf(val, tpath) |
||||
} |
||||
return strings.Trim(tpath.String(), "?#") |
||||
} |
||||
|
||||
func getSchemas(f *ast.File) []ast.Decl { |
||||
compos := orp(getFieldByLabel(f, "components")) |
||||
schemas := orp(getFieldByLabel(compos.Value, "schemas")) |
||||
return schemas.Value.(*ast.StructLit).Elts |
||||
} |
||||
|
||||
func orp[T any](t T, err error) T { |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
return t |
||||
} |
||||
|
||||
func trimThemaPathPrefix(p, base cue.Path) cue.Path { |
||||
if !pathHasPrefix(p, base) { |
||||
return p |
||||
} |
||||
|
||||
rest := p.Selectors()[len(base.Selectors()):] |
||||
if len(rest) == 0 { |
||||
return cue.Path{} |
||||
} |
||||
switch rest[0].String() { |
||||
case "schema", "_#schema", "_join", "joinSchema": |
||||
return cue.MakePath(rest[1:]...) |
||||
default: |
||||
return cue.MakePath(rest...) |
||||
} |
||||
} |
||||
@ -1,130 +0,0 @@ |
||||
package generators |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strconv" |
||||
"strings" |
||||
|
||||
"cuelang.org/go/cue" |
||||
"cuelang.org/go/cue/ast" |
||||
"cuelang.org/go/cue/token" |
||||
) |
||||
|
||||
// sanitizeLabelString strips characters from a string that are not allowed for
|
||||
// use in a CUE label.
|
||||
func sanitizeLabelString(s string) string { |
||||
return strings.Map(func(r rune) rune { |
||||
switch { |
||||
case r >= 'a' && r <= 'z': |
||||
fallthrough |
||||
case r >= 'A' && r <= 'Z': |
||||
fallthrough |
||||
case r >= '0' && r <= '9': |
||||
fallthrough |
||||
case r == '_': |
||||
return r |
||||
default: |
||||
return -1 |
||||
} |
||||
}, s) |
||||
} |
||||
|
||||
// trimPathPrefix strips the provided prefix from the provided path, if the
|
||||
// prefix exists.
|
||||
//
|
||||
// If path and prefix are equivalent, and there is at least one additional
|
||||
// selector in the provided path.
|
||||
func trimPathPrefix(path, prefix cue.Path) cue.Path { |
||||
sels, psels := path.Selectors(), prefix.Selectors() |
||||
if len(sels) == 1 { |
||||
return path |
||||
} |
||||
var i int |
||||
for ; i < len(psels) && i < len(sels); i++ { |
||||
if !selEq(psels[i], sels[i]) { |
||||
break |
||||
} |
||||
} |
||||
return cue.MakePath(sels[i:]...) |
||||
} |
||||
|
||||
// selEq indicates whether two selectors are equivalent. Selectors are equivalent if
|
||||
// they are either exactly equal, or if they are equal ignoring path optionality.
|
||||
func selEq(s1, s2 cue.Selector) bool { |
||||
return s1 == s2 || s1.Optional() == s2.Optional() |
||||
} |
||||
|
||||
// getFieldByLabel returns the ast.Field with a given label from a struct-ish input.
|
||||
func getFieldByLabel(n ast.Node, label string) (*ast.Field, error) { |
||||
var d []ast.Decl |
||||
switch x := n.(type) { |
||||
case *ast.File: |
||||
d = x.Decls |
||||
case *ast.StructLit: |
||||
d = x.Elts |
||||
default: |
||||
return nil, fmt.Errorf("not an *ast.File or *ast.StructLit") |
||||
} |
||||
|
||||
for _, el := range d { |
||||
if isFieldWithLabel(el, label) { |
||||
return el.(*ast.Field), nil |
||||
} |
||||
} |
||||
|
||||
return nil, fmt.Errorf("no field with label %q", label) |
||||
} |
||||
|
||||
func isFieldWithLabel(n ast.Node, label string) bool { |
||||
if x, is := n.(*ast.Field); is { |
||||
if l, is := x.Label.(*ast.BasicLit); is { |
||||
return strEq(l, label) |
||||
} |
||||
if l, is := x.Label.(*ast.Ident); is { |
||||
return identStrEq(l, label) |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func strEq(lit *ast.BasicLit, str string) bool { |
||||
if lit.Kind != token.STRING { |
||||
return false |
||||
} |
||||
ls, _ := strconv.Unquote(lit.Value) |
||||
return str == ls || str == lit.Value |
||||
} |
||||
|
||||
func identStrEq(id *ast.Ident, str string) bool { |
||||
if str == id.Name { |
||||
return true |
||||
} |
||||
ls, _ := strconv.Unquote(id.Name) |
||||
return str == ls |
||||
} |
||||
|
||||
// pathHasPrefix tests whether the [cue.Path] p begins with prefix.
|
||||
func pathHasPrefix(p, prefix cue.Path) bool { |
||||
ps, pres := p.Selectors(), prefix.Selectors() |
||||
if len(pres) > len(ps) { |
||||
return false |
||||
} |
||||
return pathsAreEq(ps[:len(pres)], pres) |
||||
} |
||||
|
||||
func pathsAreEq(p1s, p2s []cue.Selector) bool { |
||||
if len(p1s) != len(p2s) { |
||||
return false |
||||
} |
||||
for i := 0; i < len(p2s); i++ { |
||||
if !selEq(p2s[i], p1s[i]) { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
func getSchemaName(v cue.Value) (string, error) { |
||||
nameValue := v.LookupPath(cue.ParsePath("name")) |
||||
return nameValue.String() |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue