Skip to content

Commit

Permalink
added register custom type: closes #5
Browse files Browse the repository at this point in the history
  • Loading branch information
emilgpa committed Jun 27, 2016
1 parent d3b8700 commit fadcc5c
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 26 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -14,6 +14,7 @@ tmp
*.[568vq]
[568vq].out

formam_laboratory_test.go
*.cgo1.go
*.cgo2.c
_cgo_defun.c
Expand Down
12 changes: 12 additions & 0 deletions README.md
Expand Up @@ -17,6 +17,7 @@ Features
* decode `time.Time` with format "2006-01-02"
* decode `url.URL`
* The `slice` and `array` is possible to access without to indicate a index (If it is the last field, of course)`
* You can to register a `func` for a `custom type`
``

Performance
Expand Down Expand Up @@ -50,6 +51,17 @@ Custom Marshaling

Is possible unmarshaling data and the key of a map by the `encoding.TextUnmarshaler` interface.

Custom Type
-----------

Is possible to register a function for a custom type. For example:

```go
decoder.RegisterCustomType(func(vals []string) (interface{}, error) {
return time.Parse("2006-01-02", vals[0])
}, time.Time{})
```

Notes
-----

Expand Down
122 changes: 102 additions & 20 deletions formam.go
Expand Up @@ -33,10 +33,14 @@ func (ma pathMaps) find(id reflect.Value, key string) *pathMap {
return nil
}

// A decoder holds the values from form, the 'reflect' value of main struct
// and the 'reflect' value of current path
type decoder struct {
main reflect.Value
// DecodeCustomTypeFunc is a function that indicate how should to decode a custom type
type DecodeCustomTypeFunc func([]string) (interface{}, error)

// Decoder the main to decode the values
type Decoder struct {
main reflect.Value
formValues url.Values
opts *DecoderOptions

curr reflect.Value
value string
Expand All @@ -48,6 +52,47 @@ type decoder struct {
isKey bool

maps pathMaps

customTypes map[reflect.Type]DecodeCustomTypeFunc
}

// DecoderOptions options for decoding the values
type DecoderOptions struct {
TagName string
}

// RegisterCustomType It is the method responsible for register functions for decoding custom types
func (dec *Decoder) RegisterCustomType(fn DecodeCustomTypeFunc, types ...interface{}) *Decoder {
if dec.customTypes == nil {
dec.customTypes = make(map[reflect.Type]DecodeCustomTypeFunc)
}
for i := range types {
dec.customTypes[reflect.TypeOf(types[i])] = fn
}
return dec
}

// NewDecoder creates a new instance of Decoder
func NewDecoder(opts *DecoderOptions) *Decoder {
dec := &Decoder{opts: opts}
if dec.opts == nil {
dec.opts = &DecoderOptions{}
}
if dec.opts.TagName == "" {
dec.opts.TagName = tagName
}
return dec
}

// Decode decodes the url.Values into a element that must be a pointer to a type provided by argument
func (dec *Decoder) Decode(vs url.Values, dst interface{}) error {
main := reflect.ValueOf(dst)
if main.Kind() != reflect.Ptr {
return newError(fmt.Errorf("formam: the value passed for decode is not a pointer but a %v", main.Kind()))
}
dec.main = main.Elem()
dec.formValues = vs
return dec.prepare()
}

// Decode decodes the url.Values into a element that must be a pointer to a type provided by argument
Expand All @@ -56,9 +101,19 @@ func Decode(vs url.Values, dst interface{}) error {
if main.Kind() != reflect.Ptr {
return newError(fmt.Errorf("formam: the value passed for decode is not a pointer but a %v", main.Kind()))
}
dec := &decoder{main: main.Elem()}
dec := &Decoder{
main: main.Elem(),
formValues: vs,
opts: &DecoderOptions{
TagName: tagName,
},
}
return dec.prepare()
}

func (dec *Decoder) prepare() error {
// iterate over the form's values and decode it
for k, v := range vs {
for k, v := range dec.formValues {
dec.path = k
dec.field = k
dec.values = v
Expand Down Expand Up @@ -94,8 +149,8 @@ func Decode(vs url.Values, dst interface{}) error {
return nil
}

// begin prepare the current path to walk through it
func (dec *decoder) begin() (err error) {
// begin analyzes the current path to walk through it
func (dec *Decoder) begin() (err error) {
inBracket := false
valBracket := ""
bracketClosed := false
Expand Down Expand Up @@ -167,7 +222,7 @@ func (dec *decoder) begin() (err error) {
}

// walk traverses the current path until to the last field
func (dec *decoder) walk() error {
func (dec *Decoder) walk() error {
// check if there is field, if is so, then it should be struct or map (access by .)
if dec.field != "" {
// check if is a struct or map
Expand Down Expand Up @@ -222,7 +277,7 @@ func (dec *decoder) walk() error {
}

// walkMap puts in d.curr the map concrete for decode the current value
func (dec *decoder) walkInMap(key string) {
func (dec *Decoder) walkInMap(key string) {
n := dec.curr.Type()
takeAndAppend := func() {
m := reflect.New(n.Elem()).Elem()
Expand All @@ -240,7 +295,7 @@ func (dec *decoder) walkInMap(key string) {
}

// end finds the last field for decode its value correspondent
func (dec *decoder) end() error {
func (dec *Decoder) end() error {
switch dec.curr.Kind() {
case reflect.Struct:
if err := dec.findStructField(); err != nil {
Expand All @@ -257,9 +312,13 @@ func (dec *decoder) end() error {
}

// decode sets the value in the field
func (dec *decoder) decode() error {
func (dec *Decoder) decode() error {
// has registered a custom type? If so, then decode by it
if ok, err := dec.checkCustomType(); ok || err != nil {
return err
}
// implements UnmarshalText interface? If so, then decode by it
if ok, err := unmarshalText(dec.curr, dec.value); ok || err != nil {
if ok, err := checkUnmarshalText(dec.curr, dec.value); ok || err != nil {
return err
}

Expand Down Expand Up @@ -358,10 +417,17 @@ func (dec *decoder) decode() error {
}
dec.curr.Set(reflect.ValueOf(*u))
default:
if dec.isKey {
dec.field = dec.value
return dec.begin()
}
/*
if dec.isKey {
tmp := dec.curr
dec.field = dec.value
if err := dec.begin(); err != nil {
return err
}
dec.curr = tmp
return nil
}
*/
return newError(fmt.Errorf("formam: not supported type for field \"%v\" in path \"%v\"", dec.field, dec.path))
}
default:
Expand All @@ -373,7 +439,7 @@ func (dec *decoder) decode() error {

// findField finds a field by its name, if it is not found,
// then retry the search examining the tag "formam" of every field of struct
func (dec *decoder) findStructField() error {
func (dec *Decoder) findStructField() error {
var anon reflect.Value

num := dec.curr.NumField()
Expand All @@ -396,7 +462,7 @@ func (dec *decoder) findStructField() error {
// (a field with same name in the current struct should have preference over anonymous struct)
anon = dec.curr
dec.curr = tmp
} else if dec.field == field.Tag.Get(tagName) {
} else if dec.field == field.Tag.Get(dec.opts.TagName) {
// is not found yet, then retry by its tag name "formam"
dec.curr = dec.curr.Field(i)
return nil
Expand All @@ -411,8 +477,24 @@ func (dec *decoder) findStructField() error {
}

// expandSlice expands the length and capacity of the current slice
func (dec *decoder) expandSlice(length int) {
func (dec *Decoder) expandSlice(length int) {
n := reflect.MakeSlice(dec.curr.Type(), length, length)
reflect.Copy(n, dec.curr)
dec.curr.Set(n)
}

// checkCustomType checks if the value to decode has a custom type registered
func (dec *Decoder) checkCustomType() (bool, error) {
if dec.customTypes == nil {
return false, nil
}
if v, ok := dec.customTypes[dec.curr.Type()]; ok {
va, err := v(dec.values)
if err != nil {
return true, err
}
dec.curr.Set(reflect.ValueOf(va))
return true, nil
}
return false, nil
}
33 changes: 33 additions & 0 deletions formam_laboratory_test.go
@@ -0,0 +1,33 @@
package formam

import (
"fmt"
"net/url"
"testing"
)

type Field string

type Laboratory struct {
MapWithStruct3Key map[Field]string
Jodete Field
}

var valss = url.Values{
"Jodete": []string{"2006-01-02"},
//"MapWithStruct3Key[ID.ID]": []string{"struct key in map"},
}

func TestLaboratory(t *testing.T) {
var m Laboratory
dec := NewDecoder(nil)
dec.RegisterCustomType(func(vals []string) (interface{}, error) {
return Field("value changed by custom type"), nil
}, Field(""))
err := dec.Decode(valss, &m)
if err != nil {
t.Error(err)
t.FailNow()
}
fmt.Println("RESULT1: ", m)
}
17 changes: 16 additions & 1 deletion formam_test.go
Expand Up @@ -43,6 +43,8 @@ type Anonymous struct {
FieldOverride string
}

type FieldString string

type TestStruct struct {
Anonymous
FieldOverride string
Expand Down Expand Up @@ -141,6 +143,9 @@ type TestStruct struct {
Interface interface{}
// interface with struct as data
InterfaceStruct interface{}

// custom type
CustomType FieldString
}

type InterfaceStruct struct {
Expand Down Expand Up @@ -231,12 +236,19 @@ var vals = url.Values{
"Interface": []string{"Germany"},
"InterfaceStruct.ID": []string{"1"},
"InterfaceStruct.Name": []string{"Germany"},

// custom type
"CustomType": []string{"if you see this text, then it's a bug"},
}

func TestDecodeInStruct(t *testing.T) {
var m TestStruct
m.InterfaceStruct = &InterfaceStruct{}
err := Decode(vals, &m)

dec := NewDecoder(nil).RegisterCustomType(func(vals []string) (interface{}, error) {
return FieldString("value changed by custom type"), nil
}, FieldString(""))
err := dec.Decode(vals, &m)
if err != nil {
t.Error(err)
t.FailNow()
Expand Down Expand Up @@ -555,6 +567,9 @@ func TestDecodeInStruct(t *testing.T) {
t.Error("The value of InterfaceStruct.Name is empty")
}
}
if m.CustomType != "value changed by custom type" {
t.Error("The value of CustomType is not correct")
}

fmt.Println("RESULT: ", m)
}
Expand Down
10 changes: 5 additions & 5 deletions utils.go
Expand Up @@ -7,24 +7,24 @@ import (
)

var (
timeType = reflect.TypeOf(time.Time{})
timePType = reflect.TypeOf(&time.Time{})
typeTime = reflect.TypeOf(time.Time{})
typeTimePtr = reflect.TypeOf(&time.Time{})
)

// unmarshalText returns a boolean and error. The boolean is true if the
// value implements TextUnmarshaler, and false if not.
func unmarshalText(v reflect.Value, val string) (bool, error) {
func checkUnmarshalText(v reflect.Value, val string) (bool, error) {
// check if implements the interface
m, ok := v.Interface().(encoding.TextUnmarshaler)
addr := v.CanAddr()
if !ok && !addr {
return false, nil
} else if addr {
return unmarshalText(v.Addr(), val)
return checkUnmarshalText(v.Addr(), val)
}
// skip if the type is time.Time
n := v.Type()
if n.ConvertibleTo(timeType) || n.ConvertibleTo(timePType) {
if n.ConvertibleTo(typeTime) || n.ConvertibleTo(typeTimePtr) {
return false, nil
}
// return result
Expand Down

0 comments on commit fadcc5c

Please sign in to comment.