binding

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

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

Go to latest
Published: Jul 31, 2014 License: MIT Imports: 8 Imported by: 0

README

binding wercker status

Request data binding and validation for Martini.

GoDoc

Features

  • Automatically converts data from a request into a struct ready for your application
  • Supports form, JSON, and multipart form data (including file uploads)
  • Can use interfaces
  • Provides data validation facilities
    • Enforces required fields
    • Invoke your own data validator
  • Built-in error handling (or use your own)
  • 99% test coverage

Usage

Getting form data from a request

Suppose you have a contact form on your site where at least name and message are required. We'll need a struct to receive the data:

type ContactForm struct {
	Name    string `form:"name" binding:"required"`
	Email   string `form:"email"`
	Message string `form:"message" binding:"required"`
}

Then we simply add our route in Martini:

m.Post("/contact/submit", binding.Bind(ContactForm{}), func(contact ContactForm) string {
	return fmt.Sprintf("Name: %s\nEmail: %s\nMessage: %s",
		contact.Name, contact.Email, contact.Message)
})

That's it! The binding.Bind function takes care of validating required fields. If there are any errors (like a required field is empty), binding will return an error to the client and your app won't even see the request.

(Caveat: Don't try to bind to embedded struct pointers; it won't work. See issue 30 if you want to help with this.)

Getting JSON data from a request

To get data from JSON payloads, simply use the json: struct tags instead of form:. Pro Tip: Use JSON-to-Go to correctly convert JSON to a Go type definition. It's useful if you're new to this or the structure is large/complex.

Custom validation

If you want additional validation beyond just checking required fields, your struct can implement the binding.Validator interface like so:

func (cf ContactForm) Validate(errors binding.Errors, req *http.Request) binding.Errors {
	if strings.Contains(cf.Message, "Go needs generics") {
		errors = append(errors, binding.Error{
			FieldNames:     []string{"message"},
			Classification: "ComplaintError",
			Message:        "Go has generics. They're called interfaces.",
		})
	}
	return errors
}

Now, any contact form submissions with "Go needs generics" in the message will return an error explaining your folly.

Binding to interfaces

If you'd like to bind the data to an interface rather than to a concrete struct, you can specify the interface and use it like this:

m.Post("/contact/submit", binding.Bind(ContactForm{}, (*MyInterface)(nil)), func(contact MyInterface) {
	// ... your struct became an interface!
})

Description of Handlers

Each of these middleware handlers are independent and optional, though be aware that some handlers invoke other ones.

Bind

binding.Bind is a convenient wrapper over the other handlers in this package. It does the following boilerplate for you:

  1. Deserializes request data into a struct
  2. Performs validation with binding.Validate
  3. Bails out with binding.ErrorHandler if there are any errors

Your application (the final handler) will not even see the request if there are any errors.

Content-Type will be used to know how to deserialize the requests.

Important safety tip: Don't attempt to bind a pointer to a struct. This will cause a panic to prevent a race condition where every request would be pointing to the same struct.

Form

binding.Form deserializes form data from the request, whether in the query string or as a form-urlencoded payload. It only does these things:

  1. Deserializes request data into a struct
  2. Performs validation with binding.Validate

Note that it does not handle errors. You may receive a binding.Errors into your own handler if you want to handle errors. (For automatic error handling, use binding.Bind.)

MultipartForm and file uploads

Like binding.Form, binding.MultipartForm deserializes form data from a request into the struct you pass in. Additionally, this will deserialize a POST request that has a form of enctype="multipart/form-data". If the bound struct contains a field of type *multipart.FileHeader (or []*multipart.FileHeader), you also can read any uploaded files that were part of the form.

This handler does the following:

  1. Deserializes request data into a struct
  2. Performs validation with binding.Validate

Again, like binding.Form, no error handling is performed, but you can get the errors in your handler by receiving a binding.Errors type.

MultipartForm example
type UploadForm struct {
	Title      string                `form:"title"`
	TextUpload *multipart.FileHeader `form:"txtUpload"`
}

func main() {
	m := martini.Classic()
	m.Post("/", binding.MultipartForm(UploadForm{}), uploadHandler(uf UploadForm) string {
		file, err := uf.TextUpload.Open()
		// ... you can now read the uploaded file
	})
	m.Run()
}
Json

binding.Json deserializes JSON data in the payload of the request. It does the following things:

  1. Deserializes request data into a struct
  2. Performs validation with binding.Validate

Similar to binding.Form, no error handling is performed, but you can get the errors and handle them yourself.

Validate

binding.Validate receives a populated struct and checks it for errors, first by enforcing the binding:"required" value on struct field tags, then by executing the Validate() method on the struct, if it is a binding.Validator.

Note: Marking a field as "required" means that you do not allow the zero value for that type (i.e. if you want to allow 0 in an int field, do not make it required).

Sanitizing: If you'd like more powerful validation by sanitizing the input, take a look at jamieomatthews/martini-validate which has a few useful validate functions built-in.

ErrorHandler

binding.ErrorHandler is a small middleware that simply writes an error code to the response and also a JSON payload describing the errors, if any errors have been mapped to the context. It does nothing if there are no errors.

  • Deserialization errors yield a 400
  • Content-Type errors yield a 415
  • Any other kinds of errors (including your own) yield a 422 (Unprocessable Entity)

Contributing

Hey, cool! Let's make this package even better. We have several goals for this package as a community:

  • Lightweight (small)
  • Tightly focused (doesn't branch into other areas of concern)
  • Performant (yeah, it uses reflection, so we could improve here)
  • 100% test coverage (or close to it)

Adding more features tends to chip away at each of these goals, but let's discuss them anyway: don't feel like you can't recommend or ask something. We all want the best possible binding package.

Bug fixes will be accepted any time as pull requests, as long as tests assert correct behavior. Thanks for getting involved!

Primary Authors

Thanks to all contributors!

Documentation

Overview

Package binding transforms a raw request into a struct ready to be used your application. It can also perform validation on the data and handle errors.

Index

Constants

View Source
const (
	RequiredError        = "RequiredError"
	ContentTypeError     = "ContentTypeError"
	DeserializationError = "DeserializationError"
	TypeError            = "TypeError"
)
View Source
const (
	StatusUnprocessableEntity = 422
)

Variables

View Source
var (
	// Maximum amount of memory to use when parsing a multipart form.
	// Set this to whatever value you prefer; default is 10 MB.
	MaxMemory = int64(1024 * 1024 * 10)
)

Functions

func Bind

func Bind(obj interface{}, ifacePtr ...interface{}) martini.Handler

Bind wraps up the functionality of the Form and Json middleware according to the Content-Type and verb of the request. A Content-Type is required for POST and PUT requests. Bind invokes the ErrorHandler middleware to bail out if errors occurred. If you want to perform your own error handling, use Form or Json middleware directly. An interface pointer can be added as a second argument in order to map the struct to a specific interface.

func ErrorHandler

func ErrorHandler(errs Errors, resp http.ResponseWriter)

ErrorHandler simply counts the number of errors in the context and, if more than 0, writes a response with an error code and a JSON payload describing the errors. The response will have a JSON content-type. Middleware remaining on the stack will not even see the request if, by this point, there are any errors. This is a "default" handler, of sorts, and you are welcome to use your own instead. The Bind middleware invokes this automatically for convenience.

func Form

func Form(formStruct interface{}, ifacePtr ...interface{}) martini.Handler

Form is middleware to deserialize form-urlencoded data from the request. It gets data from the form-urlencoded body, if present, or from the query string. It uses the http.Request.ParseForm() method to perform deserialization, then reflection is used to map each field into the struct with the proper type. Structs with primitive slice types (bool, float, int, string) can support deserialization of repeated form keys, for example: key=val1&key=val2&key=val3 An interface pointer can be added as a second argument in order to map the struct to a specific interface.

func Json

func Json(jsonStruct interface{}, ifacePtr ...interface{}) martini.Handler

Json is middleware to deserialize a JSON payload from the request into the struct that is passed in. The resulting struct is then validated, but no error handling is actually performed here. An interface pointer can be added as a second argument in order to map the struct to a specific interface.

func MultipartForm

func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) martini.Handler

MultipartForm works much like Form, except it can parse multipart forms and handle file uploads. Like the other deserialization middleware handlers, you can pass in an interface to make the interface available for injection into other handlers later.

func Validate

func Validate(obj interface{}) martini.Handler

Validate is middleware to enforce required fields. If the struct passed in implements Validator, then the user-defined Validate method is executed, and its errors are mapped to the context. This middleware performs no error handling: it merely detects errors and maps them.

Types

type Error

type Error struct {
	// An error supports zero or more field names, because an
	// error can morph three ways: (1) it can indicate something
	// wrong with the request as a whole, (2) it can point to a
	// specific problem with a particular input field, or (3) it
	// can span multiple related input fields.
	FieldNames []string `json:"fieldNames,omitempty"`

	// The classification is like an error code, convenient to
	// use when processing or categorizing an error programmatically.
	// It may also be called the "kind" of error.
	Classification string `json:"classification,omitempty"`

	// Message should be human-readable and detailed enough to
	// pinpoint and resolve the problem, but it should be brief. For
	// example, a payload of 100 objects in a JSON array might have
	// an error in the 41st object. The message should help the
	// end user find and fix the error with their request.
	Message string `json:"message,omitempty"`
}

func (Error) Error

func (e Error) Error() string

Error returns this error's message.

func (Error) Fields

func (e Error) Fields() []string

Fields returns the list of field names this error is associated with.

func (Error) Kind

func (e Error) Kind() string

Kind returns this error's classification.

type Errors

type Errors []Error

Errors may be generated during deserialization, binding, or validation. This type is mapped to the context so you can inject it into your own handlers and use it in your application if you want all your errors to look the same.

func (*Errors) Add

func (e *Errors) Add(fieldNames []string, classification, message string)

Add adds an error associated with the fields indicated by fieldNames, with the given classification and message.

func (*Errors) Has

func (e *Errors) Has(class string) bool

Has determines whether an Errors slice has an Error with a given classification in it; it does not search on messages or field names.

func (*Errors) Len

func (e *Errors) Len() int

Len returns the number of errors.

type Validator

type Validator interface {
	// Validate validates that the request is OK. It is recommended
	// that validation be limited to checking values for syntax and
	// semantics, enough to know that you can make sense of the request
	// in your application. For example, you might verify that a credit
	// card number matches a valid pattern, but you probably wouldn't
	// perform an actual credit card authorization here.
	Validate(Errors, *http.Request) Errors
}

Implement the Validator interface to handle some rudimentary request validation logic so your application doesn't have to.

Jump to

Keyboard shortcuts

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