1// Copyright 2012 The Gorilla Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package schema
6
7import (
8	"errors"
9	"reflect"
10	"strconv"
11	"strings"
12	"sync"
13)
14
15var invalidPath = errors.New("schema: invalid path")
16
17// newCache returns a new cache.
18func newCache() *cache {
19	c := cache{
20		m:       make(map[reflect.Type]*structInfo),
21		regconv: make(map[reflect.Type]Converter),
22		tag:     "schema",
23	}
24	return &c
25}
26
27// cache caches meta-data about a struct.
28type cache struct {
29	l       sync.RWMutex
30	m       map[reflect.Type]*structInfo
31	regconv map[reflect.Type]Converter
32	tag     string
33}
34
35// registerConverter registers a converter function for a custom type.
36func (c *cache) registerConverter(value interface{}, converterFunc Converter) {
37	c.regconv[reflect.TypeOf(value)] = converterFunc
38}
39
40// parsePath parses a path in dotted notation verifying that it is a valid
41// path to a struct field.
42//
43// It returns "path parts" which contain indices to fields to be used by
44// reflect.Value.FieldByString(). Multiple parts are required for slices of
45// structs.
46func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) {
47	var struc *structInfo
48	var field *fieldInfo
49	var index64 int64
50	var err error
51	parts := make([]pathPart, 0)
52	path := make([]string, 0)
53	keys := strings.Split(p, ".")
54	for i := 0; i < len(keys); i++ {
55		if t.Kind() != reflect.Struct {
56			return nil, invalidPath
57		}
58		if struc = c.get(t); struc == nil {
59			return nil, invalidPath
60		}
61		if field = struc.get(keys[i]); field == nil {
62			return nil, invalidPath
63		}
64		// Valid field. Append index.
65		path = append(path, field.name)
66		if field.isSliceOfStructs && (!field.unmarshalerInfo.IsValid || (field.unmarshalerInfo.IsValid && field.unmarshalerInfo.IsSliceElement)) {
67			// Parse a special case: slices of structs.
68			// i+1 must be the slice index.
69			//
70			// Now that struct can implements TextUnmarshaler interface,
71			// we don't need to force the struct's fields to appear in the path.
72			// So checking i+2 is not necessary anymore.
73			i++
74			if i+1 > len(keys) {
75				return nil, invalidPath
76			}
77			if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil {
78				return nil, invalidPath
79			}
80			parts = append(parts, pathPart{
81				path:  path,
82				field: field,
83				index: int(index64),
84			})
85			path = make([]string, 0)
86
87			// Get the next struct type, dropping ptrs.
88			if field.typ.Kind() == reflect.Ptr {
89				t = field.typ.Elem()
90			} else {
91				t = field.typ
92			}
93			if t.Kind() == reflect.Slice {
94				t = t.Elem()
95				if t.Kind() == reflect.Ptr {
96					t = t.Elem()
97				}
98			}
99		} else if field.typ.Kind() == reflect.Ptr {
100			t = field.typ.Elem()
101		} else {
102			t = field.typ
103		}
104	}
105	// Add the remaining.
106	parts = append(parts, pathPart{
107		path:  path,
108		field: field,
109		index: -1,
110	})
111	return parts, nil
112}
113
114// get returns a cached structInfo, creating it if necessary.
115func (c *cache) get(t reflect.Type) *structInfo {
116	c.l.RLock()
117	info := c.m[t]
118	c.l.RUnlock()
119	if info == nil {
120		info = c.create(t, "")
121		c.l.Lock()
122		c.m[t] = info
123		c.l.Unlock()
124	}
125	return info
126}
127
128// create creates a structInfo with meta-data about a struct.
129func (c *cache) create(t reflect.Type, parentAlias string) *structInfo {
130	info := &structInfo{}
131	var anonymousInfos []*structInfo
132	for i := 0; i < t.NumField(); i++ {
133		if f := c.createField(t.Field(i), parentAlias); f != nil {
134			info.fields = append(info.fields, f)
135			if ft := indirectType(f.typ); ft.Kind() == reflect.Struct && f.isAnonymous {
136				anonymousInfos = append(anonymousInfos, c.create(ft, f.canonicalAlias))
137			}
138		}
139	}
140	for i, a := range anonymousInfos {
141		others := []*structInfo{info}
142		others = append(others, anonymousInfos[:i]...)
143		others = append(others, anonymousInfos[i+1:]...)
144		for _, f := range a.fields {
145			if !containsAlias(others, f.alias) {
146				info.fields = append(info.fields, f)
147			}
148		}
149	}
150	return info
151}
152
153// createField creates a fieldInfo for the given field.
154func (c *cache) createField(field reflect.StructField, parentAlias string) *fieldInfo {
155	alias, options := fieldAlias(field, c.tag)
156	if alias == "-" {
157		// Ignore this field.
158		return nil
159	}
160	canonicalAlias := alias
161	if parentAlias != "" {
162		canonicalAlias = parentAlias + "." + alias
163	}
164	// Check if the type is supported and don't cache it if not.
165	// First let's get the basic type.
166	isSlice, isStruct := false, false
167	ft := field.Type
168	m := isTextUnmarshaler(reflect.Zero(ft))
169	if ft.Kind() == reflect.Ptr {
170		ft = ft.Elem()
171	}
172	if isSlice = ft.Kind() == reflect.Slice; isSlice {
173		ft = ft.Elem()
174		if ft.Kind() == reflect.Ptr {
175			ft = ft.Elem()
176		}
177	}
178	if ft.Kind() == reflect.Array {
179		ft = ft.Elem()
180		if ft.Kind() == reflect.Ptr {
181			ft = ft.Elem()
182		}
183	}
184	if isStruct = ft.Kind() == reflect.Struct; !isStruct {
185		if c.converter(ft) == nil && builtinConverters[ft.Kind()] == nil {
186			// Type is not supported.
187			return nil
188		}
189	}
190
191	return &fieldInfo{
192		typ:              field.Type,
193		name:             field.Name,
194		alias:            alias,
195		canonicalAlias:   canonicalAlias,
196		unmarshalerInfo:  m,
197		isSliceOfStructs: isSlice && isStruct,
198		isAnonymous:      field.Anonymous,
199		isRequired:       options.Contains("required"),
200	}
201}
202
203// converter returns the converter for a type.
204func (c *cache) converter(t reflect.Type) Converter {
205	return c.regconv[t]
206}
207
208// ----------------------------------------------------------------------------
209
210type structInfo struct {
211	fields []*fieldInfo
212}
213
214func (i *structInfo) get(alias string) *fieldInfo {
215	for _, field := range i.fields {
216		if strings.EqualFold(field.alias, alias) {
217			return field
218		}
219	}
220	return nil
221}
222
223func containsAlias(infos []*structInfo, alias string) bool {
224	for _, info := range infos {
225		if info.get(alias) != nil {
226			return true
227		}
228	}
229	return false
230}
231
232type fieldInfo struct {
233	typ reflect.Type
234	// name is the field name in the struct.
235	name  string
236	alias string
237	// canonicalAlias is almost the same as the alias, but is prefixed with
238	// an embedded struct field alias in dotted notation if this field is
239	// promoted from the struct.
240	// For instance, if the alias is "N" and this field is an embedded field
241	// in a struct "X", canonicalAlias will be "X.N".
242	canonicalAlias string
243	// unmarshalerInfo contains information regarding the
244	// encoding.TextUnmarshaler implementation of the field type.
245	unmarshalerInfo unmarshaler
246	// isSliceOfStructs indicates if the field type is a slice of structs.
247	isSliceOfStructs bool
248	// isAnonymous indicates whether the field is embedded in the struct.
249	isAnonymous bool
250	isRequired  bool
251}
252
253func (f *fieldInfo) paths(prefix string) []string {
254	if f.alias == f.canonicalAlias {
255		return []string{prefix + f.alias}
256	}
257	return []string{prefix + f.alias, prefix + f.canonicalAlias}
258}
259
260type pathPart struct {
261	field *fieldInfo
262	path  []string // path to the field: walks structs using field names.
263	index int      // struct index in slices of structs.
264}
265
266// ----------------------------------------------------------------------------
267
268func indirectType(typ reflect.Type) reflect.Type {
269	if typ.Kind() == reflect.Ptr {
270		return typ.Elem()
271	}
272	return typ
273}
274
275// fieldAlias parses a field tag to get a field alias.
276func fieldAlias(field reflect.StructField, tagName string) (alias string, options tagOptions) {
277	if tag := field.Tag.Get(tagName); tag != "" {
278		alias, options = parseTag(tag)
279	}
280	if alias == "" {
281		alias = field.Name
282	}
283	return alias, options
284}
285
286// tagOptions is the string following a comma in a struct field's tag, or
287// the empty string. It does not include the leading comma.
288type tagOptions []string
289
290// parseTag splits a struct field's url tag into its name and comma-separated
291// options.
292func parseTag(tag string) (string, tagOptions) {
293	s := strings.Split(tag, ",")
294	return s[0], s[1:]
295}
296
297// Contains checks whether the tagOptions contains the specified option.
298func (o tagOptions) Contains(option string) bool {
299	for _, s := range o {
300		if s == option {
301			return true
302		}
303	}
304	return false
305}
306