form

package module
v0.0.0-...-cee066e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 21, 2025 License: MIT Imports: 17 Imported by: 0

README

form

database is a simple library that builds on top of gorilla/schema to allow for easy unmarshalling and validation of request data.

Quick start

To use the library, import it and define a new form into which request data can be unmarshalled. Below is a simple example that unmarshals the request data into the LoginForm and validates it.

type LoginForm struct {
    Username string
    Password string
}

func (f *LoginForm) Fields() form.Fields {
    return form.Fields{
        "username": f.Username,
    }
}

func (f *LoginForm) Validate(ctx context.Context) error {
    var v form.Validator

    v.Add("username", f.Username, form.Required)
    v.Add("password", f.Password, form.Required)

    return v.Validate(ctx)
}

func LoginHandler(w http.ResponseWriter, r *http.Request) {
    var f LoginForm

    if err := form.UnmarshalAndValidate(r, &f); err != nil {
        // Handle error.
    }

    // Handle the login.
}

Forms

Forms are represented via the form.Form interface which wraps two methods,

  • Fields - The fields of the form, this is used for flashing form data to the session.
  • Validate - For validating the content of the form once it has been unmarshalled.

As long as a struct implements these methods, it can be used as a form and given to the [Unmarshal][] and [UnmarshalAndValidate][] functions.

Since this library is built on top of gorilla/schema, it makes use of the schema struct tag to define which fields can be populated with request data,

type SignupForm struct {
    Email    string `schema:"email"`
    Verified bool   `schema:"-"`
}

File uploads

This library automatically handles unmarshalling of files from multipart/form-data requests. Each field in a struct that has the type of form.File will have the multipart file, or files, mapped to it, as long as there is a corresponding field in the request data for that file.

For example, assume an application is being built to handle file uploads that takes a single file, then the form implementation would look something like,

type UploadForm struct {
    Attachment *form.File
}

func (f *UploadForm) Fields() form.Fields { return nil }

func (f *UploadForm) Validate(ctx context.Context) error {
    var v form.Validator

    v.Add("attachment", f.Attachment, form.Required)

    return v.Validate(ctx)
}

with the above implementation, a handler can now be defined to handle the uploading of files,

func UploadHandler(w http.ResponseWriter, r *http.Request) {
    var f UploadForm

    if err := form.UnmarshalAndValidate(r, &f); err != nil {
        // Handle error.
    }

    dst, err := os.Create(f.Attachment.Name())

    if err != nil {
        // Handle error.
    }

    defer dst.Close()

    if _, err := io.Copy(dst, f.Attachment.Name()); err != nil {
        // Handle error.
    }
}

Multiple file uploads can be handled by simply defining a slice.

type UploadForm struct {
    Attachments []*form.File
}

With this in place, the new handler would look like,

func StoreAttachment(f *form.File) error {
    dst, err := os.Create(f.Name())

    if err != nil {
        return err
    }

    defer dst.Close()

    _, err = io.Copy(dst, f.Name())
    return err
}

func UploadHandler(w http.ResponseWriter, r *http.Request) {
    var f UploadForm

    if err := form.UnmarshalAndValidate(r, &f); err != nil {
        // Handle error.
    }

    for _, f := range f.Attachments {
        if err := StoreAttachment(f); err != nil {
            // Handle error.
        }
    }
}

The form.File type implements the following interfaces,

Validation

Validation is done via the form.Validator type. This has an the Add method which takes the name of the field being validated, the value for that field, and the [Rules][] to apply to that field value. The Validate method is then used to execute all of the rules against the given form data. If any of the rules fail, then the form.Errors type is returned. This type is a map of errors and fields for which the error occured.

The form.Validator is used in the Validate method of form implementations to validate their inputs.

type SignupForm struct {
    Email          string
    Username       string
    Password       string
    VerifyPassword string `schema:"verify_password"`
}

func (f *SignupForm) Validate(ctx context.Context) error {
    var v form.Validator

    v.Add("email", f.Email, form.Required, form.Email)
    v.Add("username", f.Username, form.Required, form.Length(3, 32))
    v.Add("password", f.Password, form.Required, form.Length(6, 60))
    v.Add("verify_password", f.VerifyPassword, form.Required, form.Equals("password", f.Password))

    return v.Validate(ctx)
}

A Rule is a function that takes a context, and the form value being validated.

type Rule func(ctx context.Context, val any) error

This library comes with the following rules out of the box,

  • Required - Make sure the field is given a value.
  • Matches - Make sure the field matches the given regex pattern.
  • Email[[] - Make sure the field matches an email pattern.
  • Length - Make sure the field is between the given length.
  • Equals - Make sure the field is equal to another field's value.

Session flashing

It is common to want to flash the form data, along with the errors, upon validation errors so it can be displayed back to the user. This library makes use of gorilla/sessions to achieve easy flashing and retrieving of form data, via the form.Flash and form.Old functions.

In the following example a handler is setup for a signup page. This will flash the form and the errors to the session so that they can be retrieved to show to the user,

var (
    hashKey = []byte(os.Getenv("HASH_KEY"))
    blockKey = []byte(os.Getenv("BLOCK_KEY"))

    store = sessions.NewFilesystemStore("", hashKey, blockKey)
)

func SignupHandler(w http.ResponseWriter, r *http.Request) {
    sess, _ := store.Get(r, "session")

    if r.Method == "POST" {
        var f SignupForm

        if err := form.UnmarshalAndValidate(r, &f); err != nil {
            errs := make(form.Errors)

            if errors.As(err, &errs) {
                form.Flash(sess, &f, errs)
            }
            http.Redirect(w, r, r.Header.Get("Referer"), http.StatusSeeOther)
            return
        }
    }

    var (
        f    SignupForm
        errs form.Errors = make(form.Errors)
    )

    form.Old(sess, &f, errs)

    // Render form data and errors in a template back to the user.
}

Note: Because form.Errors contains the error interface, the underlying concrete type will be lost when flashed to the session and subsequently retrieved. This means that the individual error types cannot be inspected once retrieved via a call to form.Old.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrEmailInvalid = errors.New("not a valid address")
View Source
var ErrFieldRequired = errors.New("field is required")

Functions

func Email

func Email(ctx context.Context, val any) error

Email checks to see if the given value is an email address. This does a very simple pattern match to check for the presence of "@" in the string. Further validation of the email address itself should be done through the sending of a verification email.

func Flash

func Flash(s *sessions.Session, f Form, errs Errors)

Flash stores the given Form and Errors to the given session, which can then be retrieved via a subsequent call to Old.

func Old

func Old(sess *sessions.Session, f Form, errs Errors)

Old unmarshals the previous Form and Errors that could be found in the given session. If found, they will be deleted from the given session.

func Required

func Required(_ context.Context, val any) error

Required checks to see if the given value was provided. If the value is a string, or returns a string via a call to String, then it will determine presence by whether or not that value is empty. If the value is a pointer then it is checked for nilness.

func Unmarshal

func Unmarshal(r *http.Request, f Form, opts ...Option) error

Unmarshal parses the given request and unmarshals the data into the given form. If the given request has the Content-Type of application/json, then it will be decoded as such. If the given request has the Content-Type of multipart/form-data, then [net.http.Request.ParseMultipartForm] is called, otherwise [net.http.Request.ParseForm] is called.

func UnmarshalAndValidate

func UnmarshalAndValidate(r *http.Request, f Form, opts ...Option) error

UnmarshalAndValidate unmarshal the given request into the given Form and validates it, returning any errors from the underlying Validate call.

Types

type Converter

type Converter = schema.Converter

type DecodeError

type DecodeError reflect.Kind

func (DecodeError) Error

func (e DecodeError) Error() string

type Decoder

type Decoder = schema.Decoder

type Errors

type Errors map[string][]error

Errors contains the errors that occurred during the validation of a form. Each key in the map will be the field for which the errors occurred.

func (Errors) Add

func (e Errors) Add(field string, err error)

Add an error to the named field.

func (Errors) Error

func (e Errors) Error() string

Error returns the string representation of the error set.

func (Errors) First

func (e Errors) First(field string) error

First returns the first error that occurred for the named field. This returns nil if there is no error.

func (Errors) Get

func (e Errors) Get(field string) []error

Get the errors that occurred for the named field. This returns nil if there are no errors.

func (Errors) Is

func (e Errors) Is(target error) bool

Is checks to see if the given target is the same as the underlying error set.

func (Errors) MarshalJSON

func (e Errors) MarshalJSON() ([]byte, error)

func (Errors) Merge

func (e Errors) Merge(errs Errors)

Merge the given errors into the underlying set of errors. This will not merge in duplicates. That is, if an error exists in the given set being merged, and also exists in the underlying set, then it will not be added.

func (Errors) UnmarshalJSON

func (e Errors) UnmarshalJSON(b []byte) error

type FieldEqualsError

type FieldEqualsError string

func (FieldEqualsError) Error

func (e FieldEqualsError) Error() string

type Fields

type Fields map[string]string

Fields represents the fields in the form. This is the value that is stored in the session data when a form is flashed to the session. This should not contain sensitive data, such as passwords.

func (Fields) Get

func (f Fields) Get(name string) string

Get returns the value of the named value from the fields. If not found this returns an empty string.

func (Fields) String

func (f Fields) String() string

type File

type File struct {
	multipart.File
	// contains filtered or unexported fields
}

File is a wrapper around the underlying [mime.multipart.File] interface. This also implements the [io.fs.FileInfo] interface. This represents a multipart file that has been sent in a request.

func (*File) IsDir

func (f *File) IsDir() bool

func (*File) MarshalJSON

func (f *File) MarshalJSON() ([]byte, error)

func (*File) MimeType

func (f *File) MimeType() string

func (*File) ModTime

func (f *File) ModTime() time.Time

func (*File) Mode

func (f *File) Mode() fs.FileMode

func (*File) Name

func (f *File) Name() string

func (*File) Size

func (f *File) Size() int64

func (*File) Stat

func (f *File) Stat() (fs.FileInfo, error)

func (*File) Sys

func (f *File) Sys() any

type Form

type Form interface {
	Fields() Fields

	Validate(ctx context.Context) error
}

Form represents a form into which request data has been unmarshalled into. It wraps the Fields and Validate methods.

Fields returns the Fields type which should contain the fields of the form. This value is used for flashing data into the session.

Validate validates the form data itself. This should return the Errors type containing the errors, if any, that occurred during validation and for which field.

type LengthError

type LengthError struct {
	Min int
	Max int
}

func (LengthError) Error

func (e LengthError) Error() string

func (LengthError) Is

func (e LengthError) Is(target error) bool

type MatchError

type MatchError struct {
	*regexp.Regexp
}

func (MatchError) Error

func (e MatchError) Error() string

func (MatchError) Is

func (e MatchError) Is(target error) bool

type Option

type Option func(opts *Options)

func AliasTag

func AliasTag(tag string) Option

AliasTag specifies the struct tag alias that should be used for mapping data to the struct fields. The default for this is "schema".

func IgnoreUnknownKeys

func IgnoreUnknownKeys() Option

IgnoreUnknownKeys will ignore any keys that exist in the request data but do not exist in the struct being decoded into.

func MaxSize

func MaxSize(size int) Option

MaxSize configures the size of slices for URL nested arrays or object arrays.

func RegisterConverter

func RegisterConverter(val any, fn Converter) Option

RegisterConverter registers a converter function for a custom type.

func ZeroEmpty

func ZeroEmpty(z bool) Option

ZeroEmpty configures the behavior when decoding empty values into a map. If true then the zero vlaue is set in the map, otherwise empty values are ignored and do not change the value being mapped into.

type Options

type Options struct {
	AliasTag          string
	IgnoreUnknownKeys bool
	MaxSize           int
	Converters        map[any]Converter
	ZeroEmpty         bool
}

Options are the decoder options that will be configured on the Decoder when decoding the request data. This will be configured via the Option function.

func (*Options) Decoder

func (o *Options) Decoder() *Decoder

Decoder returns the Decoder configured with the options from the underlying options set.

type Rule

type Rule func(ctx context.Context, val any) error

Rule is used for validating field values in a form.

func Equals

func Equals(field string, expected any) Rule

Equals returns a Rule that checks to ensure the form value matches the given expected value. The given field is the name of the other form field the form value should match. This field value is returned as the FieldEqualsError type.

func Length

func Length(min, max int) Rule

Length returns a Rule that checks to ensure the form value is between the min and max length in size. If the form value is not of type string then this errors. If the value falls outside the given bounds then the error type of LengthError is returned.

func Matches

func Matches(re *regexp.Regexp) Rule

Matches returns a rule that will check if the given form values matches the given pattern. This errors if the underlying form value is not of type string.

type Validator

type Validator struct {
	// contains filtered or unexported fields
}

Validator is used for validating form fields.

func (*Validator) Add

func (v *Validator) Add(name string, val any, rules ...Rule)

Add a set of rules for validating the form value of the given name.

func (*Validator) Validate

func (v *Validator) Validate(ctx context.Context) error

Validate will iterate over all of the fields added to the validator and perform each rule for each field. All errors will be aggregated into the Errors type which will be returned if any errors did occur.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL