Files
fn-serverless/vendor/google.golang.org/api/google-api-go-generator/gen.go
Reed Allman 9eaf824398 add jaeger support, link hot container & req span (#840)
* add jaeger support, link hot container & req span

* adds jaeger support now with FN_JAEGER_URL, there's a simple tutorial in the
operating/metrics.md file now and it's pretty easy to get up and running.
* links a hot request span to a hot container span. when we change this to
sample at a lower ratio we'll need to finagle the hot container span to always
sample or something, otherwise we'll hide that info. at least, since we're
sampling at 100% for now if this is flipped on, can see freeze/unfreeze etc.
if they hit. this is useful for debugging. note that zipkin's exporter does
not follow the link at all, hence jaeger... and they're backed by the Cloud
Empire now (CNCF) so we'll probably use it anyway.

* vendor: add thrift for jaeger
2018-03-13 15:57:12 -07:00

2373 lines
67 KiB
Go

// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"go/format"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"unicode"
"google.golang.org/api/google-api-go-generator/internal/disco"
)
const (
googleDiscoveryURL = "https://www.googleapis.com/discovery/v1/apis"
generatorVersion = "2018018"
)
var (
apiToGenerate = flag.String("api", "*", "The API ID to generate, like 'tasks:v1'. A value of '*' means all.")
useCache = flag.Bool("cache", true, "Use cache of discovered Google API discovery documents.")
genDir = flag.String("gendir", "", "Directory to use to write out generated Go files")
build = flag.Bool("build", false, "Compile generated packages.")
install = flag.Bool("install", false, "Install generated packages.")
apisURL = flag.String("discoveryurl", googleDiscoveryURL, "URL to root discovery document")
publicOnly = flag.Bool("publiconly", true, "Only build public, released APIs. Only applicable for Google employees.")
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.")
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).")
apiPackageBase = flag.String("api_pkg_base", "google.golang.org/api", "Go package prefix to use for all generated APIs.")
baseURL = flag.String("base_url", "", "(optional) Override the default service API URL. If empty, the service's root URL will be used.")
headerPath = flag.String("header_path", "", "If non-empty, prepend the contents of this file to generated services.")
contextHTTPPkg = flag.String("ctxhttp_pkg", "golang.org/x/net/context/ctxhttp", "Go package path of the 'ctxhttp' package.")
contextPkg = flag.String("context_pkg", "golang.org/x/net/context", "Go package path of the 'context' package.")
gensupportPkg = flag.String("gensupport_pkg", "google.golang.org/api/gensupport", "Go package path of the 'api/gensupport' support package.")
googleapiPkg = flag.String("googleapi_pkg", "google.golang.org/api/googleapi", "Go package path of the 'api/googleapi' support package.")
serviceTypes = []string{"Service", "APIService"}
)
// API represents an API to generate, as well as its state while it's
// generating.
type API struct {
// Fields needed before generating code, to select and find the APIs
// to generate.
// These fields usually come from the "directory item" JSON objects
// that are provided by the googleDiscoveryURL. We unmarshal a directory
// item directly into this struct.
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
DiscoveryLink string `json:"discoveryRestUrl"` // absolute
doc *disco.Document
// TODO(jba): remove m when we've fully converted to using disco.
m map[string]interface{}
forceJSON []byte // if non-nil, the JSON schema file. else fetched.
usedNames namePool
schemas map[string]*Schema // apiName -> schema
responseTypes map[string]bool
p func(format string, args ...interface{}) // print raw
pn func(format string, args ...interface{}) // print with newline
}
func (a *API) sortedSchemaNames() (names []string) {
for name := range a.schemas {
names = append(names, name)
}
sort.Strings(names)
return
}
func (a *API) Schema(name string) *Schema {
return a.schemas[name]
}
type generateError struct {
api *API
error error
}
func (e *generateError) Error() string {
return fmt.Sprintf("API %s failed to generate code: %v", e.api.ID, e.error)
}
type compileError struct {
api *API
output string
}
func (e *compileError) Error() string {
return fmt.Sprintf("API %s failed to compile:\n%v", e.api.ID, e.output)
}
func main() {
flag.Parse()
if *install {
*build = true
}
var (
apiIds = []string{}
matches = []*API{}
errors = []error{}
)
for _, api := range getAPIs() {
apiIds = append(apiIds, api.ID)
if !api.want() {
continue
}
matches = append(matches, api)
log.Printf("Generating API %s", api.ID)
err := api.WriteGeneratedCode()
if err != nil && err != errNoDoc {
errors = append(errors, &generateError{api, err})
continue
}
if *build && err == nil {
var args []string
if *install {
args = append(args, "install")
} else {
args = append(args, "build")
}
args = append(args, api.Target())
out, err := exec.Command("go", args...).CombinedOutput()
if err != nil {
errors = append(errors, &compileError{api, string(out)})
}
}
}
if len(matches) == 0 {
log.Fatalf("No APIs matched %q; options are %v", *apiToGenerate, apiIds)
}
if len(errors) > 0 {
log.Printf("%d API(s) failed to generate or compile:", len(errors))
for _, ce := range errors {
log.Printf(ce.Error())
}
os.Exit(1)
}
}
func (a *API) want() bool {
if *jsonFile != "" {
// Return true early, before calling a.JSONFile()
// which will require a GOPATH be set. This is for
// integration with Google's build system genrules
// where there is no GOPATH.
return true
}
// Skip this API if we're in cached mode and the files don't exist on disk.
if *useCache {
if _, err := os.Stat(a.JSONFile()); os.IsNotExist(err) {
return false
}
}
return *apiToGenerate == "*" || *apiToGenerate == a.ID
}
func getAPIs() []*API {
if *jsonFile != "" {
return getAPIsFromFile()
}
var bytes []byte
var source string
apiListFile := filepath.Join(genDirRoot(), "api-list.json")
if *useCache {
if !*publicOnly {
log.Fatalf("-cache=true not compatible with -publiconly=false")
}
var err error
bytes, err = ioutil.ReadFile(apiListFile)
if err != nil {
log.Fatal(err)
}
source = apiListFile
} else {
bytes = slurpURL(*apisURL)
if *publicOnly {
if err := writeFile(apiListFile, bytes); err != nil {
log.Fatal(err)
}
}
source = *apisURL
}
apis, err := unmarshalAPIs(bytes)
if err != nil {
log.Fatalf("error decoding JSON in %s: %v", source, err)
}
if !*publicOnly && *apiToGenerate != "*" {
apis = append(apis, apiFromID(*apiToGenerate))
}
return apis
}
func unmarshalAPIs(bytes []byte) ([]*API, error) {
var itemObj struct{ Items []*API }
if err := json.Unmarshal(bytes, &itemObj); err != nil {
return nil, err
}
return itemObj.Items, nil
}
func apiFromID(apiID string) *API {
parts := strings.Split(apiID, ":")
if len(parts) != 2 {
log.Fatalf("malformed API name: %q", apiID)
}
return &API{
ID: apiID,
Name: parts[0],
Version: parts[1],
}
}
// getAPIsFromFile handles the case of generating exactly one API
// from the flag given in --api_json_file
func getAPIsFromFile() []*API {
if *apiToGenerate != "*" {
log.Fatalf("Can't set --api with --api_json_file.")
}
if !*publicOnly {
log.Fatalf("Can't set --publiconly with --api_json_file.")
}
a, err := apiFromFile(*jsonFile)
if err != nil {
log.Fatal(err)
}
return []*API{a}
}
func apiFromFile(file string) (*API, error) {
jsonBytes, err := ioutil.ReadFile(file)
if err != nil {
return nil, fmt.Errorf("Error reading %s: %v", file, err)
}
doc, err := disco.NewDocument(jsonBytes)
if err != nil {
return nil, fmt.Errorf("reading document from %q: %v", file, err)
}
a := &API{
ID: doc.ID,
Name: doc.Name,
Version: doc.Version,
forceJSON: jsonBytes,
doc: doc,
}
return a, nil
}
func writeFile(file string, contents []byte) error {
// Don't write it if the contents are identical.
existing, err := ioutil.ReadFile(file)
if err == nil && (bytes.Equal(existing, contents) || basicallyEqual(existing, contents)) {
return nil
}
outdir := filepath.Dir(file)
if err = os.MkdirAll(outdir, 0755); err != nil {
return fmt.Errorf("failed to Mkdir %s: %v", outdir, err)
}
return ioutil.WriteFile(file, contents, 0644)
}
var ignoreLines = regexp.MustCompile(`(?m)^\s+"(?:etag|revision)": ".+\n`)
// basicallyEqual reports whether a and b are equal except for boring
// differences like ETag updates.
func basicallyEqual(a, b []byte) bool {
return ignoreLines.Match(a) && ignoreLines.Match(b) &&
bytes.Equal(ignoreLines.ReplaceAll(a, nil), ignoreLines.ReplaceAll(b, nil))
}
func slurpURL(urlStr string) []byte {
if *useCache {
log.Fatalf("Invalid use of slurpURL in cached mode for URL %s", urlStr)
}
req, err := http.NewRequest("GET", urlStr, nil)
if err != nil {
log.Fatal(err)
}
if *publicOnly {
req.Header.Add("X-User-IP", "0.0.0.0") // hack
}
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("Error fetching URL %s: %v", urlStr, err)
}
if res.StatusCode >= 300 {
log.Printf("WARNING: URL %s served status code %d", urlStr, res.StatusCode)
return nil
}
bs, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Fatalf("Error reading body of URL %s: %v", urlStr, err)
}
return bs
}
func panicf(format string, args ...interface{}) {
panic(fmt.Sprintf(format, args...))
}
// namePool keeps track of used names and assigns free ones based on a
// preferred name
type namePool struct {
m map[string]bool // lazily initialized
}
// oddVersionRE matches unusual API names like directory_v1.
var oddVersionRE = regexp.MustCompile(`^(.+)_(v[\d\.]+)$`)
// renameVersion conditionally rewrites the provided version such
// that the final path component of the import path doesn't look
// like a Go identifier. This keeps the consistency that import paths
// for the generated Go packages look like:
// google.golang.org/api/NAME/v<version>
// and have package NAME.
// See https://github.com/google/google-api-go-client/issues/78
func renameVersion(version string) string {
if version == "alpha" || version == "beta" {
return "v0." + version
}
if m := oddVersionRE.FindStringSubmatch(version); m != nil {
return m[1] + "/" + m[2]
}
return version
}
func (p *namePool) Get(preferred string) string {
if p.m == nil {
p.m = make(map[string]bool)
}
name := preferred
tries := 0
for p.m[name] {
tries++
name = fmt.Sprintf("%s%d", preferred, tries)
}
p.m[name] = true
return name
}
func genDirRoot() string {
if *genDir != "" {
return *genDir
}
paths := filepath.SplitList(os.Getenv("GOPATH"))
if len(paths) == 0 {
log.Fatalf("No GOPATH set.")
}
return filepath.Join(paths[0], "src", "google.golang.org", "api")
}
func (a *API) SourceDir() string {
return filepath.Join(genDirRoot(), a.Package(), renameVersion(a.Version))
}
func (a *API) DiscoveryURL() string {
if a.DiscoveryLink == "" {
log.Fatalf("API %s has no DiscoveryLink", a.ID)
}
return a.DiscoveryLink
}
func (a *API) Package() string {
return strings.ToLower(a.Name)
}
func (a *API) Target() string {
return fmt.Sprintf("%s/%s/%s", *apiPackageBase, a.Package(), renameVersion(a.Version))
}
// ServiceType returns the name of the type to use for the root API struct
// (typically "Service").
func (a *API) ServiceType() string {
switch a.Name {
case "appengine", "content": // retained for historical compatibility.
return "APIService"
default:
for _, t := range serviceTypes {
if _, ok := a.schemas[t]; !ok {
return t
}
}
panic("all service types are used, please consider introducing a new type to serviceTypes.")
}
}
// GetName returns a free top-level function/type identifier in the package.
// It tries to return your preferred match if it's free.
func (a *API) GetName(preferred string) string {
return a.usedNames.Get(preferred)
}
func (a *API) apiBaseURL() string {
var base, rel string
switch {
case *baseURL != "":
base, rel = *baseURL, a.doc.BasePath
case a.doc.RootURL != "":
base, rel = a.doc.RootURL, a.doc.ServicePath
default:
base, rel = *apisURL, a.doc.BasePath
}
return resolveRelative(base, rel)
}
func (a *API) needsDataWrapper() bool {
for _, feature := range a.doc.Features {
if feature == "dataWrapper" {
return true
}
}
return false
}
func (a *API) jsonBytes() []byte {
if a.forceJSON == nil {
var slurp []byte
var err error
if *useCache {
slurp, err = ioutil.ReadFile(a.JSONFile())
if err != nil {
log.Fatal(err)
}
} else {
slurp = slurpURL(a.DiscoveryURL())
if slurp != nil {
// Make sure that keys are sorted by re-marshalling.
d := make(map[string]interface{})
json.Unmarshal(slurp, &d)
if err != nil {
log.Fatal(err)
}
var err error
slurp, err = json.MarshalIndent(d, "", " ")
if err != nil {
log.Fatal(err)
}
}
}
a.forceJSON = slurp
}
return a.forceJSON
}
func (a *API) JSONFile() string {
return filepath.Join(a.SourceDir(), a.Package()+"-api.json")
}
var errNoDoc = errors.New("could not read discovery doc")
// WriteGeneratedCode generates code for a.
// It returns errNoDoc if we couldn't read the discovery doc.
func (a *API) WriteGeneratedCode() error {
genfilename := *output
jsonBytes := a.jsonBytes()
// Skip generation if we don't have the discovery doc.
if jsonBytes == nil {
// No message here, because slurpURL printed one.
return errNoDoc
}
if genfilename == "" {
if err := writeFile(a.JSONFile(), jsonBytes); err != nil {
return err
}
outdir := a.SourceDir()
err := os.MkdirAll(outdir, 0755)
if err != nil {
return fmt.Errorf("failed to Mkdir %s: %v", outdir, err)
}
pkg := a.Package()
genfilename = filepath.Join(outdir, pkg+"-gen.go")
}
code, err := a.GenerateCode()
errw := writeFile(genfilename, code)
if err == nil {
err = errw
}
if err != nil {
return err
}
return nil
}
var docsLink string
func (a *API) GenerateCode() ([]byte, error) {
pkg := a.Package()
jsonBytes := a.jsonBytes()
var err error
if a.doc == nil {
a.doc, err = disco.NewDocument(jsonBytes)
if err != nil {
return nil, err
}
}
// Buffer the output in memory, for gofmt'ing later.
var buf bytes.Buffer
a.p = func(format string, args ...interface{}) {
_, err := fmt.Fprintf(&buf, format, args...)
if err != nil {
panic(err)
}
}
a.pn = func(format string, args ...interface{}) {
a.p(format+"\n", args...)
}
wf := func(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(&buf, f)
return err
}
p, pn := a.p, a.pn
if *headerPath != "" {
if err := wf(*headerPath); err != nil {
return nil, err
}
}
pn("// Package %s provides access to the %s.", pkg, a.doc.Title)
docsLink = a.doc.DocumentationLink
if docsLink != "" {
pn("//")
pn("// See %s", docsLink)
}
pn("//\n// Usage example:")
pn("//")
pn("// import %q", a.Target())
pn("// ...")
pn("// %sService, err := %s.New(oauthHttpClient)", pkg, pkg)
pn("package %s // import %q", pkg, a.Target())
p("\n")
pn("import (")
for _, imp := range []struct {
pkg string
lname string
}{
{"bytes", ""},
{"encoding/json", ""},
{"errors", ""},
{"fmt", ""},
{"io", ""},
{"net/http", ""},
{"net/url", ""},
{"strconv", ""},
{"strings", ""},
{*contextHTTPPkg, "ctxhttp"},
{*contextPkg, "context"},
{*gensupportPkg, "gensupport"},
{*googleapiPkg, "googleapi"},
} {
if imp.lname == "" {
pn(" %q", imp.pkg)
} else {
pn(" %s %q", imp.lname, imp.pkg)
}
}
pn(")")
pn("\n// Always reference these packages, just in case the auto-generated code")
pn("// below doesn't.")
pn("var _ = bytes.NewBuffer")
pn("var _ = strconv.Itoa")
pn("var _ = fmt.Sprintf")
pn("var _ = json.NewDecoder")
pn("var _ = io.Copy")
pn("var _ = url.Parse")
pn("var _ = gensupport.MarshalJSON")
pn("var _ = googleapi.Version")
pn("var _ = errors.New")
pn("var _ = strings.Replace")
pn("var _ = context.Canceled")
pn("var _ = ctxhttp.Do")
pn("")
pn("const apiId = %q", a.doc.ID)
pn("const apiName = %q", a.doc.Name)
pn("const apiVersion = %q", a.doc.Version)
pn("const basePath = %q", a.apiBaseURL())
a.generateScopeConstants()
a.PopulateSchemas()
service := a.ServiceType()
// Reserve names (ignore return value; we're the first caller).
a.GetName("New")
a.GetName(service)
pn("func New(client *http.Client) (*%s, error) {", service)
pn("if client == nil { return nil, errors.New(\"client is nil\") }")
pn("s := &%s{client: client, BasePath: basePath}", service)
for _, res := range a.doc.Resources { // add top level resources.
pn("s.%s = New%s(s)", resourceGoField(res), resourceGoType(res))
}
pn("return s, nil")
pn("}")
pn("\ntype %s struct {", service)
pn(" client *http.Client")
pn(" BasePath string // API endpoint base URL")
pn(" UserAgent string // optional additional User-Agent fragment")
for _, res := range a.doc.Resources {
pn("\n\t%s\t*%s", resourceGoField(res), resourceGoType(res))
}
pn("}")
pn("\nfunc (s *%s) userAgent() string {", service)
pn(` if s.UserAgent == "" { return googleapi.UserAgent }`)
pn(` return googleapi.UserAgent + " " + s.UserAgent`)
pn("}\n")
for _, res := range a.doc.Resources {
a.generateResource(res)
}
a.responseTypes = make(map[string]bool)
for _, meth := range a.APIMethods() {
meth.cacheResponseTypes(a)
}
for _, res := range a.doc.Resources {
a.cacheResourceResponseTypes(res)
}
for _, name := range a.sortedSchemaNames() {
a.schemas[name].writeSchemaCode(a)
}
for _, meth := range a.APIMethods() {
meth.generateCode()
}
for _, res := range a.doc.Resources {
a.generateResourceMethods(res)
}
clean, err := format.Source(buf.Bytes())
if err != nil {
return buf.Bytes(), err
}
return clean, nil
}
func (a *API) generateScopeConstants() {
scopes := a.doc.Auth.OAuth2Scopes
if len(scopes) == 0 {
return
}
a.pn("// OAuth2 scopes used by this API.")
a.pn("const (")
n := 0
for _, scope := range scopes {
if n > 0 {
a.p("\n")
}
n++
ident := scopeIdentifierFromURL(scope.URL)
if scope.Description != "" {
a.p("%s", asComment("\t", scope.Description))
}
a.pn("\t%s = %q", ident, scope.URL)
}
a.p(")\n\n")
}
func scopeIdentifierFromURL(urlStr string) string {
const prefix = "https://www.googleapis.com/auth/"
if !strings.HasPrefix(urlStr, prefix) {
const https = "https://"
if !strings.HasPrefix(urlStr, https) {
log.Fatalf("Unexpected oauth2 scope %q doesn't start with %q", urlStr, https)
}
ident := validGoIdentifer(depunct(urlStr[len(https):], true)) + "Scope"
return ident
}
ident := validGoIdentifer(initialCap(urlStr[len(prefix):])) + "Scope"
return ident
}
// Schema is a disco.Schema that has been bestowed an identifier, whether by
// having an "id" field at the top of the schema or with an
// automatically generated one in populateSubSchemas.
//
// TODO: While sub-types shouldn't need to be promoted to schemas,
// API.GenerateCode iterates over API.schemas to figure out what
// top-level Go types to write. These should be separate concerns.
type Schema struct {
api *API
typ *disco.Schema
apiName string // the native API-defined name of this type
goName string // lazily populated by GoName
goReturnType string // lazily populated by GoReturnType
props []*Property
}
type Property struct {
s *Schema // the containing Schema
p *disco.Property
assignedGoName string
}
func (p *Property) Type() *disco.Schema {
return p.p.Schema
}
func (p *Property) GoName() string {
return initialCap(p.p.Name)
}
func (p *Property) Default() string {
return p.p.Schema.Default
}
func (p *Property) Description() string {
return p.p.Schema.Description
}
func (p *Property) Enum() ([]string, bool) {
typ := p.p.Schema
if typ.Enums != nil {
return typ.Enums, true
}
// Check if this has an array of string enums.
if typ.ItemSchema != nil {
if enums := typ.ItemSchema.Enums; enums != nil && typ.ItemSchema.Type == "string" {
return enums, true
}
}
return nil, false
}
func (p *Property) EnumDescriptions() []string {
if desc := p.p.Schema.EnumDescriptions; desc != nil {
return desc
}
// Check if this has an array of string enum descriptions.
if items := p.p.Schema.ItemSchema; items != nil {
if desc := items.EnumDescriptions; desc != nil {
return desc
}
}
return nil
}
func (p *Property) Pattern() (string, bool) {
return p.p.Schema.Pattern, (p.p.Schema.Pattern != "")
}
func (p *Property) TypeAsGo() string {
return p.s.api.typeAsGo(p.Type(), false)
}
// A FieldName uniquely identifies a field within a Schema struct for an API.
type fieldName struct {
api string // The ID of an API.
schema string // The Go name of a Schema struct.
field string // The Go name of a field.
}
// pointerFields is a list of fields that should use a pointer type.
// This makes it possible to distinguish between a field being unset vs having
// an empty value.
var pointerFields = []fieldName{
{api: "androidpublisher:v2", schema: "SubscriptionPurchase", field: "CancelReason"},
{api: "androidpublisher:v2", schema: "SubscriptionPurchase", field: "PaymentState"},
{api: "cloudmonitoring:v2beta2", schema: "Point", field: "BoolValue"},
{api: "cloudmonitoring:v2beta2", schema: "Point", field: "DoubleValue"},
{api: "cloudmonitoring:v2beta2", schema: "Point", field: "Int64Value"},
{api: "cloudmonitoring:v2beta2", schema: "Point", field: "StringValue"},
{api: "compute:alpha", schema: "Scheduling", field: "AutomaticRestart"},
{api: "compute:beta", schema: "MetadataItems", field: "Value"},
{api: "compute:beta", schema: "Scheduling", field: "AutomaticRestart"},
{api: "compute:v1", schema: "MetadataItems", field: "Value"},
{api: "compute:v1", schema: "Scheduling", field: "AutomaticRestart"},
{api: "content:v2", schema: "AccountUser", field: "Admin"},
{api: "datastore:v1beta2", schema: "Property", field: "BlobKeyValue"},
{api: "datastore:v1beta2", schema: "Property", field: "BlobValue"},
{api: "datastore:v1beta2", schema: "Property", field: "BooleanValue"},
{api: "datastore:v1beta2", schema: "Property", field: "DateTimeValue"},
{api: "datastore:v1beta2", schema: "Property", field: "DoubleValue"},
{api: "datastore:v1beta2", schema: "Property", field: "Indexed"},
{api: "datastore:v1beta2", schema: "Property", field: "IntegerValue"},
{api: "datastore:v1beta2", schema: "Property", field: "StringValue"},
{api: "datastore:v1beta3", schema: "Value", field: "BlobValue"},
{api: "datastore:v1beta3", schema: "Value", field: "BooleanValue"},
{api: "datastore:v1beta3", schema: "Value", field: "DoubleValue"},
{api: "datastore:v1beta3", schema: "Value", field: "IntegerValue"},
{api: "datastore:v1beta3", schema: "Value", field: "StringValue"},
{api: "datastore:v1beta3", schema: "Value", field: "TimestampValue"},
{api: "genomics:v1beta2", schema: "Dataset", field: "IsPublic"},
{api: "monitoring:v3", schema: "TypedValue", field: "BoolValue"},
{api: "monitoring:v3", schema: "TypedValue", field: "DoubleValue"},
{api: "monitoring:v3", schema: "TypedValue", field: "Int64Value"},
{api: "monitoring:v3", schema: "TypedValue", field: "StringValue"},
{api: "servicecontrol:v1", schema: "MetricValue", field: "BoolValue"},
{api: "servicecontrol:v1", schema: "MetricValue", field: "DoubleValue"},
{api: "servicecontrol:v1", schema: "MetricValue", field: "Int64Value"},
{api: "servicecontrol:v1", schema: "MetricValue", field: "StringValue"},
{api: "sqladmin:v1beta4", schema: "Settings", field: "StorageAutoResize"},
{api: "storage:v1", schema: "BucketLifecycleRuleCondition", field: "IsLive"},
{api: "storage:v1beta2", schema: "BucketLifecycleRuleCondition", field: "IsLive"},
{api: "tasks:v1", schema: "Task", field: "Completed"},
{api: "youtube:v3", schema: "ChannelSectionSnippet", field: "Position"},
}
// forcePointerType reports whether p should be represented as a pointer type in its parent schema struct.
func (p *Property) forcePointerType() bool {
if p.UnfortunateDefault() {
return true
}
name := fieldName{api: p.s.api.ID, schema: p.s.GoName(), field: p.GoName()}
for _, pf := range pointerFields {
if pf == name {
return true
}
}
return false
}
// UnfortunateDefault reports whether p may be set to a zero value, but has a non-zero default.
func (p *Property) UnfortunateDefault() bool {
switch p.TypeAsGo() {
default:
return false
case "bool":
return p.Default() == "true"
case "string":
if p.Default() == "" {
return false
}
// String fields are considered to "allow" a zero value if either:
// (a) they are an enum, and one of the permitted enum values is the empty string, or
// (b) they have a validation pattern which matches the empty string.
pattern, hasPat := p.Pattern()
enum, hasEnum := p.Enum()
if hasPat && hasEnum {
log.Printf("Encountered enum property which also has a pattern: %#v", p)
return false // don't know how to handle this, so ignore.
}
return (hasPat && emptyPattern(pattern)) ||
(hasEnum && emptyEnum(enum))
case "float64", "int64", "uint64", "int32", "uint32":
if p.Default() == "" {
return false
}
if f, err := strconv.ParseFloat(p.Default(), 64); err == nil {
return f != 0.0
}
// The default value has an unexpected form. Whatever it is, it's non-zero.
return true
}
}
// emptyPattern reports whether a pattern matches the empty string.
func emptyPattern(pattern string) bool {
if re, err := regexp.Compile(pattern); err == nil {
return re.MatchString("")
}
log.Printf("Encountered bad pattern: %s", pattern)
return false
}
// emptyEnum reports whether a property enum list contains the empty string.
func emptyEnum(enum []string) bool {
for _, val := range enum {
if val == "" {
return true
}
}
return false
}
func (a *API) typeAsGo(s *disco.Schema, elidePointers bool) string {
switch s.Kind {
case disco.SimpleKind:
return mustSimpleTypeConvert(s.Type, s.Format)
case disco.ArrayKind:
as := s.ElementSchema()
if as.Type == "string" {
switch as.Format {
case "int64":
return "googleapi.Int64s"
case "uint64":
return "googleapi.Uint64s"
case "int32":
return "googleapi.Int32s"
case "uint32":
return "googleapi.Uint32s"
case "float64":
return "googleapi.Float64s"
}
}
return "[]" + a.typeAsGo(as, elidePointers)
case disco.ReferenceKind:
rs := s.RefSchema
if rs.Kind == disco.SimpleKind {
// Simple top-level schemas get named types (see writeSchemaCode).
// Use the name instead of using the equivalent simple Go type.
return a.schemaNamed(rs.Name).GoName()
}
return a.typeAsGo(rs, elidePointers)
case disco.MapKind:
es := s.ElementSchema()
if es.Type == "string" {
// If the element schema has a type "string", it's going to be
// transmitted as a string, and the Go map type must reflect that.
// This is true even if the format is, say, "int64". When type =
// "string" and format = "int64" at top level, we can use the json
// "string" tag option to unmarshal the string to an int64, but
// inside a map we can't.
return "map[string]string"
}
// Due to historical baggage (maps used to be a separate code path),
// the element types of maps never have pointers in them. From this
// level down, elide pointers in types.
return "map[string]" + a.typeAsGo(es, true)
case disco.AnyStructKind:
return "googleapi.RawMessage"
case disco.StructKind:
tls := a.schemaNamed(s.Name)
if elidePointers || s.Variant != nil {
return tls.GoName()
}
return "*" + tls.GoName()
default:
panic(fmt.Sprintf("unhandled typeAsGo for %+v", s))
}
}
func (a *API) schemaNamed(name string) *Schema {
s := a.schemas[name]
if s == nil {
panicf("no top-level schema named %q", name)
}
return s
}
func (s *Schema) properties() []*Property {
if s.props != nil {
return s.props
}
if s.typ.Kind != disco.StructKind {
panic("called properties on non-object schema")
}
for _, p := range s.typ.Properties {
s.props = append(s.props, &Property{
s: s,
p: p,
})
}
return s.props
}
func (s *Schema) HasContentType() bool {
for _, p := range s.properties() {
if p.GoName() == "ContentType" && p.TypeAsGo() == "string" {
return true
}
}
return false
}
func (s *Schema) populateSubSchemas() (outerr error) {
defer func() {
r := recover()
if r == nil {
return
}
outerr = fmt.Errorf("%v", r)
}()
addSubStruct := func(subApiName string, t *disco.Schema) {
if s.api.schemas[subApiName] != nil {
panic("dup schema apiName: " + subApiName)
}
if t.Name != "" {
panic("subtype already has name: " + t.Name)
}
t.Name = subApiName
subs := &Schema{
api: s.api,
typ: t,
apiName: subApiName,
}
s.api.schemas[subApiName] = subs
err := subs.populateSubSchemas()
if err != nil {
panicf("in sub-struct %q: %v", subApiName, err)
}
}
switch s.typ.Kind {
case disco.StructKind:
for _, p := range s.properties() {
subApiName := fmt.Sprintf("%s.%s", s.apiName, p.p.Name)
switch p.Type().Kind {
case disco.SimpleKind, disco.ReferenceKind, disco.AnyStructKind:
// Do nothing.
case disco.MapKind:
mt := p.Type().ElementSchema()
if mt.Kind == disco.SimpleKind || mt.Kind == disco.ReferenceKind {
continue
}
addSubStruct(subApiName, mt)
case disco.ArrayKind:
at := p.Type().ElementSchema()
if at.Kind == disco.SimpleKind || at.Kind == disco.ReferenceKind {
continue
}
addSubStruct(subApiName, at)
case disco.StructKind:
addSubStruct(subApiName, p.Type())
default:
panicf("Unknown type for %q: %s", subApiName, p.Type())
}
}
case disco.ArrayKind:
subApiName := fmt.Sprintf("%s.Item", s.apiName)
switch at := s.typ.ElementSchema(); at.Kind {
case disco.SimpleKind, disco.ReferenceKind, disco.AnyStructKind:
// Do nothing.
case disco.MapKind:
mt := at.ElementSchema()
if k := mt.Kind; k != disco.SimpleKind && k != disco.ReferenceKind {
addSubStruct(subApiName, mt)
}
case disco.ArrayKind:
at := at.ElementSchema()
if k := at.Kind; k != disco.SimpleKind && k != disco.ReferenceKind {
addSubStruct(subApiName, at)
}
case disco.StructKind:
addSubStruct(subApiName, at)
default:
panicf("Unknown array type for %q: %s", subApiName, at)
}
case disco.AnyStructKind, disco.MapKind, disco.SimpleKind, disco.ReferenceKind:
// Do nothing.
default:
fmt.Fprintf(os.Stderr, "in populateSubSchemas, schema is: %v", s.typ)
panicf("populateSubSchemas: unsupported type for schema %q", s.apiName)
panic("unreachable")
}
return nil
}
// GoName returns (or creates and returns) the bare Go name
// of the apiName, making sure that it's a proper Go identifier
// and doesn't conflict with an existing name.
func (s *Schema) GoName() string {
if s.goName == "" {
if s.typ.Kind == disco.MapKind {
s.goName = s.api.typeAsGo(s.typ, false)
} else {
base := initialCap(s.apiName)
s.goName = s.api.GetName(base)
if base == "Service" && s.goName != "Service" {
// Detect the case where a resource is going to clash with the
// root service object.
panicf("Clash on name Service")
}
}
}
return s.goName
}
// GoReturnType returns the Go type to use as the return type.
// If a type is a struct, it will return *StructType,
// for a map it will return map[string]ValueType,
// for (not yet supported) slices it will return []ValueType.
func (s *Schema) GoReturnType() string {
if s.goReturnType == "" {
if s.typ.Kind == disco.MapKind {
s.goReturnType = s.GoName()
} else {
s.goReturnType = "*" + s.GoName()
}
}
return s.goReturnType
}
func (s *Schema) writeSchemaCode(api *API) {
switch s.typ.Kind {
case disco.SimpleKind:
apitype := s.typ.Type
typ := mustSimpleTypeConvert(apitype, s.typ.Format)
s.api.pn("\ntype %s %s", s.GoName(), typ)
case disco.StructKind:
s.writeSchemaStruct(api)
case disco.MapKind, disco.AnyStructKind:
// Do nothing.
case disco.ArrayKind:
log.Printf("TODO writeSchemaCode for arrays for %s", s.GoName())
default:
fmt.Fprintf(os.Stderr, "in writeSchemaCode, schema is: %+v", s.typ)
panicf("writeSchemaCode: unsupported type for schema %q", s.apiName)
}
}
func (s *Schema) writeVariant(api *API, v *disco.Variant) {
s.api.p("\ntype %s map[string]interface{}\n\n", s.GoName())
// Write out the "Type" method that identifies the variant type.
s.api.pn("func (t %s) Type() string {", s.GoName())
s.api.pn(" return googleapi.VariantType(t)")
s.api.p("}\n\n")
// Write out helper methods to convert each possible variant.
for _, m := range v.Map {
if m.TypeValue == "" && m.Ref == "" {
log.Printf("TODO variant %s ref %s not yet supported.", m.TypeValue, m.Ref)
continue
}
s.api.pn("func (t %s) %s() (r %s, ok bool) {", s.GoName(), initialCap(m.TypeValue), m.Ref)
s.api.pn(" if t.Type() != %q {", initialCap(m.TypeValue))
s.api.pn(" return r, false")
s.api.pn(" }")
s.api.pn(" ok = googleapi.ConvertVariant(map[string]interface{}(t), &r)")
s.api.pn(" return r, ok")
s.api.p("}\n\n")
}
}
func (s *Schema) Description() string {
return s.typ.Description
}
func (s *Schema) writeSchemaStruct(api *API) {
if v := s.typ.Variant; v != nil {
s.writeVariant(api, v)
return
}
s.api.p("\n")
des := s.Description()
if des != "" {
s.api.p("%s", asComment("", fmt.Sprintf("%s: %s", s.GoName(), des)))
}
s.api.pn("type %s struct {", s.GoName())
np := new(namePool)
forceSendName := np.Get("ForceSendFields")
nullFieldsName := np.Get("NullFields")
if s.isResponseType() {
np.Get("ServerResponse") // reserve the name
}
firstFieldName := "" // used to store a struct field name for use in documentation.
for i, p := range s.properties() {
if i > 0 {
s.api.p("\n")
}
pname := np.Get(p.GoName())
if pname[0] == '@' {
// HACK(cbro): ignore JSON-LD special fields until we can figure out
// the correct Go representation for them.
continue
}
p.assignedGoName = pname
des := p.Description()
if des != "" {
s.api.p("%s", asComment("\t", fmt.Sprintf("%s: %s", pname, des)))
}
addFieldValueComments(s.api.p, p, "\t", des != "")
var extraOpt string
if p.Type().IsIntAsString() {
extraOpt += ",string"
}
typ := p.TypeAsGo()
if p.forcePointerType() {
typ = "*" + typ
}
s.api.pn(" %s %s `json:\"%s,omitempty%s\"`", pname, typ, p.p.Name, extraOpt)
if firstFieldName == "" {
firstFieldName = pname
}
}
if s.isResponseType() {
if firstFieldName != "" {
s.api.p("\n")
}
s.api.p("%s", asComment("\t", "ServerResponse contains the HTTP response code and headers from the server."))
s.api.pn(" googleapi.ServerResponse `json:\"-\"`")
}
if firstFieldName == "" {
// There were no fields in the struct, so there is no point
// adding any custom JSON marshaling code.
s.api.pn("}")
return
}
commentFmtStr := "%s is a list of field names (e.g. %q) to " +
"unconditionally include in API requests. By default, fields " +
"with empty values are omitted from API requests. However, " +
"any non-pointer, non-interface field appearing in %s will " +
"be sent to the server regardless of whether the field is " +
"empty or not. This may be used to include empty fields in " +
"Patch requests."
comment := fmt.Sprintf(commentFmtStr, forceSendName, firstFieldName, forceSendName)
s.api.p("\n")
s.api.p("%s", asComment("\t", comment))
s.api.pn("\t%s []string `json:\"-\"`", forceSendName)
commentFmtStr = "%s is a list of field names (e.g. %q) to " +
"include in API requests with the JSON null value. " +
"By default, fields with empty values are omitted from API requests. However, " +
"any field with an empty value appearing in %s will be sent to the server as null. " +
"It is an error if a field in this list has a non-empty value. This may be used to " +
"include null fields in Patch requests."
comment = fmt.Sprintf(commentFmtStr, nullFieldsName, firstFieldName, nullFieldsName)
s.api.p("\n")
s.api.p("%s", asComment("\t", comment))
s.api.pn("\t%s []string `json:\"-\"`", nullFieldsName)
s.api.pn("}")
s.writeSchemaMarshal(forceSendName, nullFieldsName)
s.writeSchemaUnmarshal()
}
// writeSchemaMarshal writes a custom MarshalJSON function for s, which allows
// fields to be explicitly transmitted by listing them in the field identified
// by forceSendFieldName, and allows fields to be transmitted with the null value
// by listing them in the field identified by nullFieldsName.
func (s *Schema) writeSchemaMarshal(forceSendFieldName, nullFieldsName string) {
s.api.pn("func (s *%s) MarshalJSON() ([]byte, error) {", s.GoName())
s.api.pn("\ttype NoMethod %s", s.GoName())
// pass schema as methodless type to prevent subsequent calls to MarshalJSON from recursing indefinitely.
s.api.pn("\traw := NoMethod(*s)")
s.api.pn("\treturn gensupport.MarshalJSON(raw, s.%s, s.%s)", forceSendFieldName, nullFieldsName)
s.api.pn("}")
}
func (s *Schema) writeSchemaUnmarshal() {
var floatProps []*Property
for _, p := range s.properties() {
if p.p.Schema.Type == "number" {
floatProps = append(floatProps, p)
}
}
if len(floatProps) == 0 {
return
}
pn := s.api.pn
pn("\nfunc (s *%s) UnmarshalJSON(data []byte) error {", s.GoName())
pn(" type NoMethod %s", s.GoName()) // avoid infinite recursion
pn(" var s1 struct {")
// Hide the float64 fields of the schema with fields that correctly
// unmarshal special values.
for _, p := range floatProps {
typ := "gensupport.JSONFloat64"
if p.forcePointerType() {
typ = "*" + typ
}
pn("%s %s `json:\"%s\"`", p.assignedGoName, typ, p.p.Name)
}
pn(" *NoMethod") // embed the schema
pn(" }")
// Set the schema value into the wrapper so its other fields are unmarshaled.
pn(" s1.NoMethod = (*NoMethod)(s)")
pn(" if err := json.Unmarshal(data, &s1); err != nil {")
pn(" return err")
pn(" }")
// Copy each shadowing field into the field it shadows.
for _, p := range floatProps {
n := p.assignedGoName
if p.forcePointerType() {
pn("if s1.%s != nil { s.%s = (*float64)(s1.%s) }", n, n, n)
} else {
pn("s.%s = float64(s1.%s)", n, n)
}
}
pn(" return nil")
pn("}")
}
// isResponseType returns true for all types that are used as a response.
func (s *Schema) isResponseType() bool {
return s.api.responseTypes["*"+s.goName]
}
// PopulateSchemas reads all the API types ("schemas") from the JSON file
// and converts them to *Schema instances, returning an identically
// keyed map, additionally containing subresources. For instance,
//
// A resource "Foo" of type "object" with a property "bar", also of type
// "object" (an anonymous sub-resource), will get a synthetic API name
// of "Foo.bar".
//
// A resource "Foo" of type "array" with an "items" of type "object"
// will get a synthetic API name of "Foo.Item".
func (a *API) PopulateSchemas() {
if a.schemas != nil {
panic("")
}
a.schemas = make(map[string]*Schema)
for name, ds := range a.doc.Schemas {
s := &Schema{
api: a,
apiName: name,
typ: ds,
}
a.schemas[name] = s
err := s.populateSubSchemas()
if err != nil {
panicf("Error populating schema with API name %q: %v", name, err)
}
}
}
func (a *API) generateResource(r *disco.Resource) {
pn := a.pn
t := resourceGoType(r)
pn(fmt.Sprintf("func New%s(s *%s) *%s {", t, a.ServiceType(), t))
pn("rs := &%s{s : s}", t)
for _, res := range r.Resources {
pn("rs.%s = New%s(s)", resourceGoField(res), resourceGoType(res))
}
pn("return rs")
pn("}")
pn("\ntype %s struct {", t)
pn(" s *%s", a.ServiceType())
for _, res := range r.Resources {
pn("\n\t%s\t*%s", resourceGoField(res), resourceGoType(res))
}
pn("}")
for _, res := range r.Resources {
a.generateResource(res)
}
}
func (a *API) cacheResourceResponseTypes(r *disco.Resource) {
for _, meth := range a.resourceMethods(r) {
meth.cacheResponseTypes(a)
}
for _, res := range r.Resources {
a.cacheResourceResponseTypes(res)
}
}
func (a *API) generateResourceMethods(r *disco.Resource) {
for _, meth := range a.resourceMethods(r) {
meth.generateCode()
}
for _, res := range r.Resources {
a.generateResourceMethods(res)
}
}
func resourceGoField(r *disco.Resource) string {
return initialCap(r.Name)
}
func resourceGoType(r *disco.Resource) string {
return initialCap(r.FullName + "Service")
}
func (a *API) resourceMethods(r *disco.Resource) []*Method {
ms := []*Method{}
for _, m := range r.Methods {
ms = append(ms, &Method{
api: a,
r: r,
m: m,
})
}
return ms
}
type Method struct {
api *API
r *disco.Resource // or nil if a API-level (top-level) method
m *disco.Method
params []*Param // all Params, of each type, lazily set by first access to Parameters
}
func (m *Method) Id() string {
return m.m.ID
}
func (m *Method) responseType() *Schema {
return m.api.schemas[m.m.Response.RefSchema.Name]
}
func (m *Method) supportsMediaUpload() bool {
return m.m.MediaUpload != nil
}
func (m *Method) mediaUploadPath() string {
return m.m.MediaUpload.Protocols["simple"].Path
}
func (m *Method) supportsMediaDownload() bool {
if m.supportsMediaUpload() {
// storage.objects.insert claims support for download in
// addition to upload but attempting to do so fails.
// This situation doesn't apply to any other methods.
return false
}
return m.m.SupportsMediaDownload
}
func (m *Method) supportsPaging() (*pageTokenGenerator, string, bool) {
ptg := m.pageTokenGenerator()
if ptg == nil {
return nil, "", false
}
// Check that the response type has the next page token.
s := m.responseType()
if s == nil || s.typ.Kind != disco.StructKind {
return nil, "", false
}
for _, prop := range s.properties() {
if isPageTokenName(prop.p.Name) && prop.Type().Type == "string" {
return ptg, prop.GoName(), true
}
}
return nil, "", false
}
type pageTokenGenerator struct {
isParam bool // is the page token a URL parameter?
name string // param or request field name
requestName string // empty for URL param
}
func (p *pageTokenGenerator) genGet() string {
if p.isParam {
return fmt.Sprintf("c.urlParams_.Get(%q)", p.name)
}
return fmt.Sprintf("c.%s.%s", p.requestName, p.name)
}
func (p *pageTokenGenerator) genSet(valueExpr string) string {
if p.isParam {
return fmt.Sprintf("c.%s(%s)", initialCap(p.name), valueExpr)
}
return fmt.Sprintf("c.%s.%s = %s", p.requestName, p.name, valueExpr)
}
func (p *pageTokenGenerator) genDeferBody() string {
if p.isParam {
return p.genSet(p.genGet())
}
return fmt.Sprintf("func (pt string) { %s }(%s)", p.genSet("pt"), p.genGet())
}
// pageTokenGenerator returns a pageTokenGenerator that will generate code to
// get/set the page token for a subsequent page in the context of the generated
// Pages method. It returns nil if there is no page token.
func (m *Method) pageTokenGenerator() *pageTokenGenerator {
matches := m.grepParams(func(p *Param) bool { return isPageTokenName(p.p.Name) })
switch len(matches) {
case 1:
if matches[0].p.Required {
// The page token is a required parameter (e.g. because there is
// a separate API call to start an iteration), and so the relevant
// call factory method takes the page token instead.
return nil
}
n := matches[0].p.Name
return &pageTokenGenerator{true, n, ""}
case 0: // No URL parameter, but maybe a request field.
if m.m.Request == nil {
return nil
}
rs := m.m.Request
if rs.RefSchema != nil {
rs = rs.RefSchema
}
for _, p := range rs.Properties {
if isPageTokenName(p.Name) {
return &pageTokenGenerator{false, initialCap(p.Name), validGoIdentifer(strings.ToLower(rs.Name))}
}
}
return nil
default:
panicf("too many page token parameters for method %s", m.m.Name)
return nil
}
}
func isPageTokenName(s string) bool {
return s == "pageToken" || s == "nextPageToken"
}
func (m *Method) Params() []*Param {
if m.params == nil {
for _, p := range m.m.Parameters {
m.params = append(m.params, &Param{
method: m,
p: p,
})
}
}
return m.params
}
func (m *Method) grepParams(f func(*Param) bool) []*Param {
matches := make([]*Param, 0)
for _, param := range m.Params() {
if f(param) {
matches = append(matches, param)
}
}
return matches
}
func (m *Method) NamedParam(name string) *Param {
matches := m.grepParams(func(p *Param) bool {
return p.p.Name == name
})
if len(matches) < 1 {
log.Panicf("failed to find named parameter %q", name)
}
if len(matches) > 1 {
log.Panicf("found multiple parameters for parameter name %q", name)
}
return matches[0]
}
func (m *Method) OptParams() []*Param {
return m.grepParams(func(p *Param) bool {
return !p.p.Required
})
}
func (meth *Method) cacheResponseTypes(api *API) {
if retType := responseType(api, meth.m); retType != "" && strings.HasPrefix(retType, "*") {
api.responseTypes[retType] = true
}
}
// convertMultiParams builds a []string temp variable from a slice
// of non-strings and returns the name of the temp variable.
func convertMultiParams(a *API, param string) string {
a.pn(" var %v_ []string", param)
a.pn(" for _, v := range %v {", param)
a.pn(" %v_ = append(%v_, fmt.Sprint(v))", param, param)
a.pn(" }")
return param + "_"
}
func (meth *Method) generateCode() {
res := meth.r // may be nil if a top-level method
a := meth.api
p, pn := a.p, a.pn
pn("\n// method id %q:", meth.Id())
retType := responseType(a, meth.m)
retTypeComma := retType
if retTypeComma != "" {
retTypeComma += ", "
}
args := meth.NewArguments()
methodName := initialCap(meth.m.Name)
prefix := ""
if res != nil {
prefix = initialCap(res.FullName)
}
callName := a.GetName(prefix + methodName + "Call")
pn("\ntype %s struct {", callName)
pn(" s *%s", a.ServiceType())
for _, arg := range args.l {
if arg.location != "query" {
pn(" %s %s", arg.goname, arg.gotype)
}
}
pn(" urlParams_ gensupport.URLParams")
httpMethod := meth.m.HTTPMethod
if httpMethod == "GET" {
pn(" ifNoneMatch_ string")
}
if meth.supportsMediaUpload() {
pn(" mediaInfo_ *gensupport.MediaInfo")
}
pn(" ctx_ context.Context")
pn(" header_ http.Header")
pn("}")
p("\n%s", asComment("", methodName+": "+meth.m.Description))
if res != nil {
if url := canonicalDocsURL[fmt.Sprintf("%v%v/%v", docsLink, res.Name, meth.m.Name)]; url != "" {
pn("// For details, see %v", url)
}
}
var servicePtr string
if res == nil {
pn("func (s *Service) %s(%s) *%s {", methodName, args, callName)
servicePtr = "s"
} else {
pn("func (r *%s) %s(%s) *%s {", resourceGoType(res), methodName, args, callName)
servicePtr = "r.s"
}
pn(" c := &%s{s: %s, urlParams_: make(gensupport.URLParams)}", callName, servicePtr)
for _, arg := range args.l {
// TODO(gmlewis): clean up and consolidate this section.
// See: https://code-review.googlesource.com/#/c/3520/18/google-api-go-generator/gen.go
if arg.location == "query" {
switch arg.gotype {
case "[]string":
pn(" c.urlParams_.SetMulti(%q, append([]string{}, %v...))", arg.apiname, arg.goname)
case "string":
pn(" c.urlParams_.Set(%q, %v)", arg.apiname, arg.goname)
default:
if strings.HasPrefix(arg.gotype, "[]") {
tmpVar := convertMultiParams(a, arg.goname)
pn(" c.urlParams_.SetMulti(%q, %v)", arg.apiname, tmpVar)
} else {
pn(" c.urlParams_.Set(%q, fmt.Sprint(%v))", arg.apiname, arg.goname)
}
}
continue
}
if arg.gotype == "[]string" {
pn(" c.%s = append([]string{}, %s...)", arg.goname, arg.goname) // Make a copy of the []string.
continue
}
pn(" c.%s = %s", arg.goname, arg.goname)
}
pn(" return c")
pn("}")
for _, opt := range meth.OptParams() {
if opt.p.Location != "query" {
panicf("optional parameter has unsupported location %q", opt.p.Location)
}
setter := initialCap(opt.p.Name)
des := opt.p.Description
des = strings.Replace(des, "Optional.", "", 1)
des = strings.TrimSpace(des)
p("\n%s", asComment("", fmt.Sprintf("%s sets the optional parameter %q: %s", setter, opt.p.Name, des)))
addFieldValueComments(p, opt, "", true)
np := new(namePool)
np.Get("c") // take the receiver's name
paramName := np.Get(validGoIdentifer(opt.p.Name))
typePrefix := ""
if opt.p.Repeated {
typePrefix = "..."
}
pn("func (c *%s) %s(%s %s%s) *%s {", callName, setter, paramName, typePrefix, opt.GoType(), callName)
if opt.p.Repeated {
if opt.GoType() == "string" {
pn("c.urlParams_.SetMulti(%q, append([]string{}, %v...))", opt.p.Name, paramName)
} else {
tmpVar := convertMultiParams(a, paramName)
pn(" c.urlParams_.SetMulti(%q, %v)", opt.p.Name, tmpVar)
}
} else {
if opt.GoType() == "string" {
pn("c.urlParams_.Set(%q, %v)", opt.p.Name, paramName)
} else {
pn("c.urlParams_.Set(%q, fmt.Sprint(%v))", opt.p.Name, paramName)
}
}
pn("return c")
pn("}")
}
if meth.supportsMediaUpload() {
comment := "Media specifies the media to upload in one or more chunks. " +
"The chunk size may be controlled by supplying a MediaOption generated by googleapi.ChunkSize. " +
"The chunk size defaults to googleapi.DefaultUploadChunkSize." +
"The Content-Type header used in the upload request will be determined by sniffing the contents of r, " +
"unless a MediaOption generated by googleapi.ContentType is supplied." +
"\nAt most one of Media and ResumableMedia may be set."
// TODO(mcgreevy): Ensure that r is always closed before Do returns, and document this.
// See comments on https://code-review.googlesource.com/#/c/3970/
p("\n%s", asComment("", comment))
pn("func (c *%s) Media(r io.Reader, options ...googleapi.MediaOption) *%s {", callName, callName)
// We check if the body arg, if any, has a content type and apply it here.
// In practice, this only happens for the storage API today.
// TODO(djd): check if we can cope with the developer setting the body's Content-Type field
// after they've made this call.
if ba := args.bodyArg(); ba != nil {
if ba.schema.HasContentType() {
pn(" if ct := c.%s.ContentType; ct != \"\" {", ba.goname)
pn(" options = append([]googleapi.MediaOption{googleapi.ContentType(ct)}, options...)")
pn(" }")
}
}
pn(" c.mediaInfo_ = gensupport.NewInfoFromMedia(r, options)")
pn(" return c")
pn("}")
comment = "ResumableMedia specifies the media to upload in chunks and can be canceled with ctx. " +
"\n\nDeprecated: use Media instead." +
"\n\nAt most one of Media and ResumableMedia may be set. " +
`mediaType identifies the MIME media type of the upload, such as "image/png". ` +
`If mediaType is "", it will be auto-detected. ` +
`The provided ctx will supersede any context previously provided to ` +
`the Context method.`
p("\n%s", asComment("", comment))
pn("func (c *%s) ResumableMedia(ctx context.Context, r io.ReaderAt, size int64, mediaType string) *%s {", callName, callName)
pn(" c.ctx_ = ctx")
pn(" c.mediaInfo_ = gensupport.NewInfoFromResumableMedia(r, size, mediaType)")
pn(" return c")
pn("}")
comment = "ProgressUpdater provides a callback function that will be called after every chunk. " +
"It should be a low-latency function in order to not slow down the upload operation. " +
"This should only be called when using ResumableMedia (as opposed to Media)."
p("\n%s", asComment("", comment))
pn("func (c *%s) ProgressUpdater(pu googleapi.ProgressUpdater) *%s {", callName, callName)
pn(`c.mediaInfo_.SetProgressUpdater(pu)`)
pn("return c")
pn("}")
}
comment := "Fields allows partial responses to be retrieved. " +
"See https://developers.google.com/gdata/docs/2.0/basics#PartialResponse " +
"for more information."
p("\n%s", asComment("", comment))
pn("func (c *%s) Fields(s ...googleapi.Field) *%s {", callName, callName)
pn(`c.urlParams_.Set("fields", googleapi.CombineFields(s))`)
pn("return c")
pn("}")
if httpMethod == "GET" {
// Note that non-GET responses are excluded from supporting If-None-Match.
// See https://github.com/google/google-api-go-client/issues/107 for more info.
comment := "IfNoneMatch sets the optional parameter which makes the operation fail if " +
"the object's ETag matches the given value. This is useful for getting updates " +
"only after the object has changed since the last request. " +
"Use googleapi.IsNotModified to check whether the response error from Do " +
"is the result of In-None-Match."
p("\n%s", asComment("", comment))
pn("func (c *%s) IfNoneMatch(entityTag string) *%s {", callName, callName)
pn(" c.ifNoneMatch_ = entityTag")
pn(" return c")
pn("}")
}
doMethod := "Do method"
if meth.supportsMediaDownload() {
doMethod = "Do and Download methods"
}
commentFmtStr := "Context sets the context to be used in this call's %s. " +
"Any pending HTTP request will be aborted if the provided context is canceled."
comment = fmt.Sprintf(commentFmtStr, doMethod)
p("\n%s", asComment("", comment))
if meth.supportsMediaUpload() {
comment = "This context will supersede any context previously provided to " +
"the ResumableMedia method."
p("%s", asComment("", comment))
}
pn("func (c *%s) Context(ctx context.Context) *%s {", callName, callName)
pn(`c.ctx_ = ctx`)
pn("return c")
pn("}")
comment = "Header returns an http.Header that can be modified by the caller to add " +
"HTTP headers to the request."
p("\n%s", asComment("", comment))
pn("func (c *%s) Header() http.Header {", callName)
pn(" if c.header_ == nil {")
pn(" c.header_ = make(http.Header)")
pn(" }")
pn(" return c.header_")
pn("}")
pn("\nfunc (c *%s) doRequest(alt string) (*http.Response, error) {", callName)
pn(`reqHeaders := make(http.Header)`)
pn("for k, v := range c.header_ {")
pn(" reqHeaders[k] = v")
pn("}")
pn(`reqHeaders.Set("User-Agent",c.s.userAgent())`)
if httpMethod == "GET" {
pn(`if c.ifNoneMatch_ != "" {`)
pn(` reqHeaders.Set("If-None-Match", c.ifNoneMatch_)`)
pn("}")
}
pn("var body io.Reader = nil")
if ba := args.bodyArg(); ba != nil && httpMethod != "GET" {
style := "WithoutDataWrapper"
if a.needsDataWrapper() {
style = "WithDataWrapper"
}
pn("body, err := googleapi.%s.JSONReader(c.%s)", style, ba.goname)
pn("if err != nil { return nil, err }")
pn(`reqHeaders.Set("Content-Type", "application/json")`)
}
pn(`c.urlParams_.Set("alt", alt)`)
pn("urls := googleapi.ResolveRelative(c.s.BasePath, %q)", meth.m.Path)
if meth.supportsMediaUpload() {
pn("if c.mediaInfo_ != nil {")
// Hack guess, since we get a 404 otherwise:
//pn("urls = googleapi.ResolveRelative(%q, %q)", a.apiBaseURL(), meth.mediaUploadPath())
// Further hack. Discovery doc is wrong?
pn(" urls = strings.Replace(urls, %q, %q, 1)", "https://www.googleapis.com/", "https://www.googleapis.com/upload/")
pn(` c.urlParams_.Set("uploadType", c.mediaInfo_.UploadType())`)
pn("}")
pn("if body == nil {")
pn(" body = new(bytes.Buffer)")
pn(` reqHeaders.Set("Content-Type", "application/json")`)
pn("}")
pn("body, getBody, cleanup := c.mediaInfo_.UploadRequest(reqHeaders, body)")
pn("defer cleanup()")
}
pn("urls += \"?\" + c.urlParams_.Encode()")
pn("req, _ := http.NewRequest(%q, urls, body)", httpMethod)
pn("req.Header = reqHeaders")
if meth.supportsMediaUpload() {
pn("gensupport.SetGetBody(req, getBody)")
}
// Replace param values after NewRequest to avoid reencoding them.
// E.g. Cloud Storage API requires '%2F' in entity param to be kept, but url.Parse replaces it with '/'.
argsForLocation := args.forLocation("path")
if len(argsForLocation) > 0 {
pn(`googleapi.Expand(req.URL, map[string]string{`)
for _, arg := range argsForLocation {
pn(`"%s": %s,`, arg.apiname, arg.exprAsString("c."))
}
pn(`})`)
}
pn("return gensupport.SendRequest(c.ctx_, c.s.client, req)")
pn("}")
if meth.supportsMediaDownload() {
pn("\n// Download fetches the API endpoint's \"media\" value, instead of the normal")
pn("// API response value. If the returned error is nil, the Response is guaranteed to")
pn("// have a 2xx status code. Callers must close the Response.Body as usual.")
pn("func (c *%s) Download(opts ...googleapi.CallOption) (*http.Response, error) {", callName)
pn(`gensupport.SetOptions(c.urlParams_, opts...)`)
pn(`res, err := c.doRequest("media")`)
pn("if err != nil { return nil, err }")
pn("if err := googleapi.CheckMediaResponse(res); err != nil {")
pn("res.Body.Close()")
pn("return nil, err")
pn("}")
pn("return res, nil")
pn("}")
}
mapRetType := strings.HasPrefix(retTypeComma, "map[")
pn("\n// Do executes the %q call.", meth.m.ID)
if retTypeComma != "" && !mapRetType {
commentFmtStr := "Exactly one of %v or error will be non-nil. " +
"Any non-2xx status code is an error. " +
"Response headers are in either %v.ServerResponse.Header " +
"or (if a response was returned at all) in error.(*googleapi.Error).Header. " +
"Use googleapi.IsNotModified to check whether the returned error was because " +
"http.StatusNotModified was returned."
comment := fmt.Sprintf(commentFmtStr, retType, retType)
p("%s", asComment("", comment))
}
pn("func (c *%s) Do(opts ...googleapi.CallOption) (%serror) {", callName, retTypeComma)
nilRet := ""
if retTypeComma != "" {
nilRet = "nil, "
}
pn(`gensupport.SetOptions(c.urlParams_, opts...)`)
pn(`res, err := c.doRequest("json")`)
if retTypeComma != "" && !mapRetType {
pn("if res != nil && res.StatusCode == http.StatusNotModified {")
pn(" if res.Body != nil { res.Body.Close() }")
pn(" return nil, &googleapi.Error{")
pn(" Code: res.StatusCode,")
pn(" Header: res.Header,")
pn(" }")
pn("}")
}
pn("if err != nil { return %serr }", nilRet)
pn("defer googleapi.CloseBody(res)")
pn("if err := googleapi.CheckResponse(res); err != nil { return %serr }", nilRet)
if meth.supportsMediaUpload() {
pn(`rx := c.mediaInfo_.ResumableUpload(res.Header.Get("Location"))`)
pn("if rx != nil {")
pn(" rx.Client = c.s.client")
pn(" rx.UserAgent = c.s.userAgent()")
pn(" ctx := c.ctx_")
pn(" if ctx == nil {")
// TODO(mcgreevy): Require context when calling Media, or Do.
pn(" ctx = context.TODO()")
pn(" }")
pn(" res, err = rx.Upload(ctx)")
pn(" if err != nil { return %serr }", nilRet)
pn(" defer res.Body.Close()")
pn(" if err := googleapi.CheckResponse(res); err != nil { return %serr }", nilRet)
pn("}")
}
if retTypeComma == "" {
pn("return nil")
} else {
if mapRetType {
pn("var ret %s", responseType(a, meth.m))
} else {
pn("ret := &%s{", responseTypeLiteral(a, meth.m))
pn(" ServerResponse: googleapi.ServerResponse{")
pn(" Header: res.Header,")
pn(" HTTPStatusCode: res.StatusCode,")
pn(" },")
pn("}")
}
if a.needsDataWrapper() {
pn("target := &struct {")
pn(" Data %s `json:\"data\"`", responseType(a, meth.m))
pn("}{ret}")
} else {
pn("target := &ret")
}
pn("if err := gensupport.DecodeResponse(target, res); err != nil { return nil, err }")
pn("return ret, nil")
}
bs, _ := json.MarshalIndent(meth.m.JSONMap, "\t// ", " ")
pn("// %s\n", string(bs))
pn("}")
if ptg, rname, ok := meth.supportsPaging(); ok {
// We can assume retType is non-empty.
pn("")
pn("// Pages invokes f for each page of results.")
pn("// A non-nil error returned from f will halt the iteration.")
pn("// The provided context supersedes any context provided to the Context method.")
pn("func (c *%s) Pages(ctx context.Context, f func(%s) error) error {", callName, retType)
pn(" c.ctx_ = ctx")
pn(` defer %s // reset paging to original point`, ptg.genDeferBody())
pn(" for {")
pn(" x, err := c.Do()")
pn(" if err != nil { return err }")
pn(" if err := f(x); err != nil { return err }")
pn(` if x.%s == "" { return nil }`, rname)
pn(ptg.genSet("x." + rname))
pn(" }")
pn("}")
}
}
// A Field provides methods that describe the characteristics of a Param or Property.
type Field interface {
Default() string
Enum() ([]string, bool)
EnumDescriptions() []string
UnfortunateDefault() bool
}
type Param struct {
method *Method
p *disco.Parameter
callFieldName string // empty means to use the default
}
func (p *Param) Default() string {
return p.p.Default
}
func (p *Param) Enum() ([]string, bool) {
if e := p.p.Enums; e != nil {
return e, true
}
return nil, false
}
func (p *Param) EnumDescriptions() []string {
return p.p.EnumDescriptions
}
func (p *Param) UnfortunateDefault() bool {
// We do not do anything special for Params with unfortunate defaults.
return false
}
func (p *Param) GoType() string {
typ, format := p.p.Type, p.p.Format
if typ == "string" && strings.Contains(format, "int") && p.p.Location != "query" {
panic("unexpected int parameter encoded as string, not in query: " + p.p.Name)
}
t, ok := simpleTypeConvert(typ, format)
if !ok {
panic("failed to convert parameter type " + fmt.Sprintf("type=%q, format=%q", typ, format))
}
return t
}
// goCallFieldName returns the name of this parameter's field in a
// method's "Call" struct.
func (p *Param) goCallFieldName() string {
if p.callFieldName != "" {
return p.callFieldName
}
return validGoIdentifer(p.p.Name)
}
// APIMethods returns top-level ("API-level") methods. They don't have an associated resource.
func (a *API) APIMethods() []*Method {
meths := []*Method{}
for _, m := range a.doc.Methods {
meths = append(meths, &Method{
api: a,
r: nil, // to be explicit
m: m,
})
}
return meths
}
func resolveRelative(basestr, relstr string) string {
u, err := url.Parse(basestr)
if err != nil {
panicf("Error parsing base URL %q: %v", basestr, err)
}
rel, err := url.Parse(relstr)
if err != nil {
panicf("Error parsing relative URL %q: %v", relstr, err)
}
u = u.ResolveReference(rel)
return u.String()
}
func (meth *Method) NewArguments() (args *arguments) {
args = &arguments{
method: meth,
m: make(map[string]*argument),
}
po := meth.m.ParameterOrder
if len(po) > 0 {
for _, pname := range po {
arg := meth.NewArg(pname, meth.NamedParam(pname))
args.AddArg(arg)
}
}
if rs := meth.m.Request; rs != nil {
args.AddArg(meth.NewBodyArg(rs))
}
return
}
func (meth *Method) NewBodyArg(ds *disco.Schema) *argument {
s := meth.api.schemaNamed(ds.RefSchema.Name)
return &argument{
goname: validGoIdentifer(strings.ToLower(ds.Ref)),
apiname: "REQUEST",
gotype: "*" + s.GoName(),
apitype: ds.Ref,
location: "body",
schema: s,
}
}
func (meth *Method) NewArg(apiname string, p *Param) *argument {
apitype := p.p.Type
des := p.p.Description
goname := validGoIdentifer(apiname) // but might be changed later, if conflicts
if strings.Contains(des, "identifier") && !strings.HasSuffix(strings.ToLower(goname), "id") {
goname += "id" // yay
p.callFieldName = goname
}
gotype := mustSimpleTypeConvert(apitype, p.p.Format)
if p.p.Repeated {
gotype = "[]" + gotype
}
return &argument{
apiname: apiname,
apitype: apitype,
goname: goname,
gotype: gotype,
location: p.p.Location,
}
}
type argument struct {
method *Method
schema *Schema // Set if location == "body".
apiname, apitype string
goname, gotype string
location string // "path", "query", "body"
}
func (a *argument) String() string {
return a.goname + " " + a.gotype
}
func (a *argument) exprAsString(prefix string) string {
switch a.gotype {
case "[]string":
log.Printf("TODO(bradfitz): only including the first parameter in path query.")
return prefix + a.goname + `[0]`
case "string":
return prefix + a.goname
case "integer", "int64":
return "strconv.FormatInt(" + prefix + a.goname + ", 10)"
case "uint64":
return "strconv.FormatUint(" + prefix + a.goname + ", 10)"
case "bool":
return "strconv.FormatBool(" + prefix + a.goname + ")"
}
log.Panicf("unknown type: apitype=%q, gotype=%q", a.apitype, a.gotype)
return ""
}
// arguments are the arguments that a method takes
type arguments struct {
l []*argument
m map[string]*argument
method *Method
}
func (args *arguments) forLocation(loc string) []*argument {
matches := make([]*argument, 0)
for _, arg := range args.l {
if arg.location == loc {
matches = append(matches, arg)
}
}
return matches
}
func (args *arguments) bodyArg() *argument {
for _, arg := range args.l {
if arg.location == "body" {
return arg
}
}
return nil
}
func (args *arguments) AddArg(arg *argument) {
n := 1
oname := arg.goname
for {
_, present := args.m[arg.goname]
if !present {
args.m[arg.goname] = arg
args.l = append(args.l, arg)
return
}
n++
arg.goname = fmt.Sprintf("%s%d", oname, n)
}
}
func (a *arguments) String() string {
var buf bytes.Buffer
for i, arg := range a.l {
if i != 0 {
buf.Write([]byte(", "))
}
buf.Write([]byte(arg.String()))
}
return buf.String()
}
var urlRE = regexp.MustCompile(`^http\S+$`)
func asComment(pfx, c string) string {
var buf bytes.Buffer
const maxLen = 70
r := strings.NewReplacer(
"\n", "\n"+pfx+"// ",
"`\"", `"`,
"\"`", `"`,
)
for len(c) > 0 {
line := c
if len(line) < maxLen {
fmt.Fprintf(&buf, "%s// %s\n", pfx, r.Replace(line))
break
}
// Don't break URLs.
if !urlRE.MatchString(line[:maxLen]) {
line = line[:maxLen]
}
si := strings.LastIndex(line, " ")
if nl := strings.Index(line, "\n"); nl != -1 && nl < si {
si = nl
}
if si != -1 {
line = line[:si]
}
fmt.Fprintf(&buf, "%s// %s\n", pfx, r.Replace(line))
c = c[len(line):]
if si != -1 {
c = c[1:]
}
}
return buf.String()
}
func simpleTypeConvert(apiType, format string) (gotype string, ok bool) {
// From http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1
switch apiType {
case "boolean":
gotype = "bool"
case "string":
gotype = "string"
switch format {
case "int64", "uint64", "int32", "uint32":
gotype = format
}
case "number":
gotype = "float64"
case "integer":
gotype = "int64"
case "any":
gotype = "interface{}"
}
return gotype, gotype != ""
}
func mustSimpleTypeConvert(apiType, format string) string {
if gotype, ok := simpleTypeConvert(apiType, format); ok {
return gotype
}
panic(fmt.Sprintf("failed to simpleTypeConvert(%q, %q)", apiType, format))
}
func responseType(api *API, m *disco.Method) string {
if m.Response == nil {
return ""
}
ref := m.Response.Ref
if ref != "" {
if s := api.schemas[ref]; s != nil {
return s.GoReturnType()
}
return "*" + ref
}
return ""
}
// Strips the leading '*' from a type name so that it can be used to create a literal.
func responseTypeLiteral(api *API, m *disco.Method) string {
v := responseType(api, m)
if strings.HasPrefix(v, "*") {
return v[1:]
}
return v
}
// initialCap returns the identifier with a leading capital letter.
// it also maps "foo-bar" to "FooBar".
func initialCap(ident string) string {
if ident == "" {
panic("blank identifier")
}
return depunct(ident, true)
}
func validGoIdentifer(ident string) string {
id := depunct(ident, false)
switch id {
case "break", "default", "func", "interface", "select",
"case", "defer", "go", "map", "struct",
"chan", "else", "goto", "package", "switch",
"const", "fallthrough", "if", "range", "type",
"continue", "for", "import", "return", "var":
return id + "_"
}
return id
}
// depunct removes '-', '.', '$', '/', '_' from identifers, making the
// following character uppercase. Multiple '_' are preserved.
func depunct(ident string, needCap bool) string {
var buf bytes.Buffer
preserve_ := false
for i, c := range ident {
if c == '_' {
if preserve_ || strings.HasPrefix(ident[i:], "__") {
preserve_ = true
} else {
needCap = true
continue
}
} else {
preserve_ = false
}
if c == '-' || c == '.' || c == '$' || c == '/' {
needCap = true
continue
}
if needCap {
c = unicode.ToUpper(c)
needCap = false
}
buf.WriteByte(byte(c))
}
return buf.String()
}
func addFieldValueComments(p func(format string, args ...interface{}), field Field, indent string, blankLine bool) {
var lines []string
if enum, ok := field.Enum(); ok {
desc := field.EnumDescriptions()
lines = append(lines, asComment(indent, "Possible values:"))
defval := field.Default()
for i, v := range enum {
more := ""
if v == defval {
more = " (default)"
}
if len(desc) > i && desc[i] != "" {
more = more + " - " + desc[i]
}
lines = append(lines, asComment(indent, ` "`+v+`"`+more))
}
} else if field.UnfortunateDefault() {
lines = append(lines, asComment("\t", fmt.Sprintf("Default: %s", field.Default())))
}
if blankLine && len(lines) > 0 {
p(indent + "//\n")
}
for _, l := range lines {
p("%s", l)
}
}