Skip to content

Commit 58abc7a

Browse files
committed
Initial work on versioning support.
1 parent bf77b7d commit 58abc7a

24 files changed

+943
-505
lines changed

context.go

+15-36
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"net/http"
7-
"strings"
7+
"net/url"
88

99
"golang.org/x/net/context"
1010
log "gopkg.in/inconshreveable/log15.v2"
@@ -25,8 +25,7 @@ type key int
2525
const (
2626
reqKey key = iota
2727
respKey
28-
paramKey
29-
queryKey
28+
paramsKey
3029
payloadKey
3130
respWrittenKey
3231
respStatusKey
@@ -38,17 +37,15 @@ const (
3837
func NewContext(gctx context.Context,
3938
req *http.Request,
4039
rw http.ResponseWriter,
41-
params map[string]string,
42-
query map[string][]string,
40+
params url.Values,
4341
payload interface{}) *Context {
4442

4543
if gctx == nil {
4644
gctx = context.Background()
4745
}
4846
gctx = context.WithValue(gctx, reqKey, req)
4947
gctx = context.WithValue(gctx, respKey, rw)
50-
gctx = context.WithValue(gctx, paramKey, params)
51-
gctx = context.WithValue(gctx, queryKey, query)
48+
gctx = context.WithValue(gctx, paramsKey, params)
5249
gctx = context.WithValue(gctx, payloadKey, payload)
5350

5451
return &Context{Context: gctx}
@@ -98,37 +95,19 @@ func (ctx *Context) ResponseLength() int {
9895
return 0
9996
}
10097

101-
// Get returns the param or query string with the given name and true or an empty string and false
102-
// if there isn't one.
103-
func (ctx *Context) Get(name string) (string, bool) {
104-
iparams := ctx.Value(paramKey)
105-
if iparams == nil {
106-
return "", false
107-
}
108-
params := iparams.(map[string]string)
109-
v, ok := params[name]
110-
if !ok {
111-
var vs []string
112-
query := ctx.Value(queryKey).(map[string][]string)
113-
vs, ok = query[name]
114-
if ok {
115-
v = strings.Join(vs, ",")
116-
}
117-
}
118-
if !ok {
119-
return "", false
120-
}
121-
return v, true
98+
// Get returns the param or querystring value with the given name.
99+
func (ctx *Context) Get(name string) string {
100+
iparams := ctx.Value(paramsKey)
101+
params := iparams.(url.Values)
102+
return params.Get(name)
122103
}
123104

124-
// GetMany returns the query string values with the given name or nil if there aren't any.
125-
func (ctx *Context) GetMany(name string) []string {
126-
q := ctx.Value(queryKey)
127-
if q == nil {
128-
return nil
129-
}
130-
query := q.(map[string][]string)
131-
return query[name]
105+
// GetMany returns the querystring values with the given name or nil if there aren't any.
106+
func (ctx *Context) GetMany(name string) ([]string, bool) {
107+
iparams := ctx.Value(paramsKey)
108+
params := iparams.(url.Values)
109+
p, ok := params[name]
110+
return p, ok
132111
}
133112

134113
// GetNames returns all the querystring and URL parameter names.

design/definitions.go

+126-49
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,28 @@ type (
3030

3131
// APIDefinition defines the global properties of the API.
3232
APIDefinition struct {
33+
// APIVersionDefinition contains the default values for properties across all versions.
34+
*APIVersionDefinition
35+
// Versions contain the API properties indexed by version.
36+
Versions map[string]*APIVersionDefinition
37+
// Types indexes the user defined types by name.
38+
Types map[string]*UserTypeDefinition
39+
// MediaTypes indexes the API media types by canonical identifier.
40+
MediaTypes map[string]*MediaTypeDefinition
41+
// rand is the random generator used to generate examples.
42+
rand *RandomGenerator
43+
}
44+
45+
// APIVersionDefinition defines the properties of the API for a given version.
46+
APIVersionDefinition struct {
3347
// API name
3448
Name string
3549
// API Title
3650
Title string
3751
// API description
38-
Description string // API description
52+
Description string
53+
// API version if any
54+
Version string
3955
// API hostname
4056
Host string
4157
// API URL schemes
@@ -64,16 +80,10 @@ type (
6480
DefaultResponses map[string]*ResponseDefinition
6581
// Built-in response templates
6682
DefaultResponseTemplates map[string]*ResponseTemplateDefinition
67-
// User types
68-
Types map[string]*UserTypeDefinition
69-
// Media types
70-
MediaTypes map[string]*MediaTypeDefinition
71-
// dsl contains the DSL used to create this definition if any.
83+
// DSL contains the DSL used to create this definition if any.
7284
DSL func()
73-
// metadata is a list of key/value pairs
85+
// Metadata is a list of key/value pairs
7486
Metadata MetadataDefinition
75-
// rand is the random generator used to generate examples.
76-
rand *RandomGenerator
7787
}
7888

7989
// ContactDefinition contains the API contact information.
@@ -118,7 +128,7 @@ type (
118128
ParentName string
119129
// Optional description
120130
Description string
121-
// Optional version
131+
// API version that uses this resource.
122132
Version string
123133
// Default media type, describes the resource attributes
124134
MediaType string
@@ -322,6 +332,9 @@ type (
322332
Names []string
323333
}
324334

335+
// VersionIterator is the type of functions given to IterateVersions.
336+
VersionIterator func(v *APIVersionDefinition) error
337+
325338
// ResourceIterator is the type of functions given to IterateResources.
326339
ResourceIterator func(r *ResourceDefinition) error
327340

@@ -341,30 +354,11 @@ type (
341354
// Context returns the generic definition name used in error messages.
342355
func (a *APIDefinition) Context() string {
343356
if a.Name != "" {
344-
return fmt.Sprintf("api %#v", a.Name)
357+
return fmt.Sprintf("API %#v", a.Name)
345358
}
346359
return "unnamed API"
347360
}
348361

349-
// IterateResources calls the given iterator passing in each resource sorted in alphabetical order.
350-
// Iteration stops if an iterator returns an error and in this case IterateResources returns that
351-
// error.
352-
func (a *APIDefinition) IterateResources(it ResourceIterator) error {
353-
names := make([]string, len(a.Resources))
354-
i := 0
355-
for n := range a.Resources {
356-
names[i] = n
357-
i++
358-
}
359-
sort.Strings(names)
360-
for _, n := range names {
361-
if err := it(a.Resources[n]); err != nil {
362-
return err
363-
}
364-
}
365-
return nil
366-
}
367-
368362
// IterateMediaTypes calls the given iterator passing in each media type sorted in alphabetical order.
369363
// Iteration stops if an iterator returns an error and in this case IterateMediaTypes returns that
370364
// error.
@@ -403,25 +397,6 @@ func (a *APIDefinition) IterateUserTypes(it UserTypeIterator) error {
403397
return nil
404398
}
405399

406-
// IterateResponses calls the given iterator passing in each response sorted in alphabetical order.
407-
// Iteration stops if an iterator returns an error and in this case IterateResponses returns that
408-
// error.
409-
func (a *APIDefinition) IterateResponses(it ResponseIterator) error {
410-
names := make([]string, len(a.Responses))
411-
i := 0
412-
for n := range a.Responses {
413-
names[i] = n
414-
i++
415-
}
416-
sort.Strings(names)
417-
for _, n := range names {
418-
if err := it(a.Responses[n]); err != nil {
419-
return err
420-
}
421-
}
422-
return nil
423-
}
424-
425400
// Example returns a random value for the given data type.
426401
// If the data type has validations then the example value validates them.
427402
// Example returns the same random value for a given api name (the random
@@ -449,6 +424,108 @@ func (a *APIDefinition) MediaTypeWithIdentifier(id string) *MediaTypeDefinition
449424
return mtwi
450425
}
451426

427+
// IterateVersions calls the given iterator passing in each API version definition sorted
428+
// alphabetically by version name. If the API defines no version then the common version gets
429+
// passed to the iterator. The common version is the one built with the API DSL function.
430+
// Iteration stops if an iterator returns an error and in this case IterateVersions returns that
431+
// error.
432+
func (a *APIDefinition) IterateVersions(it VersionIterator) error {
433+
if len(a.Versions) == 0 {
434+
return it(Design.APIVersionDefinition)
435+
}
436+
versions := make([]string, len(a.Versions))
437+
i := 0
438+
for n := range a.Versions {
439+
versions[i] = n
440+
i++
441+
}
442+
sort.Strings(versions)
443+
for _, v := range versions {
444+
if err := it(Design.Versions[v]); err != nil {
445+
return err
446+
}
447+
}
448+
return nil
449+
}
450+
451+
// Context returns the generic definition name used in error messages.
452+
func (v *APIVersionDefinition) Context() string {
453+
if v.Version != "" {
454+
return fmt.Sprintf("%s version %s", Design.Context(), v.Version)
455+
}
456+
return Design.Context()
457+
}
458+
459+
// IsDefault returns true if the version definition applies to all versions (i.e. is the API
460+
// definition).
461+
func (v *APIVersionDefinition) IsDefault() bool {
462+
return v.Version == ""
463+
}
464+
465+
// IterateResources calls the given iterator passing in each resource sorted in alphabetical order.
466+
// Iteration stops if an iterator returns an error and in this case IterateResources returns that
467+
// error.
468+
func (v *APIVersionDefinition) IterateResources(it ResourceIterator) error {
469+
var allResources map[string]*ResourceDefinition
470+
common := Design.APIVersionDefinition
471+
if common != v {
472+
allResources = make(map[string]*ResourceDefinition, len(Design.Resources)+len(v.Resources))
473+
for n, r := range Design.Resources {
474+
allResources[n] = r
475+
}
476+
for n, r := range v.Resources {
477+
allResources[n] = r
478+
}
479+
} else {
480+
allResources = common.Resources
481+
}
482+
names := make([]string, len(allResources))
483+
i := 0
484+
for _, res := range allResources {
485+
names[i] = res.Name
486+
i++
487+
}
488+
sort.Strings(names)
489+
for _, n := range names {
490+
if err := it(allResources[n]); err != nil {
491+
return err
492+
}
493+
}
494+
return nil
495+
}
496+
497+
// IterateResponses calls the given iterator passing in each response sorted in alphabetical order.
498+
// Iteration stops if an iterator returns an error and in this case IterateResponses returns that
499+
// error.
500+
func (v *APIVersionDefinition) IterateResponses(it ResponseIterator) error {
501+
var allResponses map[string]*ResponseDefinition
502+
common := Design.APIVersionDefinition
503+
if common != v {
504+
allResponses = make(map[string]*ResponseDefinition, len(Design.Responses)+len(v.Responses))
505+
for n, r := range Design.Responses {
506+
allResponses[n] = r
507+
}
508+
for n, r := range v.Responses {
509+
allResponses[n] = r
510+
}
511+
} else {
512+
allResponses = common.Responses
513+
}
514+
names := make([]string, len(allResponses))
515+
i := 0
516+
for n := range allResponses {
517+
names[i] = n
518+
i++
519+
}
520+
sort.Strings(names)
521+
for _, n := range names {
522+
if err := it(allResponses[n]); err != nil {
523+
return err
524+
}
525+
}
526+
return nil
527+
}
528+
452529
// CanonicalIdentifier returns the media type identifier sans suffix
453530
// which is what the DSL uses to store and lookup media types.
454531
func CanonicalIdentifier(identifier string) string {

0 commit comments

Comments
 (0)