Skip to content

Commit

Permalink
feat: implement marshallable schema (#33)
Browse files Browse the repository at this point in the history
* feat: implement MarshalJson method for fields

* test: add tests for marshalling schema json

* docs: update readme
  • Loading branch information
miladibra10 authored Oct 18, 2022
1 parent 0827424 commit 499c027
Show file tree
Hide file tree
Showing 26 changed files with 306 additions and 41 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ func main() {

`schema` object contains a string field, named `name`. This code validates `jsonString`.

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

## Integer
Expand Down
22 changes: 22 additions & 0 deletions array.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vjson

import (
"encoding/json"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
)
Expand Down Expand Up @@ -82,6 +83,27 @@ func (a *ArrayField) MaxLength(length int) *ArrayField {
return a
}

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)
}

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)
}
return json.Marshal(ArrayFieldSpec{
Name: a.name,
Type: arrayType,
Required: a.required,
Items: items,
MinLength: a.minLength,
MaxLength: a.maxLength,
})
}

// Array is the constructor of an array field.
func Array(name string, itemField Field) *ArrayField {
return &ArrayField{
Expand Down
11 changes: 6 additions & 5 deletions array_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package vjson

// ArrayFieldSpec is a type used for parsing an ArrayField
type ArrayFieldSpec struct {
Name string `mapstructure:"name"`
Required bool `mapstructure:"required"`
Items map[string]interface{} `mapstructure:"items"`
MinLength int `mapstructure:"min_length"`
MaxLength int `mapstructure:"max_length"`
Name string `mapstructure:"name" json:"name"`
Type fieldType `json:"type"`
Required bool `mapstructure:"required" json:"required,omitempty"`
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"`
}

// NewArray receives an ArrayFieldSpec and returns and ArrayField
Expand Down
16 changes: 16 additions & 0 deletions array_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vjson

import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)
Expand Down Expand Up @@ -96,6 +97,21 @@ func TestArrayField_Validate(t *testing.T) {
})
}

func TestArrayField_MarshalJSON(t *testing.T) {
field := Array("foo", String("bar"))

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"])
assert.Equal(t, "bar", data["items"].(map[string]interface{})["name"])
}

func TestNewArray(t *testing.T) {
field := NewArray(ArrayFieldSpec{
Name: "bar",
Expand Down
14 changes: 13 additions & 1 deletion boolean.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package vjson

import "github.com/pkg/errors"
import (
"encoding/json"
"github.com/pkg/errors"
)

// BooleanField is the type for validating booleans in a JSON
type BooleanField struct {
Expand Down Expand Up @@ -55,6 +58,15 @@ func (b *BooleanField) ShouldBe(value bool) *BooleanField {
return b
}

func (b *BooleanField) MarshalJSON() ([]byte, error) {
return json.Marshal(BooleanFieldSpec{
Name: b.name,
Type: booleanType,
Required: b.required,
Value: b.value,
})
}

// Boolean is the constructor of a boolean field
func Boolean(name string) *BooleanField {
return &BooleanField{
Expand Down
7 changes: 4 additions & 3 deletions boolean_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package vjson

// BooleanFieldSpec is a type used for parsing an BooleanField
type BooleanFieldSpec struct {
Name string `mapstructure:"name"`
Required bool `mapstructure:"required"`
Value bool `mapstructure:"value"`
Name string `mapstructure:"name" json:"name"`
Type fieldType `json:"type"`
Required bool `mapstructure:"required" json:"required,omitempty"`
Value bool `mapstructure:"value" json:"value,omitempty"`
}

// NewBoolean receives an BooleanFieldSpec and returns and BooleanField
Expand Down
15 changes: 15 additions & 0 deletions boolean_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vjson

import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)
Expand Down Expand Up @@ -55,6 +56,20 @@ func TestBooleanField_Validate(t *testing.T) {
})
}

func TestBooleanField_MarshalJSON(t *testing.T) {
field := Boolean("foo")

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

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

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

func TestNewBoolean(t *testing.T) {
field := NewBoolean(BooleanFieldSpec{
Name: "bar",
Expand Down
3 changes: 3 additions & 0 deletions field.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package vjson

import "encoding/json"

// Field is the abstraction on a field in a json.
// different field types can be implemented with implementing this interface.
type Field interface {
json.Marshaler
GetName() string
Validate(interface{}) error
}
20 changes: 20 additions & 0 deletions float.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vjson

import (
"encoding/json"
"fmt"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
Expand Down Expand Up @@ -138,6 +139,25 @@ func (f *FloatField) Range(start, end float64) *FloatField {
return f
}

func (f *FloatField) MarshalJSON() ([]byte, error) {
ranges := make([]FloatRangeSpec, 0, len(f.ranges))
for _, r := range f.ranges {
ranges = append(ranges, FloatRangeSpec{
Start: r.start,
End: r.end,
})
}
return json.Marshal(FloatFieldSpec{
Name: f.name,
Type: floatType,
Required: f.required,
Min: f.min,
Max: f.max,
Positive: f.positive,
Ranges: ranges,
})
}

// Float is the constructor of a float field
func Float(name string) *FloatField {
return &FloatField{
Expand Down
17 changes: 9 additions & 8 deletions float_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ package vjson

// FloatRangeSpec is a type for parsing a float field range
type FloatRangeSpec struct {
Start float64 `mapstructure:"start"`
End float64 `mapstructure:"end"`
Start float64 `mapstructure:"start" json:"start"`
End float64 `mapstructure:"end" json:"end"`
}

// FloatFieldSpec is a type used for parsing an FloatField
type FloatFieldSpec struct {
Name string `mapstructure:"name"`
Required bool `mapstructure:"required"`
Min float64 `mapstructure:"min"`
Max float64 `mapstructure:"max"`
Positive bool `mapstructure:"positive"`
Ranges []FloatRangeSpec `mapstructure:"ranges"`
Name string `mapstructure:"name" json:"name"`
Type fieldType `json:"type"`
Required bool `mapstructure:"required" json:"required,omitempty"`
Min float64 `mapstructure:"min" json:"min,omitempty"`
Max float64 `mapstructure:"max" json:"max,omitempty"`
Positive bool `mapstructure:"positive" json:"positive,omitempty"`
Ranges []FloatRangeSpec `mapstructure:"ranges" json:"ranges,omitempty"`
}

// NewFloat receives an FloatFieldSpec and returns and FloatField
Expand Down
16 changes: 16 additions & 0 deletions float_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vjson

import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)
Expand Down Expand Up @@ -94,6 +95,21 @@ func TestFloatField_Validate(t *testing.T) {
})
}

func TestFloatField_MarshalJSON(t *testing.T) {
field := Float("foo").Range(10, 20)
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(floatType), data["type"])
assert.Equal(t, float64(10), data["ranges"].([]interface{})[0].(map[string]interface{})["start"])
assert.Equal(t, float64(20), data["ranges"].([]interface{})[0].(map[string]interface{})["end"])
}

func TestNewFloat(t *testing.T) {
field := NewFloat(FloatFieldSpec{
Name: "bar",
Expand Down
11 changes: 10 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/miladibra10/vjson

go 1.15
go 1.17

require (
github.com/hashicorp/go-multierror v1.1.1
Expand All @@ -9,3 +9,12 @@ require (
github.com/stretchr/testify v1.7.0
github.com/tidwall/gjson v1.7.5
)

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tidwall/match v1.0.3 // indirect
github.com/tidwall/pretty v1.1.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
20 changes: 20 additions & 0 deletions integer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vjson

import (
"encoding/json"
"fmt"
"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
Expand Down Expand Up @@ -147,6 +148,25 @@ func (i *IntegerField) Range(start, end int) *IntegerField {
return i
}

func (i *IntegerField) MarshalJSON() ([]byte, error) {
ranges := make([]IntRangeSpec, 0, len(i.ranges))
for _, r := range i.ranges {
ranges = append(ranges, IntRangeSpec{
Start: r.start,
End: r.end,
})
}
return json.Marshal(IntegerFieldSpec{
Name: i.name,
Required: i.required,
Min: i.min,
Max: i.max,
Positive: i.positive,
Ranges: ranges,
Type: integerType,
})
}

// Integer is the constructor of an integer field
func Integer(name string) *IntegerField {
return &IntegerField{
Expand Down
17 changes: 9 additions & 8 deletions integer_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ package vjson

// IntRangeSpec is a type for parsing an integer field range
type IntRangeSpec struct {
Start int `mapstructure:"start"`
End int `mapstructure:"end"`
Start int `mapstructure:"start" json:"start"`
End int `mapstructure:"end" json:"end"`
}

// IntegerFieldSpec is a type used for parsing an IntegerField
type IntegerFieldSpec struct {
Name string `mapstructure:"name"`
Required bool `mapstructure:"required"`
Min int `mapstructure:"min"`
Max int `mapstructure:"max"`
Positive bool `mapstructure:"positive"`
Ranges []IntRangeSpec `mapstructure:"ranges"`
Name string `mapstructure:"name" json:"name"`
Type fieldType `json:"type"`
Required bool `mapstructure:"required" json:"required,omitempty"`
Min int `mapstructure:"min" json:"min,omitempty"`
Max int `mapstructure:"max" json:"max,omitempty"`
Positive bool `mapstructure:"positive" json:"positive,omitempty"`
Ranges []IntRangeSpec `mapstructure:"ranges" json:"ranges,omitempty"`
}

// NewInteger receives an IntegerFieldSpec and returns and IntegerField
Expand Down
16 changes: 16 additions & 0 deletions integer_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vjson

import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)
Expand Down Expand Up @@ -120,6 +121,21 @@ func TestIntegerField_Validate(t *testing.T) {
})
}

func TestIntegerField_MarshalJSON(t *testing.T) {
field := Integer("foo").Range(10, 20)
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(integerType), data["type"])
assert.Equal(t, float64(10), data["ranges"].([]interface{})[0].(map[string]interface{})["start"])
assert.Equal(t, float64(20), data["ranges"].([]interface{})[0].(map[string]interface{})["end"])
}

func TestNewInteger(t *testing.T) {
field := NewInteger(IntegerFieldSpec{
Name: "bar",
Expand Down
Loading

0 comments on commit 499c027

Please sign in to comment.