diff --git a/entc/entc.go b/entc/entc.go index c590a9447..4161ab8d1 100644 --- a/entc/entc.go +++ b/entc/entc.go @@ -125,8 +125,15 @@ func FeatureNames(names ...string) Option { } } +// Annotation is used to attach arbitrary metadata to the schema objects in codegen. +// Unlike schema annotations, being serializable to JSON raw value is not mandatory. +// +// Template extensions can retrieve this metadata and use it inside their execution. +// Read more about it in ent website: https://entgo.io/docs/templates/#annotations. +type Annotation = schema.Annotation + // Annotations appends the given annotations to the codegen config. -func Annotations(annotations ...schema.Annotation) Option { +func Annotations(annotations ...Annotation) Option { return func(cfg *gen.Config) error { if cfg.Annotations == nil { cfg.Annotations = gen.Annotations{} @@ -166,6 +173,94 @@ func TemplateDir(path string) Option { }) } +type ( + // Extension describes an Ent code generation extension that + // allows customizing the code generation and integrate with + // other tools and libraries (e.g. GraphQL, gRPC, OpenAPI) by + // by registering hooks, templates and global annotations in + // one simple call. + // + // ex, err := entgql.NewExtension( + // entgql.WithConfig("../gqlgen.yml"), + // entgql.WithSchema("../schema.graphql"), + // ) + // if err != nil { + // log.Fatalf("creating graphql extension: %v", err) + // } + // err = entc.Generate("./schema", &gen.Config{ + // Templates: entswag.Templates, + // }, entc.Extensions(ex)) + // if err != nil { + // log.Fatalf("running ent codegen: %v", err) + // } + // + Extension interface { + // Hooks holds an optional list of Hooks to apply + // on the graph before/after the code-generation. + Hooks() []gen.Hook + + // Annotations injects global annotations to the gen.Config object that + // can be accessed globally in all templates. Unlike schema annotations, + // being serializable to JSON raw value is not mandatory. + // + // {{- with $.Config.Annotations.GQL }} + // {{/* Annotation usage goes here. */}} + // {{- end }} + // + Annotations() []Annotation + + // Templates specifies a list of alternative templates + // to execute or to override the default. + Templates() []*gen.Template + + // Options specifies a list of entc.Options to evaluate on + // the gen.Config before executing the code generation. + Options() []Option + } +) + +// Extensions evaluates the list of Extensions on the gen.Config. +func Extensions(extensions ...Extension) Option { + return func(cfg *gen.Config) error { + for _, ex := range extensions { + cfg.Hooks = append(cfg.Hooks, ex.Hooks()...) + cfg.Templates = append(cfg.Templates, ex.Templates()...) + for _, opt := range ex.Options() { + if err := opt(cfg); err != nil { + return err + } + } + if err := Annotations(ex.Annotations()...)(cfg); err != nil { + return err + } + } + return nil + } +} + +// DefaultExtension is the default implementation for entc.Extension. +// +// Embedding this type allow third-party packages to create extensions +// without implementing all methods. +// +// type Extension struct { +// entc.DefaultExtension +// } +// +type DefaultExtension struct{} + +// Hooks of the extensions. +func (DefaultExtension) Hooks() []gen.Hook { return nil } + +// Annotations of the extensions. +func (DefaultExtension) Annotations() gen.Annotations { return nil } + +// Templates of the extensions. +func (DefaultExtension) Templates() []*gen.Template { return nil } + +// Options of the extensions. +func (DefaultExtension) Options() []Option { return nil } + // templateOption ensures the template instantiate // once for config and execute the given Option. func templateOption(next func(t *gen.Template) (*gen.Template, error)) Option { diff --git a/examples/entcpkg/ent/entc.go b/examples/entcpkg/ent/entc.go index c909fe9b8..f5871e549 100644 --- a/examples/entcpkg/ent/entc.go +++ b/examples/entcpkg/ent/entc.go @@ -18,14 +18,15 @@ import ( ) func main() { - // A usage for custom templates with external functions. - // One template is defined in the option below, and the - // rest are provided with the `Templates` option. - opts := []entc.Option{ - entc.TemplateFiles("template/stringer.tmpl"), - entc.Annotations(Annotation{StructTag: "rql"}), + ex, err := NewExtension("json") + if err != nil { + log.Fatalf("creating extension: %v", err) } - err := entc.Generate("./schema", &gen.Config{ + // A usage for custom options to configure the + opts := []entc.Option{ + entc.Extensions(ex), + } + err = entc.Generate("./schema", &gen.Config{ Header: ` // Copyright 2019-present Facebook Inc. All rights reserved. // This source code is licensed under the Apache 2.0 license found @@ -34,20 +35,83 @@ func main() { // Code generated by entc, DO NOT EDIT. `, Templates: []*gen.Template{ - gen.MustParse(gen.NewTemplate("static"). - Funcs(template.FuncMap{"title": strings.ToTitle}). - ParseFiles("template/static.tmpl")), - gen.MustParse(gen.NewTemplate("debug"). - Funcs(template.FuncMap{"byName": byName}). - ParseFiles("template/debug.tmpl")), + // Custom templates can be provided by entc.Extension (see below), + // or by setting templates on the gen.Config object. + // + // gen.MustParse(gen.NewTemplate("static"). + // Funcs(template.FuncMap{"title": strings.ToTitle}). + // ParseFiles("template/static.tmpl")), + // + }, + Hooks: []gen.Hook{ + // Hooks can be provided by entc.Extension (see below), + // or by setting hooks on the gen.Config object. + // + // CustomHook1("config 1"), + // CustomHook2("config 2"), + // }, - Hooks: []gen.Hook{TagFields("json")}, }, opts...) if err != nil { log.Fatalf("running ent codegen: %v", err) } } +// Extension is an example implementation of entc.Extension. +type Extension struct { + entc.DefaultExtension + tag string + templates []*gen.Template +} + +// NewExtension creates a new entc.Extension. +func NewExtension(tag string) (*Extension, error) { + ex := &Extension{tag: tag} + t, err := gen.NewTemplate("static"). + Funcs(template.FuncMap{"title": strings.ToTitle}). + ParseFiles("template/static.tmpl") + if err != nil { + return nil, err + } + ex.templates = append(ex.templates, t) + t, err = gen.NewTemplate("debug"). + Funcs(template.FuncMap{"byName": byName}). + ParseFiles("template/debug.tmpl") + if err != nil { + return nil, err + } + ex.templates = append(ex.templates, t) + return ex, nil +} + +// Templates of the extension. +func (e *Extension) Templates() []*gen.Template { + return e.templates +} + +// Hooks of the extension. +func (e *Extension) Hooks() []gen.Hook { + return []gen.Hook{ + TagFields(e.tag), + } +} + +// Annotations of the extension. +func (e *Extension) Annotations() []entc.Annotation { + return []entc.Annotation{ + Annotation{StructTag: "rql"}, + } +} + +// Options provides additional options for the extension. +func (e *Extension) Options() []entc.Option { + return []entc.Option{ + entc.TemplateFiles("template/stringer.tmpl"), + } +} + +var _ entc.Extension = (*Extension)(nil) + // byName returns a node in the graph by its label/name. func byName(g *gen.Graph, name string) (*gen.Type, error) { for _, n := range g.Nodes {