Browse Source

codec: support customization of struct tag keys; maintain default to "codec", "json".

To implement it, I will have to move from using a global variable for the typeInfos,
to using one that can be configured on the Handle.
If not configured, default to the current behaviour of looking at "codec" and "json" keys
(to support codec and migration from the standard library respectively).

This is implemented as follows:

- Define a TypeInfos which can be configured on the Handle
- If not defined, a default one will be used, which looks at codec, json keys
- codecgen will now take a parameter '-st',
  which will define a comma separated list of keys to inspect.
  It defaults to "codec,json".

The user will have to work around the possibility of being out-of-sync if using codecgen.
However, that is fine, because the user runs codecgen explicitly.

This will expand the API slightly:

- Introduce TypeInfos
- Add NewTypeInfos(tagKeys []string)
- Add TypeInfos field to BasicHandle, so every Handle can configure it
- Add -st parameter to codecgen

By making TypeInfos configurable from the Handle, a user can customize
the struct tag keys that are used when inspecting types.
This allows users to depend on custom keys e.g.

    type X struct {
      ID string `gorethinkdb:,omitempty`
    }

Fixes #105
Ugorji Nwoke 10 years ago
parent
commit
2f4b94206a
7 changed files with 71 additions and 30 deletions
  1. 7 3
      codec/codecgen/gen.go
  2. 1 0
      codec/codecgen_test.go
  3. 1 1
      codec/decode.go
  4. 1 1
      codec/encode.go
  5. 9 4
      codec/gen.go
  6. 51 20
      codec/helper.go
  7. 1 1
      codec/prebuild.sh

+ 7 - 3
codec/codecgen/gen.go

@@ -48,6 +48,7 @@ import (
 	"os"
 	"reflect"
 	"bytes"
+	"strings"
 	"go/format"
 )
 
@@ -72,7 +73,7 @@ func CodecGenTempWrite{{ .RandString }}() {
 	var t{{ $index }} {{ . }}
 	typs = append(typs, reflect.TypeOf(t{{ $index }}))
 {{ end }}
-	{{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}Gen(&out, "{{ .BuildTag }}", "{{ .PackageName }}", "{{ .RandString }}", {{ .UseUnsafe }}, typs...)
+	{{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}Gen(&out, "{{ .BuildTag }}", "{{ .PackageName }}", "{{ .RandString }}", {{ .UseUnsafe }}, {{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}NewTypeInfos(strings.Split("{{ .StructTags }}", ",")), typs...)
 	bout, err := format.Source(out.Bytes())
 	if err != nil {
 		fout.Write(out.Bytes())
@@ -93,7 +94,7 @@ func CodecGenTempWrite{{ .RandString }}() {
 // fout contains Codec(En|De)codeSelf implementations for every type T.
 //
 func Generate(outfile, buildTag, codecPkgPath string, uid int64, useUnsafe bool, goRunTag string,
-	regexName *regexp.Regexp, deleteTempFile bool, infiles ...string) (err error) {
+	st string, regexName *regexp.Regexp, deleteTempFile bool, infiles ...string) (err error) {
 	// For each file, grab AST, find each type, and write a call to it.
 	if len(infiles) == 0 {
 		return
@@ -128,6 +129,7 @@ func Generate(outfile, buildTag, codecPkgPath string, uid int64, useUnsafe bool,
 		PackageName     string
 		RandString      string
 		BuildTag        string
+		StructTags      string
 		Types           []string
 		CodecPkgFiles   bool
 		UseUnsafe       bool
@@ -139,6 +141,7 @@ func Generate(outfile, buildTag, codecPkgPath string, uid int64, useUnsafe bool,
 		BuildTag:        buildTag,
 		UseUnsafe:       useUnsafe,
 		RandString:      strconv.FormatInt(uid, 10),
+		StructTags:      st,
 	}
 	tv.ImportPath = pkg.ImportPath
 	if tv.ImportPath == tv.CodecImportPath {
@@ -269,11 +272,12 @@ func main() {
 	t := flag.String("t", "", "build tag to put in file")
 	r := flag.String("r", ".*", "regex for type name to match")
 	rt := flag.String("rt", "", "tags for go run")
+	st := flag.String("st", "codec,json", "struct tag keys to introspect")
 	x := flag.Bool("x", false, "keep temp file")
 	u := flag.Bool("u", false, "Use unsafe, e.g. to avoid unnecessary allocation on []byte->string")
 	d := flag.Int64("d", 0, "random identifier for use in generated code")
 	flag.Parse()
-	if err := Generate(*o, *t, *c, *d, *u, *rt,
+	if err := Generate(*o, *t, *c, *d, *u, *rt, *st,
 		regexp.MustCompile(*r), !*x, flag.Args()...); err != nil {
 		fmt.Fprintf(os.Stderr, "codecgen error: %v\n", err)
 		os.Exit(1)

+ 1 - 0
codec/codecgen_test.go

@@ -8,6 +8,7 @@ import (
 )
 
 func TestCodecgenJson1(t *testing.T) {
+	// This is just a simplistic test for codecgen.
 	const callCodecgenDirect bool = true
 	v := newTestStruc(2, false, !testSkipIntf, false)
 	var bs []byte

+ 1 - 1
codec/decode.go

@@ -1309,7 +1309,7 @@ func (d *Decoder) getDecFn(rt reflect.Type, checkFastpath, checkCodecSelfer bool
 	}
 
 	// debugf("\tCreating new dec fn for type: %v\n", rt)
-	ti := getTypeInfo(rtid, rt)
+	ti := d.h.getTypeInfo(rtid, rt)
 	fi := &(fn.i)
 	fi.d = d
 	fi.ti = ti

+ 1 - 1
codec/encode.go

@@ -996,7 +996,7 @@ func (e *Encoder) getEncFn(rtid uintptr, rt reflect.Type, checkFastpath, checkCo
 		fn = &(e.s[len(e.s)-1]).fn
 	}
 
-	ti := getTypeInfo(rtid, rt)
+	ti := e.h.getTypeInfo(rtid, rt)
 	fi := &(fn.i)
 	fi.e = e
 	fi.ti = ti

+ 9 - 4
codec/gen.go

@@ -144,6 +144,7 @@ type genRunner struct {
 	xs string // top level variable/constant suffix
 	hn string // fn helper type name
 
+	ti *TypeInfos
 	// rr *rand.Rand // random generator for file-specific types
 }
 
@@ -151,7 +152,7 @@ type genRunner struct {
 // type passed. All the types must be in the same package.
 //
 // Library users: *DO NOT USE IT DIRECTLY. IT WILL CHANGE CONTINOUSLY WITHOUT NOTICE.*
-func Gen(w io.Writer, buildTags, pkgName, uid string, useUnsafe bool, typ ...reflect.Type) {
+func Gen(w io.Writer, buildTags, pkgName, uid string, useUnsafe bool, ti *TypeInfos, typ ...reflect.Type) {
 	if len(typ) == 0 {
 		return
 	}
@@ -168,6 +169,10 @@ func Gen(w io.Writer, buildTags, pkgName, uid string, useUnsafe bool, typ ...ref
 		ts:     []reflect.Type{},
 		bp:     genImportPath(typ[0]),
 		xs:     uid,
+		ti:     ti, //TODO: make it configurable
+	}
+	if x.ti == nil {
+		x.ti = defTypeInfos
 	}
 	if x.xs == "" {
 		rr := rand.New(rand.NewSource(time.Now().UnixNano()))
@@ -756,7 +761,7 @@ func (x *genRunner) encStruct(varname string, rtid uintptr, t reflect.Type) {
 	// replicate code in kStruct i.e. for each field, deref type to non-pointer, and call x.enc on it
 
 	// if t === type currently running selfer on, do for all
-	ti := getTypeInfo(rtid, t)
+	ti := x.ti.get(rtid, t)
 	i := x.varsfx()
 	sepVarname := genTempVarPfx + "sep" + i
 	numfieldsvar := genTempVarPfx + "q" + i
@@ -1328,7 +1333,7 @@ func (x *genRunner) decMapFallback(varname string, rtid uintptr, t reflect.Type)
 }
 
 func (x *genRunner) decStructMapSwitch(kName string, varname string, rtid uintptr, t reflect.Type) {
-	ti := getTypeInfo(rtid, t)
+	ti := x.ti.get(rtid, t)
 	tisfi := ti.sfip // always use sequence from file. decStruct expects same thing.
 	x.line("switch (" + kName + ") {")
 	for _, si := range tisfi {
@@ -1426,7 +1431,7 @@ func (x *genRunner) decStructMap(varname, lenvarname string, rtid uintptr, t ref
 func (x *genRunner) decStructArray(varname, lenvarname, breakString string, rtid uintptr, t reflect.Type) {
 	tpfx := genTempVarPfx
 	i := x.varsfx()
-	ti := getTypeInfo(rtid, t)
+	ti := x.ti.get(rtid, t)
 	tisfi := ti.sfip // always use sequence from file. decStruct expects same thing.
 	x.linef("var %sj%s int", tpfx, i)
 	x.linef("var %sb%s bool", tpfx, i) // break

+ 51 - 20
codec/helper.go

@@ -212,9 +212,6 @@ var (
 	bigen               = binary.BigEndian
 	structInfoFieldName = "_struct"
 
-	cachedTypeInfo      = make(map[uintptr]*typeInfo, 64)
-	cachedTypeInfoMutex sync.RWMutex
-
 	// mapStrIntfTyp = reflect.TypeOf(map[string]interface{}(nil))
 	intfSliceTyp = reflect.TypeOf([]interface{}(nil))
 	intfTyp      = intfSliceTyp.Elem()
@@ -256,6 +253,8 @@ var (
 	noFieldNameToStructFieldInfoErr = errors.New("no field name passed to parseStructFieldInfo")
 )
 
+var defTypeInfos = NewTypeInfos([]string{"codec", "json"})
+
 // Selfer defines methods by which a value can encode or decode itself.
 //
 // Any type which implements Selfer will be able to encode or decode itself.
@@ -281,6 +280,11 @@ type MapBySlice interface {
 //
 // BasicHandle encapsulates the common options and extension functions.
 type BasicHandle struct {
+	// TypeInfos is used to get the type info for any type.
+	//
+	// If not configure, the default TypeInfos is used, which uses struct tag keys: codec, json
+	TypeInfos *TypeInfos
+
 	extHandle
 	EncodeOptions
 	DecodeOptions
@@ -290,6 +294,13 @@ func (x *BasicHandle) getBasicHandle() *BasicHandle {
 	return x
 }
 
+func (x *BasicHandle) getTypeInfo(rtid uintptr, rt reflect.Type) (pti *typeInfo) {
+	if x.TypeInfos != nil {
+		return x.TypeInfos.get(rtid, rt)
+	}
+	return defTypeInfos.get(rtid, rt)
+}
+
 // Handle is the interface for a specific encoding format.
 //
 // Typically, a Handle is pre-configured before first time use,
@@ -695,28 +706,48 @@ func (ti *typeInfo) indexForEncName(name string) int {
 	return -1
 }
 
-func getStructTag(t reflect.StructTag) (s string) {
+// TypeInfos caches typeInfo for each type on first inspection.
+//
+// It is configured with a set of tag keys, which are used to get
+// configuration for the type.
+type TypeInfos struct {
+	infos map[uintptr]*typeInfo
+	mu    sync.RWMutex
+	tags  []string
+}
+
+// NewTypeInfos creates a TypeInfos given a set of struct tags keys.
+//
+// This allows users customize the struct tag keys which contain configuration
+// of their types.
+func NewTypeInfos(tags []string) *TypeInfos {
+	return &TypeInfos{tags: tags, infos: make(map[uintptr]*typeInfo, 64)}
+}
+
+func (x *TypeInfos) structTag(t reflect.StructTag) (s string) {
 	// check for tags: codec, json, in that order.
 	// this allows seamless support for many configured structs.
-	s = t.Get("codec")
-	if s == "" {
-		s = t.Get("json")
+	for _, x := range x.tags {
+		s = t.Get(x)
+		if s != "" {
+			return s
+		}
 	}
 	return
 }
 
-func getTypeInfo(rtid uintptr, rt reflect.Type) (pti *typeInfo) {
+func (x *TypeInfos) get(rtid uintptr, rt reflect.Type) (pti *typeInfo) {
 	var ok bool
-	cachedTypeInfoMutex.RLock()
-	pti, ok = cachedTypeInfo[rtid]
-	cachedTypeInfoMutex.RUnlock()
+	x.mu.RLock()
+	pti, ok = x.infos[rtid]
+	x.mu.RUnlock()
 	if ok {
 		return
 	}
 
-	cachedTypeInfoMutex.Lock()
-	defer cachedTypeInfoMutex.Unlock()
-	if pti, ok = cachedTypeInfo[rtid]; ok {
+	x.mu.Lock()
+	defer x.mu.Unlock()
+	if pti, ok = x.infos[rtid]; ok {
 		return
 	}
 
@@ -768,11 +799,11 @@ func getTypeInfo(rtid uintptr, rt reflect.Type) (pti *typeInfo) {
 	if rt.Kind() == reflect.Struct {
 		var siInfo *structFieldInfo
 		if f, ok := rt.FieldByName(structInfoFieldName); ok {
-			siInfo = parseStructFieldInfo(structInfoFieldName, getStructTag(f.Tag))
+			siInfo = parseStructFieldInfo(structInfoFieldName, x.structTag(f.Tag))
 			ti.toArray = siInfo.toArray
 		}
 		sfip := make([]*structFieldInfo, 0, rt.NumField())
-		rgetTypeInfo(rt, nil, make(map[string]bool, 16), &sfip, siInfo)
+		x.rget(rt, nil, make(map[string]bool, 16), &sfip, siInfo)
 
 		ti.sfip = make([]*structFieldInfo, len(sfip))
 		ti.sfi = make([]*structFieldInfo, len(sfip))
@@ -781,11 +812,11 @@ func getTypeInfo(rtid uintptr, rt reflect.Type) (pti *typeInfo) {
 		copy(ti.sfi, sfip)
 	}
 	// sfi = sfip
-	cachedTypeInfo[rtid] = pti
+	x.infos[rtid] = pti
 	return
 }
 
-func rgetTypeInfo(rt reflect.Type, indexstack []int, fnameToHastag map[string]bool,
+func (x *TypeInfos) rget(rt reflect.Type, indexstack []int, fnameToHastag map[string]bool,
 	sfi *[]*structFieldInfo, siInfo *structFieldInfo,
 ) {
 	for j := 0; j < rt.NumField(); j++ {
@@ -794,7 +825,7 @@ func rgetTypeInfo(rt reflect.Type, indexstack []int, fnameToHastag map[string]bo
 		if tk := f.Type.Kind(); tk == reflect.Func {
 			continue
 		}
-		stag := getStructTag(f.Tag)
+		stag := x.structTag(f.Tag)
 		if stag == "-" {
 			continue
 		}
@@ -825,7 +856,7 @@ func rgetTypeInfo(rt reflect.Type, indexstack []int, fnameToHastag map[string]bo
 				copy(indexstack2, indexstack)
 				indexstack2[len(indexstack)] = j
 				// indexstack2 := append(append(make([]int, 0, len(indexstack)+4), indexstack...), j)
-				rgetTypeInfo(ft, indexstack2, fnameToHastag, sfi, siInfo)
+				x.rget(ft, indexstack2, fnameToHastag, sfi, siInfo)
 				continue
 			}
 		}

+ 1 - 1
codec/prebuild.sh

@@ -142,7 +142,7 @@ _codegenerators() {
     then
         true && \
             echo "codecgen - !unsafe ... " && \
-            codecgen  -rt codecgen -t 'x,codecgen,!unsafe' -o values_codecgen${zsfx} -d 1978 $zfin && \
+            codecgen -rt codecgen -t 'x,codecgen,!unsafe' -o values_codecgen${zsfx} -d 1978 $zfin && \
             echo "codecgen - unsafe ... " && \
             codecgen  -u -rt codecgen -t 'x,codecgen,unsafe' -o values_codecgen_unsafe${zsfx} -d 1978 $zfin && \
             echo "msgp ... " && \