Generating Schemas

Introduction

To facilitate the creation of tooling that generates ent.Schemas 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:

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "entgo.io/contrib/schemast"
  6. )
  7. func main() {
  8. ctx, err := schemast.Load("./ent/schema")
  9. if err != nil {
  10. log.Fatalf("failed: %v", err)
  11. }
  12. if ctx.HasType("user") {
  13. fmt.Println("schema directory contains a schema named User!")
  14. }
  15. }

Printing

To print back out our context to a target directory, use schemast.Print:

  1. package main
  2. import (
  3. "log"
  4. "entgo.io/contrib/schemast"
  5. )
  6. func main() {
  7. ctx, err := schemast.Load("./ent/schema")
  8. if err != nil {
  9. log.Fatalf("failed: %v", err)
  10. }
  11. // A no-op since we did not manipulate the Context at all.
  12. if err := schemast.Print("./ent/schema"); err != nil {
  13. log.Fatalf("failed: %v", err)
  14. }
  15. }

Mutators

To mutate the ent/schema directory, we can use schemast.Mutate, which takes a list of schemast.Mutators to apply to the context:

  1. package schemast
  2. // Mutator changes a Context.
  3. type Mutator interface {
  4. Mutate(ctx *Context) error
  5. }

Currently, only a single type of schemast.Mutator is implemented, UpsertSchema:

  1. package schemast
  2. // UpsertSchema implements Mutator. UpsertSchema will add to the Context the type named
  3. // Name if not present and rewrite the type's Fields, Edges, Indexes and Annotations methods.
  4. type UpsertSchema struct {
  5. Name string
  6. Fields []ent.Field
  7. Edges []ent.Edge
  8. Indexes []ent.Index
  9. Annotations []schema.Annotation
  10. }

To use it:

  1. package main
  2. import (
  3. "log"
  4. "entgo.io/contrib/schemast"
  5. "entgo.io/ent"
  6. "entgo.io/ent/schema/field"
  7. )
  8. func main() {
  9. ctx, err := schemast.Load("./ent/schema")
  10. if err != nil {
  11. log.Fatalf("failed: %v", err)
  12. }
  13. mutations := []schemast.Mutator{
  14. &schemast.UpsertSchema{
  15. Name: "User",
  16. Fields: []ent.Field{
  17. field.String("name"),
  18. },
  19. },
  20. &schemast.UpsertSchema{
  21. Name: "Team",
  22. Fields: []ent.Field{
  23. field.String("name"),
  24. },
  25. },
  26. }
  27. err = schemast.Mutate(ctx, mutations...)
  28. if err := ctx.Print("./ent/schema"); err != nil {
  29. log.Fatalf("failed: %v", err)
  30. }
  31. }

After running this program, observe two new files exist in the schema directory: user.go and team.go:

  1. // user.go
  2. package schema
  3. import (
  4. "entgo.io/ent"
  5. "entgo.io/ent/schema"
  6. "entgo.io/ent/schema/field"
  7. )
  8. type User struct {
  9. ent.Schema
  10. }
  11. func (User) Fields() []ent.Field {
  12. return []ent.Field{field.String("name")}
  13. }
  14. func (User) Edges() []ent.Edge {
  15. return nil
  16. }
  17. func (User) Annotations() []schema.Annotation {
  18. return nil
  19. }
  1. package schema
  2. import (
  3. "entgo.io/ent"
  4. "entgo.io/ent/schema"
  5. "entgo.io/ent/schema/field"
  6. )
  7. type Team struct {
  8. ent.Schema
  9. }
  10. func (Team) Fields() []ent.Field {
  11. return []ent.Field{field.String("name")}
  12. }
  13. func (Team) Edges() []ent.Edge {
  14. return nil
  15. }
  16. func (Team) Annotations() []schema.Annotation {
  17. return nil
  18. }

Working with Edges

Edges are defined in ent this way:

  1. 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:

  1. type placeholder struct {
  2. ent.Schema
  3. }
  4. func withType(e ent.Edge, typeName string) ent.Edge {
  5. e.Descriptor().Type = typeName
  6. return e
  7. }
  8. func newEdgeTo(edgeName, otherType string) ent.Edge {
  9. // we pass a placeholder type to the edge constructor:
  10. e := edge.To(edgeName, placeholder.Type)
  11. // then we override the other type's name directly on the edge descriptor:
  12. return withType(e, otherType)
  13. }

Examples

The protoc-gen-ent (doc) is a protoc plugin that programmatically generates ent.Schemas 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.