googleapi: add types for slices of numbers that marshal to stringified JSON
Thanks to Paul Carleton for debugging & initial patch.
No API rebuild yet.
R=golang-dev, r
CC=golang-dev
https://codereview.appspot.com/15900044
diff --git a/google-api-go-generator/gen.go b/google-api-go-generator/gen.go
index a5f544b..da7709f 100644
--- a/google-api-go-generator/gen.go
+++ b/google-api-go-generator/gen.go
@@ -591,6 +591,22 @@
return t
}
if at, ok := t.ArrayType(); ok {
+ if at.apiType() == "string" {
+ switch at.apiTypeFormat() {
+ 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"
+ default:
+ return "[]" + at.AsGo()
+ }
+ }
return "[]" + at.AsGo()
}
if ref, ok := t.Reference(); ok {
diff --git a/google-api-go-generator/gen_test.go b/google-api-go-generator/gen_test.go
index 75bb369..b75b3d3 100644
--- a/google-api-go-generator/gen_test.go
+++ b/google-api-go-generator/gen_test.go
@@ -11,7 +11,7 @@
var updateGolden = flag.Bool("update_golden", false, "If true, causes TestAPIs to update golden files")
func TestAPIs(t *testing.T) {
- names := []string{"blogger-3"}
+ names := []string{"blogger-3", "quotednum"}
for _, name := range names {
api, err := apiFromFile(filepath.Join("testdata", name+".json"))
if err != nil {
diff --git a/google-api-go-generator/testdata/quotednum.json b/google-api-go-generator/testdata/quotednum.json
new file mode 100644
index 0000000..738555e
--- /dev/null
+++ b/google-api-go-generator/testdata/quotednum.json
@@ -0,0 +1,94 @@
+{
+ "kind": "discovery#restDescription",
+ "etag": "\"DGgqtFnjgu83tuwvvVNNUhOiHWk/1UCG4CqfTBrxPN0MRjUm7GaLJ7Y\"",
+ "discoveryVersion": "v1",
+ "id": "adexchangebuyer:v1.1",
+ "name": "adexchangebuyer",
+ "version": "v1.1",
+ "title": "Ad Exchange Buyer API",
+ "description": "Lets you manage your Ad Exchange Buyer account.",
+ "ownerDomain": "google.com",
+ "ownerName": "Google",
+ "icons": {
+ "x16": "http://www.google.com/images/icons/product/doubleclick-16.gif",
+ "x32": "http://www.google.com/images/icons/product/doubleclick-32.gif"
+ },
+ "documentationLink": "https://developers.google.com/ad-exchange/buyer-rest",
+ "protocol": "rest",
+ "baseUrl": "https://www.googleapis.com/adexchangebuyer/v1.1/",
+ "basePath": "/adexchangebuyer/v1.1/",
+ "rootUrl": "https://www.googleapis.com/",
+ "servicePath": "adexchangebuyer/v1.1/",
+ "batchPath": "batch",
+ "parameters": {
+ "alt": {
+ "type": "string",
+ "description": "Data format for the response.",
+ "default": "json",
+ "enum": [
+ "json"
+ ],
+ "enumDescriptions": [
+ "Responses with Content-Type of application/json"
+ ],
+ "location": "query"
+ },
+ "fields": {
+ "type": "string",
+ "description": "Selector specifying which fields to include in a partial response.",
+ "location": "query"
+ },
+ "key": {
+ "type": "string",
+ "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
+ "location": "query"
+ },
+ "oauth_token": {
+ "type": "string",
+ "description": "OAuth 2.0 token for the current user.",
+ "location": "query"
+ },
+ "prettyPrint": {
+ "type": "boolean",
+ "description": "Returns response with indentations and line breaks.",
+ "default": "true",
+ "location": "query"
+ },
+ "quotaUser": {
+ "type": "string",
+ "description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.",
+ "location": "query"
+ },
+ "userIp": {
+ "type": "string",
+ "description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.",
+ "location": "query"
+ }
+ },
+ "auth": {
+ "oauth2": {
+ "scopes": {
+ "https://www.googleapis.com/auth/adexchange.buyer": {
+ "description": "Manage your Ad Exchange buyer account configuration"
+ }
+ }
+ }
+ },
+ "schemas": {
+ "Creative": {
+ "id": "Creative",
+ "type": "object",
+ "description": "A creative and its classification data.",
+ "properties": {
+ "advertiserId": {
+ "type": "array",
+ "description": "Detected advertiser id, if any. Read-only. This field should not be set in requests.",
+ "items": {
+ "type": "string",
+ "format": "int64"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/google-api-go-generator/testdata/quotednum.want b/google-api-go-generator/testdata/quotednum.want
new file mode 100644
index 0000000..b64276b
--- /dev/null
+++ b/google-api-go-generator/testdata/quotednum.want
@@ -0,0 +1,64 @@
+// Package adexchangebuyer provides access to the Ad Exchange Buyer API.
+//
+// See https://developers.google.com/ad-exchange/buyer-rest
+//
+// Usage example:
+//
+// import "code.google.com/p/google-api-go-client/adexchangebuyer/v1.1"
+// ...
+// adexchangebuyerService, err := adexchangebuyer.New(oauthHttpClient)
+package adexchangebuyer
+
+import (
+ "bytes"
+ "code.google.com/p/google-api-go-client/googleapi"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+// Always reference these packages, just in case the auto-generated code
+// below doesn't.
+var _ = bytes.NewBuffer
+var _ = strconv.Itoa
+var _ = fmt.Sprintf
+var _ = json.NewDecoder
+var _ = io.Copy
+var _ = url.Parse
+var _ = googleapi.Version
+var _ = errors.New
+var _ = strings.Replace
+
+const apiId = "adexchangebuyer:v1.1"
+const apiName = "adexchangebuyer"
+const apiVersion = "v1.1"
+const basePath = "https://www.googleapis.com/adexchangebuyer/v1.1/"
+
+// OAuth2 scopes used by this API.
+const (
+ // Manage your Ad Exchange buyer account configuration
+ AdexchangeBuyerScope = "https://www.googleapis.com/auth/adexchange.buyer"
+)
+
+func New(client *http.Client) (*Service, error) {
+ if client == nil {
+ return nil, errors.New("client is nil")
+ }
+ s := &Service{client: client}
+ return s, nil
+}
+
+type Service struct {
+ client *http.Client
+}
+
+type Creative struct {
+ // AdvertiserId: Detected advertiser id, if any. Read-only. This field
+ // should not be set in requests.
+ AdvertiserId googleapi.Int64s `json:"advertiserId,omitempty"`
+}
diff --git a/googleapi/types.go b/googleapi/types.go
new file mode 100644
index 0000000..7ed7dd9
--- /dev/null
+++ b/googleapi/types.go
@@ -0,0 +1,150 @@
+// Copyright 2013 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
+
+import (
+ "encoding/json"
+ "strconv"
+)
+
+// Int64s is a slice of int64s that marshal as quoted strings in JSON.
+type Int64s []int64
+
+func (q *Int64s) UnmarshalJSON(raw []byte) error {
+ *q = (*q)[:0]
+ var ss []string
+ if err := json.Unmarshal(raw, &ss); err != nil {
+ return err
+ }
+ for _, s := range ss {
+ v, err := strconv.ParseInt(s, 10, 64)
+ if err != nil {
+ return err
+ }
+ *q = append(*q, int64(v))
+ }
+ return nil
+}
+
+// Int32s is a slice of int32s that marshal as quoted strings in JSON.
+type Int32s []int32
+
+func (q *Int32s) UnmarshalJSON(raw []byte) error {
+ *q = (*q)[:0]
+ var ss []string
+ if err := json.Unmarshal(raw, &ss); err != nil {
+ return err
+ }
+ for _, s := range ss {
+ v, err := strconv.ParseInt(s, 10, 32)
+ if err != nil {
+ return err
+ }
+ *q = append(*q, int32(v))
+ }
+ return nil
+}
+
+// Uint64s is a slice of uint64s that marshal as quoted strings in JSON.
+type Uint64s []uint64
+
+func (q *Uint64s) UnmarshalJSON(raw []byte) error {
+ *q = (*q)[:0]
+ var ss []string
+ if err := json.Unmarshal(raw, &ss); err != nil {
+ return err
+ }
+ for _, s := range ss {
+ v, err := strconv.ParseUint(s, 10, 64)
+ if err != nil {
+ return err
+ }
+ *q = append(*q, uint64(v))
+ }
+ return nil
+}
+
+// Uint32s is a slice of uint32s that marshal as quoted strings in JSON.
+type Uint32s []uint32
+
+func (q *Uint32s) UnmarshalJSON(raw []byte) error {
+ *q = (*q)[:0]
+ var ss []string
+ if err := json.Unmarshal(raw, &ss); err != nil {
+ return err
+ }
+ for _, s := range ss {
+ v, err := strconv.ParseUint(s, 10, 32)
+ if err != nil {
+ return err
+ }
+ *q = append(*q, uint32(v))
+ }
+ return nil
+}
+
+// Float64s is a slice of float64s that marshal as quoted strings in JSON.
+type Float64s []float64
+
+func (q *Float64s) UnmarshalJSON(raw []byte) error {
+ *q = (*q)[:0]
+ var ss []string
+ if err := json.Unmarshal(raw, &ss); err != nil {
+ return err
+ }
+ for _, s := range ss {
+ v, err := strconv.ParseFloat(s, 64)
+ if err != nil {
+ return err
+ }
+ *q = append(*q, float64(v))
+ }
+ return nil
+}
+
+func quotedList(n int, fn func(dst []byte, i int) []byte) ([]byte, error) {
+ dst := make([]byte, 0, 2+n*10) // somewhat arbitrary
+ dst = append(dst, '[')
+ for i := 0; i < n; i++ {
+ if i > 0 {
+ dst = append(dst, ',')
+ }
+ dst = append(dst, '"')
+ dst = fn(dst, i)
+ dst = append(dst, '"')
+ }
+ dst = append(dst, ']')
+ return dst, nil
+}
+
+func (s Int64s) MarshalJSON() ([]byte, error) {
+ return quotedList(len(s), func(dst []byte, i int) []byte {
+ return strconv.AppendInt(dst, s[i], 10)
+ })
+}
+
+func (s Int32s) MarshalJSON() ([]byte, error) {
+ return quotedList(len(s), func(dst []byte, i int) []byte {
+ return strconv.AppendInt(dst, int64(s[i]), 10)
+ })
+}
+
+func (s Uint64s) MarshalJSON() ([]byte, error) {
+ return quotedList(len(s), func(dst []byte, i int) []byte {
+ return strconv.AppendUint(dst, s[i], 10)
+ })
+}
+
+func (s Uint32s) MarshalJSON() ([]byte, error) {
+ return quotedList(len(s), func(dst []byte, i int) []byte {
+ return strconv.AppendUint(dst, uint64(s[i]), 10)
+ })
+}
+
+func (s Float64s) MarshalJSON() ([]byte, error) {
+ return quotedList(len(s), func(dst []byte, i int) []byte {
+ return strconv.AppendFloat(dst, s[i], 'g', -1, 64)
+ })
+}
diff --git a/googleapi/types_test.go b/googleapi/types_test.go
new file mode 100644
index 0000000..a6b2045
--- /dev/null
+++ b/googleapi/types_test.go
@@ -0,0 +1,44 @@
+// Copyright 2013 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
+
+import (
+ "encoding/json"
+ "reflect"
+ "testing"
+)
+
+func TestTypes(t *testing.T) {
+ type T struct {
+ I32 Int32s
+ I64 Int64s
+ U32 Uint32s
+ U64 Uint64s
+ F64 Float64s
+ }
+ v := &T{
+ I32: Int32s{-1, 2, 3},
+ I64: Int64s{-1, 2, 1 << 33},
+ U32: Uint32s{1, 2},
+ U64: Uint64s{1, 2, 1 << 33},
+ F64: Float64s{1.5, 3.33},
+ }
+ got, err := json.Marshal(v)
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := `{"I32":["-1","2","3"],"I64":["-1","2","8589934592"],"U32":["1","2"],"U64":["1","2","8589934592"],"F64":["1.5","3.33"]}`
+ if string(got) != want {
+ t.Fatalf("Marshal mismatch.\n got: %s\nwant: %s\n", got, want)
+ }
+
+ v2 := new(T)
+ if err := json.Unmarshal(got, v2); err != nil {
+ t.Fatalf("Unmarshal: %v", err)
+ }
+ if !reflect.DeepEqual(v, v2) {
+ t.Fatalf("Unmarshal didn't produce same results.\n got: %#v\nwant: %#v\n", v, v2)
+ }
+}