Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add strict mode and array fixed position validate #36

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 41 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ import "github.com/miladibra10/vjson"
func main() {
schemaStr := `
{
"strict": true,
"fields": [
{
"name": "name",
Expand All @@ -118,15 +119,17 @@ func main() {
panic(err)
}

// field age is not validate, so validate is false
jsonString := `
{
"name": "James"
"name": "James",
"age": 10
}
`

err = schema.ValidateString(jsonString)
if err != nil {
panic(err)
fmt.Printf("schema is not valid: " + err.Error())
}
}
```
Expand All @@ -137,6 +140,8 @@ func main() {

> **Note**: You could Marshal your schema as a json object for backup usages with `json.Marshal` function.



# Fields

## Integer
Expand Down Expand Up @@ -375,6 +380,34 @@ vjson.Array("foo", vjson.Integer("item").Range(0,20)).Required().MinLength(2).Ma
}
```

### Fixed Length Array

Each item has a different type in the array.

#### Code
```go
vjson.FixArray("foo", []Field{
vjson.String("item"),
vjson.Integer("item2")
})
```

#### File
```json
{
"name": "foo",
"type": "array",
"required": true,
"fix_items": [{
"name": "item",
"type": "string",
}, {
"name": "item2",
"type": "integer",
}]
}
```

## Object
An object field could be created in code like this:
```go
Expand All @@ -388,6 +421,9 @@ the first argument is the name of object field, and the second one is the schema
some validation characteristics could be added to an array field with chaining some functions:

+ [Required()](#object) sets the field as a required field. validation will return an error if a required field is not present in json object.
+ [Strict()](#object)
When set strict mode is true, all fields in json object must validate. Default strict mode is `false`.


object field could be described by a json for schema parsing.
+ **`name`**: the name of the field
Expand All @@ -403,7 +439,7 @@ a required object field, named `foo` which its valid value is an object with `na
vjson.Object("foo", vjson.NewSchema(
vjson.String("name").Required(),
vjson.String("last_name").Required(),
)).Required()
)).Required().Strict()
```

#### File
Expand All @@ -413,6 +449,7 @@ vjson.Object("foo", vjson.NewSchema(
"type": "object",
"required": true,
"schema": {
"strict": true,
"fields": [
{
"name": "name",
Expand Down
70 changes: 59 additions & 11 deletions array.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package vjson

import (
"encoding/json"

"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
)
Expand All @@ -11,6 +12,7 @@ type ArrayField struct {
name string
required bool
items Field
fixItems []Field

minLength int
minLengthValidation bool
Expand Down Expand Up @@ -54,10 +56,28 @@ func (a *ArrayField) Validate(v interface{}) error {
}
}

thanharrow marked this conversation as resolved.
Show resolved Hide resolved
for _, value := range values {
err := a.items.Validate(value)
if err != nil {
result = multierror.Append(result, errors.Wrapf(err, "%v item is invalid in %s array", value, a.name))
if a.items != nil && a.fixItems != nil {
result = multierror.Append(result, errors.Errorf("could not using both items key and fix items for array %s", a.name))
}

if a.items != nil {
for _, value := range values {
err := a.items.Validate(value)
if err != nil {
result = multierror.Append(result, errors.Wrapf(err, "%v item is invalid in %s array", value, a.name))
}
}
}
if a.fixItems != nil {
if len(a.fixItems) != len(values) {
result = multierror.Append(result, errors.Errorf("length of %s array should is equal %d", a.name, len(a.fixItems)))
return result
}
for i, value := range values {
err := a.fixItems[i].Validate(value)
if err != nil {
result = multierror.Append(result, errors.Wrapf(err, "%v item is invalid in %s array", value, a.name))
}
}
}
return result
Expand All @@ -84,21 +104,40 @@ func (a *ArrayField) MaxLength(length int) *ArrayField {
}

func (a *ArrayField) MarshalJSON() ([]byte, error) {
itemsRaw, err := json.Marshal(a.items)
if err != nil {
return nil, errors.Wrapf(err, "could not marshal items field of array field: %s", a.name)
var items map[string]interface{}
if a.items != nil {
itemsRaw, err := json.Marshal(a.items)
if err != nil {
return nil, errors.Wrapf(err, "could not marshal items field of array field: %s", a.name)
}

items = make(map[string]interface{})
err = json.Unmarshal(itemsRaw, &items)
if err != nil {
return nil, errors.Wrapf(err, "could not unmarshal items field of array field: %s", a.name)
}
}

items := make(map[string]interface{})
err = json.Unmarshal(itemsRaw, &items)
if err != nil {
return nil, errors.Wrapf(err, "could not unmarshal items field of array field: %s", a.name)
var fixItems []map[string]interface{}
if a.fixItems != nil {
itemsRaw, err := json.Marshal(a.fixItems)
if err != nil {
return nil, errors.Wrapf(err, "could not marshal fix items field of array field: %s", a.name)
}

fixItems = []map[string]interface{}{}
err = json.Unmarshal(itemsRaw, &fixItems)
if err != nil {
return nil, errors.Wrapf(err, "could not unmarshal fix items field of array field: %s", a.name)
}
}

return json.Marshal(ArrayFieldSpec{
Name: a.name,
Type: arrayType,
Required: a.required,
Items: items,
FixItems: fixItems,
MinLength: a.minLength,
MaxLength: a.maxLength,
})
Expand All @@ -112,3 +151,12 @@ func Array(name string, itemField Field) *ArrayField {
items: itemField,
}
}

// Array is the constructor of an array field.
func FixArray(name string, itemFields []Field) *ArrayField {
return &ArrayField{
name: name,
required: false,
fixItems: itemFields,
}
}
5 changes: 4 additions & 1 deletion array_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ type ArrayFieldSpec struct {
Items map[string]interface{} `mapstructure:"items" json:"items,omitempty"`
MinLength int `mapstructure:"min_length" json:"minLength,omitempty"`
MaxLength int `mapstructure:"max_length" json:"maxLength,omitempty"`

FixItems []map[string]interface{} `mapstructure:"fix_items" json:"fix_items,omitempty"`
}

// NewArray receives an ArrayFieldSpec and returns and ArrayField
func NewArray(spec ArrayFieldSpec, itemField Field, minLengthValidation, maxLengthValidation bool) *ArrayField {
func NewArray(spec ArrayFieldSpec, itemField Field, fixItemsField []Field, minLengthValidation, maxLengthValidation bool) *ArrayField {
return &ArrayField{
name: spec.Name,
required: spec.Required,
items: itemField,
fixItems: fixItemsField,
minLength: spec.MinLength,
minLengthValidation: minLengthValidation,
maxLength: spec.MaxLength,
Expand Down
27 changes: 25 additions & 2 deletions array_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package vjson

import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"

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

func TestArrayField_GetName(t *testing.T) {
Expand Down Expand Up @@ -116,10 +117,32 @@ func TestNewArray(t *testing.T) {
field := NewArray(ArrayFieldSpec{
Name: "bar",
Required: true,
}, String("foo"), false, false)
}, String("foo"), nil, false, false)

assert.NotNil(t, field)
assert.Equal(t, "bar", field.name)
assert.Equal(t, false, field.minLengthValidation)
assert.Equal(t, false, field.maxLengthValidation)
}

// ---- for fixItems Array ---
func TestFixPositionArrayField_MarshalJSON(t *testing.T) {
field := FixArray("foo", []Field{
Integer("bar"),
String("baz"),
})

b, err := json.Marshal(field)
assert.Nil(t, err)

data := map[string]interface{}{}
err = json.Unmarshal(b, &data)
assert.Nil(t, err)

assert.Equal(t, "foo", data["name"])
assert.Equal(t, string(arrayType), data["type"])

fixItems := data["fix_items"].([]interface{})
assert.Equal(t, "bar", fixItems[0].(map[string]interface{})["name"])
assert.Equal(t, "baz", fixItems[1].(map[string]interface{})["name"])
}
59 changes: 54 additions & 5 deletions examples/schema_parsing/main.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,58 @@
package main

import "github.com/miladibra10/vjson"
import (
"github.com/miladibra10/vjson"
)

func main() {
schemaStr := `
{
"strict": true,
"fields": [
{
"name": "name",
"type": "string"
"required": true
"type": "array",
"required": true,
"fix_items": [{
"name": "11",
"type": "string"
}, {
"name": "12",
"type": "integer"
}]
},
{
"name": "person",
"type": "object",
"required": true,
"schema": {
"strict": true,
"fields": [
{
"name": "name",
"type": "object",
"required": true,
"schema": {
"strict": true,
"fields": [{
"name": "first",
"type": "string",
"required": true
},
{
"name": "last",
"type": "string",
"required": true
}]
}
},
{
"name": "gender",
"type": "string",
"required": true
}
]
}
}
]
}
Expand All @@ -18,10 +61,16 @@ func main() {
if err != nil {
panic(err)
}

jsonString := `
{
"name": "James"
"name": ["hello", 123],
"person": {
"name": {
"first": "asg",
"last": "4234"
},
"gender": "male"
}
}
`

Expand Down
6 changes: 6 additions & 0 deletions object.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package vjson

import (
"encoding/json"

"github.com/pkg/errors"
)

Expand Down Expand Up @@ -52,6 +53,11 @@ func (o *ObjectField) Required() *ObjectField {
return o
}

func (o *ObjectField) Strict() *ObjectField {
o.schema.StrictMode = true
return o
}

func (o *ObjectField) MarshalJSON() ([]byte, error) {
schemaRaw, err := json.Marshal(o.schema)
if err != nil {
Expand Down
Loading