Kindsys: Replace DefForGen with kindsys.Kind (#62642)

* Kindsys: Replace DeclForGen with kindsys.Kind

DeclForGen was always unnecessary - it just wasn't obvious on initial
implementation, when we were focused on generating unique types for each
core kind. This removes it, considerably simplifying interactions with
kindsys - virtually everything now just relies on kindsys.Kind and its
derived interfaces.

* Removed unused jenny

* Rename params in jennies
This commit is contained in:
sam boyer
2023-01-31 19:40:15 -05:00
committed by GitHub
parent 0f0a53fbbb
commit 30b4205521
25 changed files with 273 additions and 543 deletions
+13 -36
View File
@@ -10,45 +10,20 @@ import (
"github.com/grafana/thema"
)
type OneToOne codejen.OneToOne[*DefForGen]
type OneToMany codejen.OneToMany[*DefForGen]
type ManyToOne codejen.ManyToOne[*DefForGen]
type ManyToMany codejen.ManyToMany[*DefForGen]
type OneToOne codejen.OneToOne[kindsys.Kind]
type OneToMany codejen.OneToMany[kindsys.Kind]
type ManyToOne codejen.ManyToOne[kindsys.Kind]
type ManyToMany codejen.ManyToMany[kindsys.Kind]
// ForGen is a codejen input transformer that converts a pure kindsys.SomeDef into
// a DefForGen by binding its contained lineage.
func ForGen(rt *thema.Runtime, def kindsys.SomeDef) (*DefForGen, error) {
lin, err := def.BindKindLineage(rt)
if err != nil {
return nil, err
}
return &DefForGen{
SomeDef: def,
lin: lin,
}, nil
}
// DefForGen wraps [kindsys.SomeDef] to provide trivial caching of
// the lineage declared by the kind (nil for raw kinds).
// TODO this type is unneeded - kindsys.Kind is sufficient.
type DefForGen struct {
kindsys.SomeDef
lin thema.Lineage
}
// Lineage returns the [thema.Lineage] for the underlying [kindsys.SomeDef].
func (def *DefForGen) Lineage() thema.Lineage {
return def.lin
}
// ForLatestSchema returns a [SchemaForGen] for the latest schema in this
// DefForGen's lineage.
func (def *DefForGen) ForLatestSchema() SchemaForGen {
comm := def.Properties.Common()
// ForLatestSchema returns a [SchemaForGen] for the latest schema in the
// provided [kindsys.Kind]'s lineage.
//
// TODO this will be replaced by thema-native constructs
func ForLatestSchema(k kindsys.Kind) SchemaForGen {
comm := k.Props().Common()
return SchemaForGen{
Name: comm.Name,
Schema: def.Lineage().Latest(),
Schema: k.Lineage().Latest(),
IsGroup: comm.LineageIsGroup,
}
}
@@ -81,6 +56,8 @@ func SlashHeaderMapper(maingen string) codejen.FileMapper {
// SchemaForGen is an intermediate values type for jennies that holds both a thema.Schema,
// and values relevant to generating the schema that should properly, eventually, be in
// thema itself.
//
// TODO this will be replaced by thema-native constructs
type SchemaForGen struct {
// The PascalCase name of the schematized type.
Name string
+4 -3
View File
@@ -6,10 +6,11 @@ import (
"path/filepath"
"github.com/grafana/codejen"
"github.com/grafana/grafana/pkg/kindsys"
)
// BaseCoreRegistryJenny generates a static registry for core kinds that
// only initializes their [kindsys.Interface]. No slot kinds are composed.
// only initializes their [kindsys.Kind]. No slot kinds are composed.
//
// Path should be the relative path to the directory that will contain the
// generated registry. kindrelroot should be the repo-root-relative path to the
@@ -31,12 +32,12 @@ func (gen *genBaseRegistry) JennyName() string {
return "BaseCoreRegistryJenny"
}
func (gen *genBaseRegistry) Generate(defs ...*DefForGen) (*codejen.File, error) {
func (gen *genBaseRegistry) Generate(kinds ...kindsys.Kind) (*codejen.File, error) {
buf := new(bytes.Buffer)
if err := tmpls.Lookup("kind_registry.tmpl").Execute(buf, tvars_kind_registry{
PackageName: filepath.Base(gen.path),
KindPackagePrefix: filepath.ToSlash(filepath.Join("github.com/grafana/grafana", gen.kindrelroot)),
Kinds: defs,
Kinds: kinds,
}); err != nil {
return nil, fmt.Errorf("failed executing kind registry template: %w", err)
}
+8 -7
View File
@@ -6,6 +6,7 @@ import (
"path/filepath"
"github.com/grafana/codejen"
"github.com/grafana/grafana/pkg/kindsys"
)
// CoreKindJenny generates the implementation of [kindsys.Core] for the provided
@@ -20,8 +21,8 @@ func CoreKindJenny(gokindsdir string, cfg *CoreKindJennyConfig) OneToOne {
cfg = new(CoreKindJennyConfig)
}
if cfg.GenDirName == nil {
cfg.GenDirName = func(def *DefForGen) string {
return def.Properties.Common().MachineName
cfg.GenDirName = func(def kindsys.Kind) string {
return def.Props().Common().MachineName
}
}
@@ -35,7 +36,7 @@ func CoreKindJenny(gokindsdir string, cfg *CoreKindJennyConfig) OneToOne {
type CoreKindJennyConfig struct {
// GenDirName returns the name of the directory in which the file should be
// generated. Defaults to DefForGen.Lineage().Name() if nil.
GenDirName func(*DefForGen) string
GenDirName func(kindsys.Kind) string
}
type coreKindJenny struct {
@@ -49,14 +50,14 @@ func (gen *coreKindJenny) JennyName() string {
return "CoreKindJenny"
}
func (gen *coreKindJenny) Generate(def *DefForGen) (*codejen.File, error) {
if !def.IsCore() {
func (gen *coreKindJenny) Generate(kind kindsys.Kind) (*codejen.File, error) {
if _, is := kind.(kindsys.Core); !is {
return nil, nil
}
path := filepath.Join(gen.gokindsdir, gen.cfg.GenDirName(def), def.Properties.Common().MachineName+"_kind_gen.go")
path := filepath.Join(gen.gokindsdir, gen.cfg.GenDirName(kind), kind.Props().Common().MachineName+"_kind_gen.go")
buf := new(bytes.Buffer)
if err := tmpls.Lookup("kind_core.tmpl").Execute(buf, def); err != nil {
if err := tmpls.Lookup("kind_core.tmpl").Execute(buf, kind); err != nil {
return nil, fmt.Errorf("failed executing kind_core template for %s: %w", path, err)
}
b, err := postprocessGoFile(genGoFile{
+10 -4
View File
@@ -15,6 +15,7 @@ import (
"cuelang.org/go/cue/cuecontext"
"github.com/grafana/codejen"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/kindsys"
"github.com/grafana/thema/encoding/jsonschema"
"github.com/olekukonko/tablewriter"
"github.com/xeipuuv/gojsonpointer"
@@ -34,8 +35,13 @@ func (j docsJenny) JennyName() string {
return "DocsJenny"
}
func (j docsJenny) Generate(def *DefForGen) (*codejen.File, error) {
f, err := jsonschema.GenerateSchema(def.Lineage().Latest())
func (j docsJenny) Generate(kind kindsys.Kind) (*codejen.File, error) {
// TODO remove this once codejen catches nils https://github.com/grafana/codejen/issues/5
if kind == nil {
return nil, nil
}
f, err := jsonschema.GenerateSchema(kind.Lineage().Latest())
if err != nil {
return nil, fmt.Errorf("failed to generate json representation for the schema: %v", err)
}
@@ -61,10 +67,10 @@ func (j docsJenny) Generate(def *DefForGen) (*codejen.File, error) {
// fixes the references between the types within a json after making components.schema.<types> the root of the json
kindJsonStr := strings.Replace(string(obj.Components.Schemas), "#/components/schemas/", "#/", -1)
kindProps := def.Properties.Common()
kindProps := kind.Props().Common()
data := templateData{
KindName: kindProps.Name,
KindVersion: def.Lineage().Latest().Version().String(),
KindVersion: kind.Lineage().Latest().Version().String(),
KindMaturity: string(kindProps.Maturity),
Markdown: "{{ .Markdown 1 }}",
}
+5 -5
View File
@@ -29,8 +29,8 @@ func (j *lmox) JennyName() string {
return "LatestMajorsOrXJenny"
}
func (j *lmox) Generate(def *DefForGen) (codejen.Files, error) {
comm := def.Properties.Common()
func (j *lmox) Generate(kind kindsys.Kind) (codejen.Files, error) {
comm := kind.Props().Common()
sfg := SchemaForGen{
Name: comm.Name,
IsGroup: comm.LineageIsGroup,
@@ -39,7 +39,7 @@ func (j *lmox) Generate(def *DefForGen) (codejen.Files, error) {
do := func(sfg SchemaForGen, infix string) (codejen.Files, error) {
f, err := j.inner.Generate(sfg)
if err != nil {
return nil, fmt.Errorf("%s jenny failed on %s schema for %s: %w", j.inner.JennyName(), sfg.Schema.Version(), def.Properties.Common().Name, err)
return nil, fmt.Errorf("%s jenny failed on %s schema for %s: %w", j.inner.JennyName(), sfg.Schema.Version(), kind.Props().Common().Name, err)
}
if f == nil || !f.Exists() {
return nil, nil
@@ -51,12 +51,12 @@ func (j *lmox) Generate(def *DefForGen) (codejen.Files, error) {
}
if comm.Maturity.Less(kindsys.MaturityStable) {
sfg.Schema = def.Lineage().Latest()
sfg.Schema = kind.Lineage().Latest()
return do(sfg, "x")
}
var fl codejen.Files
for sch := def.Lineage().First(); sch != nil; sch = sch.Successor() {
for sch := kind.Lineage().First(); sch != nil; sch = sch.Successor() {
sfg.Schema = sch.LatestInMajor()
files, err := do(sfg, fmt.Sprintf("v%v", sch.Version()[0]))
if err != nil {
+9 -9
View File
@@ -39,20 +39,20 @@ func (gen *genTSVeneerIndex) JennyName() string {
return "TSVeneerIndexJenny"
}
func (gen *genTSVeneerIndex) Generate(decls ...*DefForGen) (*codejen.File, error) {
func (gen *genTSVeneerIndex) Generate(kinds ...kindsys.Kind) (*codejen.File, error) {
tsf := new(ast.File)
for _, def := range decls {
for _, def := range kinds {
sch := def.Lineage().Latest()
f, err := typescript.GenerateTypes(sch, &typescript.TypeConfig{
RootName: def.Properties.Common().Name,
Group: def.Properties.Common().LineageIsGroup,
RootName: def.Props().Common().Name,
Group: def.Props().Common().LineageIsGroup,
})
if err != nil {
return nil, fmt.Errorf("%s: %w", def.Properties.Common().Name, err)
return nil, fmt.Errorf("%s: %w", def.Props().Common().Name, err)
}
elems, err := gen.extractTSIndexVeneerElements(def, f)
if err != nil {
return nil, fmt.Errorf("%s: %w", def.Properties.Common().Name, err)
return nil, fmt.Errorf("%s: %w", def.Props().Common().Name, err)
}
tsf.Nodes = append(tsf.Nodes, elems...)
}
@@ -60,9 +60,9 @@ func (gen *genTSVeneerIndex) Generate(decls ...*DefForGen) (*codejen.File, error
return codejen.NewFile(filepath.Join(gen.dir, "index.gen.ts"), []byte(tsf.String()), gen), nil
}
func (gen *genTSVeneerIndex) extractTSIndexVeneerElements(def *DefForGen, tf *ast.File) ([]ast.Decl, error) {
func (gen *genTSVeneerIndex) extractTSIndexVeneerElements(def kindsys.Kind, tf *ast.File) ([]ast.Decl, error) {
lin := def.Lineage()
comm := def.Properties.Common()
comm := def.Props().Common()
// Check the root, then walk the tree
rootv := lin.Latest().Underlying()
@@ -139,7 +139,7 @@ func (gen *genTSVeneerIndex) extractTSIndexVeneerElements(def *DefForGen, tf *as
}
vpath := fmt.Sprintf("v%v", thema.LatestVersion(lin)[0])
if def.Properties.Common().Maturity.Less(kindsys.MaturityStable) {
if def.Props().Common().Maturity.Less(kindsys.MaturityStable) {
vpath = "x"
}
+5 -4
View File
@@ -5,6 +5,7 @@ import (
"path/filepath"
"github.com/grafana/codejen"
"github.com/grafana/grafana/pkg/kindsys"
)
// LatestJenny returns a jenny that runs another jenny for only the latest
@@ -31,17 +32,17 @@ func (j *latestj) JennyName() string {
return "LatestJenny"
}
func (j *latestj) Generate(def *DefForGen) (*codejen.File, error) {
comm := def.Properties.Common()
func (j *latestj) Generate(kind kindsys.Kind) (*codejen.File, error) {
comm := kind.Props().Common()
sfg := SchemaForGen{
Name: comm.Name,
Schema: def.Lineage().Latest(),
Schema: kind.Lineage().Latest(),
IsGroup: comm.LineageIsGroup,
}
f, err := j.inner.Generate(sfg)
if err != nil {
return nil, fmt.Errorf("%s jenny failed on %s schema for %s: %w", j.inner.JennyName(), sfg.Schema.Version(), def.Properties.Common().Name, err)
return nil, fmt.Errorf("%s jenny failed on %s schema for %s: %w", j.inner.JennyName(), sfg.Schema.Version(), kind.Props().Common().Name, err)
}
if f == nil || !f.Exists() {
return nil, nil
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"time"
"github.com/grafana/codejen"
"github.com/grafana/grafana/pkg/kindsys"
)
// All the parsed templates in the tmpl subdirectory
@@ -40,7 +41,7 @@ type (
// Header tvars_autogen_header
PackageName string
KindPackagePrefix string
Kinds []*DefForGen
Kinds []kindsys.Kind
}
tvars_coremodel_imports struct {
PackageName string
+20 -56
View File
@@ -1,4 +1,4 @@
package {{ .Properties.MachineName }}
package {{ .Props.MachineName }}
import (
"github.com/grafana/grafana/pkg/kindsys"
@@ -10,95 +10,59 @@ import (
// directory containing the .cue files in which this kind is defined. Necessary
// for runtime errors related to the definition and/or lineage to provide
// a real path to the correct .cue file.
const rootrel string = "kinds/{{ .Properties.MachineName }}"
const rootrel string = "kinds/{{ .Props.MachineName }}"
// TODO standard generated docs
type Kind struct {
lin thema.ConvergentLineage[*{{ .Properties.Name }}]
kindsys.Core
lin thema.ConvergentLineage[*{{ .Props.Name }}]
jcodec vmux.Codec
valmux vmux.ValueMux[*{{ .Properties.Name }}]
def kindsys.Def[kindsys.CoreProperties]
valmux vmux.ValueMux[*{{ .Props.Name }}]
}
// type guard
// type guard - ensure generated Kind type satisfies the kindsys.Core interface
var _ kindsys.Core = &Kind{}
// TODO standard generated docs
func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) {
def, err := kindsys.LoadCoreKind(rootrel, rt.Context(), nil)
if err != nil {
return nil, err
}
k := &Kind{
def: def,
}
lin, err := def.Some().BindKindLineage(rt, opts...)
def, err := kindsys.LoadCoreKindDef(rootrel, rt.Context(), nil)
if err != nil {
return nil, err
}
k := &Kind{}
k.Core, err = kindsys.BindCore(rt, def, opts...)
if err != nil {
return nil, err
}
// Get the thema.Schema that the meta says is in the current version (which
// codegen ensures is always the latest)
cursch := thema.SchemaP(lin, k.def.Properties.CurrentVersion)
tsch, err := thema.BindType[*{{ .Properties.Name }}](cursch, &{{ .Properties.Name }}{})
cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion)
tsch, err := thema.BindType[*{{ .Props.Name }}](cursch, &{{ .Props.Name }}{})
if err != nil {
// Should be unreachable, modulo bugs in the Thema->Go code generator
return nil, err
}
k.jcodec = vmux.NewJSONCodec("{{ .Properties.MachineName }}.json")
k.jcodec = vmux.NewJSONCodec("{{ .Props.MachineName }}.json")
k.lin = tsch.ConvergentLineage()
k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec)
return k, nil
}
// TODO standard generated docs
func (k *Kind) Name() string {
return "{{ .Properties.MachineName }}"
}
// TODO standard generated docs
func (k *Kind) MachineName() string {
return "{{ .Properties.MachineName }}"
}
// TODO standard generated docs
func (k *Kind) Lineage() thema.Lineage {
return k.lin
}
// TODO standard generated docs
func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*{{ .Properties.Name }}] {
// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType])
// to the the {{ .Props.Name }} type generated from the current schema, v{{ .Props.CurrentVersion }}.
func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*{{ .Props.Name }}] {
return k.lin
}
// JSONValueMux is a version multiplexer that maps a []byte containing JSON data
// at any schematized dashboard version to an instance of {{ .Properties.Name }}.
// at any schematized dashboard version to an instance of {{ .Props.Name }}.
//
// Validation and translation errors emitted from this func will identify the
// input bytes as "dashboard.json".
//
// This is a thin wrapper around Thema's [vmux.ValueMux].
func (k *Kind) JSONValueMux(b []byte) (*{{ .Properties.Name }}, thema.TranslationLacunas, error) {
func (k *Kind) JSONValueMux(b []byte) (*{{ .Props.Name }}, thema.TranslationLacunas, error) {
return k.valmux(b)
}
// TODO standard generated docs
func (k *Kind) Maturity() kindsys.Maturity {
return k.def.Properties.Maturity
}
// Def returns the [kindsys.Def] containing both CUE and Go representations of the
// {{ .Properties.MachineName }} declaration in .cue files.
func (k *Kind) Def() kindsys.Def[kindsys.CoreProperties] {
return k.def
}
// Props returns a [kindsys.SomeKindProps], with underlying type [kindsys.CoreProperties],
// representing the static properties declared in the {{ .Properties.MachineName }} kind.
//
// This method is identical to calling Def().Props. It is provided to satisfy [kindsys.Interface].
func (k *Kind) Props() kindsys.SomeKindProperties {
return k.def.Properties
}
+19 -14
View File
@@ -5,38 +5,43 @@ import (
"sync"
{{range .Kinds }}
"{{ $.KindPackagePrefix }}/{{ .Properties.MachineName }}"{{end}}
"{{ $.KindPackagePrefix }}/{{ .Props.MachineName }}"{{end}}
"github.com/grafana/grafana/pkg/cuectx"
"github.com/grafana/grafana/pkg/kindsys"
"github.com/grafana/thema"
)
// Base is a registry of kindsys.Interface. It provides two modes for accessing
// kinds: individually via literal named methods, or as a slice returned from
// an All*() method.
// Base is a registry of all Grafana core kinds. It is designed for use both inside
// of Grafana itself, and for import by external Go programs wanting to work with Grafana's
// kind system.
//
// The registry provides two modes for accessing core kinds:
// * Per-kind methods, which return the kind-specific type, e.g. Dashboard() returns [dashboard.Dashboard].
// * All(), which returns a slice of [kindsys.Core].
//
// Prefer the individual named methods for use cases where the particular kind(s) that
// are needed are known to the caller. For example, a dashboard linter can know that it
// specifically wants the dashboard kind.
//
// Prefer All*() methods when performing operations generically across all kinds.
// For example, a validation HTTP middleware for any kind-schematized object type.
// Prefer All() when performing operations generically across all kinds. For example,
// a generic HTTP middleware for validating request bodies expected to contain some
// kind-schematized type.
type Base struct {
all []kindsys.Core
{{- range .Kinds }}
{{ .Properties.MachineName }} *{{ .Properties.MachineName }}.Kind{{end}}
{{ .Props.MachineName }} *{{ .Props.MachineName }}.Kind{{end}}
}
// type guards
var (
{{- range .Kinds }}
_ kindsys.Core = &{{ .Properties.MachineName }}.Kind{}{{end}}
_ kindsys.Core = &{{ .Props.MachineName }}.Kind{}{{end}}
)
{{range .Kinds }}
// {{ .Properties.Name }} returns the [kindsys.Interface] implementation for the {{ .Properties.MachineName }} kind.
func (b *Base) {{ .Properties.Name }}() *{{ .Properties.MachineName }}.Kind {
return b.{{ .Properties.MachineName }}
// {{ .Props.Name }} returns the [kindsys.Interface] implementation for the {{ .Props.MachineName }} kind.
func (b *Base) {{ .Props.Name }}() *{{ .Props.MachineName }}.Kind {
return b.{{ .Props.MachineName }}
}
{{end}}
@@ -45,11 +50,11 @@ func doNewBase(rt *thema.Runtime) *Base {
reg := &Base{}
{{range .Kinds }}
reg.{{ .Properties.MachineName }}, err = {{ .Properties.MachineName }}.NewKind(rt)
reg.{{ .Props.MachineName }}, err = {{ .Props.MachineName }}.NewKind(rt)
if err != nil {
panic(fmt.Sprintf("error while initializing the {{ .Properties.MachineName }} Kind: %s", err))
panic(fmt.Sprintf("error while initializing the {{ .Props.MachineName }} Kind: %s", err))
}
reg.all = append(reg.all, reg.{{ .Properties.MachineName }})
reg.all = append(reg.all, reg.{{ .Props.MachineName }})
{{end}}
return reg