|
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372 |
- // 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)
- }
- }
|