validator

package module
v0.0.0-...-1decafe Latest Latest
Warning

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

Go to latest
Published: Feb 14, 2019 License: Apache-2.0 Imports: 5 Imported by: 0

README

Golang ProtoBuf Validator Compiler

Travis Build Apache 2.0 License

A protoc plugin that generates Validate() error functions on Go proto structs based on field options inside .proto files. The validation functions are code-generated and thus don't suffer on performance from tag-based reflection on deeply-nested messages.

Paint me a code picture

Let's take the following proto3 snippet:

syntax = "proto3";
package validator.examples;
import "github.com/william-lg/go-proto-validators/validator.proto";

message InnerMessage {
  // some_integer can only be in range (1, 100).
  int32 some_integer = 1 [(validator.field) = {int_gte: 0, int_lte: 100}];
  // some_float can only be in range (0;1).
  double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}];
}

message OuterMessage {
  // important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).
  string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}];
  // proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.
  InnerMessage inner = 2 [(validator.field) = {msg_exists : true}];
}

First, the required keyword is back for proto3(proto2 can also use it), under the guise of msg_exists. The painful if-nil checks are taken care of!

Second, the expected values in fields are now part of the contract .proto file. No more hunting down conditions in code!

Third, the generated code is understandable and has clear understandable error messages. Take a look:

func (this *InnerMessage) Validate() error {
	if !(this.SomeInteger > 0) {
		return fmt.Errorf("validation error: InnerMessage.SomeInteger must be greater than '0'")
	}
	if !(this.SomeInteger < 100) {
		return fmt.Errorf("validation error: InnerMessage.SomeInteger must be less than '100'")
	}
	if !(this.SomeFloat >= 0) {
		return fmt.Errorf("validation error: InnerMessage.SomeFloat must be greater than or equal to '0'")
	}
	if !(this.SomeFloat <= 1) {
		return fmt.Errorf("validation error: InnerMessage.SomeFloat must be less than or equal to '1'")
	}
	return nil
}

var _regex_OuterMessage_ImportantString = regexp.MustCompile("^[a-z]{2,5}$")

func (this *OuterMessage) Validate() error {
	if !_regex_OuterMessage_ImportantString.MatchString(this.ImportantString) {
		return fmt.Errorf("validation error: OuterMessage.ImportantString must conform to regex '^[a-z]{2,5}$'")
	}
	if nil == this.Inner {
		return fmt.Errorf("validation error: OuterMessage.Inner message must exist")
	}
	if this.Inner != nil {
		if err := validators.CallValidatorIfExists(this.Inner); err != nil {
			return err
		}
	}
	return nil
}

Installing and using

The protoc compiler expects to find plugins named proto-gen-XYZ on the execution $PATH. So first:

export PATH=${PATH}:${GOPATH}/bin

Then, do the usual

go get github.com/william-lg/go-proto-validators/protoc-gen-govalidators

Your protoc builds probably look very simple like:

protoc  \
	--proto_path=. \
	--go_out=. \
	*.proto

That's fine, until you encounter .proto includes. Because go-proto-validators uses field options inside the .proto files themselves, it's .proto definition (and the Google descriptor.proto itself) need to on the protoc include path. Hence the above becomes:

protoc  \
	--proto_path=${GOPATH}/src \
	--proto_path=${GOPATH}/src/github.com/google/protobuf/src \
	--proto_path=. \
	--go_out=. \
	--govalidators_out=. \
	*.proto

Or with gogo protobufs:

protoc  \
	--proto_path=${GOPATH}/src \
	--proto_path=${GOPATH}/src/github.com/gogo/protobuf/protobuf \
	--proto_path=. \
	--gogo_out=. \
	--govalidators_out=gogoimport=true:. \
	*.proto

Basically the magical incantation (apart from includes) is the --govalidators_out. That triggers the protoc-gen-govalidators plugin to generate mymessage.validator.pb.go. That's it :)

###License

go-proto-validators is released under the Apache 2.0 license. See the LICENSE file for details.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var E_Field = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.FieldOptions)(nil),
	ExtensionType: (*FieldValidator)(nil),
	Field:         65020,
	Name:          "validator.field",
	Tag:           "bytes,65020,opt,name=field",
	Filename:      "validator.proto",
}

Functions

func CallValidatorIfExists

func CallValidatorIfExists(candidate interface{}) error

func FieldError

func FieldError(fieldName string, err error) error

FieldError wraps a given Validator error providing a message call stack.

Types

type FieldValidator

type FieldValidator struct {
	// Uses a Golang RE2-syntax regex to match the field contents.
	Regex *string `protobuf:"bytes,1,opt,name=regex" json:"regex,omitempty"`
	// Field value of integer strictly greater than this value.
	IntGte *int64 `protobuf:"varint,2,opt,name=int_gte,json=intGte" json:"int_gte,omitempty"`
	// Field value of integer strictly smaller than this value.
	IntLte *int64 `protobuf:"varint,3,opt,name=int_lte,json=intLte" json:"int_lte,omitempty"`
	// Used for nested message types, requires that the message type exists.
	MsgExists *bool `protobuf:"varint,4,opt,name=msg_exists,json=msgExists" json:"msg_exists,omitempty"`
	// Human error specifies a user-customizable error that is visible to the user.
	HumanError *string `protobuf:"bytes,5,opt,name=human_error,json=humanError" json:"human_error,omitempty"`
	// Field value of double strictly greater than this value.
	// Note that this value can only take on a valid floating point
	// value. Use together with float_epsilon if you need something more specific.
	FloatGt *float64 `protobuf:"fixed64,6,opt,name=float_gt,json=floatGt" json:"float_gt,omitempty"`
	// Field value of double strictly smaller than this value.
	// Note that this value can only take on a valid floating point
	// value. Use together with float_epsilon if you need something more specific.
	FloatLt *float64 `protobuf:"fixed64,7,opt,name=float_lt,json=floatLt" json:"float_lt,omitempty"`
	// Field value of double describing the epsilon within which
	// any comparison should be considered to be true. For example,
	// when using float_gt = 0.35, using a float_epsilon of 0.05
	// would mean that any value above 0.30 is acceptable. It can be
	// thought of as a {float_value_condition} +- {float_epsilon}.
	// If unset, no correction for floating point inaccuracies in
	// comparisons will be attempted.
	FloatEpsilon *float64 `protobuf:"fixed64,8,opt,name=float_epsilon,json=floatEpsilon" json:"float_epsilon,omitempty"`
	// Floating-point value compared to which the field content should be greater or equal.
	FloatGte *float64 `protobuf:"fixed64,9,opt,name=float_gte,json=floatGte" json:"float_gte,omitempty"`
	// Floating-point value compared to which the field content should be smaller or equal.
	FloatLte *float64 `protobuf:"fixed64,10,opt,name=float_lte,json=floatLte" json:"float_lte,omitempty"`
	// Used for string fields, requires the string to be not empty (i.e different from "").
	StringNotEmpty *bool `protobuf:"varint,11,opt,name=string_not_empty,json=stringNotEmpty" json:"string_not_empty,omitempty"`
	// Repeated field with at least this number of elements.
	RepeatedCountMin *int64 `protobuf:"varint,12,opt,name=repeated_count_min,json=repeatedCountMin" json:"repeated_count_min,omitempty"`
	// Repeated field with at most this number of elements.
	RepeatedCountMax *int64 `protobuf:"varint,13,opt,name=repeated_count_max,json=repeatedCountMax" json:"repeated_count_max,omitempty"`
	// Field value of length greater than this value.
	LengthGt *int64 `protobuf:"varint,14,opt,name=length_gt,json=lengthGt" json:"length_gt,omitempty"`
	// Field value of length smaller than this value.
	LengthLt *int64 `protobuf:"varint,15,opt,name=length_lt,json=lengthLt" json:"length_lt,omitempty"`
	// Field value of integer strictly equal this value.
	LengthEq *int64 `protobuf:"varint,16,opt,name=length_eq,json=lengthEq" json:"length_eq,omitempty"`
	// Requires that the value is in the enum.
	IsInEnum             *bool    `protobuf:"varint,17,opt,name=is_in_enum,json=isInEnum" json:"is_in_enum,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (*FieldValidator) Descriptor

func (*FieldValidator) Descriptor() ([]byte, []int)

func (*FieldValidator) GetFloatEpsilon

func (m *FieldValidator) GetFloatEpsilon() float64

func (*FieldValidator) GetFloatGt

func (m *FieldValidator) GetFloatGt() float64

func (*FieldValidator) GetFloatGte

func (m *FieldValidator) GetFloatGte() float64

func (*FieldValidator) GetFloatLt

func (m *FieldValidator) GetFloatLt() float64

func (*FieldValidator) GetFloatLte

func (m *FieldValidator) GetFloatLte() float64

func (*FieldValidator) GetHumanError

func (m *FieldValidator) GetHumanError() string

func (*FieldValidator) GetIntGte

func (m *FieldValidator) GetIntGte() int64

func (*FieldValidator) GetIntLte

func (m *FieldValidator) GetIntLte() int64

func (*FieldValidator) GetIsInEnum

func (m *FieldValidator) GetIsInEnum() bool

func (*FieldValidator) GetLengthEq

func (m *FieldValidator) GetLengthEq() int64

func (*FieldValidator) GetLengthGt

func (m *FieldValidator) GetLengthGt() int64

func (*FieldValidator) GetLengthLt

func (m *FieldValidator) GetLengthLt() int64

func (*FieldValidator) GetMsgExists

func (m *FieldValidator) GetMsgExists() bool

func (*FieldValidator) GetRegex

func (m *FieldValidator) GetRegex() string

func (*FieldValidator) GetRepeatedCountMax

func (m *FieldValidator) GetRepeatedCountMax() int64

func (*FieldValidator) GetRepeatedCountMin

func (m *FieldValidator) GetRepeatedCountMin() int64

func (*FieldValidator) GetStringNotEmpty

func (m *FieldValidator) GetStringNotEmpty() bool

func (*FieldValidator) ProtoMessage

func (*FieldValidator) ProtoMessage()

func (*FieldValidator) Reset

func (m *FieldValidator) Reset()

func (*FieldValidator) String

func (m *FieldValidator) String() string

func (*FieldValidator) XXX_DiscardUnknown

func (m *FieldValidator) XXX_DiscardUnknown()

func (*FieldValidator) XXX_Marshal

func (m *FieldValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*FieldValidator) XXX_Merge

func (m *FieldValidator) XXX_Merge(src proto.Message)

func (*FieldValidator) XXX_Size

func (m *FieldValidator) XXX_Size() int

func (*FieldValidator) XXX_Unmarshal

func (m *FieldValidator) XXX_Unmarshal(b []byte) error

type Validator

type Validator interface {
	Validate() error
}

Validator is a general interface that allows a message to be validated.

Directories

Path Synopsis
examples
go
The validator plugin generates a Validate method for each message.
The validator plugin generates a Validate method for each message.

Jump to

Keyboard shortcuts

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