Generating Schemas
Introduction
To facilitate the creation of tooling that generates ent.Schema
s programmatically, ent
supports the manipulation of the schema/
directory using the entgo.io/contrib/schemast
package.
API
Loading
In order to manipulate an existing schema directory we must first load it into a schemast.Context
object:
package main
import (
"fmt"
"log"
"entgo.io/contrib/schemast"
)
func main() {
ctx, err := schemast.Load("./ent/schema")
if err != nil {
log.Fatalf("failed: %v", err)
}
if ctx.HasType("user") {
fmt.Println("schema directory contains a schema named User!")
}
}
Printing
To print back out our context to a target directory, use schemast.Print
:
package main
import (
"log"
"entgo.io/contrib/schemast"
)
func main() {
ctx, err := schemast.Load("./ent/schema")
if err != nil {
log.Fatalf("failed: %v", err)
}
// A no-op since we did not manipulate the Context at all.
if err := schemast.Print("./ent/schema"); err != nil {
log.Fatalf("failed: %v", err)
}
}
Mutators
To mutate the ent/schema
directory, we can use schemast.Mutate
, which takes a list of schemast.Mutator
s to apply to the context:
package schemast
// Mutator changes a Context.
type Mutator interface {
Mutate(ctx *Context) error
}
Currently, only a single type of schemast.Mutator
is implemented, UpsertSchema
:
package schemast
// UpsertSchema implements Mutator. UpsertSchema will add to the Context the type named
// Name if not present and rewrite the type's Fields, Edges, Indexes and Annotations methods.
type UpsertSchema struct {
Name string
Fields []ent.Field
Edges []ent.Edge
Indexes []ent.Index
Annotations []schema.Annotation
}
To use it:
package main
import (
"log"
"entgo.io/contrib/schemast"
"entgo.io/ent"
"entgo.io/ent/schema/field"
)
func main() {
ctx, err := schemast.Load("./ent/schema")
if err != nil {
log.Fatalf("failed: %v", err)
}
mutations := []schemast.Mutator{
&schemast.UpsertSchema{
Name: "User",
Fields: []ent.Field{
field.String("name"),
},
},
&schemast.UpsertSchema{
Name: "Team",
Fields: []ent.Field{
field.String("name"),
},
},
}
err = schemast.Mutate(ctx, mutations...)
if err := ctx.Print("./ent/schema"); err != nil {
log.Fatalf("failed: %v", err)
}
}
After running this program, observe two new files exist in the schema directory: user.go
and team.go
:
// user.go
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
)
type User struct {
ent.Schema
}
func (User) Fields() []ent.Field {
return []ent.Field{field.String("name")}
}
func (User) Edges() []ent.Edge {
return nil
}
func (User) Annotations() []schema.Annotation {
return nil
}
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
)
type Team struct {
ent.Schema
}
func (Team) Fields() []ent.Field {
return []ent.Field{field.String("name")}
}
func (Team) Edges() []ent.Edge {
return nil
}
func (Team) Annotations() []schema.Annotation {
return nil
}
Working with Edges
Edges are defined in ent
this way:
edge.To("edge_name", OtherSchema.Type)
This syntax relies on the fact that the OtherSchema
struct already exists when we define the edge so we can refer to its Type
method. When we are generating schemas programmatically, obviously we need somehow to describe the edge to the code-generator before the type definitions exist. To do this you can do something like:
type placeholder struct {
ent.Schema
}
func withType(e ent.Edge, typeName string) ent.Edge {
e.Descriptor().Type = typeName
return e
}
func newEdgeTo(edgeName, otherType string) ent.Edge {
// we pass a placeholder type to the edge constructor:
e := edge.To(edgeName, placeholder.Type)
// then we override the other type's name directly on the edge descriptor:
return withType(e, otherType)
}
Examples
The protoc-gen-ent
(doc) is a protoc plugin that programmatically generates ent.Schema
s from .proto files, it uses the schemast
to manipulate the target schema
directory. To see how, read the source code.
Caveats
schemast
is still experimental, APIs are subject to change in the future. In addition, a small portion of the ent.Field
definition API is unsupported at this point in time, to see a full list of unsupported features see the source code.