a Google API -> Go code generator and base library
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 0000000..9d5826f
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,12 @@
+_obj
+_testmain.go
+_test
+clientid.dat
+clientsecret.dat
+google-api-go-gen
+
+syntax:glob
+*.6
+*.8
+*~
+*.out
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..263aa7a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2011 Google Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..94462d6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,4 @@
+all:
+	make -C google-api install
+	make -C google-api-go-generator install
+	google-api-go-generator/google-api-go-gen -cache -install -api=*
diff --git a/NOTES b/NOTES
new file mode 100644
index 0000000..3b10889
--- /dev/null
+++ b/NOTES
@@ -0,0 +1,13 @@
+Discovery Service:
+http://code.google.com/apis/discovery/
+http://code.google.com/apis/discovery/v1/reference.html
+
+The "type" key:
+http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1
+
+The "format" key:
+http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23
+http://code.google.com/apis/discovery/v1/reference.html#parameter-format-summary
+
+Google JSON format docs:
+http://google-styleguide.googlecode.com/svn/trunk/jsoncstyleguide.xml
diff --git a/README b/README
new file mode 100644
index 0000000..9aca57b
--- /dev/null
+++ b/README
@@ -0,0 +1,10 @@
+Most of this project is auto-generated.
+
+The notable directories which are not auto-generated:
+
+   google-api-go-generator/ -- the generator itself
+   google-api/              -- shared common code, used by auto-generated code
+   examples/                -- sample code
+
+When changing the generator, re-compile all APIs and submit the
+modified APIs in the same CL as the generator changes itself.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..4d75f62
--- /dev/null
+++ b/TODO
@@ -0,0 +1,8 @@
+-- stringified ints (type "string", format "uint64", etc)
+   http://codereview.appspot.com/4918051/
+
+-- more auto-generate docs
+
+-- enums (check them? document them?)
+
+-- more examples
diff --git a/build-examples.sh b/build-examples.sh
new file mode 100755
index 0000000..ed63454
--- /dev/null
+++ b/build-examples.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+set -e
+
+make -C google-api install
+make -C google-api-go-generator install
+google-api-go-generator/google-api-go-gen -cache -api=buzz:v1
+google-api-go-generator/google-api-go-gen -cache -api=tasks:v1
+google-api-go-generator/google-api-go-gen -cache -api=urlshortener:v1
+make -C buzz/v1 install
+make -C tasks/v1 install
+make -C urlshortener/v1 install
+make -C examples
diff --git a/examples/Makefile b/examples/Makefile
new file mode 100644
index 0000000..b9df745
--- /dev/null
+++ b/examples/Makefile
@@ -0,0 +1,15 @@
+include $(GOROOT)/src/Make.inc
+
+PREREQ=$(QUOTED_GOROOT)/pkg/$(GOOS)_$(GOARCH)/google-api-go-client.googlecode.com/hg/buzz/v1.a\
+	$(QUOTED_GOROOT)/pkg/$(GOOS)_$(GOARCH)/google-api-go-client.googlecode.com/hg/tasks/v1.a\
+	$(QUOTED_GOROOT)/pkg/$(GOOS)_$(GOARCH)/google-api-go-client.googlecode.com/hg/urlshortener/v1.a\
+
+TARG=go-api-demo
+
+GOFILES=main.go\
+	debug.go\
+	buzz.go\
+	tasks.go\
+	urlshortener.go\
+
+include $(GOROOT)/src/Make.cmd
diff --git a/examples/buzz.go b/examples/buzz.go
new file mode 100644
index 0000000..44a20d7
--- /dev/null
+++ b/examples/buzz.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+	"flag"
+	"http"
+	"os"
+	"log"
+
+	buzz "google-api-go-client.googlecode.com/hg/buzz/v1"
+)
+
+func init() {
+	registerDemo("buzz", buzz.BuzzScope, buzzMain)
+}
+
+func buzzMain(client *http.Client, argv []string) {
+	fs := flag.NewFlagSet("buzz", flag.ExitOnError)
+	includeMedia := fs.Bool("media", false, "Include an image")
+	fs.Parse(argv)
+
+	svc, _ := buzz.New(client)
+	call := svc.Activities.Insert("@me", &buzz.Activity{
+		Title: "a buzz post",
+		Object: &buzz.ActivityObject{
+			Type:    "http://activitystrea.ms/schema/1.0/note",
+			Content: "the content of a buzz post",
+		},
+	})
+	if *includeMedia {
+		if f, err := findGopherFile(); err == nil {
+			call.Media(f)
+			defer f.Close()
+		}
+	}
+	activity, err := call.Do()
+	log.Printf("Buzz Activities.Insert: (%#v, %v)", activity, err)
+}
+
+func findGopherFile() (*os.File, os.Error) {
+	f, err := os.Open("gopher.png")
+	if err == nil {
+		return f, nil
+	}
+	return os.Open("examples/gopher.png")
+}
diff --git a/examples/debug.go b/examples/debug.go
new file mode 100644
index 0000000..fc3b45d
--- /dev/null
+++ b/examples/debug.go
@@ -0,0 +1,72 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"http"
+	"io"
+	"io/ioutil"
+	"os"
+)
+
+type logTransport struct {
+	rt http.RoundTripper
+}
+
+func (t *logTransport) RoundTrip(req *http.Request) (*http.Response, os.Error) {
+	var buf bytes.Buffer
+
+	os.Stdout.Write([]byte("\n[request]\n"))
+	if req.Body != nil {
+		req.Body = ioutil.NopCloser(&readButCopy{req.Body, &buf})
+	}
+	req.Write(os.Stdout)
+	if req.Body != nil {
+		req.Body = ioutil.NopCloser(&buf)
+	}
+	os.Stdout.Write([]byte("\n[/request]\n"))
+
+	res, err := t.rt.RoundTrip(req)
+
+	fmt.Printf("[response]\n")
+	if err != nil {
+		fmt.Printf("ERROR: %v", err)
+	} else {
+		body := res.Body
+		res.Body = nil
+		res.Write(os.Stdout)
+		if body != nil {
+			res.Body = ioutil.NopCloser(&echoAsRead{body})
+		}
+	}
+
+	return res, err
+}
+
+type echoAsRead struct {
+	src io.Reader
+}
+
+func (r *echoAsRead) Read(p []byte) (int, os.Error) {
+	n, err := r.src.Read(p)
+	if n > 0 {
+		os.Stdout.Write(p[:n])
+	}
+	if err == os.EOF {
+		fmt.Printf("\n[/response]\n")
+	}
+	return n, err
+}
+
+type readButCopy struct {
+	src io.Reader
+	dst io.Writer
+}
+
+func (r *readButCopy) Read(p []byte) (int, os.Error) {
+	n, err := r.src.Read(p)
+	if n > 0 {
+		r.dst.Write(p[:n])
+	}
+	return n, err
+}
diff --git a/examples/gopher.png b/examples/gopher.png
new file mode 100644
index 0000000..4cf1da8
--- /dev/null
+++ b/examples/gopher.png
Binary files differ
diff --git a/examples/main.go b/examples/main.go
new file mode 100644
index 0000000..57d28f4
--- /dev/null
+++ b/examples/main.go
@@ -0,0 +1,213 @@
+// 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 (
+	"exec"
+	"flag"
+	"fmt"
+	"gob"
+	"http"
+	"http/httptest"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"log"
+	"runtime"
+	"strings"
+	"time"
+	"url"
+
+	"goauth2.googlecode.com/hg/oauth"
+)
+
+var config = &oauth.Config{
+	ClientId:     "", // Set by --clientid or --clientid_file
+	ClientSecret: "", // Set by --secret or --secret_file
+	Scope:        "", // filled in per-API
+	AuthURL:      "https://accounts.google.com/o/oauth2/auth",
+	TokenURL:     "https://accounts.google.com/o/oauth2/token",
+}
+
+// Flags
+var (
+	clientId     = flag.String("clientid", "", "OAuth Client ID.  If non-empty, overrides --clientid_file")
+	clientIdFile = flag.String("clientid_file", "clientid.dat",
+		"Name of a file containing just the project's OAuth Client ID from https://code.google.com/apis/console/")
+	secret     = flag.String("secret", "", "OAuth Client Secret.  If non-empty, overrides --secret_file")
+	secretFile = flag.String("secret_file", "clientsecret.dat",
+		"Name of a file containing just the project's OAuth Client Secret from https://code.google.com/apis/console/")
+	cacheToken = flag.Bool("cachetoken", true, "cache the OAuth token")
+	debug      = flag.Bool("debug", false, "show HTTP traffic")
+)
+
+func usage() {
+	fmt.Fprintf(os.Stderr, "Usage: go-api-demo <api-demo-name> [api name args]\n\nPossible APIs:\n\n")
+	for n, _ := range demoFunc {
+		fmt.Fprintf(os.Stderr, "  * %s\n", n)
+	}
+	os.Exit(2)
+}
+
+func main() {
+	flag.Parse()
+	if flag.NArg() == 0 {
+		usage()
+	}
+
+	name := flag.Arg(0)
+	demo, ok := demoFunc[name]
+	if !ok {
+		usage()
+	}
+
+	config.Scope = demoScope[name]
+	config.ClientId = valueOrFileContents(*clientId, *clientIdFile)
+	config.ClientSecret = valueOrFileContents(*secret, *secretFile)
+
+	c := getOAuthClient(config)
+	demo(c, flag.Args()[1:])
+}
+
+var (
+	demoFunc  = make(map[string]func(*http.Client, []string))
+	demoScope = make(map[string]string)
+)
+
+func registerDemo(name, scope string, main func(c *http.Client, argv []string)) {
+	if demoFunc[name] != nil {
+		panic(name + " already registered")
+	}
+	demoFunc[name] = main
+	demoScope[name] = scope
+}
+
+func osUserCacheDir() string {
+	switch runtime.GOOS {
+	case "darwin":
+		return filepath.Join(os.Getenv("HOME"), "Library", "Caches")
+	case "linux", "freebsd":
+		return filepath.Join(os.Getenv("HOME"), ".cache")
+	}
+	log.Printf("TODO: osUserCacheDir on GOOS %q", runtime.GOOS)
+	return "."
+}
+
+func tokenCacheFile(config *oauth.Config) string {
+	return filepath.Join(osUserCacheDir(), url.QueryEscape(
+		fmt.Sprintf("go-api-demo-%s-%s-%s", config.ClientId, config.ClientSecret, config.Scope)))
+}
+
+func tokenFromFile(file string) (*oauth.Token, os.Error) {
+	if !*cacheToken {
+		return nil, os.NewError("--cachetoken is false")
+	}
+	f, err := os.Open(file)
+	if err != nil {
+		return nil, err
+	}
+	t := new(oauth.Token)
+	err = gob.NewDecoder(f).Decode(t)
+	return t, err
+}
+
+func saveToken(file string, token *oauth.Token) {
+	f, err := os.Create(file)
+	if err != nil {
+		log.Printf("Warning: failed to cache oauth token: %v", err)
+		return
+	}
+	defer f.Close()
+	gob.NewEncoder(f).Encode(token)
+}
+
+func condDebugTransport(rt http.RoundTripper) http.RoundTripper {
+	if *debug {
+		return &logTransport{rt}
+	}
+	return rt
+}
+
+func getOAuthClient(config *oauth.Config) *http.Client {
+	cacheFile := tokenCacheFile(config)
+	token, err := tokenFromFile(cacheFile)
+	if err != nil {
+		token = tokenFromWeb(config)
+		saveToken(cacheFile, token)
+	} else {
+		log.Printf("Using cached token %#v from %q", token, cacheFile)
+	}
+
+	t := &oauth.Transport{
+		Token:     token,
+		Config:    config,
+		Transport: condDebugTransport(http.DefaultTransport),
+	}
+	return t.Client()
+}
+
+func tokenFromWeb(config *oauth.Config) *oauth.Token {
+	ch := make(chan string)
+	randState := fmt.Sprintf("st%d", time.Nanoseconds())
+	ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+		if req.URL.Path == "/favicon.ico" {
+			http.Error(rw, "", 404)
+			return
+		}
+		if req.FormValue("state") != randState {
+			log.Printf("State doesn't match: req = %#v", req)
+			http.Error(rw, "", 500)
+			return
+		}
+		if code := req.FormValue("code"); code != "" {
+			fmt.Fprintf(rw, "<h1>Success</h1>Authorized.")
+			rw.(http.Flusher).Flush()
+			ch <- code
+			return
+		}
+		log.Printf("no code")
+		http.Error(rw, "", 500)
+	}))
+	defer ts.Close()
+
+	config.RedirectURL = ts.URL
+	authUrl := config.AuthCodeURL(randState)
+	go openUrl(authUrl)
+	log.Printf("Authorize this app at: %s", authUrl)
+	code := <-ch
+	log.Printf("Got code: %s", code)
+
+	t := &oauth.Transport{
+		Config:    config,
+		Transport: condDebugTransport(http.DefaultTransport),
+	}
+	_, err := t.Exchange(code)
+	if err != nil {
+		log.Fatalf("Token exchange error: %v", err)
+	}
+	return t.Token
+}
+
+func openUrl(url string) {
+	try := []string{"xdg-open", "google-chrome", "open"}
+	for _, bin := range try {
+		err := exec.Command(bin, url).Run()
+		if err == nil {
+			return
+		}
+	}
+	log.Printf("Error opening URL in browser.")
+}
+
+func valueOrFileContents(value string, filename string) string {
+	if value != "" {
+		return value
+	}
+	slurp, err := ioutil.ReadFile(filename)
+	if err != nil {
+		log.Fatalf("Error reading %q: %v", filename, err)
+	}
+	return strings.TrimSpace(string(slurp))
+}
diff --git a/examples/tasks.go b/examples/tasks.go
new file mode 100644
index 0000000..9d123cd
--- /dev/null
+++ b/examples/tasks.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+	"http"
+	"log"
+
+	tasks "google-api-go-client.googlecode.com/hg/tasks/v1"
+)
+
+func init() {
+	registerDemo("tasks", tasks.TasksScope, tasksMain)
+}
+
+func tasksMain(client *http.Client, argv []string) {
+	taskapi, _ := tasks.New(client)
+	task, err := taskapi.Tasks.Insert("@default", &tasks.Task{
+		Title: "finish this API code generator thing",
+		Notes: "ummmm",
+		Due:   "2011-10-15T12:00:00.000Z",
+	}).Do()
+	log.Printf("Got task, err: %#v, %v", task, err)
+}
diff --git a/examples/urlshortener.go b/examples/urlshortener.go
new file mode 100644
index 0000000..553b42a
--- /dev/null
+++ b/examples/urlshortener.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+	"fmt"
+	"http"
+	"os"
+	"log"
+	"strings"
+
+	urlshortener "google-api-go-client.googlecode.com/hg/urlshortener/v1"
+)
+
+func init() {
+	registerDemo("urlshortener", urlshortener.UrlshortenerScope, urlShortenerMain)
+}
+
+func urlShortenerMain(client *http.Client, argv []string) {
+	if len(argv) != 1 {
+		fmt.Fprintf(os.Stderr, "Usage: urlshortener http://goo.gl/xxxxx     (to look up details)\n")
+		fmt.Fprintf(os.Stderr, "       urlshortener http://example.com/long (to shorten)\n")
+		return
+	}
+
+	svc, _ := urlshortener.New(client)
+	urlstr := argv[0]
+
+	// short -> long
+	if strings.HasPrefix(urlstr, "http://goo.gl/") || strings.HasPrefix(urlstr, "https://goo.gl/") {
+		url, err := svc.Url.Get(urlstr).Do()
+		if err != nil {
+			log.Fatalf("URL Get: %v", err)
+		}
+		fmt.Printf("Lookup of %s: %s\n", urlstr, url.LongUrl)
+		return
+	}
+
+	// long -> short
+	url, err := svc.Url.Insert(&urlshortener.Url{
+		Kind:    "urlshortener#url", // Not really needed
+		LongUrl: urlstr,
+	}).Do()
+	if err != nil {
+		log.Fatalf("URL Insert: %v", err)
+	}
+	fmt.Printf("Shortened %s => %s\n", urlstr, url.Id)
+}
diff --git a/google-api/Makefile b/google-api/Makefile
new file mode 100644
index 0000000..11cb3fb
--- /dev/null
+++ b/google-api/Makefile
@@ -0,0 +1,4 @@
+include $(GOROOT)/src/Make.inc
+TARG=google-api-go-client.googlecode.com/hg/google-api
+GOFILES=googleapi.go
+include $(GOROOT)/src/Make.pkg
diff --git a/google-api/googleapi.go b/google-api/googleapi.go
new file mode 100644
index 0000000..1b3bba0
--- /dev/null
+++ b/google-api/googleapi.go
@@ -0,0 +1,234 @@
+// 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 googleapi contains the common code shared by all Google API
+// libraries.
+package googleapi
+
+import (
+	"bytes"
+	"fmt"
+	"http"
+	"io"
+	"io/ioutil"
+	"json"
+	"mime/multipart"
+	"net/textproto"
+	"os"
+	"strings"
+	"url"
+)
+
+// ContentTyper is an interface for Readers which know (or would like
+// to override) their Content-Type. If a media body doesn't implement
+// ContentTyper, the type is sniffed from the content using
+// http.DetectContentType.
+type ContentTyper interface {
+	ContentType() string
+}
+
+const Version = "0.5"
+
+type Error struct {
+	Code    int    `json:"code"`
+	Message string `json:"message"`
+}
+
+func (e *Error) String() string {
+	return fmt.Sprintf("googleapi: Error %d: %s", e.Code, e.Message)
+}
+
+type errorReply struct {
+	Error *Error `json:"error"`
+}
+
+func CheckResponse(res *http.Response) os.Error {
+	if res.StatusCode >= 200 && res.StatusCode <= 299 {
+		return nil
+	}
+	slurp, err := ioutil.ReadAll(res.Body)
+	if err == nil {
+		jerr := new(errorReply)
+		err = json.Unmarshal(slurp, jerr)
+		if err == nil && jerr.Error != nil {
+			return jerr.Error
+		}
+	}
+	return fmt.Errorf("googleapi: got HTTP response code %d and error reading body: %v",
+		res.StatusCode, err)
+}
+
+type MarshalStyle bool
+
+var WithDataWrapper = MarshalStyle(true)
+var WithoutDataWrapper = MarshalStyle(false)
+
+func (wrap MarshalStyle) JSONReader(v interface{}) (io.Reader, os.Error) {
+	buf := new(bytes.Buffer)
+	if wrap {
+		buf.Write([]byte(`{"data": `))
+	}
+	err := json.NewEncoder(buf).Encode(v)
+	if err != nil {
+		return nil, err
+	}
+	if wrap {
+		buf.Write([]byte(`}`))
+	}
+	return buf, nil
+}
+
+func getMediaType(media io.Reader) (io.Reader, string) {
+	if typer, ok := media.(ContentTyper); ok {
+		return media, typer.ContentType()
+	}
+
+	typ := "application/octet-stream"
+	buf := make([]byte, 1024)
+	n, err := media.Read(buf)
+	buf = buf[:n]
+	if err == nil {
+		typ = http.DetectContentType(buf)
+	}
+	return io.MultiReader(bytes.NewBuffer(buf), media), typ
+}
+
+type Lengther interface {
+	Len() int
+}
+
+// endingWithErrorReader from r until it returns an error.  If the
+// final error from r is os.EOF and e is non-nil, e is used instead.
+type endingWithErrorReader struct {
+	r io.Reader
+	e os.Error
+}
+
+func (er endingWithErrorReader) Read(p []byte) (n int, err os.Error) {
+	n, err = er.r.Read(p)
+	if err == os.EOF && er.e != nil {
+		err = er.e
+	}
+	return
+}
+
+func getReaderSize(r io.Reader) (io.Reader, int64) {
+	// Ideal case, the reader knows its own size.
+	if lr, ok := r.(Lengther); ok {
+		return r, int64(lr.Len())
+	}
+
+	// But maybe it's a seeker and we can seek to the end to find its size.
+	if s, ok := r.(io.Seeker); ok {
+		pos0, err := s.Seek(0, os.SEEK_CUR)
+		if err == nil {
+			posend, err := s.Seek(0, os.SEEK_END)
+			if err == nil {
+				_, err = s.Seek(pos0, os.SEEK_SET)
+				if err == nil {
+					return r, posend - pos0
+				} else {
+					// We moved it forward but can't restore it.
+					// Seems unlikely, but can't really restore now.
+					return endingWithErrorReader{strings.NewReader(""), err}, posend - pos0
+				}
+			}
+		}
+	}
+
+	// Otherwise we have to make a copy to calculate how big the reader is.
+	buf := new(bytes.Buffer)
+	// TODO(bradfitz): put a cap on this copy? spill to disk after
+	// a certain point?
+	_, err := io.Copy(buf, r)
+	return endingWithErrorReader{buf, err}, int64(buf.Len())
+}
+
+func typeHeader(contentType string) textproto.MIMEHeader {
+	h := make(textproto.MIMEHeader)
+	h.Set("Content-Type", contentType)
+	return h
+}
+
+// countingWriter counts the number of bytes it receives to write, but
+// discards them.
+type countingWriter struct {
+	n *int64
+}
+
+func (w countingWriter) Write(p []byte) (int, os.Error) {
+	*w.n += int64(len(p))
+	return len(p), nil
+}
+
+// ConditionallyIncludeMedia does nothing if media is nil.
+//
+// bodyp is an in/out parameter.  It should initially point to the
+// reader of the application/json (or whatever) payload to send in the
+// API request.  It's updated to point to the multipart body reader.
+//
+// ctypep is an in/out parameter.  It should initially point to the
+// content type of the bodyp, usually "application/json".  It's updated
+// to the "multipart/related" content type, with random boundary.
+//
+// The return value is the content-length of the entire multpart body.
+func ConditionallyIncludeMedia(media io.Reader, bodyp *io.Reader, ctypep *string) (totalContentLength int64, ok bool) {
+	if media == nil {
+		return
+	}
+	// Get the media type and size. The type check might return a
+	// different reader instance, so do the size check first,
+	// which looks at the specific type of the io.Reader.
+	media, mediaSize := getReaderSize(media)
+	media, mediaType := getMediaType(media)
+	body, bodyType := *bodyp, *ctypep
+	body, bodySize := getReaderSize(body)
+
+	// Calculate how big the the multipart will be.
+	{
+		totalContentLength = bodySize + mediaSize
+		mpw := multipart.NewWriter(countingWriter{&totalContentLength})
+		mpw.CreatePart(typeHeader(bodyType))
+		mpw.CreatePart(typeHeader(mediaType))
+		mpw.Close()
+	}
+
+	pr, pw := io.Pipe()
+	mpw := multipart.NewWriter(pw)
+	*bodyp = pr
+	*ctypep = "multipart/related; boundary=" + mpw.Boundary()
+	go func() {
+		defer pw.Close()
+		defer mpw.Close()
+
+		w, err := mpw.CreatePart(typeHeader(bodyType))
+		if err != nil {
+			return
+		}
+		_, err = io.Copy(w, body)
+		if err != nil {
+			return
+		}
+
+		w, err = mpw.CreatePart(typeHeader(mediaType))
+		if err != nil {
+			return
+		}
+		_, err = io.Copy(w, media)
+		if err != nil {
+			return
+		}
+	}()
+	return totalContentLength, true
+}
+
+func ResolveRelative(basestr, relstr string) string {
+	u, _ := url.Parse(basestr)
+	rel, _ := url.Parse(relstr)
+	u = u.ResolveReference(rel)
+	us := u.String()
+	us = strings.Replace(us, "%7B", "{", -1)
+	us = strings.Replace(us, "%7D", "}", -1)
+	return us
+}