...

Source file src/google.golang.org/api/google-api-go-generator/gen.go

Documentation: google.golang.org/api/google-api-go-generator

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

View as plain text