Permalink
Browse files

Builtin Json params support (#27)

* added param interface and map implementation

* updated param type

* moved params to its own package

* use untyped nil

* use reflect make slice

* added json params support

* updated Params interface

* added example test
  • Loading branch information...
Fs02 committed Aug 21, 2018
1 parent 65acb44 commit 43243580c77d07984ac52588732cb121d3ab3009

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
@@ -26,10 +26,11 @@ package main
import (
"time"
"github.com/Fs02/grimoire"
. "github.com/Fs02/grimoire/c"
"github.com/Fs02/grimoire/adapter/mysql"
"github.com/Fs02/grimoire/changeset"
"github.com/Fs02/grimoire/params"
)
type Product struct {
@@ -41,7 +42,8 @@ type Product struct {
}
// ChangeProduct prepares data before database operation.
func ChangeProduct(product interface{}, params map[string]interface{}) *changeset.Changeset {
// Such as casting value to appropriate types and perform validations.
func ChangeProduct(product interface{}, params params.Params) *changeset.Changeset {
ch := changeset.Cast(product, params, []string{"name", "price"})
changeset.ValidateRequired(ch, []string{"name", "price"})
changeset.ValidateMin(ch, "price", 100)
@@ -61,25 +63,40 @@ func main() {
var product Product
// Inserting Products.
// Changeset is used when creating or updating your data.
ch := ChangeProduct(product, map[string]interface{}{
"name": "shampoo",
"price": 1000
ch := ChangeProduct(product, params.Map{
"name": "shampoo",
"price": 1000,
})
if ch.Error() != nil {
// do something
// handle error
}
// Changeset can also be created directly from json string.
jsonch := ChangeProduct(product, params.ParseJSON(`{
"name": "soap",
"price": 2000,
}`))
// Create products with changeset and return the result to &product,
repo.From("products").MustCreate(&product, ch)
if err = repo.From("products").Insert(&product, ch); err != nil {
// handle error
}
// or panic when insertion pailed
repo.From("products").MustInsert(&product, jsonch)
// Querying Products.
// Find a product with id 1.
repo.From("products").Find(1).MustOne(&product)
// Updating Products.
// Update products with id=1.
repo.From("products").Find(1).MustUpdate(&product, ch)
// Deleting Products.
// Delete Product with id=1.
repo.From("products").Find(1).MustDelete()
}
@@ -7,6 +7,7 @@ import (

"github.com/Fs02/grimoire"
"github.com/Fs02/grimoire/changeset"
"github.com/Fs02/grimoire/params"
"github.com/stretchr/testify/assert"
)

@@ -18,20 +19,20 @@ func Insert(t *testing.T, repo grimoire.Repo) {
tests := []struct {
query grimoire.Query
record interface{}
params map[string]interface{}
input params.Params
}{
{repo.From(users), &User{}, map[string]interface{}{}},
{repo.From(users), &User{}, map[string]interface{}{"name": "insert", "age": 100}},
{repo.From(users), &User{}, map[string]interface{}{"name": "insert", "age": 100, "note": "note"}},
{repo.From(users), &User{}, map[string]interface{}{"note": "note"}},
{repo.From(addresses), &Address{}, map[string]interface{}{}},
{repo.From(addresses), &Address{}, map[string]interface{}{"address": "address"}},
{repo.From(addresses), &Address{}, map[string]interface{}{"user_id": user.ID}},
{repo.From(addresses), &Address{}, map[string]interface{}{"address": "address", "user_id": user.ID}},
{repo.From(users), &User{}, params.Map{}},
{repo.From(users), &User{}, params.Map{"name": "insert", "age": 100}},
{repo.From(users), &User{}, params.Map{"name": "insert", "age": 100, "note": "note"}},
{repo.From(users), &User{}, params.Map{"note": "note"}},
{repo.From(addresses), &Address{}, params.Map{}},
{repo.From(addresses), &Address{}, params.Map{"address": "address"}},
{repo.From(addresses), &Address{}, params.Map{"user_id": user.ID}},
{repo.From(addresses), &Address{}, params.Map{"address": "address", "user_id": user.ID}},
}

for _, test := range tests {
ch := changeset.Cast(test.record, test.params, []string{"name", "age", "note", "address", "user_id"})
ch := changeset.Cast(test.record, test.input, []string{"name", "age", "note", "address", "user_id"})
statement, _ := builder.Insert(test.query.Collection, ch.Changes())

t.Run("Insert|"+statement, func(t *testing.T) {
@@ -55,16 +56,16 @@ func InsertAll(t *testing.T, repo grimoire.Repo) {
query grimoire.Query
schema interface{}
record interface{}
params map[string]interface{}
params params.Params
}{
{repo.From(users), User{}, &[]User{}, map[string]interface{}{}},
{repo.From(users), User{}, &[]User{}, map[string]interface{}{"name": "insert", "age": 100}},
{repo.From(users), User{}, &[]User{}, map[string]interface{}{"name": "insert", "age": 100, "note": "note"}},
{repo.From(users), User{}, &[]User{}, map[string]interface{}{"note": "note"}},
{repo.From(addresses), &Address{}, &[]Address{}, map[string]interface{}{}},
{repo.From(addresses), &Address{}, &[]Address{}, map[string]interface{}{"address": "address"}},
{repo.From(addresses), &Address{}, &[]Address{}, map[string]interface{}{"user_id": user.ID}},
{repo.From(addresses), &Address{}, &[]Address{}, map[string]interface{}{"address": "address", "user_id": user.ID}},
{repo.From(users), User{}, &[]User{}, params.Map{}},
{repo.From(users), User{}, &[]User{}, params.Map{"name": "insert", "age": 100}},
{repo.From(users), User{}, &[]User{}, params.Map{"name": "insert", "age": 100, "note": "note"}},
{repo.From(users), User{}, &[]User{}, params.Map{"note": "note"}},
{repo.From(addresses), &Address{}, &[]Address{}, params.Map{}},
{repo.From(addresses), &Address{}, &[]Address{}, params.Map{"address": "address"}},
{repo.From(addresses), &Address{}, &[]Address{}, params.Map{"user_id": user.ID}},
{repo.From(addresses), &Address{}, &[]Address{}, params.Map{"address": "address", "user_id": user.ID}},
}

for _, test := range tests {
@@ -7,15 +7,16 @@ import (
"github.com/Fs02/grimoire/c"
"github.com/Fs02/grimoire/changeset"
"github.com/Fs02/grimoire/errors"
"github.com/Fs02/grimoire/params"
"github.com/stretchr/testify/assert"
)

var params = map[string]interface{}{
var input = params.Map{
"name": "whiteviolet",
"gender": "male",
"age": 18,
"note": "some note here",
"addresses": []map[string]interface{}{
"addresses": []params.Map{
{
"address": "Aceh, Indonesia",
},
@@ -60,7 +61,7 @@ func queryAll(t *testing.T) func(repo grimoire.Repo) error {
func insertWithAssoc(t *testing.T) func(repo grimoire.Repo) error {
user := User{}

ch := changeUser(user, params)
ch := changeUser(user, input)
assert.Nil(t, ch.Error())

// transaction block
@@ -76,7 +77,7 @@ func insertWithAssoc(t *testing.T) func(repo grimoire.Repo) error {
func insertWithAssocError(t *testing.T) func(repo grimoire.Repo) error {
user := User{}

ch := changeUser(user, params)
ch := changeUser(user, input)
assert.Nil(t, ch.Error())

// transaction block
@@ -93,7 +94,7 @@ func insertWithAssocError(t *testing.T) func(repo grimoire.Repo) error {
func insertWithAssocPanic(t *testing.T) func(repo grimoire.Repo) error {
user := User{}

ch := changeUser(user, params)
ch := changeUser(user, input)
assert.Nil(t, ch.Error())

// transaction block
@@ -110,7 +111,7 @@ func insertWithAssocPanic(t *testing.T) func(repo grimoire.Repo) error {
func replaceAssoc(t *testing.T) func(repo grimoire.Repo) error {
user := User{}

ch := changeUser(user, params)
ch := changeUser(user, input)
assert.Nil(t, ch.Error())

// transaction block
@@ -125,7 +126,7 @@ func replaceAssoc(t *testing.T) func(repo grimoire.Repo) error {
}
}

func changeUser(user interface{}, params map[string]interface{}) *changeset.Changeset {
func changeUser(user interface{}, params params.Params) *changeset.Changeset {
ch := changeset.Cast(user, params, []string{
"name",
"gender",
@@ -136,7 +137,7 @@ func changeUser(user interface{}, params map[string]interface{}) *changeset.Chan
return ch
}

func changeAddress(address interface{}, params map[string]interface{}) *changeset.Changeset {
func changeAddress(address interface{}, params params.Params) *changeset.Changeset {
ch := changeset.Cast(address, params, []string{"address"})
return ch
}
@@ -6,6 +6,7 @@ import (
"github.com/Fs02/grimoire"
"github.com/Fs02/grimoire/c"
"github.com/Fs02/grimoire/changeset"
"github.com/Fs02/grimoire/params"
"github.com/stretchr/testify/assert"
)

@@ -20,14 +21,14 @@ func Update(t *testing.T, repo grimoire.Repo) {
tests := []struct {
query grimoire.Query
record interface{}
params map[string]interface{}
params params.Params
}{
{repo.From(users).Find(user.ID), &User{}, map[string]interface{}{"name": "insert", "age": 100}},
{repo.From(users).Find(user.ID), &User{}, map[string]interface{}{"name": "insert", "age": 100, "note": "note"}},
{repo.From(users).Find(user.ID), &User{}, map[string]interface{}{"note": "note"}},
{repo.From(addresses).Find(address.ID), &Address{}, map[string]interface{}{"address": "address"}},
{repo.From(addresses).Find(address.ID), &Address{}, map[string]interface{}{"user_id": user.ID}},
{repo.From(addresses).Find(address.ID), &Address{}, map[string]interface{}{"address": "address", "user_id": user.ID}},
{repo.From(users).Find(user.ID), &User{}, params.Map{"name": "insert", "age": 100}},
{repo.From(users).Find(user.ID), &User{}, params.Map{"name": "insert", "age": 100, "note": "note"}},
{repo.From(users).Find(user.ID), &User{}, params.Map{"note": "note"}},
{repo.From(addresses).Find(address.ID), &Address{}, params.Map{"address": "address"}},
{repo.From(addresses).Find(address.ID), &Address{}, params.Map{"user_id": user.ID}},
{repo.From(addresses).Find(address.ID), &Address{}, params.Map{"address": "address", "user_id": user.ID}},
}

for _, test := range tests {
@@ -55,10 +56,10 @@ func UpdateWhere(t *testing.T, repo grimoire.Repo) {
query grimoire.Query
schema interface{}
record interface{}
params map[string]interface{}
params params.Params
}{
{repo.From(users).Where(c.Eq(name, "update all")), User{}, &[]User{}, map[string]interface{}{"name": "insert", "age": 100}},
{repo.From(addresses).Where(c.Eq(c.I("address"), "update_all")), Address{}, &[]Address{}, map[string]interface{}{"address": "address", "user_id": user.ID}},
{repo.From(users).Where(c.Eq(name, "update all")), User{}, &[]User{}, params.Map{"name": "insert", "age": 100}},
{repo.From(addresses).Where(c.Eq(c.I("address"), "update_all")), Address{}, &[]Address{}, params.Map{"address": "address", "user_id": user.ID}},
}

for _, test := range tests {
@@ -4,6 +4,7 @@ import (
"strings"
"testing"

"github.com/Fs02/grimoire/params"
"github.com/stretchr/testify/assert"
)

@@ -13,11 +14,11 @@ func TestApplyString(t *testing.T) {
}

user := User{}
params := map[string]interface{}{
input := params.Map{
"name": "¡¡¡Hello, Gophers!!!",
}

ch := Cast(user, params, []string{"name"})
ch := Cast(user, input, []string{"name"})
ApplyString(ch, "name", func(s string) string {
return strings.TrimPrefix(s, "¡¡¡Hello, ")
})
@@ -31,11 +32,11 @@ func TestApplyString_ignored(t *testing.T) {
}

user := User{}
params := map[string]interface{}{
input := params.Map{
"name": 1,
}

ch := Cast(user, params, []string{"name"})
ch := Cast(user, input, []string{"name"})
ApplyString(ch, "name", func(s string) string {
return strings.TrimPrefix(s, "¡¡¡Hello, ")
})
@@ -4,6 +4,7 @@ import (
"reflect"
"strings"

"github.com/Fs02/grimoire/params"
"github.com/azer/snakecase"
)

@@ -12,7 +13,7 @@ var CastErrorMessage = "{field} is invalid"

// Cast params as changes for the given data according to the permitted fields. Returns a new changeset.
// params will only be added as changes if it does not have the same value as the field in the data.
func Cast(data interface{}, params map[string]interface{}, fields []string, opts ...Option) *Changeset {
func Cast(data interface{}, params params.Params, fields []string, opts ...Option) *Changeset {
options := Options{
message: CastErrorMessage,
}
@@ -30,52 +31,25 @@ func Cast(data interface{}, params map[string]interface{}, fields []string, opts
ch.values, ch.types = mapSchema(data)
}

for _, f := range fields {
castField(ch, f, params, options)
}

return ch
}

func castField(ch *Changeset, field string, params map[string]interface{}, options Options) {
par, pexist := params[field]
val, vexist := ch.values[field]
typ, texist := ch.types[field]
for _, field := range fields {
typ, texist := ch.types[field]
currentValue, vexist := ch.values[field]

if !pexist || !texist {
return
}

if par != (interface{})(nil) {
// cast value from param if not nil.
parTyp := reflect.TypeOf(par)
parVal := reflect.ValueOf(par)
if parTyp.Kind() == reflect.Ptr {
parTyp = parTyp.Elem()
parVal = parVal.Elem()
if !params.Exists(field) || !texist {
continue
}

if parTyp.ConvertibleTo(typ) {
if parVal.IsValid() {
cpar := parVal.Convert(typ).Interface()
if typ.Kind() == reflect.Slice || !vexist || (vexist && cpar != val) {
ch.changes[field] = cpar
}
} else {
ch.changes[field] = reflect.Zero(reflect.PtrTo(typ)).Interface()
if value, valid := params.GetWithType(field, typ); valid {
if (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Array) || (!vexist && value != nil) || (vexist && currentValue != value) {
ch.changes[field] = value
}
return
}
} else {
// create nil for the respected type if current value is not nil.
if ch.values[field] != nil {
ch.changes[field] = reflect.Zero(reflect.PtrTo(typ)).Interface()
} else {
msg := strings.Replace(options.message, "{field}", field, 1)
AddError(ch, field, msg)
}
return
}

msg := strings.Replace(options.message, "{field}", field, 1)
AddError(ch, field, msg)
return ch
}

func mapSchema(data interface{}) (map[string]interface{}, map[string]reflect.Type) {
Oops, something went wrong.

0 comments on commit 4324358

Please sign in to comment.