govalid

command module
v0.0.0-...-5bc9077 Latest Latest
Warning

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

Go to latest
Published: Aug 27, 2015 License: MIT Imports: 16 Imported by: 0

README

govalid generates validation code for maps of strings to strings to marshal the data into well-typed structures.

Documentation

Testing

Run go test to test. You may want to run go test -short to avoid fuzz testing of random input programs based off of the test/struct.ebnf grammar.

Future Work

  • Real functional testing.
  • Nicer error reporting. Ideally, the validation library would give you an error object from which you could easily generate a human-readable string indicating all of the bad fields passed in and why they were bad.
  • Add more types to validate:
    • Enums?
  • Include comments from original source.
  • Handle weirder pre-existing imports source files, such as strconv imported as some other name.

Bugs

  • Users cannot add more types to validate without modifying this library.

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) {
	...

Directories

Path Synopsis
Demo server for validation suite.
Demo server for validation suite.

Jump to

Keyboard shortcuts

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