validation

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

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

Go to latest
Published: Dec 2, 2019 License: BSD-3-Clause Imports: 7 Imported by: 0

README

protoc-gen-validation

A protobuf v3 validation code generator. Supports (some of) google's Well Known Types as well as handing back all errors that occur during validation, not just the first one.

Usage

func (s *server) MyRPC(ctx context.Context, req *pb.Request) (*pb.Response, error) {
    err = req.Validate()
    if err != nil {
        // do something with the errors
    }
    ...
}

Supported Options

Common
  • error: string - override predefined error messages. You can use {field} and {value} as macros that get replaced with the field name and the required value.
  • transform_func: string - a function name that will be called like m.Field = FuncName(m.Field) allowing you to do any sort of custom transformation of the value that might not be supported in this package
String
  • not_empty_string: bool - make sure a string isn't ""
  • matches: string - must match this value exactly, why, I don't know
  • contains: string - must contain this string, simpler regex really
  • regex: string - must match this regex
  • min_len: int - must be at least this long
  • max_len: int - must be at most this long
  • eq_len: int - must be exactly this long
  • is_uuid: bool - uses github.com/google/uuid to validate the value is a uuid
  • is_email: bool - uses net/mail ParseAddress to validate this is an email address
  • is_iso8601_date: bool - uses time.Parse to validate this is a date in the format YYYY-MM-DD
  • trim: bool - uses strings.Trim(value, " ") to remove whitespace before comparing value with other rules
  • lc: bool - uses strings.ToLower before comparing with other rules
  • uc: bool - uses strings.ToUpper before comparing with other rules
Ints
  • int_lte: int - must be <= this value
  • int_gte: int - must be >= this value
  • int_eq: int - must equal this value
Float
  • float_lte: double - must be <= this value
  • float_gte: double - must be >= this value
  • float_eq: double - must equal this value
Message Options
  • return_on_error: bool - returns when we encounter an error instead of collecting all of them
  • trim_strings: bool - apply strings.Trim(value, " ") to all strings in this message

Errors

Each Validate function returns a typical error, but underneath that error is a ValidationErrors struct. This contains a slice of ValidationError pointers. Each ValidationError has a Field that will be the name of the field that caused the error, and an ErrorMessage that is the human readable message. Each ValidationError can then also contain an Errors array, if this is a message in a message in a message and we need some structure to see where the problems were.

The errors are defined as such

type ValidationError struct {
    Field string
    ErrorMessage string
    Errors []*ValidationError
}

type ValidationErrors struct {
    Errors []*ValidationError
}

Usage example:

err = req.Validate()
if err != nil {
    if verr, ok := err.(*ValidationErrors); ok {
        for _, v := range verr.Errors {
	    fmt.Printf("%s\n", v.ErrorMessage)
        }
    }
}

Example Protobuf Definition

This was just a copy + past from a test proto I was messing with, so there are repeated examples I'm sure

import "validation.proto";
import "google/protobuf/wrappers.proto";

message Inner {
	option (validation.message).return_on_error = true;
	string hello = 1 [(validation.field) = {not_empty_string: true}];
}

message InnerArEl {
	string element = 1;
}

message TestRequest {
	string foo = 1 [(validation.field) = {regex: "^[a-zA-Z]{2}$"}];
	string bar = 2;
	google.protobuf.StringValue baz = 3 [(validation.field) = {not_empty_string: true}];
	Inner inner = 4;
	string my_field = 5 [(validation.field) = {contains: "foo"}];
	string uuid = 6 [(validation.field) = {is_uuid: true}];
	string email = 7 [(validation.field) = {is_email: true}];
	google.protobuf.StringValue other_uuid = 8 [(validation.field) = {is_uuid: true}];
	repeated string array = 9 [(validation.field) = {not_empty_string: true}];
	repeated InnerArEl elements = 10;
}

message TestResponse {
	int64 baz = 1 [(validation.field) = {int_lte: 10, int_gte: 5}];
	float qux = 2 [(validation.field) = {float_eq: 3.14}];
}

message StringTests {
	option (validation.message) = {return_on_error: true, trim_strings: true};
	string min = 1 [(validation.field) = {min_len: 10}];
	string max = 2 [(validation.field) = {max_len: 10}];
	string eq = 3 [(validation.field) = {eq_len: 10}];
}

But Why?

This was heavily inspired / cloned in places from https://github.com/mwitkow/go-proto-validators and https://github.com/envoyproxy/protoc-gen-validate I could not find good documentation on how to build a validator so much of the time was spent going through those projects, seeing how they did things and trying to mimic it. I'm sure there is some code lifted from either where I just gave up and pasted something in and it worked.

So why not just use one of them?

I ran into some showstoppers in both that I felt I couldn't easily overcome. Mostly I didn't want to have to modify the generated code that much to deal with the issues. mwitkow's does not, to my knowledge, support the well known types. Working around that lead to ugly translations from protobuf <-> JSON. Lyft's is still alpha as documented in their repo, and I've found for my use cases it generates invalid code in at least 2 instances (both have open issues already).

Despite their shortcomings for my cases, the other repos support more features and my assumption is that eventually when they resolve the issues, I'll just switch back to using one of them as I'm sure they will be better maintained.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidLengthValidation        = fmt.Errorf("proto: negative length found during unmarshaling")
	ErrIntOverflowValidation          = fmt.Errorf("proto: integer overflow")
	ErrUnexpectedEndOfGroupValidation = fmt.Errorf("proto: unexpected end of group")
)
View Source
var E_Field = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.FieldOptions)(nil),
	ExtensionType: (*FieldValidation)(nil),
	Field:         61032,
	Name:          "validation.field",
	Tag:           "bytes,61032,opt,name=field",
	Filename:      "validation.proto",
}
View Source
var E_Message = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.MessageOptions)(nil),
	ExtensionType: (*MessageValidation)(nil),
	Field:         61032,
	Name:          "validation.message",
	Tag:           "bytes,61032,opt,name=message",
	Filename:      "validation.proto",
}

Functions

This section is empty.

Types

type FieldValidation

type FieldValidation struct {
	// string options
	// if value is defined, it can not be ""
	NotEmptyString *bool `protobuf:"varint,1,opt,name=not_empty_string,json=notEmptyString" json:"not_empty_string,omitempty"`
	// the value has to be exactly this
	Matches *string `protobuf:"bytes,2,opt,name=matches" json:"matches,omitempty"`
	// the value must contain this substring
	Contains *string `protobuf:"bytes,3,opt,name=contains" json:"contains,omitempty"`
	// the value must match this regex
	Regex *string `protobuf:"bytes,4,opt,name=regex" json:"regex,omitempty"`
	// integer options
	// value must be less than or equal to this
	IntLte *int64 `protobuf:"varint,5,opt,name=int_lte,json=intLte" json:"int_lte,omitempty"`
	// value must be greater than or equal to this
	IntGte *int64 `protobuf:"varint,6,opt,name=int_gte,json=intGte" json:"int_gte,omitempty"`
	// value must equal this
	IntEq *int64 `protobuf:"varint,7,opt,name=int_eq,json=intEq" json:"int_eq,omitempty"`
	// floating point type options
	// value must be less than or equal to this
	FloatLte *float64 `protobuf:"fixed64,8,opt,name=float_lte,json=floatLte" json:"float_lte,omitempty"`
	// value must be greater than or equal to this
	FloatGte *float64 `protobuf:"fixed64,9,opt,name=float_gte,json=floatGte" json:"float_gte,omitempty"`
	// value must equal this
	FloatEq *float64 `protobuf:"fixed64,10,opt,name=float_eq,json=floatEq" json:"float_eq,omitempty"`
	// more string options
	// value must be at least this long
	MinLen *int64 `protobuf:"varint,11,opt,name=min_len,json=minLen" json:"min_len,omitempty"`
	// value must be at most this long
	MaxLen *int64 `protobuf:"varint,12,opt,name=max_len,json=maxLen" json:"max_len,omitempty"`
	// value must be exactly this long
	EqLen *int64 `protobuf:"varint,13,opt,name=eq_len,json=eqLen" json:"eq_len,omitempty"`
	// common option - define an error message instead of the default
	// {field} and {value} can be used as placeholders and will be replaced with the actual values
	Error *string `protobuf:"bytes,14,opt,name=error" json:"error,omitempty"`
	// more strings
	// validate using github.com/google/uuid that this is an actual uuid
	IsUuid *bool `protobuf:"varint,15,opt,name=is_uuid,json=isUuid" json:"is_uuid,omitempty"`
	// validate using net/mail ParseAddress that this is a valid email
	IsEmail *bool `protobuf:"varint,16,opt,name=is_email,json=isEmail" json:"is_email,omitempty"`
	// validate using time.Parse that this is a valid date using the default format of YYYY-MM-DD (2006-01-02 in go's
	// crazy syntax)
	IsIso8601Date *bool `protobuf:"varint,17,opt,name=is_iso8601_date,json=isIso8601Date" json:"is_iso8601_date,omitempty"`
	// use strings.Trim to trim whitespace from the value
	Trim *bool `protobuf:"varint,18,opt,name=trim" json:"trim,omitempty"`
	// use strings.ToLower before validating
	Lc *bool `protobuf:"varint,19,opt,name=lc" json:"lc,omitempty"`
	// use strings.ToUpper before validating
	Uc *bool `protobuf:"varint,20,opt,name=uc" json:"uc,omitempty"`
	// common option - a function name that you presumably defined in the same package space as the
	// code we generate that will be called via m.Field = FuncName(m.Field) allowing for any custom transformation
	TransformFunc *string `protobuf:"bytes,21,opt,name=transform_func,json=transformFunc" json:"transform_func,omitempty"`
	// do not generate validation code for a field; field type may not be supported i.e. oneof
	DoNotValidate        *string  `protobuf:"bytes,22,opt,name=do_not_validate,json=doNotValidate" json:"do_not_validate,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (*FieldValidation) Descriptor

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

func (*FieldValidation) GetContains

func (m *FieldValidation) GetContains() string

func (*FieldValidation) GetDoNotValidate

func (m *FieldValidation) GetDoNotValidate() string

func (*FieldValidation) GetEqLen

func (m *FieldValidation) GetEqLen() int64

func (*FieldValidation) GetError

func (m *FieldValidation) GetError() string

func (*FieldValidation) GetFloatEq

func (m *FieldValidation) GetFloatEq() float64

func (*FieldValidation) GetFloatGte

func (m *FieldValidation) GetFloatGte() float64

func (*FieldValidation) GetFloatLte

func (m *FieldValidation) GetFloatLte() float64

func (*FieldValidation) GetIntEq

func (m *FieldValidation) GetIntEq() int64

func (*FieldValidation) GetIntGte

func (m *FieldValidation) GetIntGte() int64

func (*FieldValidation) GetIntLte

func (m *FieldValidation) GetIntLte() int64

func (*FieldValidation) GetIsEmail

func (m *FieldValidation) GetIsEmail() bool

func (*FieldValidation) GetIsIso8601Date

func (m *FieldValidation) GetIsIso8601Date() bool

func (*FieldValidation) GetIsUuid

func (m *FieldValidation) GetIsUuid() bool

func (*FieldValidation) GetLc

func (m *FieldValidation) GetLc() bool

func (*FieldValidation) GetMatches

func (m *FieldValidation) GetMatches() string

func (*FieldValidation) GetMaxLen

func (m *FieldValidation) GetMaxLen() int64

func (*FieldValidation) GetMinLen

func (m *FieldValidation) GetMinLen() int64

func (*FieldValidation) GetNotEmptyString

func (m *FieldValidation) GetNotEmptyString() bool

func (*FieldValidation) GetRegex

func (m *FieldValidation) GetRegex() string

func (*FieldValidation) GetTransformFunc

func (m *FieldValidation) GetTransformFunc() string

func (*FieldValidation) GetTrim

func (m *FieldValidation) GetTrim() bool

func (*FieldValidation) GetUc

func (m *FieldValidation) GetUc() bool

func (*FieldValidation) Marshal

func (m *FieldValidation) Marshal() (dAtA []byte, err error)

func (*FieldValidation) MarshalTo

func (m *FieldValidation) MarshalTo(dAtA []byte) (int, error)

func (*FieldValidation) MarshalToSizedBuffer

func (m *FieldValidation) MarshalToSizedBuffer(dAtA []byte) (int, error)

func (*FieldValidation) ProtoMessage

func (*FieldValidation) ProtoMessage()

func (*FieldValidation) Reset

func (m *FieldValidation) Reset()

func (*FieldValidation) Size

func (m *FieldValidation) Size() (n int)

func (*FieldValidation) String

func (m *FieldValidation) String() string

func (*FieldValidation) Unmarshal

func (m *FieldValidation) Unmarshal(dAtA []byte) error

func (*FieldValidation) XXX_DiscardUnknown

func (m *FieldValidation) XXX_DiscardUnknown()

func (*FieldValidation) XXX_Marshal

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

func (*FieldValidation) XXX_Merge

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

func (*FieldValidation) XXX_Size

func (m *FieldValidation) XXX_Size() int

func (*FieldValidation) XXX_Unmarshal

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

type MessageValidation

type MessageValidation struct {
	// returns right away after the first error instead of the default of validating all fields
	ReturnOnError *bool `protobuf:"varint,1,opt,name=return_on_error,json=returnOnError" json:"return_on_error,omitempty"`
	// uses strings.Trim on all strings in this message
	TrimStrings          *bool    `protobuf:"varint,2,opt,name=trim_strings,json=trimStrings" json:"trim_strings,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (*MessageValidation) Descriptor

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

func (*MessageValidation) GetReturnOnError

func (m *MessageValidation) GetReturnOnError() bool

func (*MessageValidation) GetTrimStrings

func (m *MessageValidation) GetTrimStrings() bool

func (*MessageValidation) Marshal

func (m *MessageValidation) Marshal() (dAtA []byte, err error)

func (*MessageValidation) MarshalTo

func (m *MessageValidation) MarshalTo(dAtA []byte) (int, error)

func (*MessageValidation) MarshalToSizedBuffer

func (m *MessageValidation) MarshalToSizedBuffer(dAtA []byte) (int, error)

func (*MessageValidation) ProtoMessage

func (*MessageValidation) ProtoMessage()

func (*MessageValidation) Reset

func (m *MessageValidation) Reset()

func (*MessageValidation) Size

func (m *MessageValidation) Size() (n int)

func (*MessageValidation) String

func (m *MessageValidation) String() string

func (*MessageValidation) Unmarshal

func (m *MessageValidation) Unmarshal(dAtA []byte) error

func (*MessageValidation) XXX_DiscardUnknown

func (m *MessageValidation) XXX_DiscardUnknown()

func (*MessageValidation) XXX_Marshal

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

func (*MessageValidation) XXX_Merge

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

func (*MessageValidation) XXX_Size

func (m *MessageValidation) XXX_Size() int

func (*MessageValidation) XXX_Unmarshal

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

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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