Core: Remove thema and kindsys dependencies (#84499)

* Move some thema code inside grafana

* Use new codegen instead of thema for core kinds

* Replace TS generator

* Use new generator for go types

* Remove thema from oapi generator

* Remove thema from generators

* Don't use kindsys/thema for core kinds

* Remove kindsys/thema from plugins

* Remove last thema related

* Remove most of cuectx and move utils_ts into codegen. It also deletes wire dependency

* Merge plugins generators

* Delete thema dependency 🎉

* Fix CODEOWNERS

* Fix package name

* Fix TS output names

* More path fixes

* Fix mod codeowners

* Use original plugin's name

* Remove kindsys dependency 🎉

* Modify oapi schema and create an apply function to fix elasticsearch errors

* cue.mod was deleted by mistake

* Fix TS panels

* sort imports

* Fixing elasticsearch output

* Downgrade oapi-codegen library

* Update output ts files

* More fixes

* Restore old elasticsearch generated file and skip its generation. Remove core imports into plugins

* More lint fixes

* Add codeowners

* restore embed.go file

* Fix embed.go
This commit is contained in:
Selene
2024-03-21 11:11:29 +01:00
committed by GitHub
parent 856e410480
commit 473898e47c
56 changed files with 1433 additions and 1631 deletions

View File

@@ -0,0 +1,282 @@
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"}
}

View File

@@ -0,0 +1,193 @@
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/deepmap/oapi-codegen/pkg/codegen"
"github.com/getkin/kin-openapi/openapi3"
"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/deepmap/oapi-codegen/pkg/runtime"
openapi_types "github.com/deepmap/oapi-codegen/pkg/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"
)
`

View File

@@ -0,0 +1,199 @@
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...)
}
}

View File

@@ -0,0 +1,56 @@
package generators
import (
"fmt"
"cuelang.org/go/cue"
"cuelang.org/go/cue/cuecontext"
"github.com/grafana/cuetsy"
"github.com/grafana/cuetsy/ts"
"github.com/grafana/cuetsy/ts/ast"
)
type TSConfig struct {
CuetsyConfig *cuetsy.Config
IsGroup bool
RootName string
}
// GenerateTypesTS generates native TypeScript types and defaults corresponding to
// the provided Schema.
func GenerateTypesTS(v cue.Value, cfg *TSConfig) (*ast.File, error) {
if cfg == nil {
return nil, fmt.Errorf("configuration cannot be empty")
}
if cfg.CuetsyConfig == nil {
cfg.CuetsyConfig = &cuetsy.Config{
Export: true,
}
}
// Thema #schema was a unification between the schema and cue.TopKind(_). For any reason, without this unification,
// cuetsy fails finding some enums ¯\_(ツ)_/¯.
oldSchema := cuecontext.New().CompileBytes([]byte("_"))
schdef := v.LookupPath(cue.ParsePath("lineage.schemas[0].schema")).Unify(oldSchema)
tf, err := cuetsy.GenerateAST(schdef, *cfg.CuetsyConfig)
if err != nil {
return nil, fmt.Errorf("generating TS for child elements of schema failed: %w", err)
}
file := &ts.File{
Nodes: tf.Nodes,
}
if !cfg.IsGroup {
top, err := cuetsy.GenerateSingleAST(cfg.RootName, schdef, cuetsy.TypeInterface)
if err != nil {
return nil, fmt.Errorf("generating TS for schema root failed: %w", err)
}
file.Nodes = append(file.Nodes, top.T)
if top.D != nil {
file.Nodes = append(file.Nodes, top.D)
}
}
return file, nil
}

View File

@@ -0,0 +1,130 @@
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()
}