Documentation ¶
Overview ¶
govalid generates validation code for maps of strings to strings to marshal the data into well-typed structures.
Command-line invocation is as follows.
usage: govalid [-h] [-o file.go] [file.v] -o="": output file name
If no input file is specified as a positional argument, govalid reads from standard in. If no output file is specified and reading from standard in, output will be written to standard out. Otherwise, output will be written to the a path generated by replacing the input path extension with ".go".
The generated code abides by gofmt. It leverages strconv.Parse, net/mail.ParseAddress, and net/url.Parse.
Usage ¶
govalid is meant to be used with go generate. In your project, you write .v files, which are valid Go files that define structure types into which you want to marshal input data. Here is an example of a .v file.
package worker type jobInput struct { // Specifying just the type means the field is required. jobId uint // Specifying a tag "valid", with the item "def" means that the // field is optional, and that the default value should be the // zero value of the type. nodeBlock bool `valid:"def"` // You can specify maximum lengths for strings. encryptionKey string `valid:"max:128"` encryptionIv string `valid:"max:8"` // Maximum lengths also work for URLs. destination *url.URL `valid:"max:256"` // You can also mandate minimum lengths and set explicit default // values. language string `valid:"min:2,max:4,def:\"enUS\""` // Bounds apply to numeric types as well. threads uint `valid:"max:8"` // Fields in the input data not mentioned in the struct will not // appear in the validated output. }
Given this input file, govalid will generate the following Go file.
// *** GENERATED BY GOVALID; DO NOT EDIT *** package worker // *** IMPORT ADDED BY GOVALID *** import "errors" // *** IMPORT ADDED BY GOVALID *** import "strconv" type jobInput struct { jobId uint nodeBlock bool `valid:"def"` encryptionKey string `valid:"max:128"` encryptionIv string `valid:"max:8"` destination *url.URL `valid:"max:256"` language string `valid:"min:2,max:4,def:\"enUS\""` threads uint `valid:"max:8"` } // validateJobInput reads data from the given map of strings to // strings and validates the data into a new *jobInput. // Fields named in a jobInput will be recognized as keys. // Keys in the input data that are not fields in the // jobInput will be ignored. If there is an error // validating any fields, an appropriate error will // be returned. func validateJobInput(data map[string]string) (*jobInput, error) { ret := new(jobInput) var ( field_encryptionIv string field_destination_s string field_language string field_jobId_s string field_jobId uint64 err error field_encryptionKey string ok bool field_nodeBlock_s string field_threads_s string field_threads uint64 ) // jobId uint field_jobId_s, ok = data["jobId"] if ok { field_jobId, err = strconv.ParseUint(field_jobId_s, 0, 0) if err != nil { return nil, err } ret.jobId = uint(field_jobId) } else { return nil, errors.New("jobId is required") } // nodeBlock bool field_nodeBlock_s, ok = data["nodeBlock"] if ok { ret.nodeBlock, err = strconv.ParseBool(field_nodeBlock_s) if err != nil { return nil, err } } else { // nodeBlock is optional. // Zero value already set. } // encryptionKey string field_encryptionKey, ok = data["encryptionKey"] if ok { if len(field_encryptionKey) > 128 { return nil, errors.New("encryptionKey can have a length of at most 128") } ret.encryptionKey = field_encryptionKey } else { return nil, errors.New("encryptionKey is required") } // encryptionIv string field_encryptionIv, ok = data["encryptionIv"] if ok { if len(field_encryptionIv) > 8 { return nil, errors.New("encryptionIv can have a length of at most 8") } ret.encryptionIv = field_encryptionIv } else { return nil, errors.New("encryptionIv is required") } // destination *url.URL field_destination_s, ok = data["destination"] if ok { if len(data["destination"]) > 256 { return nil, errors.New("destination can have a length of at most 256") } ret.destination, err = url.Parse(field_destination_s) if err != nil { return nil, err } } else { return nil, errors.New("destination is required") } // language string field_language, ok = data["language"] if ok { if len(field_language) > 4 { return nil, errors.New("language can have a length of at most 4") } if len(field_language) < 2 { return nil, errors.New("language must have a length of at least 2") } ret.language = field_language } else { // language is optional. ret.language = "enUS" } // threads uint field_threads_s, ok = data["threads"] if ok { field_threads, err = strconv.ParseUint(field_threads_s, 0, 0) if err != nil { return nil, err } if field_threads > 8 { return nil, errors.New("threads can be at most 8") } ret.threads = uint(field_threads) } else { return nil, errors.New("threads is required") } return ret, nil }
Look at all that code you didn't have to write. govalid is meant to help you avoid having to write such boilerplate data validation code.
Note that any fields not mentioned in the input structure will be ignored.
Supported Types
string bool int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 float32 float64 *mail.Address *url.URL
You are responsible for importing net/mail or net/url in your .v file yourself if using a mail address or URL.
Integer Bases ¶
The generated validation code for ints and uints uses a base of 0, so input strings may be in any base represented by the strconv.ParseInt or strconv.ParseUint functions. For example, a hexadecimal value would be parsed by passing in "0xbeef" or "48879".
Tags ¶
You may tag your struct fields to activate extra validation logic. Use the tag key "valid". Three tags are supported.
def[:value] max:m min:n
If "def" is unaccompanied by a value, the field is optional, and the default is the zero value given by the field's type. If provided, it is injected directly into the generated code. max and min do not apply to bools. On numeric types, max and min can be used to bound the value. Like the default value, the bounds you specify in the tag will be injected directly into the generated code. For strings, URLs and email addresses, the bounds apply to the length of the input data string.
Export ¶
If your structure name indicates it is to be (un)exported, then the validation function will also be (un)exported. For example, in the above sample, the validation function was unexported. But if we had written out
type JobInput struct { ...
instead, then our validation function would have had the following signature.
func ValidateJobInput(data map[string]string) (*jobInput, error) { ...