surrealpatch/util/item/each.go
2016-11-04 09:42:00 +00:00

468 lines
11 KiB
Go

// Copyright © 2016 Abcum Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package item
import (
"fmt"
"regexp"
"github.com/robertkrimen/otto"
"github.com/yuin/gopher-lua"
"github.com/abcum/surreal/cnf"
"github.com/abcum/surreal/sql"
"github.com/abcum/surreal/util/conv"
)
func (this *Doc) each(fld *sql.DefineFieldStatement) (err error) {
return this.current.Walk(func(key string, val interface{}) error {
old := this.initial.Get(key).Data()
if fld.Readonly && old != nil {
this.current.Set(old, key)
return nil
}
if fld.Code != "" {
if cnf.Settings.DB.Lang == "js" {
vm := otto.New()
vm.Set("doc", this.current.Copy())
ret, err := vm.Run("(function() { " + fld.Code + " })()")
if err != nil {
return fmt.Errorf("Problem executing code: %v %v", fld.Code, err.Error())
}
if ret.IsUndefined() {
this.current.Del(key)
} else {
val, _ := ret.Export()
this.current.Set(val, key)
}
}
if cnf.Settings.DB.Lang == "lua" {
vm := lua.NewState()
defer vm.Close()
vm.SetGlobal("doc", toLUA(vm, this.current.Copy()))
if err := vm.DoString(fld.Code); err != nil {
return fmt.Errorf("Problem executing code: %v %v", fld.Code, err.Error())
}
ret := vm.Get(-1)
if ret == lua.LNil {
this.current.Del(key)
} else {
this.current.Set(frLUA(ret), key)
}
}
}
// Ensure that any defined fields are correctly
// formatted according to their defined type.
if err = this.chck(fld, key, old, val); err != nil {
return err
}
// Ensure that any default fields are correctly
// formatted according to their defined type.
if err = this.chck(fld, key, old, val); err != nil {
return err
}
// Otherwise this is all good.
return nil
}, fld.Name)
}
func (this *Doc) chck(fld *sql.DefineFieldStatement, key string, old, val interface{}) (err error) {
var exi bool
// Ensure that any fields which have been set to
// null are reset back to default if specified.
exi = this.current.Exists(key)
val = this.current.Get(key).Data()
if fld.Default != nil && (exi == false || val == nil && fld.Notnull) {
switch def := fld.Default.(type) {
case sql.Null, *sql.Null:
this.current.Set(nil, key)
default:
this.current.Set(fld.Default, key)
case sql.Ident:
this.current.Set(this.current.Get(def.ID).Data(), key)
case *sql.Ident:
this.current.Set(this.current.Get(def.ID).Data(), key)
}
}
// Check to see if any field which has been set
// to defaults now satisfies the field constraints.
exi = this.current.Exists(key)
val = this.current.Get(key).Data()
if fld.Notnull && exi == true && val == nil {
return fmt.Errorf("Field '%v' can't be null", key)
}
if fld.Mandatory && exi == false {
return fmt.Errorf("Need to set field '%v'", key)
}
// Ensure that any defined fields are correctly
// formatted according to their defined type.
if val != nil {
switch fld.Type {
case "url":
if cnv, err := conv.ConvertToUrl(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be a URL, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "uuid":
if cnv, err := conv.ConvertToUuid(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be a UUID, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "color":
if cnv, err := conv.ConvertToColor(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be a HEX or RGB color, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "email":
if cnv, err := conv.ConvertToEmail(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be an email address, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "phone":
if cnv, err := conv.ConvertToPhone(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be a phone number, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "array":
if cnv, err := conv.ConvertToArray(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be an array, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "object":
if cnv, err := conv.ConvertToObject(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be an object, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "domain":
if cnv, err := conv.ConvertToDomain(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be a domain name, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "base64":
if cnv, err := conv.ConvertToBase64(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be base64 data, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "record":
if cnv, ok := val.(*sql.Thing); ok {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be a record, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "string":
if cnv, err := conv.ConvertToString(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be a string, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "number":
if cnv, err := conv.ConvertToNumber(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be a number, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "double":
if cnv, err := conv.ConvertToDouble(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be a double, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "boolean":
if cnv, err := conv.ConvertToBoolean(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be a boolean, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "datetime":
if cnv, err := conv.ConvertToDatetime(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be a datetime, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "latitude":
if cnv, err := conv.ConvertToLatitude(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be a latitude value, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "longitude":
if cnv, err := conv.ConvertToLongitude(val); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be a longitude value, but found '%v'", key, val)
} else {
this.current.Iff(old, key)
}
}
case "custom":
if cnv, err := conv.ConvertToOneOf(val, fld.Enum...); err == nil {
this.current.Set(cnv, key)
} else {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be one of %v, but found '%v'", key, fld.Enum, val)
} else {
this.current.Iff(old, key)
}
}
}
}
if fld.Match != "" {
if reg, err := regexp.Compile(fld.Match); err != nil {
return fmt.Errorf("Regular expression /%v/ is invalid", fld.Match)
} else {
if !reg.MatchString(fmt.Sprintf("%v", val)) {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to match the regular expression /%v/", key, fld.Match)
} else {
this.current.Iff(old, key)
}
}
}
}
if fld.Min != 0 {
if val = this.current.Get(key).Data(); val != nil {
switch now := val.(type) {
case []interface{}:
if len(now) < int(fld.Min) {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to have at least %v items", key, fld.Min)
} else {
this.current.Iff(old, key)
}
}
case string:
if len(now) < int(fld.Min) {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to have at least %v characters", key, fld.Min)
} else {
this.current.Iff(old, key)
}
}
case int64:
if now < int64(fld.Min) {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be >= %v", key, fld.Min)
} else {
this.current.Iff(old, key)
}
}
case float64:
if now < float64(fld.Min) {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be >= %v", key, fld.Min)
} else {
this.current.Iff(old, key)
}
}
}
}
}
if fld.Max != 0 {
if val = this.current.Get(key).Data(); val != nil {
switch now := val.(type) {
case []interface{}:
if len(now) > int(fld.Max) {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to have %v or fewer items", key, fld.Max)
} else {
this.current.Iff(old, key)
}
}
case string:
if len(now) > int(fld.Max) {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to have %v or fewer characters", key, fld.Max)
} else {
this.current.Iff(old, key)
}
}
case int64:
if now > int64(fld.Max) {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be <= %v", key, fld.Max)
} else {
this.current.Iff(old, key)
}
}
case float64:
if now > float64(fld.Max) {
if fld.Validate {
return fmt.Errorf("Field '%v' needs to be <= %v", key, fld.Max)
} else {
this.current.Iff(old, key)
}
}
}
}
}
return
}