ent: support external uuid types (#181)

Summary:
Pull Request resolved: https://github.com/facebookincubator/ent/pull/181

The lack of generic typing requires to check the function type in the "loading" phase of the schema.

Reviewed By: alexsn

Differential Revision: D18636969

fbshipit-source-id: d8229779bf3c2490e51f5d0d4c550c70e2cd2550
This commit is contained in:
Ariel Mashraki
2019-11-21 08:48:12 -08:00
committed by Facebook Github Bot
parent e54373f410
commit 1e49561634
5 changed files with 56 additions and 34 deletions

File diff suppressed because one or more lines are too long

View File

@@ -115,6 +115,12 @@ func NewField(fd *field.Descriptor) (*Field, error) {
if _, err := json.Marshal(fd.Default); err == nil {
sf.DefaultValue = fd.Default
}
if fd.Info.Type == field.TypeUUID && fd.Default != nil {
typ := reflect.TypeOf(fd.Default)
if typ.Kind() != reflect.Func || typ.NumIn() != 0 || typ.NumOut() != 1 || typ.Out(0).String() != fd.Info.String() {
return nil, fmt.Errorf("expect type (func() %s) for uuid default value", fd.Info.String())
}
}
return sf, nil
}

View File

@@ -15,6 +15,7 @@ import (
"github.com/facebookincubator/ent/schema/field"
"github.com/facebookincubator/ent/schema/index"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
@@ -38,6 +39,8 @@ func (User) Fields() []ent.Field {
Sensitive(),
field.Time("creation_time").
Default(time.Now),
field.UUID("uuid", uuid.UUID{}).
Default(uuid.New),
}
}
@@ -79,7 +82,7 @@ func TestMarshalSchema(t *testing.T) {
schema := &Schema{}
require.NoError(t, json.Unmarshal(buf, schema))
require.Equal(t, "User", schema.Name)
require.Len(t, schema.Fields, 7)
require.Len(t, schema.Fields, 8)
require.Equal(t, "age", schema.Fields[0].Name)
require.Equal(t, field.TypeInt, schema.Fields[0].Info.Type)
@@ -110,6 +113,10 @@ func TestMarshalSchema(t *testing.T) {
require.Equal(t, field.TypeTime, schema.Fields[6].Info.Type)
require.Nil(t, schema.Fields[6].DefaultValue)
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.Len(t, schema.Edges, 2)
require.Equal(t, "groups", schema.Edges[0].Name)
require.Equal(t, "Group", schema.Edges[0].Type)
@@ -129,22 +136,38 @@ func TestMarshalSchema(t *testing.T) {
}
}
type Invalid struct {
type InvalidEdge struct {
ent.Schema
}
// Edge panics because the edge declaration is invalid.
func (Invalid) Edges() []ent.Edge {
func (InvalidEdge) Edges() []ent.Edge {
return []ent.Edge{
edge.From("invalid", Invalid{}.Type),
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) {
i := Invalid{}
buf, err := MarshalSchema(i)
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": expect type (func() uuid.UUID) for uuid default value`)
}
type WithDefaults struct {

View File

@@ -5,6 +5,7 @@
package field
import (
"database/sql/driver"
"errors"
"math"
"reflect"
@@ -133,13 +134,18 @@ func Enum(name string) *enumBuilder {
// UUID returns a new Field with type UUID. An example for defining UUID field is as follows:
//
// field.UUID("id").
// Default(uuid.New)
// field.UUID("id", uuid.New())
//
func UUID(name string) *uuidBuilder {
func UUID(name string, typ driver.Valuer) *uuidBuilder {
rt := reflect.TypeOf(typ)
return &uuidBuilder{&Descriptor{
Name: name,
Info: &TypeInfo{Type: TypeUUID, Nillable: true},
Info: &TypeInfo{
Type: TypeUUID,
Nillable: true,
Ident: rt.String(),
PkgPath: rt.PkgPath(),
},
}}
}
@@ -575,22 +581,15 @@ func (b *uuidBuilder) StructTag(s string) *uuidBuilder {
return b
}
// Type sets a custom type of the UUID field. It defaults to [16]byte.
func (b *uuidBuilder) Type(typ interface{}) *uuidBuilder {
t := reflect.TypeOf(typ)
b.desc.Info.Ident = t.String()
b.desc.Info.PkgPath = t.PkgPath()
return b
}
// Default sets the function that is applied to set default value
// of the field on creation. For example:
// of the field on creation. Codegen fails if the default function
// doesn't return the same concrete that was set for the UUID type.
//
// field.UUID("id").
// field.UUID("id", uuid.UUID{}).
// Default(uuid.New)
//
func (b *uuidBuilder) Default(f func() [16]byte) *uuidBuilder {
b.desc.Default = f
func (b *uuidBuilder) Default(fn interface{}) *uuidBuilder {
b.desc.Default = fn
return b
}

View File

@@ -155,18 +155,12 @@ func TestField_Enums(t *testing.T) {
}
func TestField_UUID(t *testing.T) {
fd := field.UUID("id").
Default(func() [16]byte { return uuid.New() }).
Descriptor()
assert.Equal(t, "id", fd.Name)
assert.Equal(t, "[16]byte", fd.Info.String())
assert.NotNil(t, fd.Default)
fd = field.UUID("id").
Type(uuid.UUID{}).
fd := field.UUID("id", uuid.UUID{}).
Default(uuid.New).
Descriptor()
assert.Equal(t, "id", fd.Name)
assert.Equal(t, "uuid.UUID", fd.Info.String())
assert.Equal(t, "github.com/google/uuid", fd.Info.PkgPath)
assert.Nil(t, fd.Default)
assert.NotNil(t, fd.Default)
assert.NotEmpty(t, fd.Default.(func() uuid.UUID)())
}