1// Copyright 2011 Google Inc. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package main 6 7import ( 8 "bytes" 9 "encoding/json" 10 "errors" 11 "flag" 12 "fmt" 13 "go/format" 14 "io" 15 "io/ioutil" 16 "log" 17 "net/http" 18 "net/url" 19 "os" 20 "os/exec" 21 "path/filepath" 22 "regexp" 23 "sort" 24 "strconv" 25 "strings" 26 "time" 27 "unicode" 28 29 "google.golang.org/api/google-api-go-generator/internal/disco" 30) 31 32const ( 33 googleDiscoveryURL = "https://www.googleapis.com/discovery/v1/apis" 34 generatorVersion = "2018018" 35) 36 37var ( 38 apiToGenerate = flag.String("api", "*", "The API ID to generate, like 'tasks:v1'. A value of '*' means all.") 39 useCache = flag.Bool("cache", true, "Use cache of discovered Google API discovery documents.") 40 genDir = flag.String("gendir", defaultGenDir(), "Directory to use to write out generated Go files") 41 build = flag.Bool("build", false, "Compile generated packages.") 42 install = flag.Bool("install", false, "Install generated packages.") 43 apisURL = flag.String("discoveryurl", googleDiscoveryURL, "URL to root discovery document") 44 45 publicOnly = flag.Bool("publiconly", true, "Only build public, released APIs. Only applicable for Google employees.") 46 47 jsonFile = flag.String("api_json_file", "", "If non-empty, the path to a local file on disk containing the API to generate. Exclusive with setting --api.") 48 output = flag.String("output", "", "(optional) Path to source output file. If not specified, the API name and version are used to construct an output path (e.g. tasks/v1).") 49 apiPackageBase = flag.String("api_pkg_base", "google.golang.org/api", "Go package prefix to use for all generated APIs.") 50 baseURL = flag.String("base_url", "", "(optional) Override the default service API URL. If empty, the service's root URL will be used.") 51 headerPath = flag.String("header_path", "", "If non-empty, prepend the contents of this file to generated services.") 52 53 gensupportPkg = flag.String("gensupport_pkg", "google.golang.org/api/gensupport", "Go package path of the 'api/gensupport' support package.") 54 googleapiPkg = flag.String("googleapi_pkg", "google.golang.org/api/googleapi", "Go package path of the 'api/googleapi' support package.") 55 optionPkg = flag.String("option_pkg", "google.golang.org/api/option", "Go package path of the 'api/option' support package.") 56 htransportPkg = flag.String("htransport_pkg", "google.golang.org/api/transport/http", "Go package path of the 'api/transport/http' support package.") 57 58 copyrightYear = flag.String("copyright_year", fmt.Sprintf("%d", time.Now().Year()), "Year for copyright.") 59 60 serviceTypes = []string{"Service", "APIService"} 61) 62 63// API represents an API to generate, as well as its state while it's 64// generating. 65type API struct { 66 // Fields needed before generating code, to select and find the APIs 67 // to generate. 68 // These fields usually come from the "directory item" JSON objects 69 // that are provided by the googleDiscoveryURL. We unmarshal a directory 70 // item directly into this struct. 71 ID string `json:"id"` 72 Name string `json:"name"` 73 Version string `json:"version"` 74 DiscoveryLink string `json:"discoveryRestUrl"` // absolute 75 76 doc *disco.Document 77 // TODO(jba): remove m when we've fully converted to using disco. 78 m map[string]interface{} 79 80 forceJSON []byte // if non-nil, the JSON schema file. else fetched. 81 usedNames namePool 82 schemas map[string]*Schema // apiName -> schema 83 responseTypes map[string]bool 84 85 p func(format string, args ...interface{}) // print raw 86 pn func(format string, args ...interface{}) // print with newline 87} 88 89func (a *API) sortedSchemaNames() (names []string) { 90 for name := range a.schemas { 91 names = append(names, name) 92 } 93 sort.Strings(names) 94 return 95} 96 97func (a *API) Schema(name string) *Schema { 98 return a.schemas[name] 99} 100 101type generateError struct { 102 api *API 103 error error 104} 105 106func (e *generateError) Error() string { 107 return fmt.Sprintf("API %s failed to generate code: %v", e.api.ID, e.error) 108} 109 110type compileError struct { 111 api *API 112 output string 113} 114 115func (e *compileError) Error() string { 116 return fmt.Sprintf("API %s failed to compile:\n%v", e.api.ID, e.output) 117} 118 119func main() { 120 flag.Parse() 121 122 if *install { 123 *build = true 124 } 125 126 var ( 127 apiIds = []string{} 128 matches = []*API{} 129 errors = []error{} 130 ) 131 for _, api := range getAPIs() { 132 apiIds = append(apiIds, api.ID) 133 if !api.want() { 134 continue 135 } 136 matches = append(matches, api) 137 log.Printf("Generating API %s", api.ID) 138 err := api.WriteGeneratedCode() 139 if err != nil && err != errNoDoc { 140 errors = append(errors, &generateError{api, err}) 141 continue 142 } 143 if *build && err == nil { 144 var args []string 145 if *install { 146 args = append(args, "install") 147 } else { 148 args = append(args, "build") 149 } 150 args = append(args, api.Target()) 151 out, err := exec.Command("go", args...).CombinedOutput() 152 if err != nil { 153 errors = append(errors, &compileError{api, string(out)}) 154 } 155 } 156 } 157 158 if len(matches) == 0 { 159 log.Fatalf("No APIs matched %q; options are %v", *apiToGenerate, apiIds) 160 } 161 162 if len(errors) > 0 { 163 log.Printf("%d API(s) failed to generate or compile:", len(errors)) 164 for _, ce := range errors { 165 log.Println(ce.Error()) 166 } 167 os.Exit(1) 168 } 169} 170 171func (a *API) want() bool { 172 if *jsonFile != "" { 173 // Return true early, before calling a.JSONFile() 174 // which will require a GOPATH be set. This is for 175 // integration with Google's build system genrules 176 // where there is no GOPATH. 177 return true 178 } 179 // Skip this API if we're in cached mode and the files don't exist on disk. 180 if *useCache { 181 if _, err := os.Stat(a.JSONFile()); os.IsNotExist(err) { 182 return false 183 } 184 } 185 return *apiToGenerate == "*" || *apiToGenerate == a.ID 186} 187 188func getAPIs() []*API { 189 if *jsonFile != "" { 190 return getAPIsFromFile() 191 } 192 var bytes []byte 193 var source string 194 apiListFile := filepath.Join(genDirRoot(), "api-list.json") 195 if *useCache { 196 if !*publicOnly { 197 log.Fatalf("-cache=true not compatible with -publiconly=false") 198 } 199 var err error 200 bytes, err = ioutil.ReadFile(apiListFile) 201 if err != nil { 202 log.Fatal(err) 203 } 204 source = apiListFile 205 } else { 206 bytes = slurpURL(*apisURL) 207 if *publicOnly { 208 if err := writeFile(apiListFile, bytes); err != nil { 209 log.Fatal(err) 210 } 211 } 212 source = *apisURL 213 } 214 apis, err := unmarshalAPIs(bytes) 215 if err != nil { 216 log.Fatalf("error decoding JSON in %s: %v", source, err) 217 } 218 if !*publicOnly && *apiToGenerate != "*" { 219 apis = append(apis, apiFromID(*apiToGenerate)) 220 } 221 return apis 222} 223 224func unmarshalAPIs(bytes []byte) ([]*API, error) { 225 var itemObj struct{ Items []*API } 226 if err := json.Unmarshal(bytes, &itemObj); err != nil { 227 return nil, err 228 } 229 return itemObj.Items, nil 230} 231 232func apiFromID(apiID string) *API { 233 parts := strings.Split(apiID, ":") 234 if len(parts) != 2 { 235 log.Fatalf("malformed API name: %q", apiID) 236 } 237 return &API{ 238 ID: apiID, 239 Name: parts[0], 240 Version: parts[1], 241 } 242} 243 244// getAPIsFromFile handles the case of generating exactly one API 245// from the flag given in --api_json_file 246func getAPIsFromFile() []*API { 247 if *apiToGenerate != "*" { 248 log.Fatalf("Can't set --api with --api_json_file.") 249 } 250 if !*publicOnly { 251 log.Fatalf("Can't set --publiconly with --api_json_file.") 252 } 253 a, err := apiFromFile(*jsonFile) 254 if err != nil { 255 log.Fatal(err) 256 } 257 return []*API{a} 258} 259 260func apiFromFile(file string) (*API, error) { 261 jsonBytes, err := ioutil.ReadFile(file) 262 if err != nil { 263 return nil, fmt.Errorf("Error reading %s: %v", file, err) 264 } 265 doc, err := disco.NewDocument(jsonBytes) 266 if err != nil { 267 return nil, fmt.Errorf("reading document from %q: %v", file, err) 268 } 269 a := &API{ 270 ID: doc.ID, 271 Name: doc.Name, 272 Version: doc.Version, 273 forceJSON: jsonBytes, 274 doc: doc, 275 } 276 return a, nil 277} 278 279func writeFile(file string, contents []byte) error { 280 // Don't write it if the contents are identical. 281 existing, err := ioutil.ReadFile(file) 282 if err == nil && (bytes.Equal(existing, contents) || basicallyEqual(existing, contents)) { 283 return nil 284 } 285 outdir := filepath.Dir(file) 286 if err = os.MkdirAll(outdir, 0755); err != nil { 287 return fmt.Errorf("failed to Mkdir %s: %v", outdir, err) 288 } 289 return ioutil.WriteFile(file, contents, 0644) 290} 291 292var ignoreLines = regexp.MustCompile(`(?m)^\s+"(?:etag|revision)": ".+\n`) 293 294// basicallyEqual reports whether a and b are equal except for boring 295// differences like ETag updates. 296func basicallyEqual(a, b []byte) bool { 297 return ignoreLines.Match(a) && ignoreLines.Match(b) && 298 bytes.Equal(ignoreLines.ReplaceAll(a, nil), ignoreLines.ReplaceAll(b, nil)) 299} 300 301func slurpURL(urlStr string) []byte { 302 if *useCache { 303 log.Fatalf("Invalid use of slurpURL in cached mode for URL %s", urlStr) 304 } 305 req, err := http.NewRequest("GET", urlStr, nil) 306 if err != nil { 307 log.Fatal(err) 308 } 309 if *publicOnly { 310 req.Header.Add("X-User-IP", "0.0.0.0") // hack 311 } 312 res, err := http.DefaultClient.Do(req) 313 if err != nil { 314 log.Fatalf("Error fetching URL %s: %v", urlStr, err) 315 } 316 if res.StatusCode >= 300 { 317 log.Printf("WARNING: URL %s served status code %d", urlStr, res.StatusCode) 318 return nil 319 } 320 bs, err := ioutil.ReadAll(res.Body) 321 if err != nil { 322 log.Fatalf("Error reading body of URL %s: %v", urlStr, err) 323 } 324 return bs 325} 326 327func panicf(format string, args ...interface{}) { 328 panic(fmt.Sprintf(format, args...)) 329} 330 331// namePool keeps track of used names and assigns free ones based on a 332// preferred name 333type namePool struct { 334 m map[string]bool // lazily initialized 335} 336 337// oddVersionRE matches unusual API names like directory_v1. 338var oddVersionRE = regexp.MustCompile(`^(.+)_(v[\d\.]+)$`) 339 340// renameVersion conditionally rewrites the provided version such 341// that the final path component of the import path doesn't look 342// like a Go identifier. This keeps the consistency that import paths 343// for the generated Go packages look like: 344// google.golang.org/api/NAME/v<version> 345// and have package NAME. 346// See https://github.com/google/google-api-go-client/issues/78 347func renameVersion(version string) string { 348 if version == "alpha" || version == "beta" { 349 return "v0." + version 350 } 351 if m := oddVersionRE.FindStringSubmatch(version); m != nil { 352 return m[1] + "/" + m[2] 353 } 354 return version 355} 356 357func (p *namePool) Get(preferred string) string { 358 if p.m == nil { 359 p.m = make(map[string]bool) 360 } 361 name := preferred 362 tries := 0 363 for p.m[name] { 364 tries++ 365 name = fmt.Sprintf("%s%d", preferred, tries) 366 } 367 p.m[name] = true 368 return name 369} 370 371func genDirRoot() string { 372 if *genDir == "" { 373 log.Fatalf("-gendir option must be set.") 374 } 375 return *genDir 376} 377 378func defaultGenDir() string { 379 // TODO(cbro): consider using $CWD 380 paths := filepath.SplitList(os.Getenv("GOPATH")) 381 if len(paths) == 0 { 382 return "" 383 } 384 return filepath.Join(paths[0], "src", "google.golang.org", "api") 385} 386 387func (a *API) SourceDir() string { 388 return filepath.Join(genDirRoot(), a.Package(), renameVersion(a.Version)) 389} 390 391func (a *API) DiscoveryURL() string { 392 if a.DiscoveryLink == "" { 393 log.Fatalf("API %s has no DiscoveryLink", a.ID) 394 } 395 return a.DiscoveryLink 396} 397 398func (a *API) Package() string { 399 return strings.ToLower(a.Name) 400} 401 402func (a *API) Target() string { 403 return fmt.Sprintf("%s/%s/%s", *apiPackageBase, a.Package(), renameVersion(a.Version)) 404} 405 406// ServiceType returns the name of the type to use for the root API struct 407// (typically "Service"). 408func (a *API) ServiceType() string { 409 switch a.Name { 410 case "appengine", "content": // retained for historical compatibility. 411 return "APIService" 412 default: 413 for _, t := range serviceTypes { 414 if _, ok := a.schemas[t]; !ok { 415 return t 416 } 417 } 418 panic("all service types are used, please consider introducing a new type to serviceTypes.") 419 } 420} 421 422// GetName returns a free top-level function/type identifier in the package. 423// It tries to return your preferred match if it's free. 424func (a *API) GetName(preferred string) string { 425 return a.usedNames.Get(preferred) 426} 427 428func (a *API) apiBaseURL() string { 429 var base, rel string 430 switch { 431 case *baseURL != "": 432 base, rel = *baseURL, a.doc.BasePath 433 case a.doc.RootURL != "": 434 base, rel = a.doc.RootURL, a.doc.ServicePath 435 default: 436 base, rel = *apisURL, a.doc.BasePath 437 } 438 return resolveRelative(base, rel) 439} 440 441func (a *API) needsDataWrapper() bool { 442 for _, feature := range a.doc.Features { 443 if feature == "dataWrapper" { 444 return true 445 } 446 } 447 return false 448} 449 450func (a *API) jsonBytes() []byte { 451 if a.forceJSON == nil { 452 var slurp []byte 453 var err error 454 if *useCache { 455 slurp, err = ioutil.ReadFile(a.JSONFile()) 456 if err != nil { 457 log.Fatal(err) 458 } 459 } else { 460 slurp = slurpURL(a.DiscoveryURL()) 461 if slurp != nil { 462 // Make sure that keys are sorted by re-marshalling. 463 d := make(map[string]interface{}) 464 json.Unmarshal(slurp, &d) 465 if err != nil { 466 log.Fatal(err) 467 } 468 var err error 469 slurp, err = json.MarshalIndent(d, "", " ") 470 if err != nil { 471 log.Fatal(err) 472 } 473 } 474 } 475 a.forceJSON = slurp 476 } 477 return a.forceJSON 478} 479 480func (a *API) JSONFile() string { 481 return filepath.Join(a.SourceDir(), a.Package()+"-api.json") 482} 483 484var errNoDoc = errors.New("could not read discovery doc") 485 486// WriteGeneratedCode generates code for a. 487// It returns errNoDoc if we couldn't read the discovery doc. 488func (a *API) WriteGeneratedCode() error { 489 genfilename := *output 490 jsonBytes := a.jsonBytes() 491 // Skip generation if we don't have the discovery doc. 492 if jsonBytes == nil { 493 // No message here, because slurpURL printed one. 494 return errNoDoc 495 } 496 if genfilename == "" { 497 if err := writeFile(a.JSONFile(), jsonBytes); err != nil { 498 return err 499 } 500 outdir := a.SourceDir() 501 err := os.MkdirAll(outdir, 0755) 502 if err != nil { 503 return fmt.Errorf("failed to Mkdir %s: %v", outdir, err) 504 } 505 pkg := a.Package() 506 genfilename = filepath.Join(outdir, pkg+"-gen.go") 507 } 508 509 code, err := a.GenerateCode() 510 errw := writeFile(genfilename, code) 511 if err == nil { 512 err = errw 513 } 514 if err != nil { 515 return err 516 } 517 return nil 518} 519 520var docsLink string 521 522func (a *API) GenerateCode() ([]byte, error) { 523 pkg := a.Package() 524 525 jsonBytes := a.jsonBytes() 526 var err error 527 if a.doc == nil { 528 a.doc, err = disco.NewDocument(jsonBytes) 529 if err != nil { 530 return nil, err 531 } 532 } 533 534 // Buffer the output in memory, for gofmt'ing later. 535 var buf bytes.Buffer 536 a.p = func(format string, args ...interface{}) { 537 _, err := fmt.Fprintf(&buf, format, args...) 538 if err != nil { 539 panic(err) 540 } 541 } 542 a.pn = func(format string, args ...interface{}) { 543 a.p(format+"\n", args...) 544 } 545 wf := func(path string) error { 546 f, err := os.Open(path) 547 if err != nil { 548 return err 549 } 550 defer f.Close() 551 552 _, err = io.Copy(&buf, f) 553 return err 554 } 555 556 p, pn := a.p, a.pn 557 558 if *headerPath != "" { 559 if err := wf(*headerPath); err != nil { 560 return nil, err 561 } 562 } 563 564 pn(`// Copyright %s Google LLC. 565// Use of this source code is governed by a BSD-style 566// license that can be found in the LICENSE file. 567 568// Code generated file. DO NOT EDIT. 569`, *copyrightYear) 570 571 pn("// Package %s provides access to the %s.", pkg, a.doc.Title) 572 if r := replacementPackage[pkg]; r != "" { 573 pn("//") 574 pn("// This package is DEPRECATED. Use package %s instead.", r) 575 } 576 docsLink = a.doc.DocumentationLink 577 if docsLink != "" { 578 pn("//") 579 pn("// For product documentation, see: %s", docsLink) 580 } 581 pn("//") 582 pn("// Creating a client") 583 pn("//") 584 pn("// Usage example:") 585 pn("//") 586 pn("// import %q", a.Target()) 587 pn("// ...") 588 pn("// ctx := context.Background()") 589 pn("// %sService, err := %s.NewService(ctx)", pkg, pkg) 590 pn("//") 591 pn("// In this example, Google Application Default Credentials are used for authentication.") 592 pn("//") 593 pn("// For information on how to create and obtain Application Default Credentials, see https://developers.google.com/identity/protocols/application-default-credentials.") 594 pn("//") 595 pn("// Other authentication options") 596 pn("//") 597 if len(a.doc.Auth.OAuth2Scopes) > 1 { 598 pn(`// By default, all available scopes (see "Constants") are used to authenticate. To restrict scopes, use option.WithScopes:`) 599 pn("//") 600 // NOTE: the first scope tends to be the broadest. Use the last one to demonstrate restriction. 601 pn("// %sService, err := %s.NewService(ctx, option.WithScopes(%s.%s))", pkg, pkg, pkg, scopeIdentifierFromURL(a.doc.Auth.OAuth2Scopes[len(a.doc.Auth.OAuth2Scopes)-1].URL)) 602 pn("//") 603 } 604 pn("// To use an API key for authentication (note: some APIs do not support API keys), use option.WithAPIKey:") 605 pn("//") 606 pn(`// %sService, err := %s.NewService(ctx, option.WithAPIKey("AIza..."))`, pkg, pkg) 607 pn("//") 608 pn("// To use an OAuth token (e.g., a user token obtained via a three-legged OAuth flow), use option.WithTokenSource:") 609 pn("//") 610 pn("// config := &oauth2.Config{...}") 611 pn("// // ...") 612 pn("// token, err := config.Exchange(ctx, ...)") 613 pn("// %sService, err := %s.NewService(ctx, option.WithTokenSource(config.TokenSource(ctx, token)))", pkg, pkg) 614 pn("//") 615 pn("// See https://godoc.org/google.golang.org/api/option/ for details on options.") 616 pn("package %s // import %q", pkg, a.Target()) 617 p("\n") 618 pn("import (") 619 for _, imp := range []string{ 620 "bytes", 621 "context", 622 "encoding/json", 623 "errors", 624 "fmt", 625 "io", 626 "net/http", 627 "net/url", 628 "strconv", 629 "strings", 630 } { 631 pn(" %q", imp) 632 } 633 pn("") 634 for _, imp := range []struct { 635 pkg string 636 lname string 637 }{ 638 {*gensupportPkg, "gensupport"}, 639 {*googleapiPkg, "googleapi"}, 640 {*optionPkg, "option"}, 641 {*htransportPkg, "htransport"}, 642 } { 643 pn(" %s %q", imp.lname, imp.pkg) 644 } 645 pn(")") 646 pn("\n// Always reference these packages, just in case the auto-generated code") 647 pn("// below doesn't.") 648 pn("var _ = bytes.NewBuffer") 649 pn("var _ = strconv.Itoa") 650 pn("var _ = fmt.Sprintf") 651 pn("var _ = json.NewDecoder") 652 pn("var _ = io.Copy") 653 pn("var _ = url.Parse") 654 pn("var _ = gensupport.MarshalJSON") 655 pn("var _ = googleapi.Version") 656 pn("var _ = errors.New") 657 pn("var _ = strings.Replace") 658 pn("var _ = context.Canceled") 659 pn("") 660 pn("const apiId = %q", a.doc.ID) 661 pn("const apiName = %q", a.doc.Name) 662 pn("const apiVersion = %q", a.doc.Version) 663 pn("const basePath = %q", a.apiBaseURL()) 664 665 a.generateScopeConstants() 666 a.PopulateSchemas() 667 668 service := a.ServiceType() 669 670 // Reserve names (ignore return value; we're the first caller). 671 a.GetName("New") 672 a.GetName(service) 673 674 pn("// NewService creates a new %s.", service) 675 pn("func NewService(ctx context.Context, opts ...option.ClientOption) (*%s, error) {", service) 676 if len(a.doc.Auth.OAuth2Scopes) != 0 { 677 pn("scopesOption := option.WithScopes(") 678 for _, scope := range a.doc.Auth.OAuth2Scopes { 679 pn("%q,", scope.URL) 680 } 681 pn(")") 682 pn("// NOTE: prepend, so we don't override user-specified scopes.") 683 pn("opts = append([]option.ClientOption{scopesOption}, opts...)") 684 } 685 pn("client, endpoint, err := htransport.NewClient(ctx, opts...)") 686 pn("if err != nil { return nil, err }") 687 pn("s, err := New(client)") 688 pn("if err != nil { return nil, err }") 689 pn(`if endpoint != "" { s.BasePath = endpoint }`) 690 pn("return s, nil") 691 pn("}\n") 692 693 pn("// New creates a new %s. It uses the provided http.Client for requests.", service) 694 pn("//") 695 pn("// Deprecated: please use NewService instead.") 696 pn("// To provide a custom HTTP client, use option.WithHTTPClient.") 697 pn("// If you are using google.golang.org/api/googleapis/transport.APIKey, use option.WithAPIKey with NewService instead.") 698 pn("func New(client *http.Client) (*%s, error) {", service) 699 pn("if client == nil { return nil, errors.New(\"client is nil\") }") 700 pn("s := &%s{client: client, BasePath: basePath}", service) 701 for _, res := range a.doc.Resources { // add top level resources. 702 pn("s.%s = New%s(s)", resourceGoField(res, nil), resourceGoType(res)) 703 } 704 pn("return s, nil") 705 pn("}") 706 707 pn("\ntype %s struct {", service) 708 pn(" client *http.Client") 709 pn(" BasePath string // API endpoint base URL") 710 pn(" UserAgent string // optional additional User-Agent fragment") 711 712 for _, res := range a.doc.Resources { 713 pn("\n\t%s\t*%s", resourceGoField(res, nil), resourceGoType(res)) 714 } 715 pn("}") 716 pn("\nfunc (s *%s) userAgent() string {", service) 717 pn(` if s.UserAgent == "" { return googleapi.UserAgent }`) 718 pn(` return googleapi.UserAgent + " " + s.UserAgent`) 719 pn("}\n") 720 721 for _, res := range a.doc.Resources { 722 a.generateResource(res) 723 } 724 725 a.responseTypes = make(map[string]bool) 726 for _, meth := range a.APIMethods() { 727 meth.cacheResponseTypes(a) 728 } 729 for _, res := range a.doc.Resources { 730 a.cacheResourceResponseTypes(res) 731 } 732 733 for _, name := range a.sortedSchemaNames() { 734 a.schemas[name].writeSchemaCode(a) 735 } 736 737 for _, meth := range a.APIMethods() { 738 meth.generateCode() 739 } 740 741 for _, res := range a.doc.Resources { 742 a.generateResourceMethods(res) 743 } 744 745 clean, err := format.Source(buf.Bytes()) 746 if err != nil { 747 return buf.Bytes(), err 748 } 749 return clean, nil 750} 751 752func (a *API) generateScopeConstants() { 753 scopes := a.doc.Auth.OAuth2Scopes 754 if len(scopes) == 0 { 755 return 756 } 757 758 a.pn("// OAuth2 scopes used by this API.") 759 a.pn("const (") 760 n := 0 761 for _, scope := range scopes { 762 if n > 0 { 763 a.p("\n") 764 } 765 n++ 766 ident := scopeIdentifierFromURL(scope.URL) 767 if scope.Description != "" { 768 a.p("%s", asComment("\t", scope.Description)) 769 } 770 a.pn("\t%s = %q", ident, scope.URL) 771 } 772 a.p(")\n\n") 773} 774 775func scopeIdentifierFromURL(urlStr string) string { 776 const prefix = "https://www.googleapis.com/auth/" 777 if !strings.HasPrefix(urlStr, prefix) { 778 const https = "https://" 779 if !strings.HasPrefix(urlStr, https) { 780 log.Fatalf("Unexpected oauth2 scope %q doesn't start with %q", urlStr, https) 781 } 782 ident := validGoIdentifer(depunct(urlStr[len(https):], true)) + "Scope" 783 return ident 784 } 785 ident := validGoIdentifer(initialCap(urlStr[len(prefix):])) + "Scope" 786 return ident 787} 788 789// Schema is a disco.Schema that has been bestowed an identifier, whether by 790// having an "id" field at the top of the schema or with an 791// automatically generated one in populateSubSchemas. 792// 793// TODO: While sub-types shouldn't need to be promoted to schemas, 794// API.GenerateCode iterates over API.schemas to figure out what 795// top-level Go types to write. These should be separate concerns. 796type Schema struct { 797 api *API 798 799 typ *disco.Schema 800 801 apiName string // the native API-defined name of this type 802 goName string // lazily populated by GoName 803 goReturnType string // lazily populated by GoReturnType 804 props []*Property 805} 806 807type Property struct { 808 s *Schema // the containing Schema 809 p *disco.Property 810 assignedGoName string 811} 812 813func (p *Property) Type() *disco.Schema { 814 return p.p.Schema 815} 816 817func (p *Property) GoName() string { 818 return initialCap(p.p.Name) 819} 820 821func (p *Property) Default() string { 822 return p.p.Schema.Default 823} 824 825func (p *Property) Description() string { 826 return p.p.Schema.Description 827} 828 829func (p *Property) Enum() ([]string, bool) { 830 typ := p.p.Schema 831 if typ.Enums != nil { 832 return typ.Enums, true 833 } 834 // Check if this has an array of string enums. 835 if typ.ItemSchema != nil { 836 if enums := typ.ItemSchema.Enums; enums != nil && typ.ItemSchema.Type == "string" { 837 return enums, true 838 } 839 } 840 return nil, false 841} 842 843func (p *Property) EnumDescriptions() []string { 844 if desc := p.p.Schema.EnumDescriptions; desc != nil { 845 return desc 846 } 847 // Check if this has an array of string enum descriptions. 848 if items := p.p.Schema.ItemSchema; items != nil { 849 if desc := items.EnumDescriptions; desc != nil { 850 return desc 851 } 852 } 853 return nil 854} 855 856func (p *Property) Pattern() (string, bool) { 857 return p.p.Schema.Pattern, (p.p.Schema.Pattern != "") 858} 859 860func (p *Property) TypeAsGo() string { 861 return p.s.api.typeAsGo(p.Type(), false) 862} 863 864// A FieldName uniquely identifies a field within a Schema struct for an API. 865type fieldName struct { 866 api string // The ID of an API. 867 schema string // The Go name of a Schema struct. 868 field string // The Go name of a field. 869} 870 871// pointerFields is a list of fields that should use a pointer type. 872// This makes it possible to distinguish between a field being unset vs having 873// an empty value. 874var pointerFields = []fieldName{ 875 {api: "androidpublisher:v1.1", schema: "InappPurchase", field: "PurchaseType"}, 876 {api: "androidpublisher:v2", schema: "ProductPurchase", field: "PurchaseType"}, 877 {api: "androidpublisher:v3", schema: "ProductPurchase", field: "PurchaseType"}, 878 {api: "androidpublisher:v2", schema: "SubscriptionPurchase", field: "CancelReason"}, 879 {api: "androidpublisher:v2", schema: "SubscriptionPurchase", field: "PaymentState"}, 880 {api: "androidpublisher:v2", schema: "SubscriptionPurchase", field: "PurchaseType"}, 881 {api: "androidpublisher:v3", schema: "SubscriptionPurchase", field: "PurchaseType"}, 882 {api: "cloudmonitoring:v2beta2", schema: "Point", field: "BoolValue"}, 883 {api: "cloudmonitoring:v2beta2", schema: "Point", field: "DoubleValue"}, 884 {api: "cloudmonitoring:v2beta2", schema: "Point", field: "Int64Value"}, 885 {api: "cloudmonitoring:v2beta2", schema: "Point", field: "StringValue"}, 886 {api: "compute:alpha", schema: "Scheduling", field: "AutomaticRestart"}, 887 {api: "compute:beta", schema: "MetadataItems", field: "Value"}, 888 {api: "compute:beta", schema: "Scheduling", field: "AutomaticRestart"}, 889 {api: "compute:v1", schema: "MetadataItems", field: "Value"}, 890 {api: "compute:v1", schema: "Scheduling", field: "AutomaticRestart"}, 891 {api: "content:v2", schema: "AccountUser", field: "Admin"}, 892 {api: "datastore:v1beta2", schema: "Property", field: "BlobKeyValue"}, 893 {api: "datastore:v1beta2", schema: "Property", field: "BlobValue"}, 894 {api: "datastore:v1beta2", schema: "Property", field: "BooleanValue"}, 895 {api: "datastore:v1beta2", schema: "Property", field: "DateTimeValue"}, 896 {api: "datastore:v1beta2", schema: "Property", field: "DoubleValue"}, 897 {api: "datastore:v1beta2", schema: "Property", field: "Indexed"}, 898 {api: "datastore:v1beta2", schema: "Property", field: "IntegerValue"}, 899 {api: "datastore:v1beta2", schema: "Property", field: "StringValue"}, 900 {api: "datastore:v1beta3", schema: "Value", field: "BlobValue"}, 901 {api: "datastore:v1beta3", schema: "Value", field: "BooleanValue"}, 902 {api: "datastore:v1beta3", schema: "Value", field: "DoubleValue"}, 903 {api: "datastore:v1beta3", schema: "Value", field: "IntegerValue"}, 904 {api: "datastore:v1beta3", schema: "Value", field: "StringValue"}, 905 {api: "datastore:v1beta3", schema: "Value", field: "TimestampValue"}, 906 {api: "genomics:v1beta2", schema: "Dataset", field: "IsPublic"}, 907 {api: "monitoring:v3", schema: "TypedValue", field: "BoolValue"}, 908 {api: "monitoring:v3", schema: "TypedValue", field: "DoubleValue"}, 909 {api: "monitoring:v3", schema: "TypedValue", field: "Int64Value"}, 910 {api: "monitoring:v3", schema: "TypedValue", field: "StringValue"}, 911 {api: "servicecontrol:v1", schema: "MetricValue", field: "BoolValue"}, 912 {api: "servicecontrol:v1", schema: "MetricValue", field: "DoubleValue"}, 913 {api: "servicecontrol:v1", schema: "MetricValue", field: "Int64Value"}, 914 {api: "servicecontrol:v1", schema: "MetricValue", field: "StringValue"}, 915 {api: "sqladmin:v1beta4", schema: "Settings", field: "StorageAutoResize"}, 916 {api: "storage:v1", schema: "BucketLifecycleRuleCondition", field: "IsLive"}, 917 {api: "storage:v1beta2", schema: "BucketLifecycleRuleCondition", field: "IsLive"}, 918 {api: "tasks:v1", schema: "Task", field: "Completed"}, 919 {api: "youtube:v3", schema: "ChannelSectionSnippet", field: "Position"}, 920} 921 922// forcePointerType reports whether p should be represented as a pointer type in its parent schema struct. 923func (p *Property) forcePointerType() bool { 924 if p.UnfortunateDefault() { 925 return true 926 } 927 928 name := fieldName{api: p.s.api.ID, schema: p.s.GoName(), field: p.GoName()} 929 for _, pf := range pointerFields { 930 if pf == name { 931 return true 932 } 933 } 934 return false 935} 936 937// UnfortunateDefault reports whether p may be set to a zero value, but has a non-zero default. 938func (p *Property) UnfortunateDefault() bool { 939 switch p.TypeAsGo() { 940 default: 941 return false 942 943 case "bool": 944 return p.Default() == "true" 945 946 case "string": 947 if p.Default() == "" { 948 return false 949 } 950 // String fields are considered to "allow" a zero value if either: 951 // (a) they are an enum, and one of the permitted enum values is the empty string, or 952 // (b) they have a validation pattern which matches the empty string. 953 pattern, hasPat := p.Pattern() 954 enum, hasEnum := p.Enum() 955 if hasPat && hasEnum { 956 log.Printf("Encountered enum property which also has a pattern: %#v", p) 957 return false // don't know how to handle this, so ignore. 958 } 959 return (hasPat && emptyPattern(pattern)) || 960 (hasEnum && emptyEnum(enum)) 961 962 case "float64", "int64", "uint64", "int32", "uint32": 963 if p.Default() == "" { 964 return false 965 } 966 if f, err := strconv.ParseFloat(p.Default(), 64); err == nil { 967 return f != 0.0 968 } 969 // The default value has an unexpected form. Whatever it is, it's non-zero. 970 return true 971 } 972} 973 974// emptyPattern reports whether a pattern matches the empty string. 975func emptyPattern(pattern string) bool { 976 if re, err := regexp.Compile(pattern); err == nil { 977 return re.MatchString("") 978 } 979 log.Printf("Encountered bad pattern: %s", pattern) 980 return false 981} 982 983// emptyEnum reports whether a property enum list contains the empty string. 984func emptyEnum(enum []string) bool { 985 for _, val := range enum { 986 if val == "" { 987 return true 988 } 989 } 990 return false 991} 992 993func (a *API) typeAsGo(s *disco.Schema, elidePointers bool) string { 994 switch s.Kind { 995 case disco.SimpleKind: 996 return mustSimpleTypeConvert(s.Type, s.Format) 997 case disco.ArrayKind: 998 as := s.ElementSchema() 999 if as.Type == "string" { 1000 switch as.Format { 1001 case "int64": 1002 return "googleapi.Int64s" 1003 case "uint64": 1004 return "googleapi.Uint64s" 1005 case "int32": 1006 return "googleapi.Int32s" 1007 case "uint32": 1008 return "googleapi.Uint32s" 1009 case "float64": 1010 return "googleapi.Float64s" 1011 } 1012 } 1013 return "[]" + a.typeAsGo(as, elidePointers) 1014 case disco.ReferenceKind: 1015 rs := s.RefSchema 1016 if rs.Kind == disco.SimpleKind { 1017 // Simple top-level schemas get named types (see writeSchemaCode). 1018 // Use the name instead of using the equivalent simple Go type. 1019 return a.schemaNamed(rs.Name).GoName() 1020 } 1021 return a.typeAsGo(rs, elidePointers) 1022 case disco.MapKind: 1023 es := s.ElementSchema() 1024 if es.Type == "string" { 1025 // If the element schema has a type "string", it's going to be 1026 // transmitted as a string, and the Go map type must reflect that. 1027 // This is true even if the format is, say, "int64". When type = 1028 // "string" and format = "int64" at top level, we can use the json 1029 // "string" tag option to unmarshal the string to an int64, but 1030 // inside a map we can't. 1031 return "map[string]string" 1032 } 1033 // Due to historical baggage (maps used to be a separate code path), 1034 // the element types of maps never have pointers in them. From this 1035 // level down, elide pointers in types. 1036 return "map[string]" + a.typeAsGo(es, true) 1037 case disco.AnyStructKind: 1038 return "googleapi.RawMessage" 1039 case disco.StructKind: 1040 tls := a.schemaNamed(s.Name) 1041 if elidePointers || s.Variant != nil { 1042 return tls.GoName() 1043 } 1044 return "*" + tls.GoName() 1045 default: 1046 panic(fmt.Sprintf("unhandled typeAsGo for %+v", s)) 1047 } 1048} 1049 1050func (a *API) schemaNamed(name string) *Schema { 1051 s := a.schemas[name] 1052 if s == nil { 1053 panicf("no top-level schema named %q", name) 1054 } 1055 return s 1056} 1057 1058func (s *Schema) properties() []*Property { 1059 if s.props != nil { 1060 return s.props 1061 } 1062 if s.typ.Kind != disco.StructKind { 1063 panic("called properties on non-object schema") 1064 } 1065 for _, p := range s.typ.Properties { 1066 s.props = append(s.props, &Property{ 1067 s: s, 1068 p: p, 1069 }) 1070 } 1071 return s.props 1072} 1073 1074func (s *Schema) HasContentType() bool { 1075 for _, p := range s.properties() { 1076 if p.GoName() == "ContentType" && p.TypeAsGo() == "string" { 1077 return true 1078 } 1079 } 1080 return false 1081} 1082 1083func (s *Schema) populateSubSchemas() (outerr error) { 1084 defer func() { 1085 r := recover() 1086 if r == nil { 1087 return 1088 } 1089 outerr = fmt.Errorf("%v", r) 1090 }() 1091 1092 addSubStruct := func(subApiName string, t *disco.Schema) { 1093 if s.api.schemas[subApiName] != nil { 1094 panic("dup schema apiName: " + subApiName) 1095 } 1096 if t.Name != "" { 1097 panic("subtype already has name: " + t.Name) 1098 } 1099 t.Name = subApiName 1100 subs := &Schema{ 1101 api: s.api, 1102 typ: t, 1103 apiName: subApiName, 1104 } 1105 s.api.schemas[subApiName] = subs 1106 err := subs.populateSubSchemas() 1107 if err != nil { 1108 panicf("in sub-struct %q: %v", subApiName, err) 1109 } 1110 } 1111 1112 switch s.typ.Kind { 1113 case disco.StructKind: 1114 for _, p := range s.properties() { 1115 subApiName := fmt.Sprintf("%s.%s", s.apiName, p.p.Name) 1116 switch p.Type().Kind { 1117 case disco.SimpleKind, disco.ReferenceKind, disco.AnyStructKind: 1118 // Do nothing. 1119 case disco.MapKind: 1120 mt := p.Type().ElementSchema() 1121 if mt.Kind == disco.SimpleKind || mt.Kind == disco.ReferenceKind { 1122 continue 1123 } 1124 addSubStruct(subApiName, mt) 1125 case disco.ArrayKind: 1126 at := p.Type().ElementSchema() 1127 if at.Kind == disco.SimpleKind || at.Kind == disco.ReferenceKind { 1128 continue 1129 } 1130 addSubStruct(subApiName, at) 1131 case disco.StructKind: 1132 addSubStruct(subApiName, p.Type()) 1133 default: 1134 panicf("Unknown type for %q: %v", subApiName, p.Type()) 1135 } 1136 } 1137 case disco.ArrayKind: 1138 subApiName := fmt.Sprintf("%s.Item", s.apiName) 1139 switch at := s.typ.ElementSchema(); at.Kind { 1140 case disco.SimpleKind, disco.ReferenceKind, disco.AnyStructKind: 1141 // Do nothing. 1142 case disco.MapKind: 1143 mt := at.ElementSchema() 1144 if k := mt.Kind; k != disco.SimpleKind && k != disco.ReferenceKind { 1145 addSubStruct(subApiName, mt) 1146 } 1147 case disco.ArrayKind: 1148 at := at.ElementSchema() 1149 if k := at.Kind; k != disco.SimpleKind && k != disco.ReferenceKind { 1150 addSubStruct(subApiName, at) 1151 } 1152 case disco.StructKind: 1153 addSubStruct(subApiName, at) 1154 default: 1155 panicf("Unknown array type for %q: %v", subApiName, at) 1156 } 1157 case disco.AnyStructKind, disco.MapKind, disco.SimpleKind, disco.ReferenceKind: 1158 // Do nothing. 1159 default: 1160 fmt.Fprintf(os.Stderr, "in populateSubSchemas, schema is: %v", s.typ) 1161 panicf("populateSubSchemas: unsupported type for schema %q", s.apiName) 1162 panic("unreachable") 1163 } 1164 return nil 1165} 1166 1167// GoName returns (or creates and returns) the bare Go name 1168// of the apiName, making sure that it's a proper Go identifier 1169// and doesn't conflict with an existing name. 1170func (s *Schema) GoName() string { 1171 if s.goName == "" { 1172 if s.typ.Kind == disco.MapKind { 1173 s.goName = s.api.typeAsGo(s.typ, false) 1174 } else { 1175 base := initialCap(s.apiName) 1176 s.goName = s.api.GetName(base) 1177 if base == "Service" && s.goName != "Service" { 1178 // Detect the case where a resource is going to clash with the 1179 // root service object. 1180 panicf("Clash on name Service") 1181 } 1182 } 1183 } 1184 return s.goName 1185} 1186 1187// GoReturnType returns the Go type to use as the return type. 1188// If a type is a struct, it will return *StructType, 1189// for a map it will return map[string]ValueType, 1190// for (not yet supported) slices it will return []ValueType. 1191func (s *Schema) GoReturnType() string { 1192 if s.goReturnType == "" { 1193 if s.typ.Kind == disco.MapKind { 1194 s.goReturnType = s.GoName() 1195 } else { 1196 s.goReturnType = "*" + s.GoName() 1197 } 1198 } 1199 return s.goReturnType 1200} 1201 1202func (s *Schema) writeSchemaCode(api *API) { 1203 switch s.typ.Kind { 1204 case disco.SimpleKind: 1205 apitype := s.typ.Type 1206 typ := mustSimpleTypeConvert(apitype, s.typ.Format) 1207 s.api.pn("\ntype %s %s", s.GoName(), typ) 1208 case disco.StructKind: 1209 s.writeSchemaStruct(api) 1210 case disco.MapKind, disco.AnyStructKind: 1211 // Do nothing. 1212 case disco.ArrayKind: 1213 log.Printf("TODO writeSchemaCode for arrays for %s", s.GoName()) 1214 default: 1215 fmt.Fprintf(os.Stderr, "in writeSchemaCode, schema is: %+v", s.typ) 1216 panicf("writeSchemaCode: unsupported type for schema %q", s.apiName) 1217 } 1218} 1219 1220func (s *Schema) writeVariant(api *API, v *disco.Variant) { 1221 s.api.p("\ntype %s map[string]interface{}\n\n", s.GoName()) 1222 1223 // Write out the "Type" method that identifies the variant type. 1224 s.api.pn("func (t %s) Type() string {", s.GoName()) 1225 s.api.pn(" return googleapi.VariantType(t)") 1226 s.api.p("}\n\n") 1227 1228 // Write out helper methods to convert each possible variant. 1229 for _, m := range v.Map { 1230 if m.TypeValue == "" && m.Ref == "" { 1231 log.Printf("TODO variant %s ref %s not yet supported.", m.TypeValue, m.Ref) 1232 continue 1233 } 1234 1235 s.api.pn("func (t %s) %s() (r %s, ok bool) {", s.GoName(), initialCap(m.TypeValue), m.Ref) 1236 s.api.pn(" if t.Type() != %q {", initialCap(m.TypeValue)) 1237 s.api.pn(" return r, false") 1238 s.api.pn(" }") 1239 s.api.pn(" ok = googleapi.ConvertVariant(map[string]interface{}(t), &r)") 1240 s.api.pn(" return r, ok") 1241 s.api.p("}\n\n") 1242 } 1243} 1244 1245func (s *Schema) Description() string { 1246 return s.typ.Description 1247} 1248 1249func (s *Schema) writeSchemaStruct(api *API) { 1250 if v := s.typ.Variant; v != nil { 1251 s.writeVariant(api, v) 1252 return 1253 } 1254 s.api.p("\n") 1255 des := s.Description() 1256 if des != "" { 1257 s.api.p("%s", asComment("", fmt.Sprintf("%s: %s", s.GoName(), des))) 1258 } 1259 s.api.pn("type %s struct {", s.GoName()) 1260 1261 np := new(namePool) 1262 forceSendName := np.Get("ForceSendFields") 1263 nullFieldsName := np.Get("NullFields") 1264 if s.isResponseType() { 1265 np.Get("ServerResponse") // reserve the name 1266 } 1267 1268 firstFieldName := "" // used to store a struct field name for use in documentation. 1269 for i, p := range s.properties() { 1270 if i > 0 { 1271 s.api.p("\n") 1272 } 1273 pname := np.Get(p.GoName()) 1274 if pname[0] == '@' { 1275 // HACK(cbro): ignore JSON-LD special fields until we can figure out 1276 // the correct Go representation for them. 1277 continue 1278 } 1279 p.assignedGoName = pname 1280 des := p.Description() 1281 if des != "" { 1282 s.api.p("%s", asComment("\t", fmt.Sprintf("%s: %s", pname, des))) 1283 } 1284 addFieldValueComments(s.api.p, p, "\t", des != "") 1285 1286 var extraOpt string 1287 if p.Type().IsIntAsString() { 1288 extraOpt += ",string" 1289 } 1290 1291 typ := p.TypeAsGo() 1292 if p.forcePointerType() { 1293 typ = "*" + typ 1294 } 1295 1296 s.api.pn(" %s %s `json:\"%s,omitempty%s\"`", pname, typ, p.p.Name, extraOpt) 1297 if firstFieldName == "" { 1298 firstFieldName = pname 1299 } 1300 } 1301 1302 if s.isResponseType() { 1303 if firstFieldName != "" { 1304 s.api.p("\n") 1305 } 1306 s.api.p("%s", asComment("\t", "ServerResponse contains the HTTP response code and headers from the server.")) 1307 s.api.pn(" googleapi.ServerResponse `json:\"-\"`") 1308 } 1309 1310 if firstFieldName == "" { 1311 // There were no fields in the struct, so there is no point 1312 // adding any custom JSON marshaling code. 1313 s.api.pn("}") 1314 return 1315 } 1316 1317 commentFmtStr := "%s is a list of field names (e.g. %q) to " + 1318 "unconditionally include in API requests. By default, fields " + 1319 "with empty values are omitted from API requests. However, " + 1320 "any non-pointer, non-interface field appearing in %s will " + 1321 "be sent to the server regardless of whether the field is " + 1322 "empty or not. This may be used to include empty fields in " + 1323 "Patch requests." 1324 comment := fmt.Sprintf(commentFmtStr, forceSendName, firstFieldName, forceSendName) 1325 s.api.p("\n") 1326 s.api.p("%s", asComment("\t", comment)) 1327 1328 s.api.pn("\t%s []string `json:\"-\"`", forceSendName) 1329 1330 commentFmtStr = "%s is a list of field names (e.g. %q) to " + 1331 "include in API requests with the JSON null value. " + 1332 "By default, fields with empty values are omitted from API requests. However, " + 1333 "any field with an empty value appearing in %s will be sent to the server as null. " + 1334 "It is an error if a field in this list has a non-empty value. This may be used to " + 1335 "include null fields in Patch requests." 1336 comment = fmt.Sprintf(commentFmtStr, nullFieldsName, firstFieldName, nullFieldsName) 1337 s.api.p("\n") 1338 s.api.p("%s", asComment("\t", comment)) 1339 1340 s.api.pn("\t%s []string `json:\"-\"`", nullFieldsName) 1341 1342 s.api.pn("}") 1343 s.writeSchemaMarshal(forceSendName, nullFieldsName) 1344 s.writeSchemaUnmarshal() 1345} 1346 1347// writeSchemaMarshal writes a custom MarshalJSON function for s, which allows 1348// fields to be explicitly transmitted by listing them in the field identified 1349// by forceSendFieldName, and allows fields to be transmitted with the null value 1350// by listing them in the field identified by nullFieldsName. 1351func (s *Schema) writeSchemaMarshal(forceSendFieldName, nullFieldsName string) { 1352 s.api.pn("func (s *%s) MarshalJSON() ([]byte, error) {", s.GoName()) 1353 s.api.pn("\ttype NoMethod %s", s.GoName()) 1354 // pass schema as methodless type to prevent subsequent calls to MarshalJSON from recursing indefinitely. 1355 s.api.pn("\traw := NoMethod(*s)") 1356 s.api.pn("\treturn gensupport.MarshalJSON(raw, s.%s, s.%s)", forceSendFieldName, nullFieldsName) 1357 s.api.pn("}") 1358} 1359 1360func (s *Schema) writeSchemaUnmarshal() { 1361 var floatProps []*Property 1362 for _, p := range s.properties() { 1363 if p.p.Schema.Type == "number" { 1364 floatProps = append(floatProps, p) 1365 } 1366 } 1367 if len(floatProps) == 0 { 1368 return 1369 } 1370 pn := s.api.pn 1371 pn("\nfunc (s *%s) UnmarshalJSON(data []byte) error {", s.GoName()) 1372 pn(" type NoMethod %s", s.GoName()) // avoid infinite recursion 1373 pn(" var s1 struct {") 1374 // Hide the float64 fields of the schema with fields that correctly 1375 // unmarshal special values. 1376 for _, p := range floatProps { 1377 typ := "gensupport.JSONFloat64" 1378 if p.forcePointerType() { 1379 typ = "*" + typ 1380 } 1381 pn("%s %s `json:\"%s\"`", p.assignedGoName, typ, p.p.Name) 1382 } 1383 pn(" *NoMethod") // embed the schema 1384 pn(" }") 1385 // Set the schema value into the wrapper so its other fields are unmarshaled. 1386 pn(" s1.NoMethod = (*NoMethod)(s)") 1387 pn(" if err := json.Unmarshal(data, &s1); err != nil {") 1388 pn(" return err") 1389 pn(" }") 1390 // Copy each shadowing field into the field it shadows. 1391 for _, p := range floatProps { 1392 n := p.assignedGoName 1393 if p.forcePointerType() { 1394 pn("if s1.%s != nil { s.%s = (*float64)(s1.%s) }", n, n, n) 1395 } else { 1396 pn("s.%s = float64(s1.%s)", n, n) 1397 } 1398 } 1399 pn(" return nil") 1400 pn("}") 1401} 1402 1403// isResponseType returns true for all types that are used as a response. 1404func (s *Schema) isResponseType() bool { 1405 return s.api.responseTypes["*"+s.goName] 1406} 1407 1408// PopulateSchemas reads all the API types ("schemas") from the JSON file 1409// and converts them to *Schema instances, returning an identically 1410// keyed map, additionally containing subresources. For instance, 1411// 1412// A resource "Foo" of type "object" with a property "bar", also of type 1413// "object" (an anonymous sub-resource), will get a synthetic API name 1414// of "Foo.bar". 1415// 1416// A resource "Foo" of type "array" with an "items" of type "object" 1417// will get a synthetic API name of "Foo.Item". 1418func (a *API) PopulateSchemas() { 1419 if a.schemas != nil { 1420 panic("") 1421 } 1422 a.schemas = make(map[string]*Schema) 1423 for name, ds := range a.doc.Schemas { 1424 s := &Schema{ 1425 api: a, 1426 apiName: name, 1427 typ: ds, 1428 } 1429 a.schemas[name] = s 1430 err := s.populateSubSchemas() 1431 if err != nil { 1432 panicf("Error populating schema with API name %q: %v", name, err) 1433 } 1434 } 1435} 1436 1437func (a *API) generateResource(r *disco.Resource) { 1438 pn := a.pn 1439 t := resourceGoType(r) 1440 pn(fmt.Sprintf("func New%s(s *%s) *%s {", t, a.ServiceType(), t)) 1441 pn("rs := &%s{s : s}", t) 1442 for _, res := range r.Resources { 1443 pn("rs.%s = New%s(s)", resourceGoField(res, r), resourceGoType(res)) 1444 } 1445 pn("return rs") 1446 pn("}") 1447 1448 pn("\ntype %s struct {", t) 1449 pn(" s *%s", a.ServiceType()) 1450 for _, res := range r.Resources { 1451 pn("\n\t%s\t*%s", resourceGoField(res, r), resourceGoType(res)) 1452 } 1453 pn("}") 1454 1455 for _, res := range r.Resources { 1456 a.generateResource(res) 1457 } 1458} 1459 1460func (a *API) cacheResourceResponseTypes(r *disco.Resource) { 1461 for _, meth := range a.resourceMethods(r) { 1462 meth.cacheResponseTypes(a) 1463 } 1464 for _, res := range r.Resources { 1465 a.cacheResourceResponseTypes(res) 1466 } 1467} 1468 1469func (a *API) generateResourceMethods(r *disco.Resource) { 1470 for _, meth := range a.resourceMethods(r) { 1471 meth.generateCode() 1472 } 1473 for _, res := range r.Resources { 1474 a.generateResourceMethods(res) 1475 } 1476} 1477 1478func resourceGoField(r, parent *disco.Resource) string { 1479 // Avoid conflicts with method names. 1480 und := "" 1481 if parent != nil { 1482 for _, m := range parent.Methods { 1483 if m.Name == r.Name { 1484 und = "_" 1485 break 1486 } 1487 } 1488 } 1489 // Note: initialCap(r.Name + "_") doesn't work because initialCap calls depunct. 1490 return initialCap(r.Name) + und 1491} 1492 1493func resourceGoType(r *disco.Resource) string { 1494 return initialCap(r.FullName + "Service") 1495} 1496 1497func (a *API) resourceMethods(r *disco.Resource) []*Method { 1498 ms := []*Method{} 1499 for _, m := range r.Methods { 1500 ms = append(ms, &Method{ 1501 api: a, 1502 r: r, 1503 m: m, 1504 }) 1505 } 1506 return ms 1507} 1508 1509type Method struct { 1510 api *API 1511 r *disco.Resource // or nil if a API-level (top-level) method 1512 m *disco.Method 1513 1514 params []*Param // all Params, of each type, lazily set by first call of Params method. 1515} 1516 1517func (m *Method) Id() string { 1518 return m.m.ID 1519} 1520 1521func (m *Method) responseType() *Schema { 1522 return m.api.schemas[m.m.Response.RefSchema.Name] 1523} 1524 1525func (m *Method) supportsMediaUpload() bool { 1526 return m.m.MediaUpload != nil 1527} 1528 1529func (m *Method) mediaUploadPath() string { 1530 return m.m.MediaUpload.Protocols["simple"].Path 1531} 1532 1533func (m *Method) supportsMediaDownload() bool { 1534 if m.supportsMediaUpload() { 1535 // storage.objects.insert claims support for download in 1536 // addition to upload but attempting to do so fails. 1537 // This situation doesn't apply to any other methods. 1538 return false 1539 } 1540 return m.m.SupportsMediaDownload 1541} 1542 1543func (m *Method) supportsPaging() (*pageTokenGenerator, string, bool) { 1544 ptg := m.pageTokenGenerator() 1545 if ptg == nil { 1546 return nil, "", false 1547 } 1548 1549 // Check that the response type has the next page token. 1550 s := m.responseType() 1551 if s == nil || s.typ.Kind != disco.StructKind { 1552 return nil, "", false 1553 } 1554 for _, prop := range s.properties() { 1555 if isPageTokenName(prop.p.Name) && prop.Type().Type == "string" { 1556 return ptg, prop.GoName(), true 1557 } 1558 } 1559 1560 return nil, "", false 1561} 1562 1563type pageTokenGenerator struct { 1564 isParam bool // is the page token a URL parameter? 1565 name string // param or request field name 1566 requestName string // empty for URL param 1567} 1568 1569func (p *pageTokenGenerator) genGet() string { 1570 if p.isParam { 1571 return fmt.Sprintf("c.urlParams_.Get(%q)", p.name) 1572 } 1573 return fmt.Sprintf("c.%s.%s", p.requestName, p.name) 1574} 1575 1576func (p *pageTokenGenerator) genSet(valueExpr string) string { 1577 if p.isParam { 1578 return fmt.Sprintf("c.%s(%s)", initialCap(p.name), valueExpr) 1579 } 1580 return fmt.Sprintf("c.%s.%s = %s", p.requestName, p.name, valueExpr) 1581} 1582 1583func (p *pageTokenGenerator) genDeferBody() string { 1584 if p.isParam { 1585 return p.genSet(p.genGet()) 1586 } 1587 return fmt.Sprintf("func (pt string) { %s }(%s)", p.genSet("pt"), p.genGet()) 1588} 1589 1590// pageTokenGenerator returns a pageTokenGenerator that will generate code to 1591// get/set the page token for a subsequent page in the context of the generated 1592// Pages method. It returns nil if there is no page token. 1593func (m *Method) pageTokenGenerator() *pageTokenGenerator { 1594 matches := m.grepParams(func(p *Param) bool { return isPageTokenName(p.p.Name) }) 1595 switch len(matches) { 1596 case 1: 1597 if matches[0].p.Required { 1598 // The page token is a required parameter (e.g. because there is 1599 // a separate API call to start an iteration), and so the relevant 1600 // call factory method takes the page token instead. 1601 return nil 1602 } 1603 n := matches[0].p.Name 1604 return &pageTokenGenerator{true, n, ""} 1605 1606 case 0: // No URL parameter, but maybe a request field. 1607 if m.m.Request == nil { 1608 return nil 1609 } 1610 rs := m.m.Request 1611 if rs.RefSchema != nil { 1612 rs = rs.RefSchema 1613 } 1614 for _, p := range rs.Properties { 1615 if isPageTokenName(p.Name) { 1616 return &pageTokenGenerator{false, initialCap(p.Name), validGoIdentifer(strings.ToLower(rs.Name))} 1617 } 1618 } 1619 return nil 1620 1621 default: 1622 panicf("too many page token parameters for method %s", m.m.Name) 1623 return nil 1624 } 1625} 1626 1627func isPageTokenName(s string) bool { 1628 return s == "pageToken" || s == "nextPageToken" 1629} 1630 1631func (m *Method) Params() []*Param { 1632 if m.params == nil { 1633 for _, p := range m.m.Parameters { 1634 m.params = append(m.params, &Param{ 1635 method: m, 1636 p: p, 1637 }) 1638 } 1639 } 1640 return m.params 1641} 1642 1643func (m *Method) grepParams(f func(*Param) bool) []*Param { 1644 matches := make([]*Param, 0) 1645 for _, param := range m.Params() { 1646 if f(param) { 1647 matches = append(matches, param) 1648 } 1649 } 1650 return matches 1651} 1652 1653func (m *Method) NamedParam(name string) *Param { 1654 matches := m.grepParams(func(p *Param) bool { 1655 return p.p.Name == name 1656 }) 1657 if len(matches) < 1 { 1658 log.Panicf("failed to find named parameter %q", name) 1659 } 1660 if len(matches) > 1 { 1661 log.Panicf("found multiple parameters for parameter name %q", name) 1662 } 1663 return matches[0] 1664} 1665 1666func (m *Method) OptParams() []*Param { 1667 return m.grepParams(func(p *Param) bool { 1668 return !p.p.Required 1669 }) 1670} 1671 1672func (meth *Method) cacheResponseTypes(api *API) { 1673 if retType := responseType(api, meth.m); retType != "" && strings.HasPrefix(retType, "*") { 1674 api.responseTypes[retType] = true 1675 } 1676} 1677 1678// convertMultiParams builds a []string temp variable from a slice 1679// of non-strings and returns the name of the temp variable. 1680func convertMultiParams(a *API, param string) string { 1681 a.pn(" var %v_ []string", param) 1682 a.pn(" for _, v := range %v {", param) 1683 a.pn(" %v_ = append(%v_, fmt.Sprint(v))", param, param) 1684 a.pn(" }") 1685 return param + "_" 1686} 1687 1688func (meth *Method) generateCode() { 1689 res := meth.r // may be nil if a top-level method 1690 a := meth.api 1691 p, pn := a.p, a.pn 1692 1693 pn("\n// method id %q:", meth.Id()) 1694 1695 retType := responseType(a, meth.m) 1696 if meth.IsRawResponse() { 1697 retType = "*http.Response" 1698 } 1699 retTypeComma := retType 1700 if retTypeComma != "" { 1701 retTypeComma += ", " 1702 } 1703 1704 args := meth.NewArguments() 1705 methodName := initialCap(meth.m.Name) 1706 prefix := "" 1707 if res != nil { 1708 prefix = initialCap(res.FullName) 1709 } 1710 callName := a.GetName(prefix + methodName + "Call") 1711 1712 pn("\ntype %s struct {", callName) 1713 pn(" s *%s", a.ServiceType()) 1714 for _, arg := range args.l { 1715 if arg.location != "query" { 1716 pn(" %s %s", arg.goname, arg.gotype) 1717 } 1718 } 1719 pn(" urlParams_ gensupport.URLParams") 1720 httpMethod := meth.m.HTTPMethod 1721 if httpMethod == "GET" { 1722 pn(" ifNoneMatch_ string") 1723 } 1724 1725 if meth.supportsMediaUpload() { 1726 pn(" mediaInfo_ *gensupport.MediaInfo") 1727 } 1728 pn(" ctx_ context.Context") 1729 pn(" header_ http.Header") 1730 pn("}") 1731 1732 p("\n%s", asComment("", methodName+": "+meth.m.Description)) 1733 if res != nil { 1734 if url := canonicalDocsURL[fmt.Sprintf("%v%v/%v", docsLink, res.Name, meth.m.Name)]; url != "" { 1735 pn("// For details, see %v", url) 1736 } 1737 } 1738 1739 var servicePtr string 1740 if res == nil { 1741 pn("func (s *Service) %s(%s) *%s {", methodName, args, callName) 1742 servicePtr = "s" 1743 } else { 1744 pn("func (r *%s) %s(%s) *%s {", resourceGoType(res), methodName, args, callName) 1745 servicePtr = "r.s" 1746 } 1747 1748 pn(" c := &%s{s: %s, urlParams_: make(gensupport.URLParams)}", callName, servicePtr) 1749 for _, arg := range args.l { 1750 // TODO(gmlewis): clean up and consolidate this section. 1751 // See: https://code-review.googlesource.com/#/c/3520/18/google-api-go-generator/gen.go 1752 if arg.location == "query" { 1753 switch arg.gotype { 1754 case "[]string": 1755 pn(" c.urlParams_.SetMulti(%q, append([]string{}, %v...))", arg.apiname, arg.goname) 1756 case "string": 1757 pn(" c.urlParams_.Set(%q, %v)", arg.apiname, arg.goname) 1758 default: 1759 if strings.HasPrefix(arg.gotype, "[]") { 1760 tmpVar := convertMultiParams(a, arg.goname) 1761 pn(" c.urlParams_.SetMulti(%q, %v)", arg.apiname, tmpVar) 1762 } else { 1763 pn(" c.urlParams_.Set(%q, fmt.Sprint(%v))", arg.apiname, arg.goname) 1764 } 1765 } 1766 continue 1767 } 1768 if arg.gotype == "[]string" { 1769 pn(" c.%s = append([]string{}, %s...)", arg.goname, arg.goname) // Make a copy of the []string. 1770 continue 1771 } 1772 pn(" c.%s = %s", arg.goname, arg.goname) 1773 } 1774 pn(" return c") 1775 pn("}") 1776 1777 for _, opt := range meth.OptParams() { 1778 if opt.p.Location != "query" { 1779 panicf("optional parameter has unsupported location %q", opt.p.Location) 1780 } 1781 setter := initialCap(opt.p.Name) 1782 des := opt.p.Description 1783 des = strings.Replace(des, "Optional.", "", 1) 1784 des = strings.TrimSpace(des) 1785 p("\n%s", asComment("", fmt.Sprintf("%s sets the optional parameter %q: %s", setter, opt.p.Name, des))) 1786 addFieldValueComments(p, opt, "", true) 1787 np := new(namePool) 1788 np.Get("c") // take the receiver's name 1789 paramName := np.Get(validGoIdentifer(opt.p.Name)) 1790 typePrefix := "" 1791 if opt.p.Repeated { 1792 typePrefix = "..." 1793 } 1794 pn("func (c *%s) %s(%s %s%s) *%s {", callName, setter, paramName, typePrefix, opt.GoType(), callName) 1795 if opt.p.Repeated { 1796 if opt.GoType() == "string" { 1797 pn("c.urlParams_.SetMulti(%q, append([]string{}, %v...))", opt.p.Name, paramName) 1798 } else { 1799 tmpVar := convertMultiParams(a, paramName) 1800 pn(" c.urlParams_.SetMulti(%q, %v)", opt.p.Name, tmpVar) 1801 } 1802 } else { 1803 if opt.GoType() == "string" { 1804 pn("c.urlParams_.Set(%q, %v)", opt.p.Name, paramName) 1805 } else { 1806 pn("c.urlParams_.Set(%q, fmt.Sprint(%v))", opt.p.Name, paramName) 1807 } 1808 } 1809 pn("return c") 1810 pn("}") 1811 } 1812 1813 if meth.supportsMediaUpload() { 1814 comment := "Media specifies the media to upload in one or more chunks. " + 1815 "The chunk size may be controlled by supplying a MediaOption generated by googleapi.ChunkSize. " + 1816 "The chunk size defaults to googleapi.DefaultUploadChunkSize." + 1817 "The Content-Type header used in the upload request will be determined by sniffing the contents of r, " + 1818 "unless a MediaOption generated by googleapi.ContentType is supplied." + 1819 "\nAt most one of Media and ResumableMedia may be set." 1820 // TODO(mcgreevy): Ensure that r is always closed before Do returns, and document this. 1821 // See comments on https://code-review.googlesource.com/#/c/3970/ 1822 p("\n%s", asComment("", comment)) 1823 pn("func (c *%s) Media(r io.Reader, options ...googleapi.MediaOption) *%s {", callName, callName) 1824 // We check if the body arg, if any, has a content type and apply it here. 1825 // In practice, this only happens for the storage API today. 1826 // TODO(djd): check if we can cope with the developer setting the body's Content-Type field 1827 // after they've made this call. 1828 if ba := args.bodyArg(); ba != nil { 1829 if ba.schema.HasContentType() { 1830 pn(" if ct := c.%s.ContentType; ct != \"\" {", ba.goname) 1831 pn(" options = append([]googleapi.MediaOption{googleapi.ContentType(ct)}, options...)") 1832 pn(" }") 1833 } 1834 } 1835 pn(" c.mediaInfo_ = gensupport.NewInfoFromMedia(r, options)") 1836 pn(" return c") 1837 pn("}") 1838 comment = "ResumableMedia specifies the media to upload in chunks and can be canceled with ctx. " + 1839 "\n\nDeprecated: use Media instead." + 1840 "\n\nAt most one of Media and ResumableMedia may be set. " + 1841 `mediaType identifies the MIME media type of the upload, such as "image/png". ` + 1842 `If mediaType is "", it will be auto-detected. ` + 1843 `The provided ctx will supersede any context previously provided to ` + 1844 `the Context method.` 1845 p("\n%s", asComment("", comment)) 1846 pn("func (c *%s) ResumableMedia(ctx context.Context, r io.ReaderAt, size int64, mediaType string) *%s {", callName, callName) 1847 pn(" c.ctx_ = ctx") 1848 pn(" c.mediaInfo_ = gensupport.NewInfoFromResumableMedia(r, size, mediaType)") 1849 pn(" return c") 1850 pn("}") 1851 comment = "ProgressUpdater provides a callback function that will be called after every chunk. " + 1852 "It should be a low-latency function in order to not slow down the upload operation. " + 1853 "This should only be called when using ResumableMedia (as opposed to Media)." 1854 p("\n%s", asComment("", comment)) 1855 pn("func (c *%s) ProgressUpdater(pu googleapi.ProgressUpdater) *%s {", callName, callName) 1856 pn(`c.mediaInfo_.SetProgressUpdater(pu)`) 1857 pn("return c") 1858 pn("}") 1859 } 1860 1861 comment := "Fields allows partial responses to be retrieved. " + 1862 "See https://developers.google.com/gdata/docs/2.0/basics#PartialResponse " + 1863 "for more information." 1864 p("\n%s", asComment("", comment)) 1865 pn("func (c *%s) Fields(s ...googleapi.Field) *%s {", callName, callName) 1866 pn(`c.urlParams_.Set("fields", googleapi.CombineFields(s))`) 1867 pn("return c") 1868 pn("}") 1869 if httpMethod == "GET" { 1870 // Note that non-GET responses are excluded from supporting If-None-Match. 1871 // See https://github.com/google/google-api-go-client/issues/107 for more info. 1872 comment := "IfNoneMatch sets the optional parameter which makes the operation fail if " + 1873 "the object's ETag matches the given value. This is useful for getting updates " + 1874 "only after the object has changed since the last request. " + 1875 "Use googleapi.IsNotModified to check whether the response error from Do " + 1876 "is the result of In-None-Match." 1877 p("\n%s", asComment("", comment)) 1878 pn("func (c *%s) IfNoneMatch(entityTag string) *%s {", callName, callName) 1879 pn(" c.ifNoneMatch_ = entityTag") 1880 pn(" return c") 1881 pn("}") 1882 } 1883 1884 doMethod := "Do method" 1885 if meth.supportsMediaDownload() { 1886 doMethod = "Do and Download methods" 1887 } 1888 commentFmtStr := "Context sets the context to be used in this call's %s. " + 1889 "Any pending HTTP request will be aborted if the provided context is canceled." 1890 comment = fmt.Sprintf(commentFmtStr, doMethod) 1891 p("\n%s", asComment("", comment)) 1892 if meth.supportsMediaUpload() { 1893 comment = "This context will supersede any context previously provided to " + 1894 "the ResumableMedia method." 1895 p("%s", asComment("", comment)) 1896 } 1897 pn("func (c *%s) Context(ctx context.Context) *%s {", callName, callName) 1898 pn(`c.ctx_ = ctx`) 1899 pn("return c") 1900 pn("}") 1901 1902 comment = "Header returns an http.Header that can be modified by the caller to add " + 1903 "HTTP headers to the request." 1904 p("\n%s", asComment("", comment)) 1905 pn("func (c *%s) Header() http.Header {", callName) 1906 pn(" if c.header_ == nil {") 1907 pn(" c.header_ = make(http.Header)") 1908 pn(" }") 1909 pn(" return c.header_") 1910 pn("}") 1911 1912 pn("\nfunc (c *%s) doRequest(alt string) (*http.Response, error) {", callName) 1913 pn(`reqHeaders := make(http.Header)`) 1914 pn("for k, v := range c.header_ {") 1915 pn(" reqHeaders[k] = v") 1916 pn("}") 1917 pn(`reqHeaders.Set("User-Agent",c.s.userAgent())`) 1918 if httpMethod == "GET" { 1919 pn(`if c.ifNoneMatch_ != "" {`) 1920 pn(` reqHeaders.Set("If-None-Match", c.ifNoneMatch_)`) 1921 pn("}") 1922 } 1923 pn("var body io.Reader = nil") 1924 if meth.IsRawRequest() { 1925 pn("body = c.body_") 1926 } else { 1927 if ba := args.bodyArg(); ba != nil && httpMethod != "GET" { 1928 if meth.m.ID == "ml.projects.predict" { 1929 // TODO(cbro): move ML API to rawHTTP (it will be a breaking change) 1930 // Skip JSONReader for APIs that require clients to pass in JSON already. 1931 pn("body = strings.NewReader(c.%s.HttpBody.Data)", ba.goname) 1932 } else { 1933 style := "WithoutDataWrapper" 1934 if a.needsDataWrapper() { 1935 style = "WithDataWrapper" 1936 } 1937 pn("body, err := googleapi.%s.JSONReader(c.%s)", style, ba.goname) 1938 pn("if err != nil { return nil, err }") 1939 } 1940 1941 pn(`reqHeaders.Set("Content-Type", "application/json")`) 1942 } 1943 pn(`c.urlParams_.Set("alt", alt)`) 1944 pn(`c.urlParams_.Set("prettyPrint", "false")`) 1945 } 1946 1947 pn("urls := googleapi.ResolveRelative(c.s.BasePath, %q)", meth.m.Path) 1948 if meth.supportsMediaUpload() { 1949 pn("if c.mediaInfo_ != nil {") 1950 // Hack guess, since we get a 404 otherwise: 1951 //pn("urls = googleapi.ResolveRelative(%q, %q)", a.apiBaseURL(), meth.mediaUploadPath()) 1952 // Further hack. Discovery doc is wrong? 1953 pn(" urls = strings.Replace(urls, %q, %q, 1)", "https://www.googleapis.com/", "https://www.googleapis.com/upload/") 1954 pn(` c.urlParams_.Set("uploadType", c.mediaInfo_.UploadType())`) 1955 pn("}") 1956 1957 pn("if body == nil {") 1958 pn(" body = new(bytes.Buffer)") 1959 pn(` reqHeaders.Set("Content-Type", "application/json")`) 1960 pn("}") 1961 pn("body, getBody, cleanup := c.mediaInfo_.UploadRequest(reqHeaders, body)") 1962 pn("defer cleanup()") 1963 } 1964 pn(`urls += "?" + c.urlParams_.Encode()`) 1965 pn("req, err := http.NewRequest(%q, urls, body)", httpMethod) 1966 pn("if err != nil { return nil, err }") 1967 pn("req.Header = reqHeaders") 1968 if meth.supportsMediaUpload() { 1969 pn("req.GetBody = getBody") 1970 } 1971 1972 // Replace param values after NewRequest to avoid reencoding them. 1973 // E.g. Cloud Storage API requires '%2F' in entity param to be kept, but url.Parse replaces it with '/'. 1974 argsForLocation := args.forLocation("path") 1975 if len(argsForLocation) > 0 { 1976 pn(`googleapi.Expand(req.URL, map[string]string{`) 1977 for _, arg := range argsForLocation { 1978 pn(`"%s": %s,`, arg.apiname, arg.exprAsString("c.")) 1979 } 1980 pn(`})`) 1981 } 1982 1983 pn("return gensupport.SendRequest(c.ctx_, c.s.client, req)") 1984 pn("}") 1985 1986 if meth.supportsMediaDownload() { 1987 pn("\n// Download fetches the API endpoint's \"media\" value, instead of the normal") 1988 pn("// API response value. If the returned error is nil, the Response is guaranteed to") 1989 pn("// have a 2xx status code. Callers must close the Response.Body as usual.") 1990 pn("func (c *%s) Download(opts ...googleapi.CallOption) (*http.Response, error) {", callName) 1991 pn(`gensupport.SetOptions(c.urlParams_, opts...)`) 1992 pn(`res, err := c.doRequest("media")`) 1993 pn("if err != nil { return nil, err }") 1994 pn("if err := googleapi.CheckMediaResponse(res); err != nil {") 1995 pn("res.Body.Close()") 1996 pn("return nil, err") 1997 pn("}") 1998 pn("return res, nil") 1999 pn("}") 2000 } 2001 2002 mapRetType := strings.HasPrefix(retTypeComma, "map[") 2003 pn("\n// Do executes the %q call.", meth.m.ID) 2004 if retTypeComma != "" && !mapRetType && !meth.IsRawResponse() { 2005 commentFmtStr := "Exactly one of %v or error will be non-nil. " + 2006 "Any non-2xx status code is an error. " + 2007 "Response headers are in either %v.ServerResponse.Header " + 2008 "or (if a response was returned at all) in error.(*googleapi.Error).Header. " + 2009 "Use googleapi.IsNotModified to check whether the returned error was because " + 2010 "http.StatusNotModified was returned." 2011 comment := fmt.Sprintf(commentFmtStr, retType, retType) 2012 p("%s", asComment("", comment)) 2013 } 2014 pn("func (c *%s) Do(opts ...googleapi.CallOption) (%serror) {", callName, retTypeComma) 2015 nilRet := "" 2016 if retTypeComma != "" { 2017 nilRet = "nil, " 2018 } 2019 pn(`gensupport.SetOptions(c.urlParams_, opts...)`) 2020 if meth.IsRawResponse() { 2021 pn(`return c.doRequest("")`) 2022 } else { 2023 pn(`res, err := c.doRequest("json")`) 2024 2025 if retTypeComma != "" && !mapRetType { 2026 pn("if res != nil && res.StatusCode == http.StatusNotModified {") 2027 pn(" if res.Body != nil { res.Body.Close() }") 2028 pn(" return nil, &googleapi.Error{") 2029 pn(" Code: res.StatusCode,") 2030 pn(" Header: res.Header,") 2031 pn(" }") 2032 pn("}") 2033 } 2034 pn("if err != nil { return %serr }", nilRet) 2035 pn("defer googleapi.CloseBody(res)") 2036 pn("if err := googleapi.CheckResponse(res); err != nil { return %serr }", nilRet) 2037 if meth.supportsMediaUpload() { 2038 pn(`rx := c.mediaInfo_.ResumableUpload(res.Header.Get("Location"))`) 2039 pn("if rx != nil {") 2040 pn(" rx.Client = c.s.client") 2041 pn(" rx.UserAgent = c.s.userAgent()") 2042 pn(" ctx := c.ctx_") 2043 pn(" if ctx == nil {") 2044 // TODO(mcgreevy): Require context when calling Media, or Do. 2045 pn(" ctx = context.TODO()") 2046 pn(" }") 2047 pn(" res, err = rx.Upload(ctx)") 2048 pn(" if err != nil { return %serr }", nilRet) 2049 pn(" defer res.Body.Close()") 2050 pn(" if err := googleapi.CheckResponse(res); err != nil { return %serr }", nilRet) 2051 pn("}") 2052 } 2053 if retTypeComma == "" { 2054 pn("return nil") 2055 } else { 2056 if mapRetType { 2057 pn("var ret %s", responseType(a, meth.m)) 2058 } else { 2059 pn("ret := &%s{", responseTypeLiteral(a, meth.m)) 2060 pn(" ServerResponse: googleapi.ServerResponse{") 2061 pn(" Header: res.Header,") 2062 pn(" HTTPStatusCode: res.StatusCode,") 2063 pn(" },") 2064 pn("}") 2065 } 2066 if a.needsDataWrapper() { 2067 pn("target := &struct {") 2068 pn(" Data %s `json:\"data\"`", responseType(a, meth.m)) 2069 pn("}{ret}") 2070 } else { 2071 pn("target := &ret") 2072 } 2073 2074 if meth.m.ID == "ml.projects.predict" { 2075 pn("var b bytes.Buffer") 2076 pn("if _, err := io.Copy(&b, res.Body); err != nil { return nil, err }") 2077 pn("if err := res.Body.Close(); err != nil { return nil, err }") 2078 pn("if err := json.NewDecoder(bytes.NewReader(b.Bytes())).Decode(target); err != nil { return nil, err }") 2079 pn("ret.Data = b.String()") 2080 } else { 2081 pn("if err := gensupport.DecodeResponse(target, res); err != nil { return nil, err }") 2082 } 2083 pn("return ret, nil") 2084 } 2085 } 2086 2087 bs, err := json.MarshalIndent(meth.m.JSONMap, "\t// ", " ") 2088 if err != nil { 2089 panic(err) 2090 } 2091 pn("// %s\n", string(bs)) 2092 pn("}") 2093 2094 if ptg, rname, ok := meth.supportsPaging(); ok { 2095 // We can assume retType is non-empty. 2096 pn("") 2097 pn("// Pages invokes f for each page of results.") 2098 pn("// A non-nil error returned from f will halt the iteration.") 2099 pn("// The provided context supersedes any context provided to the Context method.") 2100 pn("func (c *%s) Pages(ctx context.Context, f func(%s) error) error {", callName, retType) 2101 pn(" c.ctx_ = ctx") 2102 pn(` defer %s // reset paging to original point`, ptg.genDeferBody()) 2103 pn(" for {") 2104 pn(" x, err := c.Do()") 2105 pn(" if err != nil { return err }") 2106 pn(" if err := f(x); err != nil { return err }") 2107 pn(` if x.%s == "" { return nil }`, rname) 2108 pn(ptg.genSet("x." + rname)) 2109 pn(" }") 2110 pn("}") 2111 } 2112} 2113 2114// A Field provides methods that describe the characteristics of a Param or Property. 2115type Field interface { 2116 Default() string 2117 Enum() ([]string, bool) 2118 EnumDescriptions() []string 2119 UnfortunateDefault() bool 2120} 2121 2122type Param struct { 2123 method *Method 2124 p *disco.Parameter 2125 callFieldName string // empty means to use the default 2126} 2127 2128func (p *Param) Default() string { 2129 return p.p.Default 2130} 2131 2132func (p *Param) Enum() ([]string, bool) { 2133 if e := p.p.Enums; e != nil { 2134 return e, true 2135 } 2136 return nil, false 2137} 2138 2139func (p *Param) EnumDescriptions() []string { 2140 return p.p.EnumDescriptions 2141} 2142 2143func (p *Param) UnfortunateDefault() bool { 2144 // We do not do anything special for Params with unfortunate defaults. 2145 return false 2146} 2147 2148func (p *Param) GoType() string { 2149 typ, format := p.p.Type, p.p.Format 2150 if typ == "string" && strings.Contains(format, "int") && p.p.Location != "query" { 2151 panic("unexpected int parameter encoded as string, not in query: " + p.p.Name) 2152 } 2153 t, ok := simpleTypeConvert(typ, format) 2154 if !ok { 2155 panic("failed to convert parameter type " + fmt.Sprintf("type=%q, format=%q", typ, format)) 2156 } 2157 return t 2158} 2159 2160// goCallFieldName returns the name of this parameter's field in a 2161// method's "Call" struct. 2162func (p *Param) goCallFieldName() string { 2163 if p.callFieldName != "" { 2164 return p.callFieldName 2165 } 2166 return validGoIdentifer(p.p.Name) 2167} 2168 2169// APIMethods returns top-level ("API-level") methods. They don't have an associated resource. 2170func (a *API) APIMethods() []*Method { 2171 meths := []*Method{} 2172 for _, m := range a.doc.Methods { 2173 meths = append(meths, &Method{ 2174 api: a, 2175 r: nil, // to be explicit 2176 m: m, 2177 }) 2178 } 2179 return meths 2180} 2181 2182func resolveRelative(basestr, relstr string) string { 2183 u, err := url.Parse(basestr) 2184 if err != nil { 2185 panicf("Error parsing base URL %q: %v", basestr, err) 2186 } 2187 rel, err := url.Parse(relstr) 2188 if err != nil { 2189 panicf("Error parsing relative URL %q: %v", relstr, err) 2190 } 2191 u = u.ResolveReference(rel) 2192 return u.String() 2193} 2194 2195func (meth *Method) IsRawRequest() bool { 2196 if meth.m.Request == nil { 2197 return false 2198 } 2199 // TODO(cbro): enable across other APIs. 2200 if meth.api.Name != "healthcare" { 2201 return false 2202 } 2203 return meth.m.Request.Ref == "HttpBody" 2204} 2205 2206func (meth *Method) IsRawResponse() bool { 2207 if meth.m.Response == nil { 2208 return false 2209 } 2210 if meth.IsRawRequest() { 2211 // always match raw requests with raw responses. 2212 return true 2213 } 2214 // TODO(cbro): enable across other APIs. 2215 if meth.api.Name != "healthcare" { 2216 return false 2217 } 2218 return meth.m.Response.Ref == "HttpBody" 2219} 2220 2221func (meth *Method) NewArguments() *arguments { 2222 args := &arguments{ 2223 method: meth, 2224 m: make(map[string]*argument), 2225 } 2226 pnames := meth.m.ParameterOrder 2227 if len(pnames) == 0 { 2228 // No parameterOrder; collect required parameters and sort by name. 2229 for _, reqParam := range meth.grepParams(func(p *Param) bool { return p.p.Required }) { 2230 pnames = append(pnames, reqParam.p.Name) 2231 } 2232 sort.Strings(pnames) 2233 } 2234 for _, pname := range pnames { 2235 arg := meth.NewArg(pname, meth.NamedParam(pname)) 2236 args.AddArg(arg) 2237 } 2238 if rs := meth.m.Request; rs != nil { 2239 if meth.IsRawRequest() { 2240 args.AddArg(&argument{ 2241 goname: "body_", 2242 gotype: "io.Reader", 2243 }) 2244 } else { 2245 args.AddArg(meth.NewBodyArg(rs)) 2246 } 2247 } 2248 return args 2249} 2250 2251func (meth *Method) NewBodyArg(ds *disco.Schema) *argument { 2252 s := meth.api.schemaNamed(ds.RefSchema.Name) 2253 return &argument{ 2254 goname: validGoIdentifer(strings.ToLower(ds.Ref)), 2255 apiname: "REQUEST", 2256 gotype: "*" + s.GoName(), 2257 apitype: ds.Ref, 2258 location: "body", 2259 schema: s, 2260 } 2261} 2262 2263func (meth *Method) NewArg(apiname string, p *Param) *argument { 2264 apitype := p.p.Type 2265 des := p.p.Description 2266 goname := validGoIdentifer(apiname) // but might be changed later, if conflicts 2267 if strings.Contains(des, "identifier") && !strings.HasSuffix(strings.ToLower(goname), "id") { 2268 goname += "id" // yay 2269 p.callFieldName = goname 2270 } 2271 gotype := mustSimpleTypeConvert(apitype, p.p.Format) 2272 if p.p.Repeated { 2273 gotype = "[]" + gotype 2274 } 2275 return &argument{ 2276 apiname: apiname, 2277 apitype: apitype, 2278 goname: goname, 2279 gotype: gotype, 2280 location: p.p.Location, 2281 } 2282} 2283 2284type argument struct { 2285 method *Method 2286 schema *Schema // Set if location == "body". 2287 apiname, apitype string 2288 goname, gotype string 2289 location string // "path", "query", "body" 2290} 2291 2292func (a *argument) String() string { 2293 return a.goname + " " + a.gotype 2294} 2295 2296func (a *argument) exprAsString(prefix string) string { 2297 switch a.gotype { 2298 case "[]string": 2299 log.Printf("TODO(bradfitz): only including the first parameter in path query.") 2300 return prefix + a.goname + `[0]` 2301 case "string": 2302 return prefix + a.goname 2303 case "integer", "int64": 2304 return "strconv.FormatInt(" + prefix + a.goname + ", 10)" 2305 case "uint64": 2306 return "strconv.FormatUint(" + prefix + a.goname + ", 10)" 2307 case "bool": 2308 return "strconv.FormatBool(" + prefix + a.goname + ")" 2309 } 2310 log.Panicf("unknown type: apitype=%q, gotype=%q", a.apitype, a.gotype) 2311 return "" 2312} 2313 2314// arguments are the arguments that a method takes 2315type arguments struct { 2316 l []*argument 2317 m map[string]*argument 2318 method *Method 2319} 2320 2321func (args *arguments) forLocation(loc string) []*argument { 2322 matches := make([]*argument, 0) 2323 for _, arg := range args.l { 2324 if arg.location == loc { 2325 matches = append(matches, arg) 2326 } 2327 } 2328 return matches 2329} 2330 2331func (args *arguments) bodyArg() *argument { 2332 for _, arg := range args.l { 2333 if arg.location == "body" { 2334 return arg 2335 } 2336 } 2337 return nil 2338} 2339 2340func (args *arguments) AddArg(arg *argument) { 2341 n := 1 2342 oname := arg.goname 2343 for { 2344 _, present := args.m[arg.goname] 2345 if !present { 2346 args.m[arg.goname] = arg 2347 args.l = append(args.l, arg) 2348 return 2349 } 2350 n++ 2351 arg.goname = fmt.Sprintf("%s%d", oname, n) 2352 } 2353} 2354 2355func (a *arguments) String() string { 2356 var buf bytes.Buffer 2357 for i, arg := range a.l { 2358 if i != 0 { 2359 buf.Write([]byte(", ")) 2360 } 2361 buf.Write([]byte(arg.String())) 2362 } 2363 return buf.String() 2364} 2365 2366var urlRE = regexp.MustCompile(`^http\S+$`) 2367 2368func asComment(pfx, c string) string { 2369 var buf bytes.Buffer 2370 const maxLen = 70 2371 r := strings.NewReplacer( 2372 "\n", "\n"+pfx+"// ", 2373 "`\"", `"`, 2374 "\"`", `"`, 2375 ) 2376 for len(c) > 0 { 2377 line := c 2378 if len(line) < maxLen { 2379 fmt.Fprintf(&buf, "%s// %s\n", pfx, r.Replace(line)) 2380 break 2381 } 2382 // Don't break URLs. 2383 if !urlRE.MatchString(line[:maxLen]) { 2384 line = line[:maxLen] 2385 } 2386 si := strings.LastIndex(line, " ") 2387 if nl := strings.Index(line, "\n"); nl != -1 && nl < si { 2388 si = nl 2389 } 2390 if si != -1 { 2391 line = line[:si] 2392 } 2393 fmt.Fprintf(&buf, "%s// %s\n", pfx, r.Replace(line)) 2394 c = c[len(line):] 2395 if si != -1 { 2396 c = c[1:] 2397 } 2398 } 2399 return buf.String() 2400} 2401 2402func simpleTypeConvert(apiType, format string) (gotype string, ok bool) { 2403 // From http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1 2404 switch apiType { 2405 case "boolean": 2406 gotype = "bool" 2407 case "string": 2408 gotype = "string" 2409 switch format { 2410 case "int64", "uint64", "int32", "uint32": 2411 gotype = format 2412 } 2413 case "number": 2414 gotype = "float64" 2415 case "integer": 2416 gotype = "int64" 2417 case "any": 2418 gotype = "interface{}" 2419 } 2420 return gotype, gotype != "" 2421} 2422 2423func mustSimpleTypeConvert(apiType, format string) string { 2424 if gotype, ok := simpleTypeConvert(apiType, format); ok { 2425 return gotype 2426 } 2427 panic(fmt.Sprintf("failed to simpleTypeConvert(%q, %q)", apiType, format)) 2428} 2429 2430func responseType(api *API, m *disco.Method) string { 2431 if m.Response == nil { 2432 return "" 2433 } 2434 ref := m.Response.Ref 2435 if ref != "" { 2436 if s := api.schemas[ref]; s != nil { 2437 return s.GoReturnType() 2438 } 2439 return "*" + ref 2440 } 2441 return "" 2442} 2443 2444// Strips the leading '*' from a type name so that it can be used to create a literal. 2445func responseTypeLiteral(api *API, m *disco.Method) string { 2446 v := responseType(api, m) 2447 if strings.HasPrefix(v, "*") { 2448 return v[1:] 2449 } 2450 return v 2451} 2452 2453// initialCap returns the identifier with a leading capital letter. 2454// it also maps "foo-bar" to "FooBar". 2455func initialCap(ident string) string { 2456 if ident == "" { 2457 panic("blank identifier") 2458 } 2459 return depunct(ident, true) 2460} 2461 2462func validGoIdentifer(ident string) string { 2463 id := depunct(ident, false) 2464 switch id { 2465 case "break", "default", "func", "interface", "select", 2466 "case", "defer", "go", "map", "struct", 2467 "chan", "else", "goto", "package", "switch", 2468 "const", "fallthrough", "if", "range", "type", 2469 "continue", "for", "import", "return", "var": 2470 return id + "_" 2471 } 2472 return id 2473} 2474 2475// depunct removes '-', '.', '$', '/', '_' from identifers, making the 2476// following character uppercase. Multiple '_' are preserved. 2477func depunct(ident string, needCap bool) string { 2478 var buf bytes.Buffer 2479 preserve_ := false 2480 for i, c := range ident { 2481 if c == '_' { 2482 if preserve_ || strings.HasPrefix(ident[i:], "__") { 2483 preserve_ = true 2484 } else { 2485 needCap = true 2486 continue 2487 } 2488 } else { 2489 preserve_ = false 2490 } 2491 if c == '-' || c == '.' || c == '$' || c == '/' { 2492 needCap = true 2493 continue 2494 } 2495 if needCap { 2496 c = unicode.ToUpper(c) 2497 needCap = false 2498 } 2499 buf.WriteByte(byte(c)) 2500 } 2501 return buf.String() 2502 2503} 2504 2505func addFieldValueComments(p func(format string, args ...interface{}), field Field, indent string, blankLine bool) { 2506 var lines []string 2507 2508 if enum, ok := field.Enum(); ok { 2509 desc := field.EnumDescriptions() 2510 lines = append(lines, asComment(indent, "Possible values:")) 2511 defval := field.Default() 2512 for i, v := range enum { 2513 more := "" 2514 if v == defval { 2515 more = " (default)" 2516 } 2517 if len(desc) > i && desc[i] != "" { 2518 more = more + " - " + desc[i] 2519 } 2520 lines = append(lines, asComment(indent, ` "`+v+`"`+more)) 2521 } 2522 } else if field.UnfortunateDefault() { 2523 lines = append(lines, asComment("\t", fmt.Sprintf("Default: %s", field.Default()))) 2524 } 2525 if blankLine && len(lines) > 0 { 2526 p(indent + "//\n") 2527 } 2528 for _, l := range lines { 2529 p("%s", l) 2530 } 2531} 2532