diff --git a/entc/integration/ent/fieldtype/fieldtype.go b/entc/integration/ent/fieldtype/fieldtype.go index 338c9cec0..335483eb0 100644 --- a/entc/integration/ent/fieldtype/fieldtype.go +++ b/entc/integration/ent/fieldtype/fieldtype.go @@ -251,6 +251,8 @@ var ( LinkValidator func(string) error // DefaultIP holds the default value on creation for the "ip" field. DefaultIP func() net.IP + // IPValidator is a validator for the "ip" field. It is called by the builders before save. + IPValidator func([]byte) error // DefaultPair holds the default value on creation for the "pair" field. DefaultPair func() schema.Pair // DefaultVstring holds the default value on creation for the "vstring" field. diff --git a/entc/integration/ent/fieldtype_create.go b/entc/integration/ent/fieldtype_create.go index 100dbe017..195cc83c7 100644 --- a/entc/integration/ent/fieldtype_create.go +++ b/entc/integration/ent/fieldtype_create.go @@ -874,6 +874,11 @@ func (ftc *FieldTypeCreate) check() error { return &ValidationError{Name: "link", err: fmt.Errorf("ent: validator failed for field \"link\": %w", err)} } } + if v, ok := ftc.mutation.IP(); ok { + if err := fieldtype.IPValidator([]byte(v)); err != nil { + return &ValidationError{Name: "ip", err: fmt.Errorf("ent: validator failed for field \"ip\": %w", err)} + } + } if _, ok := ftc.mutation.Role(); !ok { return &ValidationError{Name: "role", err: errors.New("ent: missing required field \"role\"")} } diff --git a/entc/integration/ent/fieldtype_update.go b/entc/integration/ent/fieldtype_update.go index 4aeb871e9..0dfbdddc7 100644 --- a/entc/integration/ent/fieldtype_update.go +++ b/entc/integration/ent/fieldtype_update.go @@ -1358,6 +1358,11 @@ func (ftu *FieldTypeUpdate) check() error { return &ValidationError{Name: "link", err: fmt.Errorf("ent: validator failed for field \"link\": %w", err)} } } + if v, ok := ftu.mutation.IP(); ok { + if err := fieldtype.IPValidator([]byte(v)); err != nil { + return &ValidationError{Name: "ip", err: fmt.Errorf("ent: validator failed for field \"ip\": %w", err)} + } + } if v, ok := ftu.mutation.Role(); ok { if err := fieldtype.RoleValidator(v); err != nil { return &ValidationError{Name: "role", err: fmt.Errorf("ent: validator failed for field \"role\": %w", err)} @@ -3687,6 +3692,11 @@ func (ftuo *FieldTypeUpdateOne) check() error { return &ValidationError{Name: "link", err: fmt.Errorf("ent: validator failed for field \"link\": %w", err)} } } + if v, ok := ftuo.mutation.IP(); ok { + if err := fieldtype.IPValidator([]byte(v)); err != nil { + return &ValidationError{Name: "ip", err: fmt.Errorf("ent: validator failed for field \"ip\": %w", err)} + } + } if v, ok := ftuo.mutation.Role(); ok { if err := fieldtype.RoleValidator(v); err != nil { return &ValidationError{Name: "role", err: fmt.Errorf("ent: validator failed for field \"role\": %w", err)} diff --git a/entc/integration/ent/runtime.go b/entc/integration/ent/runtime.go index 897860cb0..bea520d7f 100644 --- a/entc/integration/ent/runtime.go +++ b/entc/integration/ent/runtime.go @@ -89,6 +89,8 @@ func init() { fieldtypeDescIP := fieldtypeFields[42].Descriptor() // fieldtype.DefaultIP holds the default value on creation for the ip field. fieldtype.DefaultIP = fieldtypeDescIP.Default.(func() net.IP) + // fieldtype.IPValidator is a validator for the "ip" field. It is called by the builders before save. + fieldtype.IPValidator = fieldtypeDescIP.Validators[0].(func([]byte) error) // fieldtypeDescPair is the schema descriptor for pair field. fieldtypeDescPair := fieldtypeFields[55].Descriptor() // fieldtype.DefaultPair holds the default value on creation for the pair field. diff --git a/entc/integration/ent/schema/fieldtype.go b/entc/integration/ent/schema/fieldtype.go index 05dc146ac..88b3338c7 100644 --- a/entc/integration/ent/schema/fieldtype.go +++ b/entc/integration/ent/schema/fieldtype.go @@ -194,6 +194,12 @@ func (FieldType) Fields() []ent.Field { //nolint:funlen GoType(net.IP("127.0.0.1")). DefaultFunc(func() net.IP { return net.IP("127.0.0.1") + }). + Validate(func(i []byte) error { + if net.ParseIP(string(i)) == nil { + return fmt.Errorf("ent/schema: invalid ip %q", string(i)) + } + return nil }), field.Int("null_int64"). Optional(). diff --git a/entc/integration/gremlin/ent/fieldtype/fieldtype.go b/entc/integration/gremlin/ent/fieldtype/fieldtype.go index 47c4d65ea..05308e4de 100644 --- a/entc/integration/gremlin/ent/fieldtype/fieldtype.go +++ b/entc/integration/gremlin/ent/fieldtype/fieldtype.go @@ -162,6 +162,8 @@ var ( LinkValidator func(string) error // DefaultIP holds the default value on creation for the "ip" field. DefaultIP func() net.IP + // IPValidator is a validator for the "ip" field. It is called by the builders before save. + IPValidator func([]byte) error // DefaultPair holds the default value on creation for the "pair" field. DefaultPair func() schema.Pair // DefaultVstring holds the default value on creation for the "vstring" field. diff --git a/entc/integration/gremlin/ent/fieldtype_create.go b/entc/integration/gremlin/ent/fieldtype_create.go index d701ddd63..4bb31e6e4 100644 --- a/entc/integration/gremlin/ent/fieldtype_create.go +++ b/entc/integration/gremlin/ent/fieldtype_create.go @@ -875,6 +875,11 @@ func (ftc *FieldTypeCreate) check() error { return &ValidationError{Name: "link", err: fmt.Errorf("ent: validator failed for field \"link\": %w", err)} } } + if v, ok := ftc.mutation.IP(); ok { + if err := fieldtype.IPValidator([]byte(v)); err != nil { + return &ValidationError{Name: "ip", err: fmt.Errorf("ent: validator failed for field \"ip\": %w", err)} + } + } if _, ok := ftc.mutation.Role(); !ok { return &ValidationError{Name: "role", err: errors.New("ent: missing required field \"role\"")} } diff --git a/entc/integration/gremlin/ent/fieldtype_update.go b/entc/integration/gremlin/ent/fieldtype_update.go index 5b47da4d5..104cc17f4 100644 --- a/entc/integration/gremlin/ent/fieldtype_update.go +++ b/entc/integration/gremlin/ent/fieldtype_update.go @@ -1360,6 +1360,11 @@ func (ftu *FieldTypeUpdate) check() error { return &ValidationError{Name: "link", err: fmt.Errorf("ent: validator failed for field \"link\": %w", err)} } } + if v, ok := ftu.mutation.IP(); ok { + if err := fieldtype.IPValidator([]byte(v)); err != nil { + return &ValidationError{Name: "ip", err: fmt.Errorf("ent: validator failed for field \"ip\": %w", err)} + } + } if v, ok := ftu.mutation.Role(); ok { if err := fieldtype.RoleValidator(v); err != nil { return &ValidationError{Name: "role", err: fmt.Errorf("ent: validator failed for field \"role\": %w", err)} @@ -3168,6 +3173,11 @@ func (ftuo *FieldTypeUpdateOne) check() error { return &ValidationError{Name: "link", err: fmt.Errorf("ent: validator failed for field \"link\": %w", err)} } } + if v, ok := ftuo.mutation.IP(); ok { + if err := fieldtype.IPValidator([]byte(v)); err != nil { + return &ValidationError{Name: "ip", err: fmt.Errorf("ent: validator failed for field \"ip\": %w", err)} + } + } if v, ok := ftuo.mutation.Role(); ok { if err := fieldtype.RoleValidator(v); err != nil { return &ValidationError{Name: "role", err: fmt.Errorf("ent: validator failed for field \"role\": %w", err)} diff --git a/entc/integration/gremlin/ent/runtime.go b/entc/integration/gremlin/ent/runtime.go index dd964697d..3360ef3a7 100644 --- a/entc/integration/gremlin/ent/runtime.go +++ b/entc/integration/gremlin/ent/runtime.go @@ -89,6 +89,8 @@ func init() { fieldtypeDescIP := fieldtypeFields[42].Descriptor() // fieldtype.DefaultIP holds the default value on creation for the ip field. fieldtype.DefaultIP = fieldtypeDescIP.Default.(func() net.IP) + // fieldtype.IPValidator is a validator for the "ip" field. It is called by the builders before save. + fieldtype.IPValidator = fieldtypeDescIP.Validators[0].(func([]byte) error) // fieldtypeDescPair is the schema descriptor for pair field. fieldtypeDescPair := fieldtypeFields[55].Descriptor() // fieldtype.DefaultPair holds the default value on creation for the pair field. diff --git a/schema/field/field.go b/schema/field/field.go index e90b123d4..9f2ae2d75 100644 --- a/schema/field/field.go +++ b/schema/field/field.go @@ -575,6 +575,21 @@ func (b *bytesBuilder) MaxLen(i int) *bytesBuilder { return b } +// Validate adds a validator for this field. Operation fails if the validation fails. +// +// field.Bytes("blob"). +// Validate(func(b []byte) error { +// if len(b) % 2 == 0 { +// return fmt.Errorf("ent/schema: blob length is even: %d", len(b)) +// } +// return nil +// }) +// +func (b *bytesBuilder) Validate(fn func([]byte) error) *bytesBuilder { + b.desc.Validators = append(b.desc.Validators, fn) + return b +} + // StorageKey sets the storage key of the field. // In SQL dialects is the column name and Gremlin is the property. func (b *bytesBuilder) StorageKey(key string) *bytesBuilder { diff --git a/schema/field/field_test.go b/schema/field/field_test.go index 0eb3cdbc4..85c54cffe 100644 --- a/schema/field/field_test.go +++ b/schema/field/field_test.go @@ -187,12 +187,19 @@ func (*Pair) Scan(interface{}) error { return nil } func (Pair) Value() (driver.Value, error) { return nil, nil } func TestBytes(t *testing.T) { - fd := field.Bytes("active").Default([]byte("{}")).Comment("comment").Descriptor() + fd := field.Bytes("active"). + Default([]byte("{}")). + Comment("comment"). + Validate(func(bytes []byte) error { + return nil + }). + Descriptor() assert.Equal(t, "active", fd.Name) assert.Equal(t, field.TypeBytes, fd.Info.Type) assert.NotNil(t, fd.Default) assert.Equal(t, []byte("{}"), fd.Default) assert.Equal(t, "comment", fd.Comment) + assert.Len(t, fd.Validators, 1) fd = field.Bytes("ip").GoType(net.IP("127.0.0.1")).Descriptor() assert.NoError(t, fd.Err)