forked from cloudfoundry/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
routing.go
147 lines (134 loc) · 3.68 KB
/
routing.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
package internal
import (
"fmt"
"io"
"net/http"
"net/url"
"path"
"strings"
)
// Params map path keys to values. For example, if your route has the path
// pattern:
// /person/:person_id/pets/:pet_type
// Then a correct Params map would lool like:
// router.Params{
// "person_id": "123",
// "pet_type": "cats",
// }
type Params map[string]string
// Route defines the property of a Cloud Controller V3 endpoint.
//
// Method can be one of the following:
// GET HEAD POST PUT PATCH DELETE CONNECT OPTIONS TRACE
//
// Path conforms to Pat-style pattern matching. The following docs are taken
// from http://godoc.org/github.com/bmizerany/pat#PatternServeMux
//
// Path Patterns may contain literals or captures. Capture names start with a
// colon and consist of letters A-Z, a-z, _, and 0-9. The rest of the pattern
// matches literally. The portion of the URL matching each name ends with an
// occurrence of the character in the pattern immediately following the name,
// or a /, whichever comes first. It is possible for a name to match the empty
// string.
//
// Example pattern with one capture:
// /hello/:name
// Will match:
// /hello/blake
// /hello/keith
// Will not match:
// /hello/blake/
// /hello/blake/foo
// /foo
// /foo/bar
//
// Example 2:
// /hello/:name/
// Will match:
// /hello/blake/
// /hello/keith/foo
// /hello/blake
// /hello/keith
// Will not match:
// /foo
// /foo/bar
type Route struct {
// Name is a key specifying which HTTP route the router should associate with
// the endpoint at runtime.
Name string
// Method is any valid HTTP method
Method string
// Path contains a path pattern
Path string
// Resource is a key specifying which resource root the router should
// associate with the endpoint at runtime.
Resource string
}
// CreatePath combines the route's path pattern with a Params map
// to produce a valid path.
func (r Route) CreatePath(params Params) (string, error) {
components := strings.Split(r.Path, "/")
for i, c := range components {
if len(c) == 0 {
continue
}
if c[0] == ':' {
val, ok := params[c[1:]]
if !ok {
return "", fmt.Errorf("missing param %s", c)
}
components[i] = val
}
}
u, err := url.Parse(strings.Join(components, "/"))
if err != nil {
return "", err
}
return u.String(), nil
}
// Router combines route and resource information in order to generate HTTP
// requests.
type Router struct {
routes map[string]Route
resources map[string]string
}
// NewRouter returns a pointer to a new Router.
func NewRouter(routes []Route, resources map[string]string) *Router {
mappedRoutes := map[string]Route{}
for _, route := range routes {
mappedRoutes[route.Name] = route
}
return &Router{
routes: mappedRoutes,
resources: resources,
}
}
// CreateRequest returns a request key'd off of the name given. The params are
// merged into the URL and body is set as the request body.
func (router Router) CreateRequest(name string, params Params, body io.Reader) (*http.Request, error) {
route, ok := router.routes[name]
if !ok {
return &http.Request{}, fmt.Errorf("no route exists with the name %s", name)
}
uri, err := route.CreatePath(params)
if err != nil {
return &http.Request{}, err
}
resource, ok := router.resources[route.Resource]
if !ok {
return &http.Request{}, fmt.Errorf("no resource exists with the name %s", route.Resource)
}
url, err := router.urlFrom(resource, uri)
if err != nil {
return &http.Request{}, err
}
return http.NewRequest(route.Method, url, body)
}
func (Router) urlFrom(resource string, uri string) (string, error) {
u, err := url.Parse(resource)
if err != nil {
return "", err
}
u.Path = path.Join(u.Path, uri)
return u.String(), nil
}