feat: add fields (choice, checkbox, date)

feat: replace json encoding with mapstructure

feat: improve notblank constraint

feat: add mail contraint

feat: add template for fields
This commit is contained in:
Simon Vieille 2025-07-17 23:11:36 +02:00
commit b7c2ddeebf
Signed by: deblan
GPG key ID: 579388D585F70417
15 changed files with 702 additions and 112 deletions

View file

@ -1,53 +0,0 @@
package example
import (
"gitnet.fr/deblan/go-form/form"
"gitnet.fr/deblan/go-form/validation"
)
func CreateAddressForm() *form.Form {
return form.NewForm(
form.NewFieldText("Name").
WithOptions(
form.NewOption("label", "Name"),
form.NewOption("required", true),
form.NewOption("help", "A help!"),
).
WithConstraints(
validation.NotBlank{},
),
form.NewSubForm("Address").
WithOptions(form.NewOption("label", "Address")).
Add(
form.NewFieldTextarea("Street").
WithOptions(form.NewOption("label", "Street")).
WithConstraints(
validation.NotBlank{},
),
form.NewFieldText("City").
WithOptions(form.NewOption("label", "City")).
WithConstraints(
validation.NotBlank{},
),
form.NewFieldNumber("ZipCode").
WithOptions(
form.NewOption("label", "Zip code"),
form.NewOption("help", "A field help"),
).
WithConstraints(
validation.NotBlank{},
),
),
form.NewSubmit("submit"),
).
End().
WithMethod("POST").
// WithMethod("GET").
WithAction("/").
WithOptions(
form.NewOption("attr", map[string]string{
"id": "my-form",
}),
form.NewOption("help", "A form help!"),
)
}

135
example/form.go Normal file
View file

@ -0,0 +1,135 @@
package example
import (
"gitnet.fr/deblan/go-form/form"
"gitnet.fr/deblan/go-form/validation"
)
type Tag struct {
Name string
}
type Post struct {
Tags []Tag
Tags2 []Tag
Tags3 Tag
Tag Tag
}
func CreateExampleForm2() *form.Form {
tags := []Tag{Tag{"tag1"}, Tag{"tag2"}, Tag{"tag3"}}
choices := form.NewChoices(tags).
WithLabelBuilder(func(key int, item any) string {
return item.(Tag).Name
})
return form.NewForm(
form.NewFieldChoice("Tag").
WithOptions(
form.NewOption("choices", choices),
form.NewOption("label", "Tag"),
form.NewOption("required", true),
).
WithConstraints(
validation.NotBlank{},
),
form.NewFieldChoice("Tags").
WithSlice().
WithOptions(
form.NewOption("choices", choices),
form.NewOption("label", "Tags"),
form.NewOption("multiple", true),
form.NewOption("required", true),
).
WithConstraints(
validation.NotBlank{},
),
form.NewFieldChoice("Tags2").
WithSlice().
WithOptions(
form.NewOption("choices", choices),
form.NewOption("label", "Tags"),
form.NewOption("multiple", true),
form.NewOption("expanded", true),
form.NewOption("required", true),
).
WithConstraints(
validation.NotBlank{},
),
form.NewFieldChoice("Tag3").
WithOptions(
form.NewOption("choices", choices),
form.NewOption("label", "Tag"),
form.NewOption("expanded", true),
),
form.NewSubmit("submit"),
).
End().
WithMethod("POST").
WithAction("/")
}
func CreateExampleForm() *form.Form {
return form.NewForm(
form.NewFieldText("Name").
WithOptions(
form.NewOption("label", "Name"),
form.NewOption("required", true),
form.NewOption("help", "A help!"),
).
WithConstraints(
validation.NotBlank{},
),
form.NewFieldDate("Date").WithOptions(form.NewOption("label", "Date")),
// form.NewFieldDatetime("DateTime").WithOptions(form.NewOption("label", "DateTime")),
form.NewFieldDatetimeLocal("DateTime").WithOptions(form.NewOption("label", "DateTimeLocal")),
form.NewFieldTime("Time").WithOptions(form.NewOption("label", "Time")),
form.NewFieldCheckbox("Checkbox").WithOptions(form.NewOption("label", "Checkbox")),
form.NewSubForm("Address").
WithOptions(form.NewOption("label", "Address")).
Add(
form.NewFieldTextarea("Street").
WithOptions(form.NewOption("label", "Street")).
WithConstraints(
validation.NotBlank{},
),
form.NewFieldText("City").
WithOptions(form.NewOption("label", "City")).
WithConstraints(
validation.NotBlank{},
),
form.NewFieldNumber("ZipCode").
WithOptions(
form.NewOption("label", "Zip code"),
form.NewOption("help", "A field help"),
).
WithConstraints(
validation.NotBlank{},
),
form.NewFieldRange("Foo").
WithOptions(
form.NewOption("label", "Foo"),
),
form.NewFieldMail("Email").
WithOptions(
form.NewOption("label", "Email"),
).
WithConstraints(
validation.NotBlank{},
validation.Mail{},
),
),
form.NewSubmit("submit"),
).
End().
WithMethod("POST").
// WithMethod("GET").
WithAction("/").
WithOptions(
form.NewOption("attr", map[string]string{
"id": "my-form",
}),
form.NewOption("help", "A form help!"),
)
}

View file

@ -13,6 +13,7 @@ func FieldValidation(f *Field) bool {
isValid := true
for _, c := range f.Children {
c.ResetErrors()
isChildValid, errs := validation.Validate(c.Data, c.Constraints)
if len(errs) > 0 {
@ -24,8 +25,8 @@ func FieldValidation(f *Field) bool {
return isValid
} else {
f.ResetErrors()
isValid, errs := validation.Validate(f.Data, f.Constraints)
f.Errors = []validation.Error{}
if len(errs) > 0 {
f.Errors = errs
@ -47,6 +48,7 @@ type Field struct {
BeforeMount func(data any) (any, error)
BeforeBind func(data any) (any, error)
Validate func(f *Field) bool
IsSlice bool
Form *Form
Parent *Field
}
@ -109,6 +111,18 @@ func (f *Field) WithOptions(options ...*Option) *Field {
return f
}
func (f *Field) ResetErrors() *Field {
f.Errors = []validation.Error{}
return f
}
func (f *Field) WithSlice() *Field {
f.IsSlice = true
return f
}
func (f *Field) WithConstraints(constraints ...validation.Constraint) *Field {
for _, constraint := range constraints {
f.Constraints = append(f.Constraints, constraint)
@ -117,6 +131,18 @@ func (f *Field) WithConstraints(constraints ...validation.Constraint) *Field {
return f
}
func (f *Field) WithBeforeMount(callback func(data any) (any, error)) *Field {
f.BeforeMount = callback
return f
}
func (f *Field) WithBeforeBind(callback func(data any) (any, error)) *Field {
f.BeforeBind = callback
return f
}
func (f *Field) Add(children ...*Field) *Field {
for _, child := range children {
child.Parent = f
@ -174,18 +200,18 @@ func (f *Field) GetId() string {
}
func (f *Field) Mount(data any) error {
if len(f.Children) == 0 {
f.Data = data
return nil
}
data, err := f.BeforeMount(data)
if err != nil {
return err
}
if len(f.Children) == 0 {
f.Data = data
return nil
}
props, err := util.InspectStruct(data)
if err != nil {

32
form/field_checkbox.go Normal file
View file

@ -0,0 +1,32 @@
package form
import (
"github.com/spf13/cast"
)
func NewFieldCheckbox(name string) *Field {
f := NewField(name, "input").
WithOptions(NewOption("type", "checkbox")).
WithBeforeMount(func(data any) (any, error) {
switch data.(type) {
case string:
data = data == "1"
case bool:
return data, nil
}
return cast.ToInt(data), nil
}).
WithBeforeBind(func(data any) (any, error) {
switch data.(type) {
case string:
return data == "1", nil
case bool:
return data, nil
}
return cast.ToBool(data), nil
})
return f
}

137
form/field_choice.go Normal file
View file

@ -0,0 +1,137 @@
package form
import (
"reflect"
"github.com/spf13/cast"
)
type Choice struct {
Value string
Label string
Data any
}
func (c Choice) Match(value string) bool {
return c.Value == value
}
type Choices struct {
Data any
ValueBuilder func(key int, item any) string
LabelBuilder func(key int, item any) string
}
func (c *Choices) Match(f *Field, value string) bool {
if f.IsSlice {
v := reflect.ValueOf(f.Data)
for key, _ := range c.GetChoices() {
for i := 0; i < v.Len(); i++ {
item := v.Index(i).Interface()
switch item.(type) {
case string:
if item == value {
return true
}
default:
if c.ValueBuilder(key, item) == value {
return true
}
}
}
}
return false
}
return f.Data == value
}
func (c *Choices) WithValueBuilder(builder func(key int, item any) string) *Choices {
c.ValueBuilder = builder
return c
}
func (c *Choices) WithLabelBuilder(builder func(key int, item any) string) *Choices {
c.LabelBuilder = builder
return c
}
func (c *Choices) GetChoices() []Choice {
choices := []Choice{}
v := reflect.ValueOf(c.Data)
switch v.Kind() {
case reflect.Slice, reflect.Array, reflect.String, reflect.Map:
for i := 0; i < v.Len(); i++ {
choices = append(choices, Choice{
Value: c.ValueBuilder(i, v.Index(i).Interface()),
Label: c.LabelBuilder(i, v.Index(i).Interface()),
Data: v.Index(i).Interface(),
})
}
}
return choices
}
func NewChoices(items any) *Choices {
builder := func(key int, item any) string {
return cast.ToString(key)
}
choices := Choices{
ValueBuilder: builder,
LabelBuilder: builder,
Data: items,
}
return &choices
}
func NewFieldChoice(name string) *Field {
f := NewField(name, "choice").
WithOptions(
NewOption("choices", &Choices{}),
NewOption("expanded", false),
NewOption("multiple", false),
NewOption("empty_choice_label", ""),
)
f.WithBeforeBind(func(data any) (any, error) {
choices := f.GetOption("choices").Value.(*Choices)
switch data.(type) {
case string:
v := data.(string)
for _, c := range choices.GetChoices() {
if c.Match(v) {
return c.Data, nil
}
}
case []string:
v := reflect.ValueOf(data)
var res []interface{}
for _, choice := range choices.GetChoices() {
for i := 0; i < v.Len(); i++ {
item := v.Index(i).Interface().(string)
if choice.Match(item) {
res = append(res, choice.Data)
}
}
}
return res, nil
}
return data, nil
})
return f
}

View file

@ -13,20 +13,41 @@ func NewFieldText(name string) *Field {
func NewFieldNumber(name string) *Field {
f := NewField(name, "input").
WithOptions(NewOption("type", "number"))
WithOptions(NewOption("type", "number")).
WithBeforeBind(func(data any) (any, error) {
return cast.ToFloat64(data), nil
})
f.BeforeBind = func(data any) (any, error) {
return cast.ToFloat64(data), nil
}
return f
}
func NewFieldMail(name string) *Field {
f := NewField(name, "input").
WithOptions(NewOption("type", "email"))
return f
}
func NewFieldRange(name string) *Field {
f := NewField(name, "input").
WithOptions(NewOption("type", "range")).
WithBeforeBind(func(data any) (any, error) {
return cast.ToFloat64(data), nil
})
return f
}
func NewFieldPassword(name string) *Field {
f := NewField(name, "input").
WithOptions(NewOption("type", "password"))
return f
}
func NewSubmit(name string) *Field {
f := NewField(name, "input").
WithOptions(
NewOption("type", "submit"),
)
WithOptions(NewOption("type", "submit"))
f.Data = "Submit"

90
form/field_input_date.go Normal file
View file

@ -0,0 +1,90 @@
package form
import (
"fmt"
"time"
)
func DateBeforeMount(data any, format string) (any, error) {
if data == nil {
return nil, nil
}
switch data.(type) {
case string:
return data, nil
case time.Time:
return data.(time.Time).Format(format), nil
case *time.Time:
v := data.(*time.Time)
if v != nil {
return v.Format(format), nil
}
}
return data, nil
}
func NewFieldDate(name string) *Field {
f := NewField(name, "input").
WithOptions(NewOption("type", "date")).
WithBeforeMount(func(data any) (any, error) {
return DateBeforeMount(data, "2006-01-02")
}).
WithBeforeBind(func(data any) (any, error) {
return time.Parse(time.DateOnly, data.(string))
})
return f
}
func NewFieldDatetime(name string) *Field {
f := NewField(name, "input").
WithOptions(NewOption("type", "datetime")).
WithBeforeMount(func(data any) (any, error) {
return DateBeforeMount(data, "2006-01-02 15:04")
}).
WithBeforeBind(func(data any) (any, error) {
return time.Parse("2006-01-02T15:04", data.(string))
})
return f
}
func NewFieldDatetimeLocal(name string) *Field {
f := NewField(name, "input").
WithOptions(
NewOption("type", "datetime-local"),
).
WithBeforeMount(func(data any) (any, error) {
return DateBeforeMount(data, "2006-01-02 15:04")
}).
WithBeforeBind(func(data any) (any, error) {
a, b := time.Parse("2006-01-02T15:04", data.(string))
return a, b
})
return f
}
func NewFieldTime(name string) *Field {
f := NewField(name, "input").
WithOptions(NewOption("type", "time")).
WithBeforeMount(func(data any) (any, error) {
return DateBeforeMount(data, "15:04")
}).
WithBeforeBind(func(data any) (any, error) {
if data != nil {
v := data.(string)
if len(v) > 0 {
return time.Parse(time.TimeOnly, fmt.Sprintf("%s:00", v))
}
}
return nil, nil
})
return f
}

View file

@ -1,10 +1,10 @@
package form
import (
"encoding/json"
"net/http"
"net/url"
"github.com/mitchellh/mapstructure"
"gitnet.fr/deblan/go-form/util"
"gitnet.fr/deblan/go-form/validation"
)
@ -49,6 +49,12 @@ func (f *Form) GetOption(name string) *Option {
return nil
}
func (f *Form) ResetErrors() *Form {
f.Errors = []validation.Error{}
return f
}
func (f *Form) Add(fields ...*Field) {
for _, field := range fields {
field.Form = f
@ -123,6 +129,7 @@ func (f *Form) WithOptions(options ...*Option) *Form {
func (f *Form) IsValid() bool {
isValid := true
f.ResetErrors()
for _, field := range f.Fields {
fieldIsValid := field.Validate(field)
@ -159,9 +166,7 @@ func (f *Form) Bind(data any) error {
field.Bind(toBind, nil)
}
j, _ := json.Marshal(toBind)
return json.Unmarshal(j, data)
return mapstructure.Decode(toBind, data)
}
func (f *Form) HandleRequest(req *http.Request) {
@ -179,7 +184,12 @@ func (f *Form) HandleRequest(req *http.Request) {
for _, c := range f.GlobalFields {
if data.Has(c.GetName()) {
isSubmitted = true
c.Mount(data.Get(c.GetName()))
if c.IsSlice {
c.Mount(data[c.GetName()])
} else {
c.Mount(data.Get(c.GetName()))
}
}
}

5
go.mod
View file

@ -2,4 +2,7 @@ module gitnet.fr/deblan/go-form
go 1.23.0
require github.com/spf13/cast v1.9.2 // indirect
require (
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/spf13/cast v1.9.2 // indirect
)

2
go.sum
View file

@ -1,2 +1,4 @@
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=

94
main.go
View file

@ -1,8 +1,11 @@
package main
import (
"fmt"
"html/template"
"log"
"net/http"
"time"
"gitnet.fr/deblan/go-form/example"
"gitnet.fr/deblan/go-form/theme"
@ -15,21 +18,37 @@ func main() {
ZipCode uint
}
type Person struct {
Name string
Address Address
type Foo struct {
Name string
Address Address
Date time.Time
DateTime time.Time
Time time.Time
Checkbox bool
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
data := new(Person)
data.Name = ""
data.Address = Address{
Street: "rue des camélias",
City: "",
ZipCode: 39700,
// now := time.Now()
// data := new(Foo)
// data.Name = ""
// data.Date = now
// data.DateTime = now
// data.Time = now
// data.Address = Address{
// Street: "",
// City: "",
// ZipCode: 39700,
// }
//
// f := example.CreateExampleForm()
// f.Mount(data)
data := example.Post{
// Tags: []example.Tag{example.Tag{"tag1"}, example.Tag{"tag2"}, example.Tag{"tag3"}},
Tag: example.Tag{"tag1"},
}
f := example.CreateAddressForm()
f := example.CreateExampleForm2()
f.Mount(data)
if r.Method == f.Method {
@ -37,14 +56,65 @@ func main() {
if f.IsSubmitted() && f.IsValid() {
f.Bind(&data)
fmt.Printf("BIND=%+v\n", data)
}
}
render := theme.NewRenderer(theme.Html5)
v := render.RenderForm(f)
tpl, _ := template.New("page").Funcs(render.FuncMap()).Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form</title>
<style>
input[type="text"],
input[type="date"],
input[type="datetime"],
input[type="time"],
input[type="range"],
input[type="email"],
select,
input[type="datetime-local"],
textarea {
box-sizing: border-box;
padding: 9px;
margin: 10px 0;
display: block;
width: 100%;
border: 1px solid black;
}
.form-errors {
margin: 0;
padding: 5px 0 0 0;
color: red;
list-style: none;
}
.form-errors li {
padding: 0;
margin: 0;
}
.form-help {
color: blue;
font-size: 9px;
}
</style>
</head>
<body>
{{ form .Form }}
</body>
</html>
`)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write([]byte(v))
tpl.Execute(w, map[string]any{
"Form": f,
// "Post": data,
})
})
log.Fatal(http.ListenAndServe(":1122", nil))

View file

@ -26,18 +26,70 @@ var Html5 = map[string]string{
{{- end -}}
`,
"input": `
{{ $type := .Field.GetOption "type" }}
<input id="{{ .Field.GetId }}" {{ if .Field.HasOption "required" }}{{ if (.Field.GetOption "required").Value }}required="required"{{ end }}{{ end }} name="{{ .Field.GetName }}" value="{{ .Field.Data }}" type="{{ $type.Value }}" {{ form_widget_attr .Field }}>
{{- $type := .Field.GetOption "type" -}}
{{- $checked := and (eq (.Field.GetOption "type").Value "checkbox") (.Field.Data) -}}
{{- $required := and (.Field.HasOption "required") (.Field.GetOption "required").Value -}}
{{- $value := .Field.Data -}}
{{- if eq $type.Value "checkbox" -}}
{{- $value = 1 -}}
{{- end -}}
<input id="{{ .Field.GetId }}" {{ if $checked }}checked{{ end }} {{ if $required }}required="required"{{ end }} name="{{ .Field.GetName }}" value="{{ $value }}" type="{{ $type.Value }}" {{ form_widget_attr .Field }}>
`,
"textarea": `
<textarea id="{{ .Field.GetId }}" {{ if .Field.HasOption "required" }}{{ if (.Field.GetOption "required").Value }}required="required"{{ end }}{{ end }} name="{{ .Field.GetName }}" {{ form_widget_attr .Field }}>{{ .Field.Data }}</textarea>
`,
"sub_form": `
{{ form_widget_help .Field }}
"choice": `
{{- $required := and (.Field.HasOption "required") (.Field.GetOption "required").Value -}}
{{- $isExpanded := (.Field.GetOption "expanded").Value -}}
{{- $isMultiple := (.Field.GetOption "multiple").Value -}}
{{- $emptyChoiceLabel := (.Field.GetOption "empty_choice_label").Value -}}
{{- $choices := (.Field.GetOption "choices").Value -}}
{{- $field := .Field -}}
{{- $keyAdd := 0 -}}
{{- range $field := .Field.Children -}}
{{- form_row $field -}}
{{- if and (not $required) (not $isMultiple) -}}
{{- $keyAdd = 1 -}}
{{- end -}}
{{- if $isExpanded -}}
{{- if and (not $required) (not $isMultiple) -}}
<input value="" {{ if not $field.Data }}checked{{ end }} name="{{ $field.GetName }}" type="radio" id="{{ $field.GetId }}-0">
<label for="{{ $field.GetId }}-0">None</label>
{{- end -}}
{{- range $key, $choice := $choices.GetChoices -}}
<input name="{{ $field.GetName }}" type="{{ if $isMultiple }}checkbox{{ else }}radio{{ end }}" value="{{ $choice.Value }}" {{ if $choices.Match $field $choice.Value }}checked{{ end }} id="{{ $field.GetId }}-{{ sum $key $keyAdd }}">
<label for="{{ $field.GetId }}-{{ sum $key $keyAdd }}">{{- $choice.Label -}}</label>
{{- end -}}
{{- else -}}
<select id="{{ .Field.GetId }}" {{ if $required }}required="required"{{ end }} {{ if $isMultiple }}multiple{{ end }} name="{{ .Field.GetName }}" {{ form_widget_attr .Field }}>
{{- if not $required -}}
<option value="">{{ $emptyChoiceLabel }}</option>
{{- end -}}
{{- range $choice := $choices.GetChoices -}}
<option value="{{ $choice.Value }}" {{ if $choices.Match $field $choice.Value }}selected{{ end }}>{{ $choice.Label }}</option>
{{- end -}}
</select>
{{- end -}}
`,
"sub_form": `
<fieldset id="{{ .Field.GetId }}">
{{ if .Field.HasOption "label" }}
{{ $label := (.Field.GetOption "label").Value }}
{{- if ne $label "" -}}
<legend {{ form_label_attr .Field }}>{{ $label }}</legend>
{{- end -}}
{{- end -}}
{{ form_widget_help .Field }}
{{- range $field := .Field.Children -}}
{{- form_row $field -}}
{{- end -}}
</fieldset>
`,
"error": `
{{- if gt (len .Errors) 0 -}}
@ -49,9 +101,19 @@ var Html5 = map[string]string{
{{- end -}}
`,
"row": `<div class="row">
{{- form_label .Field -}}
{{ $labelAfterWidget := and (.Field.HasOption "type") (eq (.Field.GetOption "type").Value "checkbox") }}
{{ if and (eq (len .Field.Children) 0) (not $labelAfterWidget) }}
{{- form_label .Field -}}
{{ end }}
{{- form_error nil .Field -}}
{{- form_widget .Field -}}
{{ if and (eq (len .Field.Children) 0) ($labelAfterWidget) }}
{{- form_label .Field -}}
{{ end }}
{{- form_widget_help .Field -}}
</div>`,
}

View file

@ -4,6 +4,7 @@ import (
"bytes"
"html/template"
"github.com/spf13/cast"
"gitnet.fr/deblan/go-form/form"
"gitnet.fr/deblan/go-form/validation"
)
@ -136,8 +137,8 @@ func (r *Renderer) RenderAttr(name, tpl string, args any) template.HTMLAttr {
return template.HTMLAttr(buf.String())
}
func (r *Renderer) Render(name, tpl string, args any) template.HTML {
t, err := template.New(name).Funcs(template.FuncMap{
func (r *Renderer) FuncMap() template.FuncMap {
return template.FuncMap{
"form": r.RenderForm,
"form_row": r.RenderRow,
"form_label": r.RenderLabel,
@ -148,7 +149,19 @@ func (r *Renderer) Render(name, tpl string, args any) template.HTML {
"form_label_attr": r.RenderLabelAttr,
"form_help": r.RenderFormHelp,
"form_widget_help": r.RenderWidgetHelp,
}).Parse(tpl)
"sum": func(values ...any) float64 {
res := float64(0)
for _, value := range values {
res += cast.ToFloat64(value)
}
return res
},
}
}
func (r *Renderer) Render(name, tpl string, args any) template.HTML {
t, err := template.New(name).Funcs(r.FuncMap()).Parse(tpl)
if err != nil {
return template.HTML(err.Error())

25
validation/mail.go Normal file
View file

@ -0,0 +1,25 @@
package validation
import "net/mail"
type Mail struct {
}
func (c Mail) Validate(data any) []Error {
errors := []Error{}
notBlank := NotBlank{}
nbErrs := notBlank.Validate(data)
if len(nbErrs) > 0 {
return errors
}
_, err := mail.ParseAddress(data.(string))
if err != nil {
errors = append(errors, Error("This value is not a valid email address."))
}
return errors
}

View file

@ -2,6 +2,8 @@ package validation
import (
"reflect"
"github.com/spf13/cast"
)
type NotBlank struct {
@ -9,33 +11,48 @@ type NotBlank struct {
func (c NotBlank) Validate(data any) []Error {
isValid := true
label := "This value should not be blank."
errors := []Error{}
if data == nil {
errors = append(errors, Error(label))
return errors
}
t := reflect.TypeOf(data)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if data == nil {
isValid = false
} else if t.Kind() == reflect.Bool {
if data == false {
isValid = false
}
} else if t.Kind() == reflect.Array {
if len(data.([]interface{})) == 0 {
isValid = false
}
} else if t.Kind() == reflect.String {
if len(data.(string)) == 0 {
isValid = false
}
} else {
errors = append(errors, Error("This value can not be processed"))
switch t.Kind() {
case reflect.Bool:
isValid = data == false
case reflect.Array:
case reflect.Slice:
isValid = reflect.ValueOf(data).Len() > 0
case reflect.String:
isValid = len(data.(string)) > 0
case reflect.Float32:
case reflect.Float64:
case reflect.Int:
case reflect.Int16:
case reflect.Int32:
case reflect.Int64:
case reflect.Int8:
case reflect.Uint:
case reflect.Uint16:
case reflect.Uint32:
case reflect.Uint64:
case reflect.Uint8:
isValid = cast.ToFloat64(data.(string)) == float64(0)
default:
errors = append(errors, Error("This value can not be processed."))
}
if !isValid {
errors = append(errors, Error("This value should be blank"))
errors = append(errors, Error(label))
}
return errors