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