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