package schema
import (
"errors"
"reflect"
"strconv"
"strings"
"sync"
)
var errInvalidPath = errors .New ("schema: invalid path" )
func newCache() *cache {
c := cache {
m : make (map [reflect .Type ]*structInfo ),
regconv : make (map [reflect .Type ]Converter ),
tag : "schema" ,
}
return &c
}
type cache struct {
l sync .RWMutex
m map [reflect .Type ]*structInfo
regconv map [reflect .Type ]Converter
tag string
}
func (c *cache ) registerConverter (value interface {}, converterFunc Converter ) {
c .regconv [reflect .TypeOf (value )] = converterFunc
}
func (c *cache ) parsePath (p string , t reflect .Type ) ([]pathPart , error ) {
var struc *structInfo
var field *fieldInfo
var index64 int64
var err error
parts := make ([]pathPart , 0 )
path := make ([]string , 0 )
keys := strings .Split (p , "." )
for i := 0 ; i < len (keys ); i ++ {
if t .Kind () != reflect .Struct {
return nil , errInvalidPath
}
if struc = c .get (t ); struc == nil {
return nil , errInvalidPath
}
if field = struc .get (keys [i ]); field == nil {
return nil , errInvalidPath
}
path = append (path , field .name )
if field .isSliceOfStructs && (!field .unmarshalerInfo .IsValid || (field .unmarshalerInfo .IsValid && field .unmarshalerInfo .IsSliceElement )) {
i ++
if i +1 > len (keys ) {
return nil , errInvalidPath
}
if index64 , err = strconv .ParseInt (keys [i ], 10 , 0 ); err != nil {
return nil , errInvalidPath
}
parts = append (parts , pathPart {
path : path ,
field : field ,
index : int (index64 ),
})
path = make ([]string , 0 )
if field .typ .Kind () == reflect .Ptr {
t = field .typ .Elem ()
} else {
t = field .typ
}
if t .Kind () == reflect .Slice {
t = t .Elem ()
if t .Kind () == reflect .Ptr {
t = t .Elem ()
}
}
} else if field .typ .Kind () == reflect .Ptr {
t = field .typ .Elem ()
} else {
t = field .typ
}
}
parts = append (parts , pathPart {
path : path ,
field : field ,
index : -1 ,
})
return parts , nil
}
func (c *cache ) get (t reflect .Type ) *structInfo {
c .l .RLock ()
info := c .m [t ]
c .l .RUnlock ()
if info == nil {
info = c .create (t , "" )
c .l .Lock ()
c .m [t ] = info
c .l .Unlock ()
}
return info
}
func (c *cache ) create (t reflect .Type , parentAlias string ) *structInfo {
info := &structInfo {}
var anonymousInfos []*structInfo
for i := 0 ; i < t .NumField (); i ++ {
if f := c .createField (t .Field (i ), parentAlias ); f != nil {
info .fields = append (info .fields , f )
if ft := indirectType (f .typ ); ft .Kind () == reflect .Struct && f .isAnonymous {
anonymousInfos = append (anonymousInfos , c .create (ft , f .canonicalAlias ))
}
}
}
for i , a := range anonymousInfos {
others := []*structInfo {info }
others = append (others , anonymousInfos [:i ]...)
others = append (others , anonymousInfos [i +1 :]...)
for _ , f := range a .fields {
if !containsAlias (others , f .alias ) {
info .fields = append (info .fields , f )
}
}
}
return info
}
func (c *cache ) createField (field reflect .StructField , parentAlias string ) *fieldInfo {
alias , options := fieldAlias (field , c .tag )
if alias == "-" {
return nil
}
canonicalAlias := alias
if parentAlias != "" {
canonicalAlias = parentAlias + "." + alias
}
isSlice , isStruct := false , false
ft := field .Type
m := isTextUnmarshaler (reflect .Zero (ft ))
if ft .Kind () == reflect .Ptr {
ft = ft .Elem ()
}
if isSlice = ft .Kind () == reflect .Slice ; isSlice {
ft = ft .Elem ()
if ft .Kind () == reflect .Ptr {
ft = ft .Elem ()
}
}
if ft .Kind () == reflect .Array {
ft = ft .Elem ()
if ft .Kind () == reflect .Ptr {
ft = ft .Elem ()
}
}
if isStruct = ft .Kind () == reflect .Struct ; !isStruct {
if c .converter (ft ) == nil && builtinConverters [ft .Kind ()] == nil {
return nil
}
}
return &fieldInfo {
typ : field .Type ,
name : field .Name ,
alias : alias ,
canonicalAlias : canonicalAlias ,
unmarshalerInfo : m ,
isSliceOfStructs : isSlice && isStruct ,
isAnonymous : field .Anonymous ,
isRequired : options .Contains ("required" ),
}
}
func (c *cache ) converter (t reflect .Type ) Converter {
return c .regconv [t ]
}
type structInfo struct {
fields []*fieldInfo
}
func (i *structInfo ) get (alias string ) *fieldInfo {
for _ , field := range i .fields {
if strings .EqualFold (field .alias , alias ) {
return field
}
}
return nil
}
func containsAlias(infos []*structInfo , alias string ) bool {
for _ , info := range infos {
if info .get (alias ) != nil {
return true
}
}
return false
}
type fieldInfo struct {
typ reflect .Type
name string
alias string
canonicalAlias string
unmarshalerInfo unmarshaler
isSliceOfStructs bool
isAnonymous bool
isRequired bool
}
func (f *fieldInfo ) paths (prefix string ) []string {
if f .alias == f .canonicalAlias {
return []string {prefix + f .alias }
}
return []string {prefix + f .alias , prefix + f .canonicalAlias }
}
type pathPart struct {
field *fieldInfo
path []string
index int
}
func indirectType(typ reflect .Type ) reflect .Type {
if typ .Kind () == reflect .Ptr {
return typ .Elem ()
}
return typ
}
func fieldAlias(field reflect .StructField , tagName string ) (alias string , options tagOptions ) {
if tag := field .Tag .Get (tagName ); tag != "" {
alias , options = parseTag (tag )
}
if alias == "" {
alias = field .Name
}
return alias , options
}
type tagOptions []string
func parseTag(tag string ) (string , tagOptions ) {
s := strings .Split (tag , "," )
return s [0 ], s [1 :]
}
func (o tagOptions ) Contains (option string ) bool {
for _ , s := range o {
if s == option {
return true
}
}
return false
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .