OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package epfrontend | |
6 | |
7 import ( | |
8 "fmt" | |
9 "net/http" | |
10 "net/url" | |
11 "regexp" | |
12 "sort" | |
13 "strings" | |
14 | |
15 "github.com/GoogleCloudPlatform/go-endpoints/endpoints" | |
16 ) | |
17 | |
18 var ( | |
19 // Capture {stuff}. | |
20 reParam = regexp.MustCompile(`\{([^\}]+)\}`) | |
21 ) | |
22 | |
23 func getHostURL(r *http.Request) *url.URL { | |
24 u := url.URL{ | |
25 Scheme: "http", | |
26 Host: r.Host, | |
27 } | |
28 if r.TLS != nil { | |
29 u.Scheme = "https" | |
30 } | |
31 return &u | |
32 } | |
33 | |
34 func (s *Server) handleDirectoryList(r *http.Request) (interface{}, error) { | |
35 u := getHostURL(r) | |
36 u.Path = r.URL.Path | |
37 return s.directoryList(u), nil | |
38 } | |
39 | |
40 // directoryList converts a list of backend APIs into an API directory. | |
41 // | |
42 // The conversion isn't perfect or spec-confirming. It makes a set of minimal | |
43 // mutations to be compatible with the `google-api-go-generator` tool. | |
44 // | |
45 // Each directory item's DiscoveryLink is generated from the item's index. | |
46 // For example, directory item #0 is hosted at "./apis/0/rest". | |
47 func (s *Server) directoryList(root *url.URL) *directoryList { | |
48 serviceNames := make([]string, 0, len(s.services)) | |
49 for k := range s.services { | |
50 serviceNames = append(serviceNames, k) | |
51 } | |
52 sort.Strings(serviceNames) | |
53 | |
54 d := directoryList{ | |
55 Kind: "discovery#directoryList", | |
56 DiscoveryVersion: "v1", | |
57 Items: make([]*directoryItem, len(serviceNames)), | |
58 } | |
59 | |
60 for i, name := range serviceNames { | |
61 api := s.services[name] | |
62 di := directoryItem{ | |
63 Kind: "discovery#directoryItem", | |
64 ID: apiID(api), | |
65 Name: api.Name, | |
66 Version: api.Version, | |
67 Description: api.Desc, | |
68 DiscoveryRestURL: safeURLPathJoin(root.String(), api.Nam
e, api.Version, "rest"), | |
69 DiscoveryLink: safeURLPathJoin(".", "apis", api.Name,
api.Version, "rest"), | |
70 RootURL: root.String(), | |
71 Preferred: false, | |
72 } | |
73 d.Items[i] = &di | |
74 } | |
75 | |
76 return &d | |
77 } | |
78 | |
79 func apiID(a *endpoints.APIDescriptor) string { | |
80 return fmt.Sprintf("%s:%s", a.Name, a.Version) | |
81 } | |
82 | |
83 func (s *Server) handleRestDescription(r *http.Request, a *endpoints.APIDescript
or) (interface{}, error) { | |
84 u := getHostURL(r) | |
85 u.Path = s.root | |
86 d, err := buildRestDescription(u, a) | |
87 return d, err | |
88 } | |
89 | |
90 // buildRestDescription returns a single directory item's REST description | |
91 // structure. | |
92 // | |
93 // This is a JSON-compatible element that describes the APIs exported by a | |
94 // singleAPI. | |
95 func buildRestDescription(root *url.URL, a *endpoints.APIDescriptor) (*restDescr
iption, error) { | |
96 servicePath := safeURLPathJoin(a.Name, a.Version) | |
97 rdesc := restDescription{ | |
98 Kind: "discovery#restDescription", | |
99 DiscoveryVersion: "v1", | |
100 | |
101 Protocol: "rest", | |
102 ID: apiID(a), | |
103 Name: a.Name, | |
104 Version: a.Version, | |
105 Description: a.Desc, | |
106 RootURL: root.String(), | |
107 BasePath: safeURLPathJoin("", root.Path, servicePath, ""), | |
108 BaseURL: safeURLPathJoin(root.String(), servicePath, ""), | |
109 ServicePath: safeURLPathJoin(servicePath, ""), | |
110 DefaultVersion: a.Default, | |
111 } | |
112 if len(a.Descriptor.Schemas) > 0 { | |
113 rdesc.Schemas = make(map[string]*endpoints.APISchemaDescriptor,
len(a.Descriptor.Schemas)) | |
114 } | |
115 if len(a.Methods) > 0 { | |
116 rdesc.Methods = make(map[string]*restMethod, len(a.Methods)) | |
117 } | |
118 | |
119 for name, schema := range a.Descriptor.Schemas { | |
120 schema := *schema | |
121 rdesc.Schemas[name] = &schema | |
122 } | |
123 | |
124 for id, desc := range a.Methods { | |
125 m := restMethod{ | |
126 ID: id, | |
127 Path: desc.Path, | |
128 HTTPMethod: desc.HTTPMethod, | |
129 Scopes: desc.Scopes, | |
130 Description: desc.Desc, | |
131 } | |
132 m.ParameterOrder = parseParameterOrderFromPath(m.Path) | |
133 if nParams := len(desc.Request.Params) + len(desc.Response.Param
s); nParams > 0 { | |
134 m.Parameters = make(map[string]*restMethodParameter, nPa
rams) | |
135 } | |
136 | |
137 err := error(nil) | |
138 rosyMethod := a.Descriptor.Methods[desc.RosyMethod] | |
139 if rosyMethod == nil { | |
140 return nil, fmt.Errorf("no descriptor for RosyMethod %q"
, desc.RosyMethod) | |
141 } | |
142 m.Request, err = m.buildMethodParamRef(&desc.Request, rosyMethod
.Request, true) | |
143 if err != nil { | |
144 return nil, fmt.Errorf("failed to build method [%s] requ
est: %s", m.ID, err) | |
145 } | |
146 | |
147 m.Response, err = m.buildMethodParamRef(&desc.Response, rosyMeth
od.Response, false) | |
148 if err != nil { | |
149 return nil, fmt.Errorf("failed to build method [%s] resp
onse: %s", m.ID, err) | |
150 } | |
151 | |
152 rdesc.Methods[strings.TrimPrefix(m.ID, fmt.Sprintf("%s.", a.Name
))] = &m | |
153 } | |
154 | |
155 return &rdesc, nil | |
156 } | |
157 | |
158 func (m *restMethod) buildMethodParamRef(desc *endpoints.APIReqRespDescriptor, r
ef *endpoints.APISchemaRef, canPath bool) ( | |
159 *restMethodParameterRef, error) { | |
160 pathParams := map[string]struct{}{} | |
161 if canPath { | |
162 for _, p := range m.ParameterOrder { | |
163 pathParams[p] = struct{}{} | |
164 } | |
165 } | |
166 | |
167 for name, param := range desc.Params { | |
168 rmp := restMethodParameter{ | |
169 Required: param.Required, // All path parameters are req
uired. | |
170 } | |
171 rmp.Type, rmp.Format = convertBackendType(param.Type) | |
172 _, inPath := pathParams[name] | |
173 | |
174 if m.HTTPMethod == "GET" && !inPath { | |
175 rmp.Location = "query" | |
176 } else { | |
177 rmp.Location = "path" | |
178 rmp.Required = true | |
179 } | |
180 m.Parameters[name] = &rmp | |
181 } | |
182 | |
183 if desc.Body == "empty" { | |
184 return nil, nil | |
185 } | |
186 | |
187 paramRef := restMethodParameterRef{ | |
188 ParameterName: desc.BodyName, | |
189 } | |
190 if ref != nil { | |
191 paramRef.Ref = ref.Ref | |
192 } | |
193 | |
194 return ¶mRef, nil | |
195 } | |
196 | |
197 func convertBackendType(t string) (string, string) { | |
198 switch t { | |
199 case "int32": | |
200 return "integer", "int32" | |
201 case "int64": | |
202 return "string", "int64" | |
203 case "uint32": | |
204 return "integer", "uint32" | |
205 case "uint64": | |
206 return "string", "uint64" | |
207 case "float": | |
208 return "number", "float" | |
209 case "double": | |
210 return "number", "double" | |
211 case "boolean": | |
212 return "boolean", "" | |
213 case "string": | |
214 return "string", "" | |
215 } | |
216 | |
217 return "", "" | |
218 } | |
219 | |
220 func parseParameterOrderFromPath(path string) []string { | |
221 matches := reParam.FindAllStringSubmatch(path, -1) | |
222 if len(matches) == 0 { | |
223 return nil | |
224 } | |
225 | |
226 po := make([]string, 0, len(matches)) | |
227 for _, match := range matches { | |
228 po = append(po, match[1]) | |
229 } | |
230 return po | |
231 } | |
232 | |
233 // directoryList is a Google Cloud Endpoints frontend directory list structure. | |
234 // | |
235 // This is the first-level directory structure, which exports a series of API | |
236 // items. | |
237 type directoryList struct { | |
238 Kind string `json:"kind,omitempty"` | |
239 DiscoveryVersion string `json:"discoveryVersion,omitempty"` | |
240 Items []*directoryItem `json:"items,omitempty"` | |
241 } | |
242 | |
243 // directoryItem is a single API's directoryList entry. | |
244 // | |
245 // This is a This is the second-level directory structure which exports a single | |
246 // API's methods. | |
247 // | |
248 // The directoryItem exports a REST API (restDescription) at its relative | |
249 // DiscoveryLink. | |
250 type directoryItem struct { | |
251 Kind string `json:"kind,omitempty"` | |
252 ID string `json:"id,omitempty"` | |
253 Name string `json:"name,omitempty"` | |
254 Version string `json:"version,omitempty"` | |
255 Title string `json:"title,omitempty"` | |
256 Description string `json:"description,omitempty"` | |
257 DiscoveryRestURL string `json:"discoveryRestUrl,omitempty"` | |
258 DiscoveryLink string `json:"discoveryLink,omitempty"` | |
259 RootURL string `json:"rootUrl,omitempty"` | |
260 Preferred bool `json:"preferred,omitempty"` | |
261 } | |
262 | |
263 // restDescription is a hosted JSON at a rest endpoint. It is translated from a | |
264 // singleAPI into a form served by the frontend. | |
265 type restDescription struct { | |
266 Kind string `json:"kind,omitempty"` | |
267 DiscoveryVersion string `json:"discoveryVersion,omitempty"` | |
268 | |
269 Protocol string `json:"protocol,omitempty"` | |
270 ID string `json:"id,omitempty"` | |
271 Name string `json:"name,omitempty"` | |
272 Version string `json:"version,omitempty"` | |
273 Description string `json:"description,omitempty"` | |
274 BaseURL string `json:"baseUrl,omitempty"` | |
275 BasePath string `json:"basePath,omitempty"` | |
276 RootURL string `json:"rootUrl,omitempty"` | |
277 ServicePath string `json:"servicePath,omitempty"` | |
278 Root string `json:"root,omitempty"` | |
279 DefaultVersion bool `json:"defaultVersion,omitempty"` | |
280 | |
281 Schemas map[string]*endpoints.APISchemaDescriptor `json:"schemas,omitemp
ty"` | |
282 Methods map[string]*restMethod `json:"methods,omitemp
ty"` | |
283 } | |
284 | |
285 type restMethod struct { | |
286 ID string `json:"id,omitempty"` | |
287 Path string `json:"path,omitempty"` | |
288 HTTPMethod string `json:"httpMethod,omitemp
ty"` | |
289 Description string `json:"description,omitem
pty"` | |
290 Parameters map[string]*restMethodParameter `json:"parameters,omitemp
ty"` | |
291 ParameterOrder []string `json:"parameterOrder,omi
tempty"` | |
292 Request *restMethodParameterRef `json:"request,omitempty"
` | |
293 Response *restMethodParameterRef `json:"response,omitempty
"` | |
294 Scopes []string `json:"scopes,omitempty"` | |
295 } | |
296 | |
297 type restMethodParameter struct { | |
298 Type string `json:"type,omitempty"` | |
299 Format string `json:"format,omitempty"` | |
300 Location string `json:"location,omitempty"` | |
301 Required bool `json:"required,omitempty"` | |
302 } | |
303 | |
304 type restMethodParameterRef struct { | |
305 Ref string `json:"$ref,omitempty"` | |
306 ParameterName string `json:"parameterName,omitempty"` | |
307 } | |
OLD | NEW |