snapshot of https://github.com/ent/ent.git (master, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
357
entc/load/load.go
Normal file
357
entc/load/load.go
Normal file
@@ -0,0 +1,357 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
// Package load is the interface for loading an ent/schema package into a Go program.
|
||||
package load
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"maps"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
type (
|
||||
// A SchemaSpec holds a serializable version of an ent.Schema
|
||||
// and its Go package and module information.
|
||||
SchemaSpec struct {
|
||||
// Schemas defines the loaded schema descriptors.
|
||||
Schemas []*Schema
|
||||
|
||||
// PkgPath is the package path of the loaded
|
||||
// ent.Schema package.
|
||||
PkgPath string
|
||||
|
||||
// Module defines the module information for
|
||||
// the user schema package if exists.
|
||||
Module *packages.Module
|
||||
}
|
||||
|
||||
// Config holds the configuration for loading an ent/schema package.
|
||||
Config struct {
|
||||
// Path is the path for the schema package.
|
||||
Path string
|
||||
// Names are the schema names to load. Empty means all schemas in the directory.
|
||||
Names []string
|
||||
// BuildFlags are forwarded to the package.Config when
|
||||
// loading the schema package.
|
||||
BuildFlags []string
|
||||
}
|
||||
)
|
||||
|
||||
// Load loads the schemas package and build the Go plugin with this info.
|
||||
func (c *Config) Load() (*SchemaSpec, error) {
|
||||
spec, pos, err := c.load()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("entc/load: parse schema dir: %w", err)
|
||||
}
|
||||
if len(c.Names) == 0 {
|
||||
return nil, fmt.Errorf("entc/load: no schema found in: %s", c.Path)
|
||||
}
|
||||
var b bytes.Buffer
|
||||
err = buildTmpl.ExecuteTemplate(&b, "main", struct {
|
||||
*Config
|
||||
Package string
|
||||
}{
|
||||
Config: c,
|
||||
Package: spec.PkgPath,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("entc/load: execute template: %w", err)
|
||||
}
|
||||
buf, err := format.Source(b.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("entc/load: format template: %w", err)
|
||||
}
|
||||
if err := os.MkdirAll(".entc", os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target := fmt.Sprintf(".entc/%s.go", filename(spec.PkgPath))
|
||||
if err := os.WriteFile(target, buf, 0644); err != nil {
|
||||
return nil, fmt.Errorf("entc/load: write file %s: %w", target, err)
|
||||
}
|
||||
defer os.RemoveAll(".entc")
|
||||
out, err := gorun(target, c.BuildFlags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
schema, err := UnmarshalSchema([]byte(line))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("entc/load: unmarshal schema %s: %w", line, err)
|
||||
}
|
||||
spec.Schemas = append(spec.Schemas, schema)
|
||||
}
|
||||
for _, s := range spec.Schemas {
|
||||
s.Pos = pos[s.Name]
|
||||
}
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
// entInterface holds the reflect.Type of ent.Interface.
|
||||
var entInterface = reflect.TypeOf(struct{ ent.Interface }{}).Field(0).Type
|
||||
|
||||
// load the ent/schema info.
|
||||
func (c *Config) load() (*SchemaSpec, map[string]string, error) {
|
||||
pkgs, err := packages.Load(&packages.Config{
|
||||
BuildFlags: c.BuildFlags,
|
||||
Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedModule,
|
||||
}, c.Path, entInterface.PkgPath())
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("loading package: %w", err)
|
||||
}
|
||||
if len(pkgs) < 2 {
|
||||
// Check if the package loading failed due to Go-related
|
||||
// errors, such as 'missing go.sum entry'.
|
||||
if err := golist(c.Path, c.BuildFlags); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return nil, nil, fmt.Errorf("missing package information for: %s", c.Path)
|
||||
}
|
||||
entPkg, pkg := pkgs[0], pkgs[1]
|
||||
if len(pkg.Errors) != 0 {
|
||||
return nil, nil, c.loadError(pkg.Errors[0])
|
||||
}
|
||||
if len(entPkg.Errors) != 0 {
|
||||
return nil, nil, entPkg.Errors[0]
|
||||
}
|
||||
if pkgs[0].PkgPath != entInterface.PkgPath() {
|
||||
entPkg, pkg = pkgs[1], pkgs[0]
|
||||
}
|
||||
names := make(map[string]string)
|
||||
iface := entPkg.Types.Scope().Lookup(entInterface.Name()).Type().Underlying().(*types.Interface)
|
||||
for k, v := range pkg.TypesInfo.Defs {
|
||||
typ, ok := v.(*types.TypeName)
|
||||
if !ok || !k.IsExported() || !types.Implements(typ.Type(), iface) {
|
||||
continue
|
||||
}
|
||||
spec, ok := k.Obj.Decl.(*ast.TypeSpec)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("invalid declaration %T for %s", k.Obj.Decl, k.Name)
|
||||
}
|
||||
if _, ok := spec.Type.(*ast.StructType); !ok {
|
||||
return nil, nil, fmt.Errorf("invalid spec type %T for %s", spec.Type, k.Name)
|
||||
}
|
||||
p := pkg.Fset.Position(spec.Pos())
|
||||
names[k.Name] = fmt.Sprintf("%s:%d", p.Filename, p.Line)
|
||||
}
|
||||
if len(c.Names) == 0 {
|
||||
c.Names = slices.Sorted(maps.Keys(names))
|
||||
} else {
|
||||
sort.Strings(c.Names)
|
||||
}
|
||||
return &SchemaSpec{PkgPath: pkg.PkgPath, Module: pkg.Module}, names, nil
|
||||
}
|
||||
|
||||
func (c *Config) loadError(perr packages.Error) (err error) {
|
||||
if strings.Contains(perr.Msg, "import cycle not allowed") {
|
||||
if cause := c.cycleCause(); cause != "" {
|
||||
perr.Msg += "\n" + cause
|
||||
}
|
||||
}
|
||||
err = perr
|
||||
if perr.Pos == "" {
|
||||
// Strip "-:" prefix in case of empty position.
|
||||
err = errors.New(perr.Msg)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Config) cycleCause() (cause string) {
|
||||
dir, err := parser.ParseDir(token.NewFileSet(), c.Path, nil, 0)
|
||||
// Ignore reporting in case of parsing
|
||||
// error, or there no packages to parse.
|
||||
if err != nil || len(dir) == 0 {
|
||||
return
|
||||
}
|
||||
// Find the package that contains the schema, or
|
||||
// extract the first package if there is only one.
|
||||
pkg := dir[filepath.Base(c.Path)]
|
||||
if pkg == nil {
|
||||
for _, v := range dir {
|
||||
pkg = v
|
||||
break
|
||||
}
|
||||
}
|
||||
// Package local declarations used by schema fields.
|
||||
locals := make(map[string]bool)
|
||||
for _, f := range pkg.Files {
|
||||
for _, d := range f.Decls {
|
||||
g, ok := d.(*ast.GenDecl)
|
||||
if !ok || g.Tok != token.TYPE {
|
||||
continue
|
||||
}
|
||||
for _, s := range g.Specs {
|
||||
ts, ok := s.(*ast.TypeSpec)
|
||||
if !ok || !ts.Name.IsExported() {
|
||||
continue
|
||||
}
|
||||
// Non-struct types such as "type Role int".
|
||||
st, ok := ts.Type.(*ast.StructType)
|
||||
if !ok {
|
||||
locals[ts.Name.Name] = true
|
||||
continue
|
||||
}
|
||||
var embedSchema bool
|
||||
astutil.Apply(st.Fields, func(c *astutil.Cursor) bool {
|
||||
f, ok := c.Node().(*ast.Field)
|
||||
if ok {
|
||||
switch x := f.Type.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
if x.Sel.Name == "Schema" || x.Sel.Name == "Mixin" {
|
||||
embedSchema = true
|
||||
}
|
||||
case *ast.Ident:
|
||||
// A common pattern is to create local base schema to be embedded by other schemas.
|
||||
if name := strings.ToLower(x.Name); name == "schema" || name == "mixin" {
|
||||
embedSchema = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// Stop traversing the AST in case an ~ent.Schema is embedded.
|
||||
return !embedSchema
|
||||
}, nil)
|
||||
if !embedSchema {
|
||||
locals[ts.Name.Name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// No local declarations to report.
|
||||
if len(locals) == 0 {
|
||||
return
|
||||
}
|
||||
// Usage of local declarations by schema fields.
|
||||
goTypes := make(map[string]bool)
|
||||
for _, f := range pkg.Files {
|
||||
for _, d := range f.Decls {
|
||||
f, ok := d.(*ast.FuncDecl)
|
||||
if !ok || f.Name.Name != "Fields" || f.Type.Params.NumFields() != 0 || f.Type.Results.NumFields() != 1 {
|
||||
continue
|
||||
}
|
||||
astutil.Apply(f.Body, func(cursor *astutil.Cursor) bool {
|
||||
i, ok := cursor.Node().(*ast.Ident)
|
||||
if ok && locals[i.Name] {
|
||||
goTypes[i.Name] = true
|
||||
}
|
||||
return true
|
||||
}, nil)
|
||||
}
|
||||
}
|
||||
names := make([]string, 0, len(goTypes))
|
||||
for k := range goTypes {
|
||||
names = append(names, strconv.Quote(k))
|
||||
}
|
||||
sort.Strings(names)
|
||||
if len(names) > 0 {
|
||||
cause = fmt.Sprintf("To resolve this issue, move the custom types used by the generated code to a separate package: %s", strings.Join(names, ", "))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
//go:embed template/main.tmpl schema.go
|
||||
files embed.FS
|
||||
buildTmpl = templates()
|
||||
)
|
||||
|
||||
func templates() *template.Template {
|
||||
tmpls, err := schemaTemplates()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tmpl := template.Must(template.New("templates").
|
||||
ParseFS(files, "template/main.tmpl"))
|
||||
for _, t := range tmpls {
|
||||
tmpl = template.Must(tmpl.Parse(t))
|
||||
}
|
||||
return tmpl
|
||||
}
|
||||
|
||||
// schemaTemplates turns the schema.go file and its import block into templates.
|
||||
func schemaTemplates() ([]string, error) {
|
||||
var (
|
||||
imports []string
|
||||
code bytes.Buffer
|
||||
fset = token.NewFileSet()
|
||||
src, _ = files.ReadFile("schema.go")
|
||||
)
|
||||
f, err := parser.ParseFile(fset, "schema.go", src, parser.AllErrors)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse schema file: %w", err)
|
||||
}
|
||||
for _, decl := range f.Decls {
|
||||
if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
|
||||
for _, spec := range decl.Specs {
|
||||
imports = append(imports, spec.(*ast.ImportSpec).Path.Value)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := format.Node(&code, fset, decl); err != nil {
|
||||
return nil, fmt.Errorf("format node: %w", err)
|
||||
}
|
||||
code.WriteByte('\n')
|
||||
}
|
||||
return []string{
|
||||
fmt.Sprintf(`{{ define "schema" }} %s {{ end }}`, code.String()),
|
||||
fmt.Sprintf(`{{ define "imports" }} %s {{ end }}`, strings.Join(imports, "\n")),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func filename(pkg string) string {
|
||||
name := strings.ReplaceAll(pkg, "/", "_")
|
||||
return fmt.Sprintf("entc_%s_%d", name, time.Now().Unix())
|
||||
}
|
||||
|
||||
// run 'go run' command and return its output.
|
||||
func gorun(target string, buildFlags []string) (string, error) {
|
||||
s, err := gocmd("run", target, buildFlags)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("entc/load: %s", err)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// golist checks if 'go list' can be executed on the given target.
|
||||
func golist(target string, buildFlags []string) error {
|
||||
_, err := gocmd("list", target, buildFlags)
|
||||
return err
|
||||
}
|
||||
|
||||
// goCmd runs a go command and returns its output.
|
||||
func gocmd(command, target string, buildFlags []string) (string, error) {
|
||||
args := []string{command}
|
||||
args = append(args, buildFlags...)
|
||||
args = append(args, target)
|
||||
cmd := exec.Command("go", args...)
|
||||
stderr := bytes.NewBuffer(nil)
|
||||
stdout := bytes.NewBuffer(nil)
|
||||
cmd.Stderr = stderr
|
||||
cmd.Stdout = stdout
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", errors.New(strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
return stdout.String(), nil
|
||||
}
|
||||
99
entc/load/load_test.go
Normal file
99
entc/load/load_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package load
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"entgo.io/ent/schema/field"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
cfg := &Config{Path: "./testdata/valid"}
|
||||
spec, err := cfg.Load()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, spec.Schemas, 3)
|
||||
require.Equal(t, "entgo.io/ent/entc/load/testdata/valid", spec.PkgPath)
|
||||
|
||||
require.Equal(t, "Group", spec.Schemas[0].Name, "ordered alphabetically")
|
||||
require.Equal(t, "Tag", spec.Schemas[1].Name)
|
||||
require.Equal(t, "User", spec.Schemas[2].Name)
|
||||
}
|
||||
|
||||
func TestLoadWrongPath(t *testing.T) {
|
||||
cfg := &Config{Path: "./boring"}
|
||||
plg, err := cfg.Load()
|
||||
require.Error(t, err)
|
||||
require.Nil(t, plg)
|
||||
}
|
||||
|
||||
func TestLoadSpecific(t *testing.T) {
|
||||
cfg := &Config{Path: "./testdata/valid", Names: []string{"User"}}
|
||||
spec, err := cfg.Load()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, spec.Schemas, 1)
|
||||
require.Equal(t, "User", spec.Schemas[0].Name)
|
||||
require.Equal(t, "entgo.io/ent/entc/load/testdata/valid", spec.PkgPath)
|
||||
}
|
||||
|
||||
func TestLoadNoSchema(t *testing.T) {
|
||||
cfg := &Config{Path: "./testdata/invalid"}
|
||||
schemas, err := cfg.Load()
|
||||
require.Error(t, err)
|
||||
require.Empty(t, schemas)
|
||||
}
|
||||
|
||||
func TestLoadSchemaFailure(t *testing.T) {
|
||||
cfg := &Config{Path: "./testdata/failure"}
|
||||
spec, err := cfg.Load()
|
||||
require.Error(t, err)
|
||||
require.Nil(t, spec)
|
||||
}
|
||||
|
||||
func TestLoadBaseSchema(t *testing.T) {
|
||||
cfg := &Config{Path: "./testdata/base"}
|
||||
spec, err := cfg.Load()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, spec.Schemas, 1)
|
||||
require.Len(t, spec.Schemas[0].Fields, 2, "embedded base schema")
|
||||
f1 := spec.Schemas[0].Fields[0]
|
||||
require.Equal(t, "base_field", f1.Name)
|
||||
require.Equal(t, field.TypeInt, f1.Info.Type)
|
||||
f2 := spec.Schemas[0].Fields[1]
|
||||
require.Equal(t, "user_field", f2.Name)
|
||||
require.Equal(t, field.TypeString, f2.Info.Type)
|
||||
}
|
||||
|
||||
func TestLoadTags(t *testing.T) {
|
||||
all, err := (&Config{
|
||||
Path: "./testdata/buildflags",
|
||||
}).Load()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, all.Schemas, 2)
|
||||
require.Equal(t, "Group", all.Schemas[0].Name, "ordered alphabetically")
|
||||
require.Equal(t, "User", all.Schemas[1].Name)
|
||||
|
||||
notags, err := (&Config{
|
||||
Path: "./testdata/buildflags",
|
||||
BuildFlags: []string{"-tags", "hidegroups"},
|
||||
}).Load()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, notags.Schemas, 1)
|
||||
require.Equal(t, "User", notags.Schemas[0].Name)
|
||||
|
||||
require.Equal(t, all.Schemas[1], notags.Schemas[0])
|
||||
}
|
||||
|
||||
func TestLoadCycleError(t *testing.T) {
|
||||
cfg := &Config{Path: "./testdata/cycle"}
|
||||
spec, err := cfg.Load()
|
||||
require.Nil(t, spec)
|
||||
require.EqualError(t, err, `entc/load: parse schema dir: import cycle not allowed: import stack: [entgo.io/ent/entc/load/testdata/cycle entgo.io/ent/entc/load/testdata/cycle/fakent entgo.io/ent/entc/load/testdata/cycle]
|
||||
To resolve this issue, move the custom types used by the generated code to a separate package: "Enum", "Used"`)
|
||||
}
|
||||
521
entc/load/schema.go
Normal file
521
entc/load/schema.go
Normal file
@@ -0,0 +1,521 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package load
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/schema"
|
||||
"entgo.io/ent/schema/edge"
|
||||
"entgo.io/ent/schema/field"
|
||||
"entgo.io/ent/schema/index"
|
||||
)
|
||||
|
||||
// Schema represents an ent.Schema that was loaded from a complied user package.
|
||||
type Schema struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Pos string `json:"-"`
|
||||
View bool `json:"view,omitempty"`
|
||||
Config ent.Config `json:"config,omitempty"`
|
||||
Edges []*Edge `json:"edges,omitempty"`
|
||||
Fields []*Field `json:"fields,omitempty"`
|
||||
Indexes []*Index `json:"indexes,omitempty"`
|
||||
Hooks []*Position `json:"hooks,omitempty"`
|
||||
Interceptors []*Position `json:"interceptors,omitempty"`
|
||||
Policy []*Position `json:"policy,omitempty"`
|
||||
Annotations map[string]any `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// Position describes a position in the schema.
|
||||
type Position struct {
|
||||
Index int // Index in the field/hook list.
|
||||
MixedIn bool // Indicates if the schema object was mixed-in.
|
||||
MixinIndex int // Mixin index in the mixin list.
|
||||
}
|
||||
|
||||
// Field represents an ent.Field that was loaded from a complied user package.
|
||||
type Field struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Info *field.TypeInfo `json:"type,omitempty"`
|
||||
ValueScanner bool `json:"value_scanner,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Size *int64 `json:"size,omitempty"`
|
||||
Enums []struct{ N, V string } `json:"enums,omitempty"`
|
||||
Unique bool `json:"unique,omitempty"`
|
||||
Nillable bool `json:"nillable,omitempty"`
|
||||
Optional bool `json:"optional,omitempty"`
|
||||
Default bool `json:"default,omitempty"`
|
||||
DefaultValue any `json:"default_value,omitempty"`
|
||||
DefaultKind reflect.Kind `json:"default_kind,omitempty"`
|
||||
UpdateDefault bool `json:"update_default,omitempty"`
|
||||
Immutable bool `json:"immutable,omitempty"`
|
||||
Validators int `json:"validators,omitempty"`
|
||||
StorageKey string `json:"storage_key,omitempty"`
|
||||
Position *Position `json:"position,omitempty"`
|
||||
Sensitive bool `json:"sensitive,omitempty"`
|
||||
SchemaType map[string]string `json:"schema_type,omitempty"`
|
||||
Annotations map[string]any `json:"annotations,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty"`
|
||||
DeprecatedReason string `json:"deprecated_reason,omitempty"`
|
||||
BlobKey bool `json:"blob_key,omitempty"`
|
||||
BlobDualWrite bool `json:"blob_dual_write,omitempty"`
|
||||
BlobDWSchemaType map[string]string `json:"blob_dw_schema_type,omitempty"`
|
||||
BlobLazy bool `json:"blob_lazy,omitempty"`
|
||||
BlobCheckRefs bool `json:"blob_check_refs,omitempty"`
|
||||
}
|
||||
|
||||
// Edge represents an ent.Edge that was loaded from a complied user package.
|
||||
type Edge struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Field string `json:"field,omitempty"`
|
||||
RefName string `json:"ref_name,omitempty"`
|
||||
Ref *Edge `json:"ref,omitempty"`
|
||||
Through *struct{ N, T string } `json:"through,omitempty"`
|
||||
Unique bool `json:"unique,omitempty"`
|
||||
Inverse bool `json:"inverse,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
Immutable bool `json:"immutable,omitempty"`
|
||||
StorageKey *edge.StorageKey `json:"storage_key,omitempty"`
|
||||
Annotations map[string]any `json:"annotations,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
// Index represents an ent.Index that was loaded from a complied user package.
|
||||
type Index struct {
|
||||
Unique bool `json:"unique,omitempty"`
|
||||
Edges []string `json:"edges,omitempty"`
|
||||
Fields []string `json:"fields,omitempty"`
|
||||
StorageKey string `json:"storage_key,omitempty"`
|
||||
Annotations map[string]any `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// NewEdge creates an loaded edge from edge descriptor.
|
||||
func NewEdge(ed *edge.Descriptor) *Edge {
|
||||
ne := &Edge{
|
||||
Tag: ed.Tag,
|
||||
Type: ed.Type,
|
||||
Name: ed.Name,
|
||||
Field: ed.Field,
|
||||
Unique: ed.Unique,
|
||||
Inverse: ed.Inverse,
|
||||
Required: ed.Required,
|
||||
Immutable: ed.Immutable,
|
||||
RefName: ed.RefName,
|
||||
Through: ed.Through,
|
||||
StorageKey: ed.StorageKey,
|
||||
Comment: ed.Comment,
|
||||
Annotations: make(map[string]any),
|
||||
}
|
||||
for _, at := range ed.Annotations {
|
||||
ne.addAnnotation(at)
|
||||
}
|
||||
if ref := ed.Ref; ref != nil {
|
||||
ne.Ref = NewEdge(ref)
|
||||
ne.StorageKey = ne.Ref.StorageKey
|
||||
}
|
||||
return ne
|
||||
}
|
||||
|
||||
// NewField creates a loaded field from field descriptor.
|
||||
func NewField(fd *field.Descriptor) (*Field, error) {
|
||||
if fd.Err != nil {
|
||||
return nil, fmt.Errorf("field %q: %v", fd.Name, fd.Err)
|
||||
}
|
||||
sf := &Field{
|
||||
Name: fd.Name,
|
||||
Info: fd.Info,
|
||||
ValueScanner: fd.ValueScanner != nil,
|
||||
Tag: fd.Tag,
|
||||
Enums: fd.Enums,
|
||||
Unique: fd.Unique,
|
||||
Nillable: fd.Nillable,
|
||||
Optional: fd.Optional,
|
||||
Default: fd.Default != nil,
|
||||
UpdateDefault: fd.UpdateDefault != nil,
|
||||
Immutable: fd.Immutable,
|
||||
StorageKey: fd.StorageKey,
|
||||
Validators: len(fd.Validators),
|
||||
Sensitive: fd.Sensitive,
|
||||
SchemaType: fd.SchemaType,
|
||||
Annotations: make(map[string]any),
|
||||
Comment: fd.Comment,
|
||||
Deprecated: fd.Deprecated,
|
||||
DeprecatedReason: fd.DeprecatedReason,
|
||||
BlobKey: fd.BlobKey != nil,
|
||||
BlobDualWrite: fd.BlobDualWrite,
|
||||
BlobLazy: fd.BlobLazy,
|
||||
BlobCheckRefs: fd.BlobCheckRefs,
|
||||
BlobDWSchemaType: fd.BlobDWSchemaType,
|
||||
}
|
||||
for _, at := range fd.Annotations {
|
||||
sf.addAnnotation(at)
|
||||
}
|
||||
if sf.Info == nil {
|
||||
return nil, fmt.Errorf("missing type info for field %q", sf.Name)
|
||||
}
|
||||
if size := int64(fd.Size); size != 0 {
|
||||
sf.Size = &size
|
||||
}
|
||||
if sf.Default {
|
||||
sf.DefaultKind = reflect.TypeOf(fd.Default).Kind()
|
||||
}
|
||||
// If the default value can be encoded to the generator.
|
||||
// For example, not a function like time.Now.
|
||||
if _, err := json.Marshal(fd.Default); err == nil {
|
||||
sf.DefaultValue = fd.Default
|
||||
}
|
||||
return sf, nil
|
||||
}
|
||||
|
||||
// NewIndex creates an loaded index from index descriptor.
|
||||
func NewIndex(idx *index.Descriptor) *Index {
|
||||
ni := &Index{
|
||||
Edges: idx.Edges,
|
||||
Fields: idx.Fields,
|
||||
Unique: idx.Unique,
|
||||
StorageKey: idx.StorageKey,
|
||||
Annotations: make(map[string]any),
|
||||
}
|
||||
for _, at := range idx.Annotations {
|
||||
ni.addAnnotation(at)
|
||||
}
|
||||
return ni
|
||||
}
|
||||
|
||||
// MarshalSchema encodes the ent.Schema interface into a JSON
|
||||
// that can be decoded into the Schema objects declared above.
|
||||
func MarshalSchema(schema ent.Interface) (b []byte, err error) {
|
||||
s := &Schema{
|
||||
Config: schema.Config(),
|
||||
Name: indirect(reflect.TypeOf(schema)).Name(),
|
||||
Annotations: make(map[string]any),
|
||||
}
|
||||
_, s.View = schema.(ent.Viewer)
|
||||
if err := s.loadMixin(schema); err != nil {
|
||||
return nil, fmt.Errorf("schema %q: %w", s.Name, err)
|
||||
}
|
||||
// Schema annotations override mixed-in annotations.
|
||||
for _, at := range schema.Annotations() {
|
||||
if e, ok := at.(interface{ Err() error }); ok && e.Err() != nil {
|
||||
return nil, fmt.Errorf("schema %q: %w", s.Name, e.Err())
|
||||
}
|
||||
s.addAnnotation(at)
|
||||
}
|
||||
if err := s.loadFields(schema); err != nil {
|
||||
return nil, fmt.Errorf("schema %q: %w", s.Name, err)
|
||||
}
|
||||
edges, err := safeEdges(schema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("schema %q: %w", s.Name, err)
|
||||
}
|
||||
for _, e := range edges {
|
||||
s.Edges = append(s.Edges, NewEdge(e.Descriptor()))
|
||||
}
|
||||
indexes, err := safeIndexes(schema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("schema %q: %w", s.Name, err)
|
||||
}
|
||||
for _, idx := range indexes {
|
||||
s.Indexes = append(s.Indexes, NewIndex(idx.Descriptor()))
|
||||
}
|
||||
if err := s.loadHooks(schema); err != nil {
|
||||
return nil, fmt.Errorf("schema %q: %w", s.Name, err)
|
||||
}
|
||||
if err := s.loadInterceptors(schema); err != nil {
|
||||
return nil, fmt.Errorf("schema %q: %w", s.Name, err)
|
||||
}
|
||||
if err := s.loadPolicy(schema); err != nil {
|
||||
return nil, fmt.Errorf("schema %q: %w", s.Name, err)
|
||||
}
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
// UnmarshalSchema decodes the given buffer to a loaded schema.
|
||||
func UnmarshalSchema(buf []byte) (*Schema, error) {
|
||||
s := &Schema{}
|
||||
if err := json.Unmarshal(buf, s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range s.Fields {
|
||||
if err := f.defaults(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// loadMixin loads mixin to schema from ent.Interface.
|
||||
func (s *Schema) loadMixin(schema ent.Interface) error {
|
||||
mixin, err := safeMixin(schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, mx := range mixin {
|
||||
name := indirect(reflect.TypeOf(mx)).Name()
|
||||
fields, err := safeFields(mx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mixin %q: %w", name, err)
|
||||
}
|
||||
for j, f := range fields {
|
||||
sf, err := NewField(f.Descriptor())
|
||||
if err != nil {
|
||||
return fmt.Errorf("mixin %q: %w", name, err)
|
||||
}
|
||||
sf.Position = &Position{
|
||||
Index: j,
|
||||
MixedIn: true,
|
||||
MixinIndex: i,
|
||||
}
|
||||
s.Fields = append(s.Fields, sf)
|
||||
}
|
||||
edges, err := safeEdges(mx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mixin %q: %w", name, err)
|
||||
}
|
||||
for _, e := range edges {
|
||||
s.Edges = append(s.Edges, NewEdge(e.Descriptor()))
|
||||
}
|
||||
indexes, err := safeIndexes(mx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mixin %q: %w", name, err)
|
||||
}
|
||||
for _, idx := range indexes {
|
||||
s.Indexes = append(s.Indexes, NewIndex(idx.Descriptor()))
|
||||
}
|
||||
hooks, err := safeHooks(mx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mixin %q: %w", name, err)
|
||||
}
|
||||
for j := range hooks {
|
||||
s.Hooks = append(s.Hooks, &Position{
|
||||
Index: j,
|
||||
MixedIn: true,
|
||||
MixinIndex: i,
|
||||
})
|
||||
}
|
||||
inters, err := safeInterceptors(mx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mixin %q: %w", name, err)
|
||||
}
|
||||
for j := range inters {
|
||||
s.Interceptors = append(s.Interceptors, &Position{
|
||||
Index: j,
|
||||
MixedIn: true,
|
||||
MixinIndex: i,
|
||||
})
|
||||
}
|
||||
policy, err := safePolicy(mx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mixin %q: %w", name, err)
|
||||
}
|
||||
if policy != nil {
|
||||
s.Policy = append(s.Policy, &Position{
|
||||
MixedIn: true,
|
||||
MixinIndex: i,
|
||||
})
|
||||
}
|
||||
for _, at := range mx.Annotations() {
|
||||
s.addAnnotation(at)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadFields loads field to schema from ent.Interface.
|
||||
func (s *Schema) loadFields(schema ent.Interface) error {
|
||||
fields, err := safeFields(schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, f := range fields {
|
||||
sf, err := NewField(f.Descriptor())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sf.Position = &Position{Index: i}
|
||||
s.Fields = append(s.Fields, sf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Schema) loadHooks(schema ent.Interface) error {
|
||||
hooks, err := safeHooks(schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range hooks {
|
||||
s.Hooks = append(s.Hooks, &Position{
|
||||
Index: i,
|
||||
MixedIn: false,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Schema) loadInterceptors(schema ent.Interface) error {
|
||||
inters, err := safeInterceptors(schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range inters {
|
||||
s.Interceptors = append(s.Interceptors, &Position{
|
||||
Index: i,
|
||||
MixedIn: false,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Schema) loadPolicy(schema ent.Interface) error {
|
||||
policy, err := safePolicy(schema)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if policy != nil {
|
||||
s.Policy = append(s.Policy, &Position{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Schema) addAnnotation(an schema.Annotation) {
|
||||
curr, ok := s.Annotations[an.Name()]
|
||||
if !ok {
|
||||
s.Annotations[an.Name()] = an
|
||||
return
|
||||
}
|
||||
if m, ok := curr.(schema.Merger); ok {
|
||||
s.Annotations[an.Name()] = m.Merge(an)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Edge) addAnnotation(an schema.Annotation) {
|
||||
addAnnotation(e.Annotations, an)
|
||||
}
|
||||
|
||||
func (i *Index) addAnnotation(an schema.Annotation) {
|
||||
addAnnotation(i.Annotations, an)
|
||||
}
|
||||
|
||||
func (f *Field) addAnnotation(an schema.Annotation) {
|
||||
addAnnotation(f.Annotations, an)
|
||||
}
|
||||
|
||||
func addAnnotation(annotations map[string]any, an schema.Annotation) {
|
||||
curr, ok := annotations[an.Name()]
|
||||
if !ok {
|
||||
annotations[an.Name()] = an
|
||||
return
|
||||
}
|
||||
if m, ok := curr.(schema.Merger); ok {
|
||||
annotations[an.Name()] = m.Merge(an)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Field) defaults() error {
|
||||
if !f.Default || !f.Info.Numeric() || f.DefaultKind == reflect.Func {
|
||||
return nil
|
||||
}
|
||||
n, ok := f.DefaultValue.(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected default value type for field: %q", f.Name)
|
||||
}
|
||||
switch t := f.Info.Type; {
|
||||
case t >= field.TypeInt8 && t <= field.TypeInt64:
|
||||
f.DefaultValue = int64(n)
|
||||
case t >= field.TypeUint8 && t <= field.TypeUint64:
|
||||
f.DefaultValue = uint64(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// safeFields wraps the schema.Fields and mixin.Fields method with recover to ensure no panics in marshaling.
|
||||
func safeFields(fd interface{ Fields() []ent.Field }) (fields []ent.Field, err error) {
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
err = fmt.Errorf("%T.Fields panics: %v", fd, v)
|
||||
fields = nil
|
||||
}
|
||||
}()
|
||||
return fd.Fields(), nil
|
||||
}
|
||||
|
||||
// safeEdges wraps the schema.Edges method with recover to ensure no panics in marshaling.
|
||||
func safeEdges(schema interface{ Edges() []ent.Edge }) (edges []ent.Edge, err error) {
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
err = fmt.Errorf("schema.Edges panics: %v", v)
|
||||
edges = nil
|
||||
}
|
||||
}()
|
||||
return schema.Edges(), nil
|
||||
}
|
||||
|
||||
// safeIndexes wraps the schema.Indexes method with recover to ensure no panics in marshaling.
|
||||
func safeIndexes(schema interface{ Indexes() []ent.Index }) (indexes []ent.Index, err error) {
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
err = fmt.Errorf("schema.Indexes panics: %v", v)
|
||||
indexes = nil
|
||||
}
|
||||
}()
|
||||
return schema.Indexes(), nil
|
||||
}
|
||||
|
||||
// safeMixin wraps the schema.Mixin method with recover to ensure no panics in marshaling.
|
||||
func safeMixin(schema ent.Interface) (mixin []ent.Mixin, err error) {
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
err = fmt.Errorf("schema.Mixin panics: %v", v)
|
||||
mixin = nil
|
||||
}
|
||||
}()
|
||||
return schema.Mixin(), nil
|
||||
}
|
||||
|
||||
// safeHooks wraps the schema.Hooks method with recover to ensure no panics in marshaling.
|
||||
func safeHooks(schema interface{ Hooks() []ent.Hook }) (hooks []ent.Hook, err error) {
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
err = fmt.Errorf("schema.Hooks panics: %v", v)
|
||||
hooks = nil
|
||||
}
|
||||
}()
|
||||
return schema.Hooks(), nil
|
||||
}
|
||||
|
||||
// safeInterceptors wraps the schema.Interceptors method with recover to ensure no panics in marshaling.
|
||||
func safeInterceptors(schema interface{ Interceptors() []ent.Interceptor }) (inters []ent.Interceptor, err error) {
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
err = fmt.Errorf("schema.Interceptors panics: %v", v)
|
||||
inters = nil
|
||||
}
|
||||
}()
|
||||
return schema.Interceptors(), nil
|
||||
}
|
||||
|
||||
// safePolicy wraps the schema.Policy method with recover to ensure no panics in marshaling.
|
||||
func safePolicy(schema interface{ Policy() ent.Policy }) (policy ent.Policy, err error) {
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
err = fmt.Errorf("schema.Policy panics: %v", v)
|
||||
policy = nil
|
||||
}
|
||||
}()
|
||||
return schema.Policy(), nil
|
||||
}
|
||||
|
||||
func indirect(t reflect.Type) reflect.Type {
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
return t
|
||||
}
|
||||
572
entc/load/schema_test.go
Normal file
572
entc/load/schema_test.go
Normal file
@@ -0,0 +1,572 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package load
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/schema"
|
||||
"entgo.io/ent/schema/edge"
|
||||
"entgo.io/ent/schema/field"
|
||||
"entgo.io/ent/schema/index"
|
||||
"entgo.io/ent/schema/mixin"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type OrderConfig struct {
|
||||
FieldName string
|
||||
}
|
||||
|
||||
func (OrderConfig) Name() string {
|
||||
return "order_config"
|
||||
}
|
||||
|
||||
func (o OrderConfig) Merge(ant schema.Annotation) schema.Annotation {
|
||||
o.FieldName = ant.(OrderConfig).FieldName
|
||||
return o
|
||||
}
|
||||
|
||||
type IDConfig struct {
|
||||
TagName string
|
||||
}
|
||||
|
||||
func (IDConfig) Name() string {
|
||||
return "id_config"
|
||||
}
|
||||
|
||||
type PartialIndex struct {
|
||||
WhereClause string
|
||||
}
|
||||
|
||||
func (PartialIndex) Name() string {
|
||||
return "partial_index"
|
||||
}
|
||||
|
||||
func (p PartialIndex) Merge(ant schema.Annotation) schema.Annotation {
|
||||
p.WhereClause = ant.(PartialIndex).WhereClause
|
||||
return p
|
||||
}
|
||||
|
||||
type AnnotationMixin struct {
|
||||
mixin.Schema
|
||||
}
|
||||
|
||||
func (AnnotationMixin) Annotations() []schema.Annotation {
|
||||
return []schema.Annotation{
|
||||
IDConfig{TagName: "id tag"},
|
||||
OrderConfig{FieldName: "mixin annotations"},
|
||||
}
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (User) Mixin() []ent.Mixin {
|
||||
return []ent.Mixin{
|
||||
AnnotationMixin{},
|
||||
}
|
||||
}
|
||||
|
||||
func (User) Annotations() []schema.Annotation {
|
||||
return []schema.Annotation{
|
||||
OrderConfig{FieldName: "type annotations"},
|
||||
}
|
||||
}
|
||||
|
||||
func (User) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Int("age").
|
||||
Comment("some comment"),
|
||||
field.String("name").
|
||||
Default("unknown").
|
||||
Annotations(&OrderConfig{FieldName: "name"}),
|
||||
field.String("nillable").
|
||||
Nillable(),
|
||||
field.String("optional").
|
||||
Optional(),
|
||||
field.Enum("state").
|
||||
Values("on", "off").
|
||||
Optional(),
|
||||
field.String("sensitive").
|
||||
Sensitive(),
|
||||
field.Time("creation_time").
|
||||
Default(time.Now),
|
||||
field.UUID("uuid", uuid.UUID{}).
|
||||
Default(uuid.New),
|
||||
field.Int("parent_id").
|
||||
Optional(),
|
||||
}
|
||||
}
|
||||
|
||||
func (User) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
edge.To("groups", Group.Type).
|
||||
Annotations(&OrderConfig{FieldName: "name"}),
|
||||
edge.To("parent", User.Type).
|
||||
Unique().
|
||||
Required().
|
||||
Immutable().
|
||||
Field("parent_id").
|
||||
StorageKey(edge.Column("parent_id")).
|
||||
From("children"),
|
||||
edge.To("following", User.Type).
|
||||
Annotations(&OrderConfig{FieldName: "following"}).
|
||||
From("followers").
|
||||
Annotations(&OrderConfig{FieldName: "followers"}),
|
||||
}
|
||||
}
|
||||
|
||||
func (User) Indexes() []ent.Index {
|
||||
return []ent.Index{
|
||||
index.Fields("name", "address").
|
||||
Unique(),
|
||||
index.Fields("name").
|
||||
Edges("parent").
|
||||
StorageKey("user_parent_name").
|
||||
Annotations(&PartialIndex{
|
||||
WhereClause: "age > 20",
|
||||
}).
|
||||
Unique(),
|
||||
}
|
||||
}
|
||||
|
||||
type Group struct{ ent.Schema }
|
||||
|
||||
func (Group) Fields() []ent.Field { return nil }
|
||||
|
||||
func (Group) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
edge.From("users", User.Type),
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalSchema(t *testing.T) {
|
||||
for _, u := range []ent.Interface{User{}, &User{}} {
|
||||
buf, err := MarshalSchema(u)
|
||||
require.NoError(t, err)
|
||||
|
||||
schema, err := UnmarshalSchema(buf)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "User", schema.Name)
|
||||
require.Len(t, schema.Annotations, 2)
|
||||
ant := schema.Annotations["order_config"].(map[string]any)
|
||||
require.Equal(t, ant["FieldName"], "type annotations")
|
||||
|
||||
require.Len(t, schema.Fields, 9)
|
||||
require.Equal(t, "age", schema.Fields[0].Name)
|
||||
require.Equal(t, field.TypeInt, schema.Fields[0].Info.Type)
|
||||
|
||||
require.Equal(t, "name", schema.Fields[1].Name)
|
||||
require.Equal(t, field.TypeString, schema.Fields[1].Info.Type)
|
||||
require.Equal(t, "unknown", schema.Fields[1].DefaultValue)
|
||||
require.NotEmpty(t, schema.Fields[1].Annotations)
|
||||
ant = schema.Fields[1].Annotations["order_config"].(map[string]any)
|
||||
require.Equal(t, ant["FieldName"], "name")
|
||||
|
||||
require.Equal(t, "nillable", schema.Fields[2].Name)
|
||||
require.Equal(t, field.TypeString, schema.Fields[2].Info.Type)
|
||||
require.True(t, schema.Fields[2].Nillable)
|
||||
require.False(t, schema.Fields[2].Optional)
|
||||
require.False(t, schema.Fields[2].Sensitive)
|
||||
|
||||
require.Equal(t, "optional", schema.Fields[3].Name)
|
||||
require.Equal(t, field.TypeString, schema.Fields[3].Info.Type)
|
||||
require.False(t, schema.Fields[3].Nillable)
|
||||
require.True(t, schema.Fields[3].Optional)
|
||||
|
||||
require.Equal(t, "state", schema.Fields[4].Name)
|
||||
require.Equal(t, field.TypeEnum, schema.Fields[4].Info.Type)
|
||||
require.Equal(t, "on", schema.Fields[4].Enums[0].V)
|
||||
require.Equal(t, "off", schema.Fields[4].Enums[1].V)
|
||||
|
||||
require.Equal(t, "sensitive", schema.Fields[5].Name)
|
||||
require.Equal(t, field.TypeString, schema.Fields[5].Info.Type)
|
||||
require.True(t, schema.Fields[5].Sensitive)
|
||||
require.Equal(t, reflect.Invalid, schema.Fields[5].DefaultKind)
|
||||
|
||||
require.Equal(t, "creation_time", schema.Fields[6].Name)
|
||||
require.Equal(t, field.TypeTime, schema.Fields[6].Info.Type)
|
||||
require.Nil(t, schema.Fields[6].DefaultValue)
|
||||
require.Equal(t, reflect.Func, schema.Fields[6].DefaultKind)
|
||||
|
||||
require.Equal(t, "uuid", schema.Fields[7].Name)
|
||||
require.Equal(t, field.TypeUUID, schema.Fields[7].Info.Type)
|
||||
require.True(t, schema.Fields[7].Default)
|
||||
require.Equal(t, "github.com/google/uuid", schema.Fields[7].Info.PkgPath)
|
||||
|
||||
require.Equal(t, "parent_id", schema.Fields[8].Name)
|
||||
require.Equal(t, field.TypeInt, schema.Fields[8].Info.Type)
|
||||
require.True(t, schema.Fields[8].Optional)
|
||||
|
||||
require.Len(t, schema.Edges, 3)
|
||||
require.Equal(t, "groups", schema.Edges[0].Name)
|
||||
require.Equal(t, "Group", schema.Edges[0].Type)
|
||||
require.False(t, schema.Edges[0].Inverse)
|
||||
require.NotEmpty(t, schema.Edges[0].Annotations)
|
||||
ant = schema.Edges[0].Annotations["order_config"].(map[string]any)
|
||||
require.Equal(t, ant["FieldName"], "name")
|
||||
|
||||
require.Equal(t, "children", schema.Edges[1].Name)
|
||||
require.Equal(t, "parent_id", schema.Edges[1].StorageKey.Columns[0])
|
||||
require.Equal(t, "User", schema.Edges[1].Type)
|
||||
require.True(t, schema.Edges[1].Inverse)
|
||||
require.Equal(t, "parent", schema.Edges[1].Ref.Name)
|
||||
require.True(t, schema.Edges[1].Ref.Unique)
|
||||
require.True(t, schema.Edges[1].Ref.Required)
|
||||
require.True(t, schema.Edges[1].Ref.Immutable)
|
||||
require.Equal(t, "parent_id", schema.Edges[1].Ref.StorageKey.Columns[0])
|
||||
|
||||
ant = schema.Edges[2].Annotations["order_config"].(map[string]any)
|
||||
require.Equal(t, ant["FieldName"], "followers")
|
||||
ant = schema.Edges[2].Ref.Annotations["order_config"].(map[string]any)
|
||||
require.Equal(t, ant["FieldName"], "following")
|
||||
|
||||
require.Equal(t, []string{"name", "address"}, schema.Indexes[0].Fields)
|
||||
require.True(t, schema.Indexes[0].Unique)
|
||||
require.Equal(t, []string{"name"}, schema.Indexes[1].Fields)
|
||||
require.Equal(t, []string{"parent"}, schema.Indexes[1].Edges)
|
||||
require.Equal(t, "user_parent_name", schema.Indexes[1].StorageKey)
|
||||
require.True(t, schema.Indexes[1].Unique)
|
||||
ant = schema.Indexes[1].Annotations["partial_index"].(map[string]any)
|
||||
require.Equal(t, "age > 20", ant["WhereClause"])
|
||||
|
||||
require.Equal(t, "some comment", schema.Fields[0].Comment)
|
||||
require.Empty(t, schema.Fields[1].Comment)
|
||||
}
|
||||
}
|
||||
|
||||
type InvalidEdge struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
// Edge panics because the edge declaration is invalid.
|
||||
func (InvalidEdge) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
edge.From("invalid", InvalidEdge{}.Type),
|
||||
}
|
||||
}
|
||||
|
||||
type InvalidUUID struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (InvalidUUID) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.UUID("invalid", uuid.New()).
|
||||
Default(time.Now),
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalFails(t *testing.T) {
|
||||
i1 := InvalidEdge{}
|
||||
buf, err := MarshalSchema(i1)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, buf)
|
||||
|
||||
i2 := InvalidUUID{}
|
||||
buf, err = MarshalSchema(i2)
|
||||
require.Nil(t, buf)
|
||||
require.EqualError(t, err, `schema "InvalidUUID": field "invalid": expect type (func() uuid.UUID) for uuid default value`)
|
||||
}
|
||||
|
||||
type WithDefaults struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (WithDefaults) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Int("int").
|
||||
Default(1),
|
||||
field.Float("float").
|
||||
Default(math.Pi),
|
||||
field.String("string").
|
||||
Default("foo"),
|
||||
field.Bool("string").
|
||||
Default(true),
|
||||
field.Time("updated_at").
|
||||
UpdateDefault(time.Now),
|
||||
// see issue #1146
|
||||
field.Int("int_default_func").
|
||||
DefaultFunc(func() int {
|
||||
return 1e9
|
||||
}),
|
||||
field.Float("balance").
|
||||
Default(0),
|
||||
field.JSON("dirs", []http.Dir{}).
|
||||
Default([]http.Dir{"/tmp"}),
|
||||
field.Float("float_default_func").
|
||||
DefaultFunc(func() float64 {
|
||||
return math.Pi
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (WithDefaults) Edges() []ent.Edge {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (WithDefaults) Indexes() []ent.Index {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMarshalDefaults(t *testing.T) {
|
||||
d := WithDefaults{}
|
||||
buf, err := MarshalSchema(d)
|
||||
require.NoError(t, err)
|
||||
|
||||
schema := &Schema{}
|
||||
err = json.Unmarshal(buf, schema)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "WithDefaults", schema.Name)
|
||||
require.True(t, schema.Fields[0].Default)
|
||||
require.True(t, schema.Fields[1].Default)
|
||||
require.True(t, schema.Fields[2].Default)
|
||||
require.True(t, schema.Fields[3].Default)
|
||||
require.False(t, schema.Fields[4].Default)
|
||||
require.True(t, schema.Fields[4].UpdateDefault)
|
||||
require.True(t, schema.Fields[5].Default)
|
||||
require.Equal(t, schema.Fields[5].DefaultKind, reflect.Func)
|
||||
require.True(t, schema.Fields[6].Default)
|
||||
require.True(t, schema.Fields[7].Default)
|
||||
require.Equal(t, schema.Fields[8].DefaultKind, reflect.Func)
|
||||
}
|
||||
|
||||
// BlobDoc is a test schema with blob-stored fields.
|
||||
type BlobDoc struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (BlobDoc) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Blob("content").
|
||||
Comment("blob content"),
|
||||
field.Blob("thumbnail"),
|
||||
}
|
||||
}
|
||||
|
||||
func (BlobDoc) Edges() []ent.Edge { return nil }
|
||||
|
||||
func TestMarshalBlobSchema(t *testing.T) {
|
||||
d := BlobDoc{}
|
||||
buf, err := MarshalSchema(d)
|
||||
require.NoError(t, err)
|
||||
|
||||
s := &Schema{}
|
||||
err = json.Unmarshal(buf, s)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "BlobDoc", s.Name)
|
||||
require.Len(t, s.Fields, 2)
|
||||
|
||||
// First blob field: content.
|
||||
f0 := s.Fields[0]
|
||||
require.Equal(t, "content", f0.Name)
|
||||
require.Equal(t, field.TypeBlob, f0.Info.Type)
|
||||
require.Equal(t, "blob content", f0.Comment)
|
||||
|
||||
// Second blob field: thumbnail.
|
||||
f1 := s.Fields[1]
|
||||
require.Equal(t, "thumbnail", f1.Name)
|
||||
require.Equal(t, field.TypeBlob, f1.Info.Type)
|
||||
|
||||
// Verify JSON roundtrip preserves type.
|
||||
buf2, err := json.Marshal(s)
|
||||
require.NoError(t, err)
|
||||
s2 := &Schema{}
|
||||
require.NoError(t, json.Unmarshal(buf2, s2))
|
||||
require.Equal(t, field.TypeBlob, s2.Fields[0].Info.Type)
|
||||
}
|
||||
|
||||
type TimeMixin struct {
|
||||
mixin.Schema
|
||||
}
|
||||
|
||||
func (TimeMixin) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Time("created_at").
|
||||
Immutable().
|
||||
Default(time.Now),
|
||||
field.Time("updated_at").
|
||||
Default(time.Now).
|
||||
UpdateDefault(time.Now),
|
||||
}
|
||||
}
|
||||
|
||||
type HooksMixin struct {
|
||||
mixin.Schema
|
||||
}
|
||||
|
||||
func (HooksMixin) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("boring"),
|
||||
}
|
||||
}
|
||||
|
||||
func (HooksMixin) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
edge.To("user", User.Type).
|
||||
Unique(),
|
||||
}
|
||||
}
|
||||
|
||||
func (HooksMixin) Indexes() []ent.Index {
|
||||
return []ent.Index{
|
||||
index.Fields("boring").
|
||||
Edges("user"),
|
||||
}
|
||||
}
|
||||
|
||||
func (HooksMixin) Hooks() []ent.Hook {
|
||||
return []ent.Hook{
|
||||
func(ent.Mutator) ent.Mutator { return nil },
|
||||
func(ent.Mutator) ent.Mutator { return nil },
|
||||
}
|
||||
}
|
||||
|
||||
type BoringPolicy struct{}
|
||||
|
||||
func (BoringPolicy) EvalMutation(context.Context, ent.Mutation) error { return nil }
|
||||
func (BoringPolicy) EvalQuery(context.Context, ent.Query) error { return nil }
|
||||
|
||||
type PrivacyMixin struct {
|
||||
mixin.Schema
|
||||
}
|
||||
|
||||
func (PrivacyMixin) Policy() ent.Policy {
|
||||
return BoringPolicy{}
|
||||
}
|
||||
|
||||
type WithMixin struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (WithMixin) Mixin() []ent.Mixin {
|
||||
return []ent.Mixin{
|
||||
TimeMixin{},
|
||||
HooksMixin{},
|
||||
PrivacyMixin{},
|
||||
}
|
||||
}
|
||||
|
||||
func (WithMixin) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Int("field"),
|
||||
}
|
||||
}
|
||||
|
||||
func (WithMixin) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
edge.To("owner", User.Type),
|
||||
}
|
||||
}
|
||||
|
||||
func (WithMixin) Indexes() []ent.Index {
|
||||
return []ent.Index{
|
||||
index.Fields("field").
|
||||
Edges("owner").
|
||||
Unique(),
|
||||
}
|
||||
}
|
||||
|
||||
func (WithMixin) Hooks() []ent.Hook {
|
||||
return []ent.Hook{
|
||||
func(ent.Mutator) ent.Mutator { return nil },
|
||||
}
|
||||
}
|
||||
|
||||
func (WithMixin) Policy() ent.Policy {
|
||||
return BoringPolicy{}
|
||||
}
|
||||
|
||||
func TestMarshalMixin(t *testing.T) {
|
||||
d := WithMixin{}
|
||||
buf, err := MarshalSchema(d)
|
||||
require.NoError(t, err)
|
||||
|
||||
schema := &Schema{}
|
||||
err = json.Unmarshal(buf, schema)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Fields", func(t *testing.T) {
|
||||
require.Equal(t, "WithMixin", schema.Name)
|
||||
require.Equal(t, "created_at", schema.Fields[0].Name)
|
||||
require.True(t, schema.Fields[0].Default)
|
||||
require.True(t, schema.Fields[0].Position.MixedIn)
|
||||
require.Equal(t, 0, schema.Fields[0].Position.MixinIndex)
|
||||
require.Equal(t, 0, schema.Fields[0].Position.Index)
|
||||
|
||||
require.Equal(t, "updated_at", schema.Fields[1].Name)
|
||||
require.True(t, schema.Fields[1].Default)
|
||||
require.True(t, schema.Fields[1].UpdateDefault)
|
||||
require.True(t, schema.Fields[1].Position.MixedIn)
|
||||
require.Equal(t, 0, schema.Fields[1].Position.MixinIndex)
|
||||
require.Equal(t, 1, schema.Fields[1].Position.Index)
|
||||
|
||||
require.Equal(t, "boring", schema.Fields[2].Name)
|
||||
require.False(t, schema.Fields[2].Default)
|
||||
require.False(t, schema.Fields[2].UpdateDefault)
|
||||
require.True(t, schema.Fields[2].Position.MixedIn)
|
||||
require.Equal(t, 1, schema.Fields[2].Position.MixinIndex)
|
||||
require.Equal(t, 0, schema.Fields[2].Position.Index)
|
||||
|
||||
require.Equal(t, "field", schema.Fields[3].Name)
|
||||
require.False(t, schema.Fields[3].Default)
|
||||
require.False(t, schema.Fields[3].Position.MixedIn)
|
||||
require.Equal(t, 0, schema.Fields[3].Position.Index)
|
||||
})
|
||||
|
||||
t.Run("Hooks", func(t *testing.T) {
|
||||
require.True(t, schema.Hooks[0].MixedIn)
|
||||
require.True(t, schema.Hooks[1].MixedIn)
|
||||
|
||||
require.Equal(t, 1, schema.Hooks[0].MixinIndex)
|
||||
require.Equal(t, 1, schema.Hooks[1].MixinIndex)
|
||||
require.Equal(t, 0, schema.Hooks[0].Index)
|
||||
require.Equal(t, 1, schema.Hooks[1].Index)
|
||||
|
||||
require.False(t, schema.Hooks[2].MixedIn)
|
||||
require.Equal(t, 0, schema.Hooks[2].Index)
|
||||
require.Equal(t, 0, schema.Hooks[2].MixinIndex)
|
||||
})
|
||||
|
||||
t.Run("Edges", func(t *testing.T) {
|
||||
require.Len(t, schema.Edges, 2)
|
||||
require.Equal(t, "user", schema.Edges[0].Name)
|
||||
require.Equal(t, "User", schema.Edges[0].Type)
|
||||
require.True(t, schema.Edges[0].Unique)
|
||||
|
||||
require.Equal(t, "owner", schema.Edges[1].Name)
|
||||
require.Equal(t, "User", schema.Edges[1].Type)
|
||||
require.False(t, schema.Edges[1].Unique)
|
||||
})
|
||||
|
||||
t.Run("Indexes", func(t *testing.T) {
|
||||
require.Len(t, schema.Indexes, 2)
|
||||
require.Equal(t, []string{"boring"}, schema.Indexes[0].Fields)
|
||||
require.Equal(t, []string{"user"}, schema.Indexes[0].Edges)
|
||||
require.False(t, schema.Indexes[0].Unique)
|
||||
|
||||
require.Equal(t, []string{"field"}, schema.Indexes[1].Fields)
|
||||
require.Equal(t, []string{"owner"}, schema.Indexes[1].Edges)
|
||||
require.True(t, schema.Indexes[1].Unique)
|
||||
})
|
||||
|
||||
t.Run("Policy", func(t *testing.T) {
|
||||
require.Len(t, schema.Policy, 2)
|
||||
require.True(t, schema.Policy[0].MixedIn)
|
||||
require.False(t, schema.Policy[1].MixedIn)
|
||||
})
|
||||
}
|
||||
44
entc/load/template/main.tmpl
Normal file
44
entc/load/template/main.tmpl
Normal file
@@ -0,0 +1,44 @@
|
||||
{{/*
|
||||
Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
This source code is licensed under the Apache 2.0 license found
|
||||
in the LICENSE file in the root directory of this source tree.
|
||||
*/}}
|
||||
|
||||
{{ define "main" }}
|
||||
// Code generated by 'entc generate'. DO NOT EDIT.
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"bytes"
|
||||
{{ template "imports" }}
|
||||
|
||||
entschema {{ printf "%q" .Package }}
|
||||
)
|
||||
|
||||
var schemas = []ent.Interface{
|
||||
{{- range $name := .Names }}
|
||||
entschema.{{ print $name "{}" }},
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var lines [][]byte
|
||||
for _, schema := range schemas {
|
||||
b, err := MarshalSchema(schema)
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
lines = append(lines, b)
|
||||
}
|
||||
os.Stdout.Write(bytes.Join(lines, []byte("\n")))
|
||||
}
|
||||
|
||||
func fail(err error) {
|
||||
os.Stderr.WriteString(err.Error()+"\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
{{ template "schema" }}
|
||||
|
||||
{{ end }}
|
||||
33
entc/load/testdata/base/schema.go
vendored
Normal file
33
entc/load/testdata/base/schema.go
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/schema/field"
|
||||
)
|
||||
|
||||
// base schema for sharing fields and edges.
|
||||
type base struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (base) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Int("base_field"),
|
||||
}
|
||||
}
|
||||
|
||||
// User holds the user schema.
|
||||
type User struct {
|
||||
base
|
||||
}
|
||||
|
||||
func (u User) Fields() []ent.Field {
|
||||
return append(
|
||||
u.base.Fields(),
|
||||
field.String("user_field"),
|
||||
)
|
||||
}
|
||||
24
entc/load/testdata/buildflags/group.go
vendored
Normal file
24
entc/load/testdata/buildflags/group.go
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
//go:build !hidegroups
|
||||
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package buildflags
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/schema/field"
|
||||
)
|
||||
|
||||
// Group holds the group schema.
|
||||
type Group struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (Group) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Time("expired_at"),
|
||||
field.String("organization"),
|
||||
}
|
||||
}
|
||||
22
entc/load/testdata/buildflags/user.go
vendored
Normal file
22
entc/load/testdata/buildflags/user.go
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package buildflags
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/schema/field"
|
||||
)
|
||||
|
||||
// User holds the user schema.
|
||||
type User struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (User) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Int("age"),
|
||||
field.String("name"),
|
||||
}
|
||||
}
|
||||
15
entc/load/testdata/cycle/fakent/fakent.go
vendored
Normal file
15
entc/load/testdata/cycle/fakent/fakent.go
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
// Package fakent fis a fake generated Ent package.
|
||||
package fakent
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/entc/load/testdata/cycle"
|
||||
)
|
||||
|
||||
type Hook = ent.Hook
|
||||
|
||||
var _ = &cycle.Used{}
|
||||
36
entc/load/testdata/cycle/schema.go
vendored
Normal file
36
entc/load/testdata/cycle/schema.go
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package cycle
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/entc/load/testdata/cycle/fakent"
|
||||
"entgo.io/ent/schema/field"
|
||||
)
|
||||
|
||||
// User holds the user schema.
|
||||
type User struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (User) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.JSON("used", &Used{}),
|
||||
field.Enum("e").
|
||||
GoType(Enum(0)),
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
Used struct{}
|
||||
NotUsed struct{}
|
||||
notExported struct{}
|
||||
Enum int
|
||||
)
|
||||
|
||||
func (Enum) Values() []string { return nil }
|
||||
|
||||
// The cause for cycle.
|
||||
var _ fakent.Hook = nil
|
||||
20
entc/load/testdata/failure/user.go
vendored
Normal file
20
entc/load/testdata/failure/user.go
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package failure
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/schema/edge"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (User) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
edge.To("panic", User{}.Type),
|
||||
}
|
||||
}
|
||||
8
entc/load/testdata/invalid/schema.go
vendored
Normal file
8
entc/load/testdata/invalid/schema.go
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package invalid
|
||||
|
||||
// User is invalid schema.
|
||||
type User struct{}
|
||||
45
entc/load/testdata/valid/schema.go
vendored
Normal file
45
entc/load/testdata/valid/schema.go
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package valid
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/schema/field"
|
||||
)
|
||||
|
||||
// User holds the user schema.
|
||||
type User struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (User) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Int("age"),
|
||||
field.String("name"),
|
||||
}
|
||||
}
|
||||
|
||||
// Group holds the group schema.
|
||||
type Group struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (Group) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Time("expired_at"),
|
||||
field.String("organization"),
|
||||
}
|
||||
}
|
||||
|
||||
// Tag holds the tag schema.
|
||||
type Tag struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (Tag) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("text"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user