protoc-gen-validate

command module
v0.0.0-...-8617e1e Latest Latest
Warning

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

Go to latest
Published: Nov 23, 2023 License: Apache-2.0 Imports: 11 Imported by: 0

README

License

protoc-gen-validate

based on proto files' annotation, generating a validate function for each message.

Article describes the past and present of protoc-gen-validate, and I welcome everyone to add my WeChat lyf987667482 to discuss together.

syntax = "proto3";

package foo.v1;

service Demo {
    rpc Numerics(NumericsReq) returns (Empty);
    rpc Strings(StringsReq) returns (Empty);
    rpc Repeated(RepeatedReq) returns (Empty);
}

message Empty {}

message RepeatedReq {
    // @min_items:1
    // @max_items:2
    repeated int32 a = 1;
    // @unique:true
    repeated int64 b = 2;
    // @unique:true
    repeated string c = 3;
    repeated NumericsReq d = 4;
    // @eq:1.23
    repeated float e = 5;
}

message NumericsReq {
    // @eq:1.23
    float a = 1;
    // @lt:20
    // @gt:10
    int32 b = 2;
    // @lte:20
    // @gte:10
    uint64 c = 3;
    // @in:[1,2,3]
    fixed32 d = 4;
    // @not_in:[1,2,3]
    float e = 5;
    // @range:(1,5)
    float f = 6;
    // @range:[1,5]
    float g = 7;
}

message StringsReq {
    // @contains:"bar"
    string a = 1;
    // @not_contains:"bar"
    string b = 2;
    // @eq:"bar"
    string c = 3;
    // @in:["foo", "bar", "baz"]
    string d = 4;
    // @not_in:["foo", "bar", "baz"]
    string e = 5;
    // @len:5
    string f = 6;
    // @min_len:5
    // @max_len:10
    string g = 7;
    // @pattern:"(?i)^[0-9a-f]+$"
    string h = 8;
    // @prefix:"foo"
    string i = 9;
    // @suffix:"bar"
    string j = 10;
    // @type:url
    string k = 11;
    // @type:phone
    string l = 12;
    // @type:email
    string m = 13;
    // @type:ip
    string n = 14;
}

message Required {
    // @required:true
    Foo a = 1;
}

message Foo {
    string a = 1;
    string b = 2;
}

Created validation method for Numerics:

func (m *NumericsReq) Validate() error {
  if m == nil {
          return nil
  }  
  if m.GetA() != 1.23 {
          return NumericsReqValidationError{
                  field:  "A",
                  reason: "value must equal 1.23",
          }
  }  
  if m.GetB() >= 20 {
          return NumericsReqValidationError{
                  field:  "B",
                  reason: "value must less than 20",
          }
  }  
  if m.GetB() <= 10 {
          return NumericsReqValidationError{
                  field:  "B",
                  reason: "value must greater than 10",
          }
  }  
  if m.GetC() > 20 {
          return NumericsReqValidationError{
                  field:  "C",
                  reason: "value must less than or equal to 20",
          }
  }  
  if m.GetC() < 10 {
          return NumericsReqValidationError{
                  field:  "C",
                  reason: "value must greater than or equal to 10",
          }
  }  
  var NumericsReq_D_In = map[uint32]struct{}{  
          1: {},  
          2: {},  
          3: {},
  }  
  if _, ok := NumericsReq_D_In[m.GetD()]; !ok {
          return NumericsReqValidationError{
                  field:  "D",
                  reason: "value must be in list [1,2,3]",
          }
  }  
  var NumericsReq_E_NotIn = map[float32]struct{}{  
          1: {},  
          2: {},  
          3: {},
  }  
  if _, ok := NumericsReq_E_NotIn[m.GetE()]; ok {
          return NumericsReqValidationError{
                  field:  "E",
                  reason: "value must be not in list [1,2,3]",
          }
  }  
  if m.GetF() <= 1 || m.GetF() >= 5 {
          return NumericsReqValidationError{
                  field:  "F",
                  reason: "value must in range (1,5)",
          }
  }  
  if m.GetG() < 1 || m.GetG() > 5 {
          return NumericsReqValidationError{
                  field:  "G",
                  reason: "value must in range [1,5]",
          }
  }  
  return nil
}

Installation

go install github.com/giftDad/protoc-gen-validate

Example

protoc  examples/foo.proto  --validate_out=Mexamples/foo.proto=examples/\;examples:. \
--go_out=Mexamples/foo.proto=examples/\;examples:.
go test -v ./examples/

Application

Suitable for RPC frameworks based on protobuf, such as gi-micro,twirp and more. There are two ways to use it, using gi-micro as an example:

  1. The first approach is to modify the protoc-gen-xxx tool, such as gi-micro's generator Add the following lines at cmd/protoc-gen-micro/plugin/micro/micro.go:480:
g.P("func (h *", unexport(servName), "Handler) ", methName, "(ctx ", contextPkg, ".Context, in *", inType, ", out *",outType, ") error {")
// Add the following three lines
g.P("if err := in.Validate();err != nil {")
g.P("return err")
g.P("}")

g.P("return h.", serveType, ".", methName, "(ctx, in, out)")
g.P("}")
g.P()

This will transform the generated method into:

func (h *greeterHandler) Hello(ctx context.Context, in *Request, out *Response) error {
    if err := in.Validate();err != nil {
        return err
    }
    return h.GreeterHandler.Hello(ctx, in, out)
}

Additionally, add --validate_out=. to your protoc command to apply protoc-gen-validate.

  1. The second approach is to manually call Validate() in the server layer:
func (g *Greeter) Hello(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
    if err := req.Validate(); err != nil {
        return err
    }
    rsp.Greeting = "Hello " + req.Name
    return nil
}

The advantage of the first approach is that it embeds validation in the program without requiring any business operations, but it introduces a certain level of intrusiveness. The second approach is more like a manual mode, where you call validation when needed.

Supported Rules

Struct Types

Required
message Required {
    // @required:true
    Foo a = 1;
}

message Foo {
    string a = 1;
    string b = 2;
}

Numeric Types

(float, double, int32, int64, uint32, uint64 , sint32, sint64, fixed32, fixed64, sfixed32, sfixed64)

Equality
// @eq:1.23
float a = 1;
Greater than, less than, equal to
// @lt:20
// @gt:10
int32 b = 2;
// @lte:20
// @gte:10
uint64 c = 3;
Inclusion in an array
// @in:[1,2,3]
fixed32 d = 4;
// @not_in:[1,2,3]
float e = 5;
Open and closed intervals
// @range:(1,5)
float f = 6;
// @range:[1,5]
float g = 7;

String Types

Equality
// @eq:"bar"
string c = 3;
Substring containment
// @contains:"bar"
string a = 1;
// @not_contains:"bar"
string b = 2;
Inclusion in an array
// @in:["foo", "bar", "baz"]
string d = 4;
// @not_in:["foo", "bar", "baz"]
string e = 5;
String length
// @len:5
string f = 6;
String length range
// @min_len:5
// @max_len:10
string g = 7;
Regular expression pattern
// @pattern:"(?i)^[0-9a-f]+$"
string h = 8;
Prefix and suffix
// @prefix:"foo"
string i = 9;
// @suffix:"bar"
string j = 10;
Common types
// @type:url
string k = 11;
// @type:phone
string l = 12;
// @type:email
string m = 13;
// @type:ip
string n = 14;

Array Types

Supports all single-item rules
// @gt:10
// @lt:10
repeated float e = 5;
Supports array length control
// @min_items:1
// @max_items:2
repeated int32 a = 1;
Supports array uniqueness check
// @unique:true
repeated int64 b = 2;
// @unique:true
repeated string c = 3;

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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