Skip to content

Commit

Permalink
update doc
Browse files Browse the repository at this point in the history
  • Loading branch information
高深 committed Oct 13, 2017
1 parent 693f976 commit aacc3a6
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 1 deletion.
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,94 @@ Generic tool to validate input json string and check the parameters
In most of the micro-service application, we use json to deliver data from one to another, but validating the parameter in json is always boring, so this tool will give you a big help. It can check which parameters are required, and if the value are exceed the limitation, or you can set a default value if some optional key is not given.

## How to Use
Assuming you have this json:
~~~
{
"name": "Matthew",
"age" : 17,
"married" : true,
"hobby" : ["music","sport"],
"major" : "cs",
"contact_info" : {
"tele" : 123456,
"city" : ""
}
}
~~~

* name is required
* age is in 0-120 range, and required
* married is required
* hobby can specify more then 4
* major can only be "cs" or "ee"
* contact_info is optional
* tele has a default value 1234
* city has a default value "shanghai"


Then you must have this struct to unmarshal the json, and check each of the parameter

~~~
type Person struct{
Name string `json:"name"`
Age int `json:"age"`
Married bool `json:"married"`
Hobby []string `json:"hobby"`
Major string `json:"major"`
CI ContactInfo `json:"contact_info"`
}
type ContactInfo struct{
Tele int `json:"tele"`
City string `json:"city"`
}
~~~

use this tool you can done all of this checking work in one line, define the struct like this:

~~~
type Person struct{
Name string `json:"name" is_required:"true"`
Age *int `json:"age" is_required:"true" min:"0" max:"120"`
Married *bool `json:"married" is_required:"true"`
Hobby []string `json:"hobby" max_len:"4"`
Major string `json:"major enum:"cs,ee""`
CI *ContactInfo `json:"contact_info"`
}
type ContactInfo struct{
Tele int `json:"tele" default:"1234"`
City string `json:"city" default:"shanghai"`
}
~~~
with all of this in hand, then run this:

~~~
p := Person{}
body := `
{
"name": "Matthew",
"age" : 17,
"married" : true,
"hobby" : ["music","sport"],
"major" : "cs",
"contact_info" : {
"tele" : 123456,
"city" : ""
}
}
`
err := ValidateJson([]byte(body), &p)
fmt.Println(err)
~~~

Cool! Isn't it?

## Note
* int and bool are two special type, because int takes 0 as a default value when doing unmarshal, bool take a false as a default value, so we have no way to know if 0 is a caller specified value or default value, so we use pointer, if it those key is not given, then it will be a nil pointer

* A embed struct must be define as a pointer, just like ContactInfo

## Unsupport Case
* set default value in array/slice is not support now, will supported later
* nested link is not supported
146 changes: 146 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package validator

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

type Person struct {
Name string `json:"name" is_required:"true"`
Age *int `json:"age" is_required:"true" min:"0" max:"120"`
Married *bool `json:"married" is_required:"true"`
Hobby []string `json:"hobby" max_len:"4"`
Major string `json:"major" enum:"cs,ee"`
CI *ContactInfo `json:"contact_info"`
}

type ContactInfo struct {
Tele int `json:"tele" default:"1234"`
City string `json:"city" default:"shanghai"`
}

func TestExample(t *testing.T) {
p := Person{}
body := `
{
"name": "Matthew",
"age" : 17,
"married" : true,
"hobby" : ["music","sport"],
"major" : "cs",
"contact_info" : {
"city" : ""
}
}
`
err := ValidateJson([]byte(body), &p)
fmt.Println(err)
fmt.Printf("%v\n", p)
fmt.Printf("%v\n", p.CI)
}

func TestExampleMissingRequired(t *testing.T) {
p := Person{}
body := `
{
"age" : 17,
"married" : true,
"hobby" : ["music","sport"],
"major" : "cs",
"contact_info" : {
"city" : ""
}
}
`
err := ValidateJson([]byte(body), &p)
fmt.Println(err)
fmt.Printf("%v\n", p)
fmt.Printf("%v\n", p.CI)

assert.NotEmpty(t, err)
}

func TestExampleAgeExceed(t *testing.T) {
p := Person{}
body := `
{
"name": "Matthew",
"age" : 130,
"married" : true,
"hobby" : ["music","sport"],
"major" : "cs",
"contact_info" : {
"city" : ""
}
}
`
err := ValidateJson([]byte(body), &p)
fmt.Println(err)
fmt.Printf("%v\n", p)
fmt.Printf("%v\n", p.CI)
assert.NotEmpty(t, err)
}

func TestExampleHobbyExceed4(t *testing.T) {
p := Person{}
body := `
{
"name": "Matthew",
"age" : 17,
"married" : true,
"hobby" : ["music","sport","1","2","5"],
"major" : "cs",
"contact_info" : {
"city" : ""
}
}
`
err := ValidateJson([]byte(body), &p)
fmt.Println(err)
fmt.Printf("%v\n", p)
fmt.Printf("%v\n", p.CI)
assert.NotEmpty(t, err)
}

func TestExampleMajorNotInEnum(t *testing.T) {
p := Person{}
body := `
{
"name": "Matthew",
"age" : 17,
"married" : true,
"hobby" : ["music","sport"],
"major" : "csx",
"contact_info" : {
"city" : ""
}
}
`
err := ValidateJson([]byte(body), &p)
fmt.Println(err)
fmt.Printf("%v\n", p)
fmt.Printf("%v\n", p.CI)
assert.NotEmpty(t, err)
}

func TestExampleEmptyCI(t *testing.T) {
p := Person{}
body := `
{
"name": "Matthew",
"age" : 17,
"married" : true,
"hobby" : ["music","sport"],
"major" : "cs"
}
`
err := ValidateJson([]byte(body), &p)
fmt.Println(err)
fmt.Printf("%v\n", p)
fmt.Printf("%v\n", p.CI)
assert.Empty(t, err)
assert.Equal(t, "shanghai", p.CI.City)
assert.Equal(t, 1234, p.CI.Tele)
}
7 changes: 6 additions & 1 deletion validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
// 1. int, bool 类型声明的时候必须用指针
// 2. 如果是嵌入对象,那么必须是required,不然会panic
// 4. 类似链表嵌套
// set default value in array/slice is not support now

func ValidateJson(in []byte, v interface{}) error {
err := json.Unmarshal(in, v)
Expand Down Expand Up @@ -126,13 +127,17 @@ func ValidateParameters(in interface{}) (err error) {
}

if maxLen, ok := sf.Tag.Lookup("max_len"); ok {
maxLenInt, _ := strconv.Atoi(maxLen)
switch sf.Type.Kind() {
case reflect.String:
maxLenInt, _ := strconv.Atoi(maxLen)
s := sv.String()
if len(s) > maxLenInt {
return fmt.Errorf("%s exceed the max len %d", sf.Name, maxLenInt)
}
case reflect.Slice, reflect.Array:
if sv.Len() > maxLenInt {
return fmt.Errorf("%s exceed the max len %d", sf.Name, maxLenInt)
}
}
}

Expand Down

0 comments on commit aacc3a6

Please sign in to comment.