From 9f917c72634ee4e989ba6013ad1b139889ca8bb8 Mon Sep 17 00:00:00 2001 From: Jannik Clausen <12862103+masseelch@users.noreply.github.com> Date: Tue, 30 May 2023 10:02:27 +0200 Subject: [PATCH] schema/fields: validate for slices builder (#3566) This PR changes the way slice types are built and adds the possibility to add a custom validation function to json slice types. --- entc/gen/template/meta.tmpl | 7 +- entc/gen/template/runtime.tmpl | 9 +- entc/gen/type.go | 4 +- entc/integration/json/ent/migrate/schema.go | 3 + entc/integration/json/ent/mutation.go | 318 ++++++++++++++++++-- entc/integration/json/ent/runtime.go | 12 + entc/integration/json/ent/schema/user.go | 18 ++ entc/integration/json/ent/user.go | 41 ++- entc/integration/json/ent/user/user.go | 15 + entc/integration/json/ent/user/where.go | 30 ++ entc/integration/json/ent/user_create.go | 45 +++ entc/integration/json/ent/user_update.go | 220 ++++++++++++++ entc/integration/json/json_test.go | 39 +++ schema/field/field.go | 126 +++++++- schema/field/field_test.go | 44 +++ 15 files changed, 894 insertions(+), 37 deletions(-) diff --git a/entc/gen/template/meta.tmpl b/entc/gen/template/meta.tmpl index f984337da2..8a6d8f2da0 100644 --- a/entc/gen/template/meta.tmpl +++ b/entc/gen/template/meta.tmpl @@ -87,9 +87,12 @@ const ( {{- end }} {{- with $f.Validators }} {{- $name := $f.Validator }} - {{- $type := printf "func (%s) error" $f.Type.Type }} + {{- $type := $f.Type.Type.String }} + {{- if $f.IsJSON }} + {{- $type = $f.Type.String }} + {{- end }} // {{ $name }} is a validator for the "{{ $f.Name }}" field. It is called by the builders before save. - {{ $name }} {{ $type }} + {{ $name }} {{ printf "func (%s) error" $type }} {{- end }} {{- end }} {{- if $.HasValueScanner }} diff --git a/entc/gen/template/runtime.tmpl b/entc/gen/template/runtime.tmpl index 56c3539ac2..117b3c9f32 100644 --- a/entc/gen/template/runtime.tmpl +++ b/entc/gen/template/runtime.tmpl @@ -195,12 +195,15 @@ func init() { {{- end }} {{- with $f.Validators }} {{- $name := print $pkg "." $f.Validator }} - {{- $type := printf "func (%s) error" $f.Type.Type }} + {{- $type := $f.Type.Type.String }} + {{- if $f.IsJSON }} + {{- $type = $f.Type.String }} + {{- end }} // {{ $name }} is a validator for the "{{ $f.Name }}" field. It is called by the builders before save. {{- if eq $f.Validators 1 }} - {{ $name }} = {{ $desc }}.Validators[0].({{ $type }}) + {{ $name }} = {{ $desc }}.Validators[0].({{ printf "func (%s) error" $type }}) {{- else }} - {{ $name }} = func() {{ $type }} { + {{ $name }} = func() {{ printf "func (%s) error" $type }} { validators := {{ $desc }}.Validators fns := [...]func({{ $f.Type.Type }}) error { {{- range $j, $n := xrange $f.Validators }} diff --git a/entc/gen/type.go b/entc/gen/type.go index 71f9cd8b94..ebb2f49cee 100644 --- a/entc/gen/type.go +++ b/entc/gen/type.go @@ -1039,7 +1039,7 @@ func (t *Type) checkField(tf *Field, f *load.Field) (err error) { // Enum types should be named as follows: typepkg.Field. f.Info.Ident = fmt.Sprintf("%s.%s", t.PackageDir(), pascal(f.Name)) } - case tf.Validators > 0 && !tf.ConvertedToBasic(): + case tf.Validators > 0 && !tf.ConvertedToBasic() && f.Info.Type != field.TypeJSON: err = fmt.Errorf("GoType %q for field %q must be converted to the basic %q type for validators", tf.Type, f.Name, tf.Type.Type) case ant != nil && ant.Default != "" && (ant.DefaultExpr != "" || ant.DefaultExprs != nil): err = fmt.Errorf("field %q cannot have both default value and default expression annotations", f.Name) @@ -1804,6 +1804,8 @@ func (f Field) BasicType(ident string) (expr string) { case rt.TypeEqual(nullStringType) || rt.TypeEqual(nullStringPType): expr = fmt.Sprintf("%s.String", ident) } + case field.TypeJSON: + expr = ident default: if t.Numeric() && rt.Kind >= reflect.Int && rt.Kind <= reflect.Float64 { expr = fmt.Sprintf("%s(%s)", rt.Kind, ident) diff --git a/entc/integration/json/ent/migrate/schema.go b/entc/integration/json/ent/migrate/schema.go index 25e0664e3b..ee2701f980 100644 --- a/entc/integration/json/ent/migrate/schema.go +++ b/entc/integration/json/ent/migrate/schema.go @@ -23,6 +23,9 @@ var ( {Name: "ints", Type: field.TypeJSON, Nullable: true}, {Name: "floats", Type: field.TypeJSON, Nullable: true}, {Name: "strings", Type: field.TypeJSON, Nullable: true}, + {Name: "ints_validate", Type: field.TypeJSON, Nullable: true}, + {Name: "floats_validate", Type: field.TypeJSON, Nullable: true}, + {Name: "strings_validate", Type: field.TypeJSON, Nullable: true}, {Name: "addr", Type: field.TypeJSON, Nullable: true}, {Name: "unknown", Type: field.TypeJSON, Nullable: true}, } diff --git a/entc/integration/json/ent/mutation.go b/entc/integration/json/ent/mutation.go index 0c6fce7542..7a0971a97d 100644 --- a/entc/integration/json/ent/mutation.go +++ b/entc/integration/json/ent/mutation.go @@ -37,29 +37,35 @@ const ( // UserMutation represents an operation that mutates the User nodes in the graph. type UserMutation struct { config - op Op - typ string - id *int - t **schema.T - url **url.URL - _URLs *[]*url.URL - append_URLs []*url.URL - raw *json.RawMessage - appendraw json.RawMessage - dirs *[]http.Dir - appenddirs []http.Dir - ints *[]int - appendints []int - floats *[]float64 - appendfloats []float64 - strings *[]string - appendstrings []string - addr *schema.Addr - unknown *any - clearedFields map[string]struct{} - done bool - oldValue func(context.Context) (*User, error) - predicates []predicate.User + op Op + typ string + id *int + t **schema.T + url **url.URL + _URLs *[]*url.URL + append_URLs []*url.URL + raw *json.RawMessage + appendraw json.RawMessage + dirs *[]http.Dir + appenddirs []http.Dir + ints *[]int + appendints []int + floats *[]float64 + appendfloats []float64 + strings *[]string + appendstrings []string + ints_validate *[]int + appendints_validate []int + floats_validate *[]float64 + appendfloats_validate []float64 + strings_validate *[]string + appendstrings_validate []string + addr *schema.Addr + unknown *any + clearedFields map[string]struct{} + done bool + oldValue func(context.Context) (*User, error) + predicates []predicate.User } var _ ent.Mutation = (*UserMutation)(nil) @@ -634,6 +640,201 @@ func (m *UserMutation) ResetStrings() { delete(m.clearedFields, user.FieldStrings) } +// SetIntsValidate sets the "ints_validate" field. +func (m *UserMutation) SetIntsValidate(i []int) { + m.ints_validate = &i + m.appendints_validate = nil +} + +// IntsValidate returns the value of the "ints_validate" field in the mutation. +func (m *UserMutation) IntsValidate() (r []int, exists bool) { + v := m.ints_validate + if v == nil { + return + } + return *v, true +} + +// OldIntsValidate returns the old "ints_validate" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldIntsValidate(ctx context.Context) (v []int, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldIntsValidate is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldIntsValidate requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldIntsValidate: %w", err) + } + return oldValue.IntsValidate, nil +} + +// AppendIntsValidate adds i to the "ints_validate" field. +func (m *UserMutation) AppendIntsValidate(i []int) { + m.appendints_validate = append(m.appendints_validate, i...) +} + +// AppendedIntsValidate returns the list of values that were appended to the "ints_validate" field in this mutation. +func (m *UserMutation) AppendedIntsValidate() ([]int, bool) { + if len(m.appendints_validate) == 0 { + return nil, false + } + return m.appendints_validate, true +} + +// ClearIntsValidate clears the value of the "ints_validate" field. +func (m *UserMutation) ClearIntsValidate() { + m.ints_validate = nil + m.appendints_validate = nil + m.clearedFields[user.FieldIntsValidate] = struct{}{} +} + +// IntsValidateCleared returns if the "ints_validate" field was cleared in this mutation. +func (m *UserMutation) IntsValidateCleared() bool { + _, ok := m.clearedFields[user.FieldIntsValidate] + return ok +} + +// ResetIntsValidate resets all changes to the "ints_validate" field. +func (m *UserMutation) ResetIntsValidate() { + m.ints_validate = nil + m.appendints_validate = nil + delete(m.clearedFields, user.FieldIntsValidate) +} + +// SetFloatsValidate sets the "floats_validate" field. +func (m *UserMutation) SetFloatsValidate(f []float64) { + m.floats_validate = &f + m.appendfloats_validate = nil +} + +// FloatsValidate returns the value of the "floats_validate" field in the mutation. +func (m *UserMutation) FloatsValidate() (r []float64, exists bool) { + v := m.floats_validate + if v == nil { + return + } + return *v, true +} + +// OldFloatsValidate returns the old "floats_validate" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldFloatsValidate(ctx context.Context) (v []float64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldFloatsValidate is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldFloatsValidate requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldFloatsValidate: %w", err) + } + return oldValue.FloatsValidate, nil +} + +// AppendFloatsValidate adds f to the "floats_validate" field. +func (m *UserMutation) AppendFloatsValidate(f []float64) { + m.appendfloats_validate = append(m.appendfloats_validate, f...) +} + +// AppendedFloatsValidate returns the list of values that were appended to the "floats_validate" field in this mutation. +func (m *UserMutation) AppendedFloatsValidate() ([]float64, bool) { + if len(m.appendfloats_validate) == 0 { + return nil, false + } + return m.appendfloats_validate, true +} + +// ClearFloatsValidate clears the value of the "floats_validate" field. +func (m *UserMutation) ClearFloatsValidate() { + m.floats_validate = nil + m.appendfloats_validate = nil + m.clearedFields[user.FieldFloatsValidate] = struct{}{} +} + +// FloatsValidateCleared returns if the "floats_validate" field was cleared in this mutation. +func (m *UserMutation) FloatsValidateCleared() bool { + _, ok := m.clearedFields[user.FieldFloatsValidate] + return ok +} + +// ResetFloatsValidate resets all changes to the "floats_validate" field. +func (m *UserMutation) ResetFloatsValidate() { + m.floats_validate = nil + m.appendfloats_validate = nil + delete(m.clearedFields, user.FieldFloatsValidate) +} + +// SetStringsValidate sets the "strings_validate" field. +func (m *UserMutation) SetStringsValidate(s []string) { + m.strings_validate = &s + m.appendstrings_validate = nil +} + +// StringsValidate returns the value of the "strings_validate" field in the mutation. +func (m *UserMutation) StringsValidate() (r []string, exists bool) { + v := m.strings_validate + if v == nil { + return + } + return *v, true +} + +// OldStringsValidate returns the old "strings_validate" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldStringsValidate(ctx context.Context) (v []string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldStringsValidate is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldStringsValidate requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldStringsValidate: %w", err) + } + return oldValue.StringsValidate, nil +} + +// AppendStringsValidate adds s to the "strings_validate" field. +func (m *UserMutation) AppendStringsValidate(s []string) { + m.appendstrings_validate = append(m.appendstrings_validate, s...) +} + +// AppendedStringsValidate returns the list of values that were appended to the "strings_validate" field in this mutation. +func (m *UserMutation) AppendedStringsValidate() ([]string, bool) { + if len(m.appendstrings_validate) == 0 { + return nil, false + } + return m.appendstrings_validate, true +} + +// ClearStringsValidate clears the value of the "strings_validate" field. +func (m *UserMutation) ClearStringsValidate() { + m.strings_validate = nil + m.appendstrings_validate = nil + m.clearedFields[user.FieldStringsValidate] = struct{}{} +} + +// StringsValidateCleared returns if the "strings_validate" field was cleared in this mutation. +func (m *UserMutation) StringsValidateCleared() bool { + _, ok := m.clearedFields[user.FieldStringsValidate] + return ok +} + +// ResetStringsValidate resets all changes to the "strings_validate" field. +func (m *UserMutation) ResetStringsValidate() { + m.strings_validate = nil + m.appendstrings_validate = nil + delete(m.clearedFields, user.FieldStringsValidate) +} + // SetAddr sets the "addr" field. func (m *UserMutation) SetAddr(s schema.Addr) { m.addr = &s @@ -766,7 +967,7 @@ func (m *UserMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *UserMutation) Fields() []string { - fields := make([]string, 0, 10) + fields := make([]string, 0, 13) if m.t != nil { fields = append(fields, user.FieldT) } @@ -791,6 +992,15 @@ func (m *UserMutation) Fields() []string { if m.strings != nil { fields = append(fields, user.FieldStrings) } + if m.ints_validate != nil { + fields = append(fields, user.FieldIntsValidate) + } + if m.floats_validate != nil { + fields = append(fields, user.FieldFloatsValidate) + } + if m.strings_validate != nil { + fields = append(fields, user.FieldStringsValidate) + } if m.addr != nil { fields = append(fields, user.FieldAddr) } @@ -821,6 +1031,12 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) { return m.Floats() case user.FieldStrings: return m.Strings() + case user.FieldIntsValidate: + return m.IntsValidate() + case user.FieldFloatsValidate: + return m.FloatsValidate() + case user.FieldStringsValidate: + return m.StringsValidate() case user.FieldAddr: return m.Addr() case user.FieldUnknown: @@ -850,6 +1066,12 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er return m.OldFloats(ctx) case user.FieldStrings: return m.OldStrings(ctx) + case user.FieldIntsValidate: + return m.OldIntsValidate(ctx) + case user.FieldFloatsValidate: + return m.OldFloatsValidate(ctx) + case user.FieldStringsValidate: + return m.OldStringsValidate(ctx) case user.FieldAddr: return m.OldAddr(ctx) case user.FieldUnknown: @@ -919,6 +1141,27 @@ func (m *UserMutation) SetField(name string, value ent.Value) error { } m.SetStrings(v) return nil + case user.FieldIntsValidate: + v, ok := value.([]int) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetIntsValidate(v) + return nil + case user.FieldFloatsValidate: + v, ok := value.([]float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetFloatsValidate(v) + return nil + case user.FieldStringsValidate: + v, ok := value.([]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetStringsValidate(v) + return nil case user.FieldAddr: v, ok := value.(schema.Addr) if !ok { @@ -984,6 +1227,15 @@ func (m *UserMutation) ClearedFields() []string { if m.FieldCleared(user.FieldStrings) { fields = append(fields, user.FieldStrings) } + if m.FieldCleared(user.FieldIntsValidate) { + fields = append(fields, user.FieldIntsValidate) + } + if m.FieldCleared(user.FieldFloatsValidate) { + fields = append(fields, user.FieldFloatsValidate) + } + if m.FieldCleared(user.FieldStringsValidate) { + fields = append(fields, user.FieldStringsValidate) + } if m.FieldCleared(user.FieldAddr) { fields = append(fields, user.FieldAddr) } @@ -1025,6 +1277,15 @@ func (m *UserMutation) ClearField(name string) error { case user.FieldStrings: m.ClearStrings() return nil + case user.FieldIntsValidate: + m.ClearIntsValidate() + return nil + case user.FieldFloatsValidate: + m.ClearFloatsValidate() + return nil + case user.FieldStringsValidate: + m.ClearStringsValidate() + return nil case user.FieldAddr: m.ClearAddr() return nil @@ -1063,6 +1324,15 @@ func (m *UserMutation) ResetField(name string) error { case user.FieldStrings: m.ResetStrings() return nil + case user.FieldIntsValidate: + m.ResetIntsValidate() + return nil + case user.FieldFloatsValidate: + m.ResetFloatsValidate() + return nil + case user.FieldStringsValidate: + m.ResetStringsValidate() + return nil case user.FieldAddr: m.ResetAddr() return nil diff --git a/entc/integration/json/ent/runtime.go b/entc/integration/json/ent/runtime.go index 6fd4c2fc66..4869a2e2fd 100644 --- a/entc/integration/json/ent/runtime.go +++ b/entc/integration/json/ent/runtime.go @@ -27,4 +27,16 @@ func init() { userDescInts := userFields[5].Descriptor() // user.DefaultInts holds the default value on creation for the ints field. user.DefaultInts = userDescInts.Default.([]int) + // userDescIntsValidate is the schema descriptor for ints_validate field. + userDescIntsValidate := userFields[8].Descriptor() + // user.IntsValidateValidator is a validator for the "ints_validate" field. It is called by the builders before save. + user.IntsValidateValidator = userDescIntsValidate.Validators[0].(func([]int) error) + // userDescFloatsValidate is the schema descriptor for floats_validate field. + userDescFloatsValidate := userFields[9].Descriptor() + // user.FloatsValidateValidator is a validator for the "floats_validate" field. It is called by the builders before save. + user.FloatsValidateValidator = userDescFloatsValidate.Validators[0].(func([]float64) error) + // userDescStringsValidate is the schema descriptor for strings_validate field. + userDescStringsValidate := userFields[10].Descriptor() + // user.StringsValidateValidator is a validator for the "strings_validate" field. It is called by the builders before save. + user.StringsValidateValidator = userDescStringsValidate.Validators[0].(func([]string) error) } diff --git a/entc/integration/json/ent/schema/user.go b/entc/integration/json/ent/schema/user.go index bc9a16e357..dc531b17d3 100644 --- a/entc/integration/json/ent/schema/user.go +++ b/entc/integration/json/ent/schema/user.go @@ -45,6 +45,15 @@ func (User) Fields() []ent.Field { Optional(), field.Strings("strings"). Optional(), + field.Ints("ints_validate"). + Optional(). + Validate(validate[int]), + field.Floats("floats_validate"). + Optional(). + Validate(validate[float64]), + field.Strings("strings_validate"). + Optional(). + Validate(validate[string]), field.JSON("addr", Addr{}). Sensitive(). Optional(), @@ -108,3 +117,12 @@ func (a Addr) String() string { } return a.Addr.String() } + +var ErrValidate = errors.New("validation error") + +func validate[T int | string | float64](xs []T) error { + if xs != nil { + return ErrValidate + } + return nil +} diff --git a/entc/integration/json/ent/user.go b/entc/integration/json/ent/user.go index 1bf93e7d10..2578950eb7 100644 --- a/entc/integration/json/ent/user.go +++ b/entc/integration/json/ent/user.go @@ -40,6 +40,12 @@ type User struct { Floats []float64 `json:"floats,omitempty"` // Strings holds the value of the "strings" field. Strings []string `json:"strings,omitempty"` + // IntsValidate holds the value of the "ints_validate" field. + IntsValidate []int `json:"ints_validate,omitempty"` + // FloatsValidate holds the value of the "floats_validate" field. + FloatsValidate []float64 `json:"floats_validate,omitempty"` + // StringsValidate holds the value of the "strings_validate" field. + StringsValidate []string `json:"strings_validate,omitempty"` // Addr holds the value of the "addr" field. Addr schema.Addr `json:"-"` // Unknown holds the value of the "unknown" field. @@ -52,7 +58,7 @@ func (*User) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case user.FieldT, user.FieldURL, user.FieldURLs, user.FieldRaw, user.FieldDirs, user.FieldInts, user.FieldFloats, user.FieldStrings, user.FieldAddr, user.FieldUnknown: + case user.FieldT, user.FieldURL, user.FieldURLs, user.FieldRaw, user.FieldDirs, user.FieldInts, user.FieldFloats, user.FieldStrings, user.FieldIntsValidate, user.FieldFloatsValidate, user.FieldStringsValidate, user.FieldAddr, user.FieldUnknown: values[i] = new([]byte) case user.FieldID: values[i] = new(sql.NullInt64) @@ -141,6 +147,30 @@ func (u *User) assignValues(columns []string, values []any) error { return fmt.Errorf("unmarshal field strings: %w", err) } } + case user.FieldIntsValidate: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field ints_validate", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &u.IntsValidate); err != nil { + return fmt.Errorf("unmarshal field ints_validate: %w", err) + } + } + case user.FieldFloatsValidate: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field floats_validate", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &u.FloatsValidate); err != nil { + return fmt.Errorf("unmarshal field floats_validate: %w", err) + } + } + case user.FieldStringsValidate: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field strings_validate", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &u.StringsValidate); err != nil { + return fmt.Errorf("unmarshal field strings_validate: %w", err) + } + } case user.FieldAddr: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field addr", values[i]) @@ -217,6 +247,15 @@ func (u *User) String() string { builder.WriteString("strings=") builder.WriteString(fmt.Sprintf("%v", u.Strings)) builder.WriteString(", ") + builder.WriteString("ints_validate=") + builder.WriteString(fmt.Sprintf("%v", u.IntsValidate)) + builder.WriteString(", ") + builder.WriteString("floats_validate=") + builder.WriteString(fmt.Sprintf("%v", u.FloatsValidate)) + builder.WriteString(", ") + builder.WriteString("strings_validate=") + builder.WriteString(fmt.Sprintf("%v", u.StringsValidate)) + builder.WriteString(", ") builder.WriteString("addr=") builder.WriteString(", ") builder.WriteString("unknown=") diff --git a/entc/integration/json/ent/user/user.go b/entc/integration/json/ent/user/user.go index 19cb834a8c..cbf91662c2 100644 --- a/entc/integration/json/ent/user/user.go +++ b/entc/integration/json/ent/user/user.go @@ -33,6 +33,12 @@ const ( FieldFloats = "floats" // FieldStrings holds the string denoting the strings field in the database. FieldStrings = "strings" + // FieldIntsValidate holds the string denoting the ints_validate field in the database. + FieldIntsValidate = "ints_validate" + // FieldFloatsValidate holds the string denoting the floats_validate field in the database. + FieldFloatsValidate = "floats_validate" + // FieldStringsValidate holds the string denoting the strings_validate field in the database. + FieldStringsValidate = "strings_validate" // FieldAddr holds the string denoting the addr field in the database. FieldAddr = "addr" // FieldUnknown holds the string denoting the unknown field in the database. @@ -52,6 +58,9 @@ var Columns = []string{ FieldInts, FieldFloats, FieldStrings, + FieldIntsValidate, + FieldFloatsValidate, + FieldStringsValidate, FieldAddr, FieldUnknown, } @@ -71,6 +80,12 @@ var ( DefaultDirs func() []http.Dir // DefaultInts holds the default value on creation for the "ints" field. DefaultInts []int + // IntsValidateValidator is a validator for the "ints_validate" field. It is called by the builders before save. + IntsValidateValidator func([]int) error + // FloatsValidateValidator is a validator for the "floats_validate" field. It is called by the builders before save. + FloatsValidateValidator func([]float64) error + // StringsValidateValidator is a validator for the "strings_validate" field. It is called by the builders before save. + StringsValidateValidator func([]string) error ) // OrderOption defines the ordering options for the User queries. diff --git a/entc/integration/json/ent/user/where.go b/entc/integration/json/ent/user/where.go index 12f9bc39a7..d450374ec2 100644 --- a/entc/integration/json/ent/user/where.go +++ b/entc/integration/json/ent/user/where.go @@ -126,6 +126,36 @@ func StringsNotNil() predicate.User { return predicate.User(sql.FieldNotNull(FieldStrings)) } +// IntsValidateIsNil applies the IsNil predicate on the "ints_validate" field. +func IntsValidateIsNil() predicate.User { + return predicate.User(sql.FieldIsNull(FieldIntsValidate)) +} + +// IntsValidateNotNil applies the NotNil predicate on the "ints_validate" field. +func IntsValidateNotNil() predicate.User { + return predicate.User(sql.FieldNotNull(FieldIntsValidate)) +} + +// FloatsValidateIsNil applies the IsNil predicate on the "floats_validate" field. +func FloatsValidateIsNil() predicate.User { + return predicate.User(sql.FieldIsNull(FieldFloatsValidate)) +} + +// FloatsValidateNotNil applies the NotNil predicate on the "floats_validate" field. +func FloatsValidateNotNil() predicate.User { + return predicate.User(sql.FieldNotNull(FieldFloatsValidate)) +} + +// StringsValidateIsNil applies the IsNil predicate on the "strings_validate" field. +func StringsValidateIsNil() predicate.User { + return predicate.User(sql.FieldIsNull(FieldStringsValidate)) +} + +// StringsValidateNotNil applies the NotNil predicate on the "strings_validate" field. +func StringsValidateNotNil() predicate.User { + return predicate.User(sql.FieldNotNull(FieldStringsValidate)) +} + // AddrIsNil applies the IsNil predicate on the "addr" field. func AddrIsNil() predicate.User { return predicate.User(sql.FieldIsNull(FieldAddr)) diff --git a/entc/integration/json/ent/user_create.go b/entc/integration/json/ent/user_create.go index ec2bcfddf8..7da3c083d8 100644 --- a/entc/integration/json/ent/user_create.go +++ b/entc/integration/json/ent/user_create.go @@ -75,6 +75,24 @@ func (uc *UserCreate) SetStrings(s []string) *UserCreate { return uc } +// SetIntsValidate sets the "ints_validate" field. +func (uc *UserCreate) SetIntsValidate(i []int) *UserCreate { + uc.mutation.SetIntsValidate(i) + return uc +} + +// SetFloatsValidate sets the "floats_validate" field. +func (uc *UserCreate) SetFloatsValidate(f []float64) *UserCreate { + uc.mutation.SetFloatsValidate(f) + return uc +} + +// SetStringsValidate sets the "strings_validate" field. +func (uc *UserCreate) SetStringsValidate(s []string) *UserCreate { + uc.mutation.SetStringsValidate(s) + return uc +} + // SetAddr sets the "addr" field. func (uc *UserCreate) SetAddr(s schema.Addr) *UserCreate { uc.mutation.SetAddr(s) @@ -145,6 +163,21 @@ func (uc *UserCreate) check() error { if _, ok := uc.mutation.Dirs(); !ok { return &ValidationError{Name: "dirs", err: errors.New(`ent: missing required field "User.dirs"`)} } + if v, ok := uc.mutation.IntsValidate(); ok { + if err := user.IntsValidateValidator(v); err != nil { + return &ValidationError{Name: "ints_validate", err: fmt.Errorf(`ent: validator failed for field "User.ints_validate": %w`, err)} + } + } + if v, ok := uc.mutation.FloatsValidate(); ok { + if err := user.FloatsValidateValidator(v); err != nil { + return &ValidationError{Name: "floats_validate", err: fmt.Errorf(`ent: validator failed for field "User.floats_validate": %w`, err)} + } + } + if v, ok := uc.mutation.StringsValidate(); ok { + if err := user.StringsValidateValidator(v); err != nil { + return &ValidationError{Name: "strings_validate", err: fmt.Errorf(`ent: validator failed for field "User.strings_validate": %w`, err)} + } + } return nil } @@ -203,6 +236,18 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { _spec.SetField(user.FieldStrings, field.TypeJSON, value) _node.Strings = value } + if value, ok := uc.mutation.IntsValidate(); ok { + _spec.SetField(user.FieldIntsValidate, field.TypeJSON, value) + _node.IntsValidate = value + } + if value, ok := uc.mutation.FloatsValidate(); ok { + _spec.SetField(user.FieldFloatsValidate, field.TypeJSON, value) + _node.FloatsValidate = value + } + if value, ok := uc.mutation.StringsValidate(); ok { + _spec.SetField(user.FieldStringsValidate, field.TypeJSON, value) + _node.StringsValidate = value + } if value, ok := uc.mutation.Addr(); ok { _spec.SetField(user.FieldAddr, field.TypeJSON, value) _node.Addr = value diff --git a/entc/integration/json/ent/user_update.go b/entc/integration/json/ent/user_update.go index 870fc20d57..eea322ffbd 100644 --- a/entc/integration/json/ent/user_update.go +++ b/entc/integration/json/ent/user_update.go @@ -163,6 +163,60 @@ func (uu *UserUpdate) ClearStrings() *UserUpdate { return uu } +// SetIntsValidate sets the "ints_validate" field. +func (uu *UserUpdate) SetIntsValidate(i []int) *UserUpdate { + uu.mutation.SetIntsValidate(i) + return uu +} + +// AppendIntsValidate appends i to the "ints_validate" field. +func (uu *UserUpdate) AppendIntsValidate(i []int) *UserUpdate { + uu.mutation.AppendIntsValidate(i) + return uu +} + +// ClearIntsValidate clears the value of the "ints_validate" field. +func (uu *UserUpdate) ClearIntsValidate() *UserUpdate { + uu.mutation.ClearIntsValidate() + return uu +} + +// SetFloatsValidate sets the "floats_validate" field. +func (uu *UserUpdate) SetFloatsValidate(f []float64) *UserUpdate { + uu.mutation.SetFloatsValidate(f) + return uu +} + +// AppendFloatsValidate appends f to the "floats_validate" field. +func (uu *UserUpdate) AppendFloatsValidate(f []float64) *UserUpdate { + uu.mutation.AppendFloatsValidate(f) + return uu +} + +// ClearFloatsValidate clears the value of the "floats_validate" field. +func (uu *UserUpdate) ClearFloatsValidate() *UserUpdate { + uu.mutation.ClearFloatsValidate() + return uu +} + +// SetStringsValidate sets the "strings_validate" field. +func (uu *UserUpdate) SetStringsValidate(s []string) *UserUpdate { + uu.mutation.SetStringsValidate(s) + return uu +} + +// AppendStringsValidate appends s to the "strings_validate" field. +func (uu *UserUpdate) AppendStringsValidate(s []string) *UserUpdate { + uu.mutation.AppendStringsValidate(s) + return uu +} + +// ClearStringsValidate clears the value of the "strings_validate" field. +func (uu *UserUpdate) ClearStringsValidate() *UserUpdate { + uu.mutation.ClearStringsValidate() + return uu +} + // SetAddr sets the "addr" field. func (uu *UserUpdate) SetAddr(s schema.Addr) *UserUpdate { uu.mutation.SetAddr(s) @@ -227,6 +281,26 @@ func (uu *UserUpdate) ExecX(ctx context.Context) { } } +// check runs all checks and user-defined validators on the builder. +func (uu *UserUpdate) check() error { + if v, ok := uu.mutation.IntsValidate(); ok { + if err := user.IntsValidateValidator(v); err != nil { + return &ValidationError{Name: "ints_validate", err: fmt.Errorf(`ent: validator failed for field "User.ints_validate": %w`, err)} + } + } + if v, ok := uu.mutation.FloatsValidate(); ok { + if err := user.FloatsValidateValidator(v); err != nil { + return &ValidationError{Name: "floats_validate", err: fmt.Errorf(`ent: validator failed for field "User.floats_validate": %w`, err)} + } + } + if v, ok := uu.mutation.StringsValidate(); ok { + if err := user.StringsValidateValidator(v); err != nil { + return &ValidationError{Name: "strings_validate", err: fmt.Errorf(`ent: validator failed for field "User.strings_validate": %w`, err)} + } + } + return nil +} + // Modify adds a statement modifier for attaching custom logic to the UPDATE statement. func (uu *UserUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *UserUpdate { uu.modifiers = append(uu.modifiers, modifiers...) @@ -234,6 +308,9 @@ func (uu *UserUpdate) Modify(modifiers ...func(u *sql.UpdateBuilder)) *UserUpdat } func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { + if err := uu.check(); err != nil { + return n, err + } _spec := sqlgraph.NewUpdateSpec(user.Table, user.Columns, sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt)) if ps := uu.mutation.predicates; len(ps) > 0 { _spec.Predicate = func(selector *sql.Selector) { @@ -317,6 +394,39 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { if uu.mutation.StringsCleared() { _spec.ClearField(user.FieldStrings, field.TypeJSON) } + if value, ok := uu.mutation.IntsValidate(); ok { + _spec.SetField(user.FieldIntsValidate, field.TypeJSON, value) + } + if value, ok := uu.mutation.AppendedIntsValidate(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, user.FieldIntsValidate, value) + }) + } + if uu.mutation.IntsValidateCleared() { + _spec.ClearField(user.FieldIntsValidate, field.TypeJSON) + } + if value, ok := uu.mutation.FloatsValidate(); ok { + _spec.SetField(user.FieldFloatsValidate, field.TypeJSON, value) + } + if value, ok := uu.mutation.AppendedFloatsValidate(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, user.FieldFloatsValidate, value) + }) + } + if uu.mutation.FloatsValidateCleared() { + _spec.ClearField(user.FieldFloatsValidate, field.TypeJSON) + } + if value, ok := uu.mutation.StringsValidate(); ok { + _spec.SetField(user.FieldStringsValidate, field.TypeJSON, value) + } + if value, ok := uu.mutation.AppendedStringsValidate(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, user.FieldStringsValidate, value) + }) + } + if uu.mutation.StringsValidateCleared() { + _spec.ClearField(user.FieldStringsValidate, field.TypeJSON) + } if value, ok := uu.mutation.Addr(); ok { _spec.SetField(user.FieldAddr, field.TypeJSON, value) } @@ -477,6 +587,60 @@ func (uuo *UserUpdateOne) ClearStrings() *UserUpdateOne { return uuo } +// SetIntsValidate sets the "ints_validate" field. +func (uuo *UserUpdateOne) SetIntsValidate(i []int) *UserUpdateOne { + uuo.mutation.SetIntsValidate(i) + return uuo +} + +// AppendIntsValidate appends i to the "ints_validate" field. +func (uuo *UserUpdateOne) AppendIntsValidate(i []int) *UserUpdateOne { + uuo.mutation.AppendIntsValidate(i) + return uuo +} + +// ClearIntsValidate clears the value of the "ints_validate" field. +func (uuo *UserUpdateOne) ClearIntsValidate() *UserUpdateOne { + uuo.mutation.ClearIntsValidate() + return uuo +} + +// SetFloatsValidate sets the "floats_validate" field. +func (uuo *UserUpdateOne) SetFloatsValidate(f []float64) *UserUpdateOne { + uuo.mutation.SetFloatsValidate(f) + return uuo +} + +// AppendFloatsValidate appends f to the "floats_validate" field. +func (uuo *UserUpdateOne) AppendFloatsValidate(f []float64) *UserUpdateOne { + uuo.mutation.AppendFloatsValidate(f) + return uuo +} + +// ClearFloatsValidate clears the value of the "floats_validate" field. +func (uuo *UserUpdateOne) ClearFloatsValidate() *UserUpdateOne { + uuo.mutation.ClearFloatsValidate() + return uuo +} + +// SetStringsValidate sets the "strings_validate" field. +func (uuo *UserUpdateOne) SetStringsValidate(s []string) *UserUpdateOne { + uuo.mutation.SetStringsValidate(s) + return uuo +} + +// AppendStringsValidate appends s to the "strings_validate" field. +func (uuo *UserUpdateOne) AppendStringsValidate(s []string) *UserUpdateOne { + uuo.mutation.AppendStringsValidate(s) + return uuo +} + +// ClearStringsValidate clears the value of the "strings_validate" field. +func (uuo *UserUpdateOne) ClearStringsValidate() *UserUpdateOne { + uuo.mutation.ClearStringsValidate() + return uuo +} + // SetAddr sets the "addr" field. func (uuo *UserUpdateOne) SetAddr(s schema.Addr) *UserUpdateOne { uuo.mutation.SetAddr(s) @@ -554,6 +718,26 @@ func (uuo *UserUpdateOne) ExecX(ctx context.Context) { } } +// check runs all checks and user-defined validators on the builder. +func (uuo *UserUpdateOne) check() error { + if v, ok := uuo.mutation.IntsValidate(); ok { + if err := user.IntsValidateValidator(v); err != nil { + return &ValidationError{Name: "ints_validate", err: fmt.Errorf(`ent: validator failed for field "User.ints_validate": %w`, err)} + } + } + if v, ok := uuo.mutation.FloatsValidate(); ok { + if err := user.FloatsValidateValidator(v); err != nil { + return &ValidationError{Name: "floats_validate", err: fmt.Errorf(`ent: validator failed for field "User.floats_validate": %w`, err)} + } + } + if v, ok := uuo.mutation.StringsValidate(); ok { + if err := user.StringsValidateValidator(v); err != nil { + return &ValidationError{Name: "strings_validate", err: fmt.Errorf(`ent: validator failed for field "User.strings_validate": %w`, err)} + } + } + return nil +} + // Modify adds a statement modifier for attaching custom logic to the UPDATE statement. func (uuo *UserUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *UserUpdateOne { uuo.modifiers = append(uuo.modifiers, modifiers...) @@ -561,6 +745,9 @@ func (uuo *UserUpdateOne) Modify(modifiers ...func(u *sql.UpdateBuilder)) *UserU } func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) { + if err := uuo.check(); err != nil { + return _node, err + } _spec := sqlgraph.NewUpdateSpec(user.Table, user.Columns, sqlgraph.NewFieldSpec(user.FieldID, field.TypeInt)) id, ok := uuo.mutation.ID() if !ok { @@ -661,6 +848,39 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) if uuo.mutation.StringsCleared() { _spec.ClearField(user.FieldStrings, field.TypeJSON) } + if value, ok := uuo.mutation.IntsValidate(); ok { + _spec.SetField(user.FieldIntsValidate, field.TypeJSON, value) + } + if value, ok := uuo.mutation.AppendedIntsValidate(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, user.FieldIntsValidate, value) + }) + } + if uuo.mutation.IntsValidateCleared() { + _spec.ClearField(user.FieldIntsValidate, field.TypeJSON) + } + if value, ok := uuo.mutation.FloatsValidate(); ok { + _spec.SetField(user.FieldFloatsValidate, field.TypeJSON, value) + } + if value, ok := uuo.mutation.AppendedFloatsValidate(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, user.FieldFloatsValidate, value) + }) + } + if uuo.mutation.FloatsValidateCleared() { + _spec.ClearField(user.FieldFloatsValidate, field.TypeJSON) + } + if value, ok := uuo.mutation.StringsValidate(); ok { + _spec.SetField(user.FieldStringsValidate, field.TypeJSON, value) + } + if value, ok := uuo.mutation.AppendedStringsValidate(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, user.FieldStringsValidate, value) + }) + } + if uuo.mutation.StringsValidateCleared() { + _spec.ClearField(user.FieldStringsValidate, field.TypeJSON) + } if value, ok := uuo.mutation.Addr(); ok { _spec.SetField(user.FieldAddr, field.TypeJSON, value) } diff --git a/entc/integration/json/json_test.go b/entc/integration/json/json_test.go index 87ded2d4b6..de210ae65e 100644 --- a/entc/integration/json/json_test.go +++ b/entc/integration/json/json_test.go @@ -54,6 +54,9 @@ func TestMySQL(t *testing.T) { URLs(t, client) Ints(t, client) Strings(t, client) + IntsValidate(t, client) + FloatsValidate(t, client) + StringsValidate(t, client) Predicates(t, client) Order(t, client) } @@ -86,6 +89,9 @@ func TestMaria(t *testing.T) { Ints(t, client) Floats(t, client) Strings(t, client) + IntsValidate(t, client) + FloatsValidate(t, client) + StringsValidate(t, client) NetAddr(t, client) RawMessage(t, client) Any(t, client) @@ -120,6 +126,9 @@ func TestPostgres(t *testing.T) { Ints(t, client) Floats(t, client) Strings(t, client) + IntsValidate(t, client) + FloatsValidate(t, client) + StringsValidate(t, client) NetAddr(t, client) RawMessage(t, client) Any(t, client) @@ -143,6 +152,9 @@ func TestSQLite(t *testing.T) { Ints(t, client) Floats(t, client) Strings(t, client) + IntsValidate(t, client) + FloatsValidate(t, client) + StringsValidate(t, client) NetAddr(t, client) RawMessage(t, client) Any(t, client) @@ -172,6 +184,15 @@ func Ints(t *testing.T, client *ent.Client) { require.Equal(t, []int{1, 2, 3, 4, 5, 6}, usr.Ints) } +func IntsValidate(t *testing.T, client *ent.Client) { + ctx := context.Background() + xs := []int{1, 2, 3} + err := client.User.Create().SetIntsValidate(xs).Exec(ctx) + require.ErrorIs(t, err, schema.ErrValidate) + err = client.User.Create().Exec(ctx) + require.NoError(t, err, schema.ErrValidate) +} + func Floats(t *testing.T, client *ent.Client) { ctx := context.Background() flts := []float64{1, 2, 3} @@ -186,6 +207,15 @@ func Floats(t *testing.T, client *ent.Client) { require.Empty(t, client.User.GetX(ctx, usr.ID).Floats) } +func FloatsValidate(t *testing.T, client *ent.Client) { + ctx := context.Background() + xs := []float64{1, 2, 3} + err := client.User.Create().SetFloatsValidate(xs).Exec(ctx) + require.ErrorIs(t, err, schema.ErrValidate) + err = client.User.Create().Exec(ctx) + require.NoError(t, err, schema.ErrValidate) +} + func Strings(t *testing.T, client *ent.Client) { ctx := context.Background() str := []string{"a", "b", "c"} @@ -269,6 +299,15 @@ func Strings(t *testing.T, client *ent.Client) { }) } +func StringsValidate(t *testing.T, client *ent.Client) { + ctx := context.Background() + xs := []string{"a", "b", "c"} + err := client.User.Create().SetStringsValidate(xs).Exec(ctx) + require.ErrorIs(t, err, schema.ErrValidate) + err = client.User.Create().Exec(ctx) + require.NoError(t, err, schema.ErrValidate) +} + func Any(t *testing.T, client *ent.Client) { ctx := context.Background() u := client.User.Create().SetUnknown("string").SaveX(ctx) diff --git a/schema/field/field.go b/schema/field/field.go index fc4e16bbe6..d991b88c05 100644 --- a/schema/field/field.go +++ b/schema/field/field.go @@ -96,18 +96,18 @@ func JSON(name string, typ any) *jsonBuilder { } // Strings returns a new JSON Field with type []string. -func Strings(name string) *jsonBuilder { - return JSON(name, []string{}) +func Strings(name string) *sliceBuilder[string] { + return sb[string](name) } // Ints returns a new JSON Field with type []int. -func Ints(name string) *jsonBuilder { - return JSON(name, []int{}) +func Ints(name string) *sliceBuilder[int] { + return sb[int](name) } // Floats returns a new JSON Field with type []float. -func Floats(name string) *jsonBuilder { - return JSON(name, []float64{}) +func Floats(name string) *sliceBuilder[float64] { + return sb[float64](name) } // Any returns a new JSON Field with type any. Although this field type can be @@ -809,6 +809,120 @@ func (b *jsonBuilder) Descriptor() *Descriptor { return b.desc } +type ( + sliceType interface { + int | string | float64 + } + // sliceBuilder is the builder for string slice fields. + sliceBuilder[T sliceType] struct { + *jsonBuilder + } +) + +// Validate adds a validator for this field. Operation fails if the validation fails. +func (b *sliceBuilder[T]) Validate(fn func([]T) error) *sliceBuilder[T] { + 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 *sliceBuilder[T]) StorageKey(key string) *sliceBuilder[T] { + b.desc.StorageKey = key + return b +} + +// Optional indicates that this field is optional on create. +// Unlike edges, fields are required by default. +func (b *sliceBuilder[T]) Optional() *sliceBuilder[T] { + b.desc.Optional = true + return b +} + +// Immutable indicates that this field cannot be updated. +func (b *sliceBuilder[T]) Immutable() *sliceBuilder[T] { + b.desc.Immutable = true + return b +} + +// Comment sets the comment of the field. +func (b *sliceBuilder[T]) Comment(c string) *sliceBuilder[T] { + b.desc.Comment = c + return b +} + +// Sensitive fields not printable and not serializable. +func (b *sliceBuilder[T]) Sensitive() *sliceBuilder[T] { + b.desc.Sensitive = true + return b +} + +// StructTag sets the struct tag of the field. +func (b *sliceBuilder[T]) StructTag(s string) *sliceBuilder[T] { + b.desc.Tag = s + return b +} + +// SchemaType overrides the default database type with a custom +// schema type (per dialect) for json. +// +// field.Strings("strings"). +// SchemaType(map[string]string{ +// dialect.MySQL: "json", +// dialect.Postgres: "jsonb", +// }) +func (b *sliceBuilder[T]) SchemaType(types map[string]string) *sliceBuilder[T] { + b.desc.SchemaType = types + return b +} + +// Annotations adds a list of annotations to the field object to be used by +// codegen extensions. +func (b *sliceBuilder[T]) Annotations(annotations ...schema.Annotation) *sliceBuilder[T] { + b.desc.Annotations = append(b.desc.Annotations, annotations...) + return b +} + +// Default sets the default value of the field. For example: +// +// field.Strings("names"). +// Default([]string{"a8m", "masseelch"}) +func (b *sliceBuilder[T]) Default(v []T) *sliceBuilder[T] { + b.desc.Default = v + return b +} + +// Descriptor implements the ent.Field interface by returning its descriptor. +func (b *sliceBuilder[T]) Descriptor() *Descriptor { + return b.desc +} + +// sb is a generic helper method to share code between Strings, Ints and Floats builder. +func sb[T sliceType](name string) *sliceBuilder[T] { + var typ []T + b := &jsonBuilder{&Descriptor{ + Name: name, + Info: &TypeInfo{ + Type: TypeJSON, + }, + }} + t := reflect.TypeOf(typ) + if t == nil { + b.desc.Err = errors.New("expect a Go value as JSON type but got nil") + return &sliceBuilder[T]{b} + } + b.desc.Info.Ident = t.String() + b.desc.Info.PkgPath = t.PkgPath() + b.desc.goType(typ) + b.desc.checkGoType(t) + switch t.Kind() { + case reflect.Slice, reflect.Array, reflect.Ptr, reflect.Map: + b.desc.Info.Nillable = true + b.desc.Info.PkgPath = pkgPath(t) + } + return &sliceBuilder[T]{b} +} + // enumBuilder is the builder for enum fields. type enumBuilder struct { desc *Descriptor diff --git a/schema/field/field_test.go b/schema/field/field_test.go index 275876860a..24b73b2816 100644 --- a/schema/field/field_test.go +++ b/schema/field/field_test.go @@ -346,6 +346,50 @@ func TestString_ValueScanner(t *testing.T) { require.True(t, ok) } +func TestSlices(t *testing.T) { + fd := field.Strings("strings"). + Default([]string{}). + Comment("comment"). + Validate(func(xs []string) error { + return nil + }). + Descriptor() + assert.Equal(t, "strings", fd.Name) + assert.Equal(t, field.TypeJSON, fd.Info.Type) + assert.NotNil(t, fd.Default) + assert.Equal(t, []string{}, fd.Default) + assert.Equal(t, "comment", fd.Comment) + assert.Len(t, fd.Validators, 1) + + fd = field.Ints("ints"). + Default([]int{}). + Comment("comment"). + Validate(func(xs []int) error { + return nil + }). + Descriptor() + assert.Equal(t, "ints", fd.Name) + assert.Equal(t, field.TypeJSON, fd.Info.Type) + assert.NotNil(t, fd.Default) + assert.Equal(t, []int{}, fd.Default) + assert.Equal(t, "comment", fd.Comment) + assert.Len(t, fd.Validators, 1) + + fd = field.Floats("floats"). + Default([]float64{}). + Comment("comment"). + Validate(func(xs []float64) error { + return nil + }). + Descriptor() + assert.Equal(t, "floats", fd.Name) + assert.Equal(t, field.TypeJSON, fd.Info.Type) + assert.NotNil(t, fd.Default) + assert.Equal(t, []float64{}, fd.Default) + assert.Equal(t, "comment", fd.Comment) + assert.Len(t, fd.Validators, 1) +} + type VString string func (s *VString) Scan(any) error {