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