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