| 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 |