File: type.go

package info (click to toggle)
golang-gopkg-httprequest.v1 0.0~git20171212.fdaf1bf-5
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid
  • size: 316 kB
  • sloc: makefile: 4
file content (390 lines) | stat: -rw-r--r-- 9,935 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
// Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.

// Package httprequest provides functionality for unmarshaling
// HTTP request parameters into a struct type.
package httprequest

import (
	"fmt"
	"net/http"
	"reflect"
	"sort"
	"strings"
	"sync"

	"github.com/julienschmidt/httprouter"
	"golang.org/x/net/context"
	"gopkg.in/errgo.v1"
)

// TODO include field name and source in error messages.

var (
	typeMutex sync.RWMutex
	typeMap   = make(map[reflect.Type]*requestType)
)

// Route is the type of a field that specifies a routing
// path and HTTP method. See Marshal and Unmarshal
// for details.
type Route struct{}

// Params holds the parameters provided to an HTTP request.
type Params struct {
	Response http.ResponseWriter
	Request  *http.Request
	PathVar  httprouter.Params
	// PathPattern holds the path pattern matched by httprouter.
	// It is only set where httprequest has the information;
	// that is where the call was made by Server.Handler
	// or Server.Handlers.
	PathPattern string
	// Context holds a context for the request. In Go 1.7 and later,
	// this should be used in preference to Request.Context.
	Context context.Context
}

// resultMaker is provided to the unmarshal functions.
// When called with the value passed to the unmarshaler,
// it returns the field value to be assigned to,
// creating it if necessary.
type resultMaker func(reflect.Value) reflect.Value

// unmarshaler unmarshals some value from params into
// the given value. The value should not be assigned to directly,
// but passed to makeResult and then updated.
type unmarshaler func(v reflect.Value, p Params, makeResult resultMaker) error

// marshaler marshals the specified value into params.
// The value is always the value type, even if the field type
// is a pointer.
type marshaler func(reflect.Value, *Params) error

// requestType holds information derived from a request
// type, preprocessed so that it's quick to marshal or unmarshal.
type requestType struct {
	method string
	path   string
	fields []field
}

// field holds preprocessed information on an individual field
// in the request.
type field struct {
	name string

	// index holds the index slice of the field.
	index []int

	// unmarshal is used to unmarshal the value into
	// the given field. The value passed as its first
	// argument is not a pointer type, but is addressable.
	unmarshal unmarshaler

	// marshal is used to marshal the value into the
	// give filed. The value passed as its first argument is not
	// a pointer type, but it is addressable.
	marshal marshaler

	// makeResult is the resultMaker that will be
	// passed into the unmarshaler.
	makeResult resultMaker

	// isPointer is true if the field is pointer to the underlying type.
	isPointer bool
}

// getRequestType is like parseRequestType except that
// it returns the cached requestType when possible,
// adding the type to the cache otherwise.
func getRequestType(t reflect.Type) (*requestType, error) {
	typeMutex.RLock()
	pt := typeMap[t]
	typeMutex.RUnlock()
	if pt != nil {
		return pt, nil
	}
	typeMutex.Lock()
	defer typeMutex.Unlock()
	if pt = typeMap[t]; pt != nil {
		// The type has been parsed after we dropped
		// the read lock, so use it.
		return pt, nil
	}
	pt, err := parseRequestType(t)
	if err != nil {
		return nil, errgo.Mask(err)
	}
	typeMap[t] = pt
	return pt, nil
}

// parseRequestType preprocesses the given type
// into a form that can be efficiently interpreted
// by Unmarshal.
func parseRequestType(t reflect.Type) (*requestType, error) {
	if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
		return nil, fmt.Errorf("type is not pointer to struct")
	}

	hasBody := false
	var pt requestType
	foundRoute := false
	// taggedFieldIndex holds the index of most recent anonymous
	// tagged field - we will skip any fields inside that.
	// It is nil when we're not inside an anonymous tagged field.
	var taggedFieldIndex []int
	for _, f := range fields(t.Elem()) {
		if f.PkgPath != "" && !f.Anonymous {
			// Ignore non-anonymous unexported fields.
			continue
		}
		if taggedFieldIndex != nil && withinIndex(f.Index, taggedFieldIndex) {
			// Ignore fields within tagged anonymous fields.
			continue
		}
		taggedFieldIndex = nil
		if !foundRoute && f.Anonymous && f.Type == reflect.TypeOf(Route{}) {
			var err error
			pt.method, pt.path, err = parseRouteTag(f.Tag)
			if err != nil {
				return nil, errgo.Notef(err, "bad route tag %q", f.Tag)
			}
			foundRoute = true
			continue
		}
		tag, err := parseTag(f.Tag, f.Name)
		if err != nil {
			return nil, errgo.Notef(err, "bad tag %q in field %s", f.Tag, f.Name)
		}
		if tag.source == sourceBody {
			if hasBody {
				return nil, errgo.New("more than one body field specified")
			}
			hasBody = true
		}
		field := field{
			index: f.Index,
			name:  f.Name,
		}
		if f.Type.Kind() == reflect.Ptr {
			// The field is a pointer, so when the value is set,
			// we need to create a new pointer to put
			// it into.
			field.makeResult = makePointerResult
			field.isPointer = true
			f.Type = f.Type.Elem()
		} else {
			field.makeResult = makeValueResult
			field.isPointer = false
		}

		field.unmarshal, err = getUnmarshaler(tag, f.Type)
		if err != nil {
			return nil, errgo.Mask(err)
		}

		field.marshal, err = getMarshaler(tag, f.Type)
		if err != nil {
			return nil, errgo.Mask(err)
		}

		if f.Anonymous && tag.source != sourceNone {
			taggedFieldIndex = f.Index
		}
		pt.fields = append(pt.fields, field)
	}
	return &pt, nil
}

// withinIndex reports whether the field with index i0 should be
// considered to be within the field with index i1.
func withinIndex(i0, i1 []int) bool {
	// The index of a field within an anonymous field is formed by
	// appending its field offset to the anonymous field's index, so
	// it is sufficient that we check that i0 is prefixed by i1.
	if len(i0) < len(i1) {
		return false
	}
	for i := range i1 {
		if i0[i] != i1[i] {
			return false
		}
	}
	return true
}

// Note: we deliberately omit HEAD and OPTIONS
// from this list. HEAD will be routed through GET handlers
// and OPTIONS is handled separately.
var validMethod = map[string]bool{
	"PUT":    true,
	"POST":   true,
	"DELETE": true,
	"GET":    true,
	"PATCH":  true,
}

func parseRouteTag(tag reflect.StructTag) (method, path string, err error) {
	tagStr := tag.Get("httprequest")
	if tagStr == "" {
		return "", "", errgo.New("no httprequest tag")
	}
	f := strings.Fields(tagStr)
	switch len(f) {
	case 2:
		path = f[1]
		fallthrough
	case 1:
		method = f[0]
	default:
		return "", "", errgo.New("wrong field count")
	}
	if !validMethod[method] {
		return "", "", errgo.Newf("invalid method")
	}
	// TODO check that path looks valid
	return method, path, nil
}

func makePointerResult(v reflect.Value) reflect.Value {
	if v.IsNil() {
		v.Set(reflect.New(v.Type().Elem()))
	}
	return v.Elem()
}

func makeValueResult(v reflect.Value) reflect.Value {
	return v
}

type tagSource uint8

const (
	sourceNone = iota
	sourcePath
	sourceForm
	sourceBody
	sourceHeader
)

type tag struct {
	name      string
	source    tagSource
	omitempty bool
}

// parseTag parses the given struct tag attached to the given
// field name into a tag structure.
func parseTag(rtag reflect.StructTag, fieldName string) (tag, error) {
	t := tag{
		name: fieldName,
	}
	tagStr := rtag.Get("httprequest")
	if tagStr == "" {
		return t, nil
	}
	fields := strings.Split(tagStr, ",")
	if fields[0] != "" {
		t.name = fields[0]
	}
	for _, f := range fields[1:] {
		switch f {
		case "path":
			t.source = sourcePath
		case "form":
			t.source = sourceForm
		case "body":
			t.source = sourceBody
		case "header":
			t.source = sourceHeader
		case "omitempty":
			t.omitempty = true
		default:
			return tag{}, fmt.Errorf("unknown tag flag %q", f)
		}
	}
	if t.omitempty && t.source != sourceForm && t.source != sourceHeader {
		return tag{}, fmt.Errorf("can only use omitempty with form or header fields")
	}
	return t, nil
}

// fields returns all the fields in the given struct type
// including fields inside anonymous struct members.
// The fields are ordered with top level fields first
// followed by the members of those fields
// for anonymous fields.
func fields(t reflect.Type) []reflect.StructField {
	byName := make(map[string]reflect.StructField)
	addFields(t, byName, nil)
	fields := make(fieldsByIndex, 0, len(byName))
	for _, f := range byName {
		if f.Name != "" {
			fields = append(fields, f)
		}
	}
	sort.Sort(fields)
	return fields
}

func addFields(t reflect.Type, byName map[string]reflect.StructField, index []int) {
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		index := append(index, i)
		var add bool
		old, ok := byName[f.Name]
		switch {
		case ok && len(old.Index) == len(index):
			// Fields with the same name at the same depth
			// cancel one another out. Set the field name
			// to empty to signify that has happened.
			old.Name = ""
			byName[f.Name] = old
			add = false
		case ok:
			// Fields at less depth win.
			add = len(index) < len(old.Index)
		default:
			// The field did not previously exist.
			add = true
		}
		if add {
			// copy the index so that it's not overwritten
			// by the other appends.
			f.Index = append([]int(nil), index...)
			byName[f.Name] = f
		}
		if f.Anonymous {
			if f.Type.Kind() == reflect.Ptr {
				f.Type = f.Type.Elem()
			}
			if f.Type.Kind() == reflect.Struct {
				addFields(f.Type, byName, index)
			}
		}
	}
}

type fieldsByIndex []reflect.StructField

func (f fieldsByIndex) Len() int {
	return len(f)
}

func (f fieldsByIndex) Swap(i, j int) {
	f[i], f[j] = f[j], f[i]
}

func (f fieldsByIndex) Less(i, j int) bool {
	indexi, indexj := f[i].Index, f[j].Index
	for len(indexi) != 0 && len(indexj) != 0 {
		ii, ij := indexi[0], indexj[0]
		if ii != ij {
			return ii < ij
		}
		indexi, indexj = indexi[1:], indexj[1:]
	}
	return len(indexi) < len(indexj)
}