Skip to content

Commit

Permalink
Minor refactoring/Add Readme (#9)
Browse files Browse the repository at this point in the history
* Simplify interface of generated functionality

* Add README
  • Loading branch information
Daniil Kukharau authored Sep 24, 2018
1 parent d9d2dfd commit 710631a
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 20 deletions.
7 changes: 4 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ SRCPATH := $(patsubst %/,%,$(GOPATH))/src

default: options install


.PHONY: options
options:
protoc -I. -I$(SRCPATH) -I./vendor \
Expand All @@ -14,9 +13,11 @@ options:
install:
go install

test: ./example/*
echo ${SRCPATH}
.PHONY: example
example: default
protoc -I. -I${SRCPATH} -I./vendor -I./vendor/github.com/grpc-ecosystem/grpc-gateway --atlas-query-validate_out=. example/example.proto

test: example
go test ./...

.PHONY: vendor
Expand Down
158 changes: 157 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,157 @@
# protoc-gen-atlas-query-validate
# protoc-gen-atlas-query-validate

### Purpose

A [protobuf](https://developers.google.com/protocol-buffers/) compiler plugin
designed to simplify validation of [Atlas](https://github.com/infobloxopen/atlas-app-toolkit)
[gRPC](https://grpc.io/) List [query](https://github.com/infobloxopen/atlas-app-toolkit/blob/master/query/collection_operators.proto) parameters
by generating .pb.atlas.query.validate.go files with validation rules and functions.
Currently query.Filtering and query.Sorting are supported for validation.

### Prerequisites

#### 1. Protobuf Compiler

The protobuf compiler (protoc) is required.

[Official instructions](https://github.com/google/protobuf#protocol-compiler-installation)

[Abbreviated version](https://github.com/grpc-ecosystem/grpc-gateway#installation)

#### 2. Golang Protobuf Code Generator

Get the golang protobuf code generator:

```
go get -u github.com/golang/protobuf/protoc-gen-go
```

#### 3. Vendored Dependencies

Retrieve and install the vendored dependencies for this project with [dep](https://github.com/golang/dep):

```
dep ensure
```

### Installation

To use this tool, install it from code with `make install`, `go install` directly,
or `go get github.com/infobloxopen/protoc-gen-atlas-query-validate`.

### Usage

Once installed, the `atlas-query-validate_out=.` or `--atlas-query-validate_out=${GOPATH}src`
option can be specified in a protoc command to generate the .pb.atlas.query.validate.go files.

#### Validation rules

Validation rules are generated for each gRPC method containing query.Filtering and/or query.Sorting in it's request message
based on the message included in the method's response message(will call it *resource message* for the rest of the document).

By default all fields of *resource message` are allowed for filtering/sorting.

The list of allowed filtering operators and filtering value type/condition type depends on the *filter_type* of the field,
which is either taken from `(atlas.query.validate).filter_type` proto field option or is computed by the
plugin based on the field type if the option is not supplied. Currently *filter_type* can be either STRING or NUMBER.
The following table shows what is allowed for each *filter_type*:

| | STRING | NUMBER |
|-------------------------------------|--------|--------|
| **Filtering operators** | EQ, MATCH, GT, GE, LT, LE | EQ, GT, GE, LT, LE |
| **Filtering value type/condition type** | String, null/StringCondition, NullCondition | Number, null/NumberCondition, NullCondition |

The next table shows how *filter_type* is computed from a proto field type:

| Proto field type | filter_type |
|-----------------------------|---------------|
| enum | STRING |
| string | STRING |
| double | NUMBER |
| float | NUMBER |
| int32 | NUMBER |
| int64 | NUMBER |
| sint32 | NUMBER |
| sint64 | NUMBER |
| uint32 | NUMBER |
| uint64 | NUMBER |
| google.protobuf.StringValue | STRING |
| google.protobuf.DoubleValue | NUMBER |
| google.protobuf.FloatValue | NUMBER |
| google.protobuf.Int32Value | NUMBER |
| google.protobuf.Int64Value | NUMBER |
| google.protobuf.UInt32Value | NUMBER |
| google.protobuf.UInt64Value | NUMBER |
| google.protobuf.Timestamp | STRING |
| gorm.types.UUID | STRING |
| gorm.types.UUIDValue | STRING |
| atlas.rpc.Identifier | STRING |
| gorm.types.InetValue | STRING |

#### Validation functions

Validation functions are the entry points for the generated functionality.
The following validation functions are generated:

```golang
func {Proto_file_name}ValidateFiltering(methodName string, f *query.Filtering) error
```

```golang
func {Proto_file_name}ValidateSorting(methodName string, s *query.Sorting) error
```
Not nil `error` is returned by the functions if validation is not passed.


### Customization

Currently only field-level proto options are supported as customization means. We're planning to add method-level options which will override
field-level options in order to support different validation rules for List methods having the same *resource message*.

* In order to disable sorting for a field set `(atlas.query.validate).disable_sorting` option to `true`.
```golang
bool on_vacation = 3 [(atlas.query.validate).disable_sorting = true];
```

* In order to customize the list of allowed filtering operators pass either a set of `(atlas.query.validate).allow` or
a set of `(atlas.query.validate).deny` options.
- In case of using `(atlas.query.validate).allow` only specified filtering operators are allowed:
```golang
string first_name = 1 [(atlas.query.validate) = {allow: MATCH, allow: EQ}];
```
- In case of using `(atlas.query.validate).deny` all appropriate(for the field type) filtering operators except specified ones are allowed:
```golang
string first_name = 1 [(atlas.query.validate) = {deny: GT, deny: GE, deny: LT, deny: LE}];
```
* In order to change default *filter_type* for a field pass `(atlas.query.validate).filter_type` option.
```golang
CustomType custom_type_string = 10 [(atlas.query.validate) = {filter_type: STRING}];
```
* In order to enable filtering/sorting by nested fields set `(atlas.query.validate).enable_nested_fields` option to true
on the field of a message type.

```golang
message User {
Address home_address = 11 [(atlas.query.validate) = {enable_nested_fields: true}];
Address work_address = 12;
}
message Address {
string city = 1 [(atlas.query.validate) = {allow: EQ, disable_sorting: true}];
string country = 2;
}
```


### Examples

The best way to get started with the plugin is to check out our [example](example/example.proto).
Example .proto files and generated .pb.atlas.query.validate.go demonstrate most of the use cases of the plugin.

Running `make example` will recompile all these test proto files, if you want
to test the effects of changing the options and fields.

### Limitations

This project is currently in development, and is expected to undergo "breaking"
(and fixing) changes
42 changes: 34 additions & 8 deletions example/example.pb.atlas.query.validate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions example/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"testing"

"github.com/infobloxopen/atlas-app-toolkit/query"
"github.com/infobloxopen/protoc-gen-atlas-query-validate/options"
)

func TestFilteringPermissionsValidation(t *testing.T) {
Expand Down Expand Up @@ -40,7 +39,7 @@ func TestFilteringPermissionsValidation(t *testing.T) {
if err != nil {
t.Fatalf("Invalid filtering data '%s'", test.Query)
}
err = options.ValidateFiltering(f, ExampleMessagesRequireQueryValidation["User"])
err = ExampleValidateFiltering("/example.TestService/List", f)
if err != nil {
if test.Err == false {
t.Errorf("Unexpected error for %s query: %s", test.Query, err)
Expand Down Expand Up @@ -76,7 +75,7 @@ func TestSortingPermissionsValidation(t *testing.T) {
if err != nil {
t.Fatalf("Invalid sorting data '%s'", test.Query)
}
err = options.ValidateSorting(s, ExampleMessagesRequireQueryValidation["User"])
err = ExampleValidateSorting("/example.TestService/List", s)
if err != nil {
if test.Err == false {
t.Errorf("Unexpected error for %s query: %s", test.Query, err)
Expand Down
1 change: 1 addition & 0 deletions plugin/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ func CleanImports(pFileText *string) *string {
// GenerateImports writes out required imports for the generated files
func (p *QueryValidatePlugin) GenerateImports(file *generator.FileDescriptor) {
p.PrintImport("options", "github.com/infobloxopen/protoc-gen-atlas-query-validate/options")
p.PrintImport("query", "github.com/infobloxopen/atlas-app-toolkit/query")
}
48 changes: 43 additions & 5 deletions plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import (
)

const (
filtering = ".infoblox.api.Filtering"
sorting = ".infoblox.api.Sorting"
messagesValidationVarSuffix = "MessagesRequireQueryValidation"
methodFilteringVarSuffix = "MethodsRequireFilteringValidation"
methodSortingVarSuffix = "MethodsRequireSortingValidation"
filtering = ".infoblox.api.Filtering"
sorting = ".infoblox.api.Sorting"
messagesValidationVarSuffix = "MessagesRequireQueryValidation"
methodFilteringVarSuffix = "MethodsRequireFilteringValidation"
methodSortingVarSuffix = "MethodsRequireSortingValidation"
validateFilteringMethodSuffix = "ValidateFiltering"
validateSortingMethodSuffix = "ValidateSorting"

protoTypeTimestamp = ".google.protobuf.Timestamp"
protoTypeUUID = ".gorm.types.UUID"
Expand All @@ -41,6 +43,8 @@ type QueryValidatePlugin struct {
messagesValidationVarName string
requiredFilteringValidationVarName string
requiredSortingValidationVarName string
validateFilteringMethodName string
validateSortingMethodName string
}

func (p *QueryValidatePlugin) setFile(file *generator.FileDescriptor) {
Expand All @@ -51,6 +55,8 @@ func (p *QueryValidatePlugin) setFile(file *generator.FileDescriptor) {
p.messagesValidationVarName = generator.CamelCase(strings.TrimSuffix(baseFileName, filepath.Ext(baseFileName)) + messagesValidationVarSuffix)
p.requiredFilteringValidationVarName = generator.CamelCase(strings.TrimSuffix(baseFileName, filepath.Ext(baseFileName)) + methodFilteringVarSuffix)
p.requiredSortingValidationVarName = generator.CamelCase(strings.TrimSuffix(baseFileName, filepath.Ext(baseFileName)) + methodSortingVarSuffix)
p.validateFilteringMethodName = generator.CamelCase(strings.TrimSuffix(baseFileName, filepath.Ext(baseFileName)) + validateFilteringMethodSuffix)
p.validateSortingMethodName = generator.CamelCase(strings.TrimSuffix(baseFileName, filepath.Ext(baseFileName)) + validateSortingMethodSuffix)
}

// Name identifies the plugin
Expand All @@ -69,6 +75,8 @@ func (p *QueryValidatePlugin) Init(g *generator.Generator) {
func (p *QueryValidatePlugin) Generate(file *generator.FileDescriptor) {
p.setFile(file)
p.genValidationData()
p.genValidateFiltering()
p.genValidateSorting()
}

func (p *QueryValidatePlugin) genValidationData() {
Expand Down Expand Up @@ -323,6 +331,36 @@ func (p *QueryValidatePlugin) getDenyRules(field *descriptor.FieldDescriptorProt
return res
}

func (p *QueryValidatePlugin) genValidateFiltering() {
p.P(`func `, p.validateFilteringMethodName, `(methodName string, f *query.Filtering) error {`)
p.P(`objName, ok := `, p.requiredFilteringValidationVarName, `[methodName]`)
p.P(`if !ok {`)
p.P(`return nil`)
p.P(`}`)
p.P(`var info map[string]options.FilteringOption`)
p.P(`info, ok = `, p.messagesValidationVarName, `[objName]`)
p.P(`if !ok {`)
p.P(`return nil`)
p.P(`}`)
p.P(`return options.ValidateFiltering(f, info)`)
p.P(`}`)
}

func (p *QueryValidatePlugin) genValidateSorting() {
p.P(`func `, p.validateSortingMethodName, `(methodName string, s *query.Sorting) error {`)
p.P(`objName, ok := `, p.requiredSortingValidationVarName, `[methodName]`)
p.P(`if !ok {`)
p.P(`return nil`)
p.P(`}`)
p.P(`var info map[string]options.FilteringOption`)
p.P(`info, ok = `, p.messagesValidationVarName, `[objName]`)
p.P(`if !ok {`)
p.P(`return nil`)
p.P(`}`)
p.P(`return options.ValidateSorting(s, info)`)
p.P(`}`)
}

func getQueryValidationOptions(field *descriptor.FieldDescriptorProto) *options.QueryValidate {
if field.Options == nil {
return nil
Expand Down

0 comments on commit 710631a

Please sign in to comment.