-
Notifications
You must be signed in to change notification settings - Fork 5
/
pinned.go
180 lines (147 loc) Β· 3.86 KB
/
pinned.go
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
package pinned
import (
"errors"
"net/http"
"reflect"
"sort"
"time"
)
var (
// ErrInvalidVersion means the version supplied is not included
// in the versions supplied to the VersionManager or it is malformed.
ErrInvalidVersion = errors.New("invalid version")
// ErrNoVersionSupplied means no version was supplied.
ErrNoVersionSupplied = errors.New("no version supplied")
// ErrVersionDeprecated means the version is deprecated.
ErrVersionDeprecated = errors.New("version is deprecated")
)
const (
defaultLayout = "2006-01-02"
defaultHeader = "Version"
defaultQuery = "v"
)
// VersionManager represents a list of versions.
type VersionManager struct {
Layout string
Header string
Query string
versions []*Version
}
// Add adds a version.
func (vm *VersionManager) Add(v *Version) error {
var err error
v.layout = vm.layout()
v.date, err = time.Parse(vm.layout(), v.Date)
if err != nil {
return err
}
vm.versions = append(vm.versions, v)
// Ensure the versions are in descending order by date.
sort.Sort(sort.Reverse(versions(vm.versions)))
return nil
}
// Latest returns the most current active version.
func (vm *VersionManager) Latest() *Version {
if len(vm.versions) == 0 {
return nil
}
return vm.versions[0]
}
// Parse evaluates an http.Request object to determine an API version.
// It inspects the query parameters and request headers. Whichever
// is most recent is the version to use.
func (vm *VersionManager) Parse(r *http.Request) (*Version, error) {
h := r.Header.Get(vm.header())
q := r.URL.Query().Get(vm.query())
if h == "" && q == "" {
return nil, ErrNoVersionSupplied
}
hDate, qDate := time.Time{}, time.Time{}
var err error
if h != "" {
hDate, err = time.Parse(vm.layout(), h)
if err != nil {
return nil, ErrInvalidVersion
}
}
if q != "" {
qDate, err = time.Parse(vm.layout(), q)
if err != nil {
return nil, ErrInvalidVersion
}
}
t := hDate
if hDate.Before(qDate) {
t = qDate
}
v, err := vm.getVersionByTime(t)
if err != nil {
return nil, err
}
if v.Deprecated {
return v, ErrVersionDeprecated
}
return v, nil
}
func (vm *VersionManager) getVersionByTime(t time.Time) (*Version, error) {
for _, v := range vm.versions {
if t.Equal(v.date) {
return v, nil
}
}
return nil, ErrInvalidVersion
}
// Apply processes a Versionable object by applying all changes between the
// latest version and the version requested. The altered object is returned.
//
// Concretely, if the supplied version is two versions behind the latest, the changes
// in those two versions are applied sequentially to the object. This essentially
// "undoes" the changes made to the API so that the object is structured according to
// the specified version.
func (vm *VersionManager) Apply(version *Version, obj Versionable) (map[string]interface{}, error) {
data := obj.Data()
for _, v := range vm.versions {
// If the requested version is >= to the version, do not apply.
if version.date.After(v.date) || version.date.Equal(v.date) {
break
}
// Iterate through each change and execute
// actions as appropriate.
for _, c := range v.Changes {
typ := reflect.TypeOf(obj).Elem().Name()
// If there is an action for this obj type
// execute the action.
a, ok := c.Actions[typ]
if ok {
data = a(data)
}
}
}
return data, nil
}
// Versions returns a list of all versions as strings.
func (vm *VersionManager) Versions() []string {
versions := make([]string, len(vm.versions))
for i := range versions {
versions[i] = vm.versions[i].String()
}
return versions
}
func (vm *VersionManager) layout() string {
if vm.Layout != "" {
return vm.Layout
}
return defaultLayout
}
func (vm *VersionManager) header() string {
if vm.Header != "" {
return vm.Header
}
return defaultHeader
}
func (vm *VersionManager) query() string {
if vm.Query != "" {
return vm.Query
}
return defaultQuery
}