forked from kataras/iris
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgroup.go
146 lines (130 loc) · 4.06 KB
/
group.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
package versioning
import (
"strings"
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/core/router"
"github.com/blang/semver/v4"
)
// API is a type alias of router.Party.
// This is required in order for a Group instance
// to implement the Party interface without field conflict.
type API = router.Party
// Group represents a group of resources that should
// be handled based on a version requested by the client.
// See `NewGroup` for more.
type Group struct {
API
validate semver.Range
deprecation DeprecationOptions
}
// NewGroup returns a version Group based on the given "version" constraint.
// Group completes the Party interface.
// The returned Group wraps a cloned Party of the given "r" Party therefore,
// any changes to its parent won't affect this one (e.g. register global middlewares afterwards).
//
// A version is extracted through the versioning.GetVersion function:
//
// Accept-Version: 1.0.0
// Accept: application/json; version=1.0.0
//
// You can customize it by setting a version based on the request context:
//
// api.Use(func(ctx *context.Context) {
// if version := ctx.URLParam("version"); version != "" {
// SetVersion(ctx, version)
// }
//
// ctx.Next()
// })
//
// OR:
//
// api.Use(versioning.FromQuery("version", ""))
//
// Examples at: _examples/routing/versioning
// Usage:
//
// app := iris.New()
// api := app.Party("/api")
// v1 := versioning.NewGroup(api, ">=1.0.0 <2.0.0")
// v1.Get/Post/Put/Delete...
//
// Valid ranges are:
// - "<1.0.0"
// - "<=1.0.0"
// - ">1.0.0"
// - ">=1.0.0"
// - "1.0.0", "=1.0.0", "==1.0.0"
// - "!1.0.0", "!=1.0.0"
//
// A Range can consist of multiple ranges separated by space:
// Ranges can be linked by logical AND:
// - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7"
//
// but not "1.0.0" or "2.0.0"
// - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0
//
// except 2.0.3-beta.2
//
// Ranges can also be linked by logical OR:
// - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"
//
// AND has a higher precedence than OR. It's not possible to use brackets.
//
// Ranges can be combined by both AND and OR
//
// - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`,
//
// but not `4.2.1`, `2.1.1`
func NewGroup(r API, version string) *Group {
version = strings.ReplaceAll(version, ",", " ")
version = strings.TrimSpace(version)
verRange, err := semver.ParseRange(version)
if err != nil {
r.Logger().Errorf("versioning: %s: %s", r.GetRelPath(), strings.ToLower(err.Error()))
return &Group{API: r}
}
// Clone this one.
r = r.Party("/")
// Note that this feature alters the RouteRegisterRule to RouteOverlap
// the RouteOverlap rule does not contain any performance downside
// but it's good to know that if you registered other mode, this wanna change it.
r.SetRegisterRule(router.RouteOverlap)
handler := makeHandler(verRange)
// This is required in order to not populate this middleware to the next group.
r.UseOnce(handler)
// This is required for versioned custom error handlers,
// of course if the parent registered one then this will do nothing.
r.UseError(handler)
return &Group{
API: r,
validate: verRange,
}
}
// Deprecated marks this group and all its versioned routes
// as deprecated versions of that endpoint.
func (g *Group) Deprecated(options DeprecationOptions) *Group {
// store it for future use, e.g. collect all deprecated APIs and notify the developer.
g.deprecation = options
g.API.UseOnce(func(ctx *context.Context) {
WriteDeprecated(ctx, options)
ctx.Next()
})
return g
}
func makeHandler(validate semver.Range) context.Handler {
return func(ctx *context.Context) {
if !matchVersionRange(ctx, validate) {
// The overlapped handler has an exception
// of a type of context.NotFound (which versioning.ErrNotFound wraps)
// to clear the status code
// and the error to ignore this
// when available match version exists (see `NewGroup`).
if h := NotFoundHandler; h != nil {
h(ctx)
return
}
}
ctx.Next()
}
}