mirror of
https://github.com/optim-enterprises-bv/kubernetes.git
synced 2025-12-03 23:03:53 +00:00
cloudprovider/gce: use golang.org/x/oauth2
code.google.com/p/goauth2 is deprecated. use golang.org/x/oauth2 instead. hooks/prepare-commit-msg ignore Godeps for sh's boilerplate check.
This commit is contained in:
36
Godeps/Godeps.json
generated
36
Godeps/Godeps.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ImportPath": "github.com/GoogleCloudPlatform/kubernetes",
|
"ImportPath": "github.com/GoogleCloudPlatform/kubernetes",
|
||||||
"GoVersion": "go1.3",
|
"GoVersion": "go1.4",
|
||||||
"Packages": [
|
"Packages": [
|
||||||
"./..."
|
"./..."
|
||||||
],
|
],
|
||||||
@@ -14,16 +14,6 @@
|
|||||||
"Comment": "null-12",
|
"Comment": "null-12",
|
||||||
"Rev": "7dda39b2e7d5e265014674c5af696ba4186679e9"
|
"Rev": "7dda39b2e7d5e265014674c5af696ba4186679e9"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "code.google.com/p/goauth2/compute/serviceaccount",
|
|
||||||
"Comment": "weekly-50",
|
|
||||||
"Rev": "7fc9d958c83464bd7650240569bf93a102266e6a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "code.google.com/p/goauth2/oauth",
|
|
||||||
"Comment": "weekly-50",
|
|
||||||
"Rev": "7fc9d958c83464bd7650240569bf93a102266e6a"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "code.google.com/p/google-api-go-client/compute/v1",
|
"ImportPath": "code.google.com/p/google-api-go-client/compute/v1",
|
||||||
"Comment": "release-96",
|
"Comment": "release-96",
|
||||||
@@ -115,6 +105,10 @@
|
|||||||
"ImportPath": "github.com/golang/glog",
|
"ImportPath": "github.com/golang/glog",
|
||||||
"Rev": "44145f04b68cf362d9c4df2182967c2275eaefed"
|
"Rev": "44145f04b68cf362d9c4df2182967c2275eaefed"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/golang/protobuf/proto",
|
||||||
|
"Rev": "7f07925444bb51fa4cf9dfe6f7661876f8852275"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/google/cadvisor/client",
|
"ImportPath": "github.com/google/cadvisor/client",
|
||||||
"Comment": "0.6.2",
|
"Comment": "0.6.2",
|
||||||
@@ -146,10 +140,6 @@
|
|||||||
"ImportPath": "github.com/mitchellh/goamz/ec2",
|
"ImportPath": "github.com/mitchellh/goamz/ec2",
|
||||||
"Rev": "703cfb45985762869e465f37ed030ff01615ff1e"
|
"Rev": "703cfb45985762869e465f37ed030ff01615ff1e"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/mitchellh/goamz/elb",
|
|
||||||
"Rev": "703cfb45985762869e465f37ed030ff01615ff1e"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/mitchellh/mapstructure",
|
"ImportPath": "github.com/mitchellh/mapstructure",
|
||||||
"Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf"
|
"Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf"
|
||||||
@@ -209,6 +199,22 @@
|
|||||||
"ImportPath": "golang.org/x/net/websocket",
|
"ImportPath": "golang.org/x/net/websocket",
|
||||||
"Rev": "cbcac7bb8415db9b6cb4d1ebab1dc9afbd688b97"
|
"Rev": "cbcac7bb8415db9b6cb4d1ebab1dc9afbd688b97"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/oauth2",
|
||||||
|
"Rev": "2e66694fea36dc820636630792a55cdc6987e05b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "google.golang.org/appengine",
|
||||||
|
"Rev": "6aa67407028217c352e215f5af320a429d0bcf5f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "google.golang.org/cloud/compute/metadata",
|
||||||
|
"Rev": "2e43671e4ad874a7bca65746ff3edb38e6e93762"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "google.golang.org/cloud/internal",
|
||||||
|
"Rev": "2e43671e4ad874a7bca65746ff3edb38e6e93762"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "gopkg.in/yaml.v2",
|
"ImportPath": "gopkg.in/yaml.v2",
|
||||||
"Rev": "d466437aa4adc35830964cffc5b5f262c63ddcb4"
|
"Rev": "d466437aa4adc35830964cffc5b5f262c63ddcb4"
|
||||||
|
|||||||
172
Godeps/_workspace/src/code.google.com/p/goauth2/compute/serviceaccount/serviceaccount.go
generated
vendored
172
Godeps/_workspace/src/code.google.com/p/goauth2/compute/serviceaccount/serviceaccount.go
generated
vendored
@@ -1,172 +0,0 @@
|
|||||||
// Copyright 2013 The goauth2 Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package serviceaccount provides support for making OAuth2-authorized
|
|
||||||
// HTTP requests from Google Compute Engine instances using service accounts.
|
|
||||||
//
|
|
||||||
// See: https://developers.google.com/compute/docs/authentication
|
|
||||||
//
|
|
||||||
// Example usage:
|
|
||||||
//
|
|
||||||
// client, err := serviceaccount.NewClient(&serviceaccount.Options{})
|
|
||||||
// if err != nil {
|
|
||||||
// c.Errorf("failed to create service account client: %q", err)
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// client.Post("https://www.googleapis.com/compute/...", ...)
|
|
||||||
// client.Post("https://www.googleapis.com/bigquery/...", ...)
|
|
||||||
//
|
|
||||||
package serviceaccount
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.google.com/p/goauth2/oauth"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
metadataServer = "metadata"
|
|
||||||
serviceAccountPath = "/computeMetadata/v1/instance/service-accounts"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Options configures a service account Client.
|
|
||||||
type Options struct {
|
|
||||||
// Underlying transport of service account Client.
|
|
||||||
// If nil, http.DefaultTransport is used.
|
|
||||||
Transport http.RoundTripper
|
|
||||||
|
|
||||||
// Service account name.
|
|
||||||
// If empty, "default" is used.
|
|
||||||
Account string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient returns an *http.Client authorized with the service account
|
|
||||||
// configured in the Google Compute Engine instance.
|
|
||||||
func NewClient(opt *Options) (*http.Client, error) {
|
|
||||||
tr := http.DefaultTransport
|
|
||||||
account := "default"
|
|
||||||
if opt != nil {
|
|
||||||
if opt.Transport != nil {
|
|
||||||
tr = opt.Transport
|
|
||||||
}
|
|
||||||
if opt.Account != "" {
|
|
||||||
account = opt.Account
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t := &transport{
|
|
||||||
Transport: tr,
|
|
||||||
Account: account,
|
|
||||||
}
|
|
||||||
// Get the initial access token.
|
|
||||||
if _, err := fetchToken(t); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &http.Client{
|
|
||||||
Transport: t,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type tokenData struct {
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
ExpiresIn float64 `json:"expires_in"`
|
|
||||||
TokenType string `json:"token_type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// transport is an oauth.Transport with a custom Refresh and RoundTrip implementation.
|
|
||||||
type transport struct {
|
|
||||||
Transport http.RoundTripper
|
|
||||||
Account string
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
*oauth.Token
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh renews the transport's AccessToken.
|
|
||||||
// t.mu sould be held when this is called.
|
|
||||||
func (t *transport) refresh() error {
|
|
||||||
// https://developers.google.com/compute/docs/metadata#transitioning
|
|
||||||
// v1 requires "Metadata-Flavor: Google" header.
|
|
||||||
tokenURL := &url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: metadataServer,
|
|
||||||
Path: path.Join(serviceAccountPath, t.Account, "token"),
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest("GET", tokenURL.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Add("Metadata-Flavor", "Google")
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
d := json.NewDecoder(resp.Body)
|
|
||||||
var token tokenData
|
|
||||||
err = d.Decode(&token)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t.Token = &oauth.Token{
|
|
||||||
AccessToken: token.AccessToken,
|
|
||||||
Expiry: time.Now().Add(time.Duration(token.ExpiresIn) * time.Second),
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh renews the transport's AccessToken.
|
|
||||||
func (t *transport) Refresh() error {
|
|
||||||
t.mu.Lock()
|
|
||||||
defer t.mu.Unlock()
|
|
||||||
return t.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch token from cache or generate a new one if cache miss or expired.
|
|
||||||
func fetchToken(t *transport) (*oauth.Token, error) {
|
|
||||||
// Get a new token using Refresh in case of a cache miss of if it has expired.
|
|
||||||
t.mu.Lock()
|
|
||||||
defer t.mu.Unlock()
|
|
||||||
if t.Token == nil || t.Expired() {
|
|
||||||
if err := t.refresh(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t.Token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cloneRequest returns a clone of the provided *http.Request.
|
|
||||||
// The clone is a shallow copy of the struct and its Header map.
|
|
||||||
func cloneRequest(r *http.Request) *http.Request {
|
|
||||||
// shallow copy of the struct
|
|
||||||
r2 := new(http.Request)
|
|
||||||
*r2 = *r
|
|
||||||
// deep copy of the Header
|
|
||||||
r2.Header = make(http.Header)
|
|
||||||
for k, s := range r.Header {
|
|
||||||
r2.Header[k] = s
|
|
||||||
}
|
|
||||||
return r2
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundTrip issues an authorized HTTP request and returns its response.
|
|
||||||
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
token, err := fetchToken(t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// To set the Authorization header, we must make a copy of the Request
|
|
||||||
// so that we don't modify the Request we were given.
|
|
||||||
// This is required by the specification of http.RoundTripper.
|
|
||||||
newReq := cloneRequest(req)
|
|
||||||
newReq.Header.Set("Authorization", "Bearer "+token.AccessToken)
|
|
||||||
|
|
||||||
// Make the HTTP request.
|
|
||||||
return t.Transport.RoundTrip(newReq)
|
|
||||||
}
|
|
||||||
100
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/example/oauthreq.go
generated
vendored
100
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/example/oauthreq.go
generated
vendored
@@ -1,100 +0,0 @@
|
|||||||
// Copyright 2011 The goauth2 Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This program makes a call to the specified API, authenticated with OAuth2.
|
|
||||||
// a list of example APIs can be found at https://code.google.com/oauthplayground/
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"code.google.com/p/goauth2/oauth"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
clientId = flag.String("id", "", "Client ID")
|
|
||||||
clientSecret = flag.String("secret", "", "Client Secret")
|
|
||||||
scope = flag.String("scope", "https://www.googleapis.com/auth/userinfo.profile", "OAuth scope")
|
|
||||||
redirectURL = flag.String("redirect_url", "oob", "Redirect URL")
|
|
||||||
authURL = flag.String("auth_url", "https://accounts.google.com/o/oauth2/auth", "Authentication URL")
|
|
||||||
tokenURL = flag.String("token_url", "https://accounts.google.com/o/oauth2/token", "Token URL")
|
|
||||||
requestURL = flag.String("request_url", "https://www.googleapis.com/oauth2/v1/userinfo", "API request")
|
|
||||||
code = flag.String("code", "", "Authorization Code")
|
|
||||||
cachefile = flag.String("cache", "cache.json", "Token cache file")
|
|
||||||
)
|
|
||||||
|
|
||||||
const usageMsg = `
|
|
||||||
To obtain a request token you must specify both -id and -secret.
|
|
||||||
|
|
||||||
To obtain Client ID and Secret, see the "OAuth 2 Credentials" section under
|
|
||||||
the "API Access" tab on this page: https://code.google.com/apis/console/
|
|
||||||
|
|
||||||
Once you have completed the OAuth flow, the credentials should be stored inside
|
|
||||||
the file specified by -cache and you may run without the -id and -secret flags.
|
|
||||||
`
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
// Set up a configuration.
|
|
||||||
config := &oauth.Config{
|
|
||||||
ClientId: *clientId,
|
|
||||||
ClientSecret: *clientSecret,
|
|
||||||
RedirectURL: *redirectURL,
|
|
||||||
Scope: *scope,
|
|
||||||
AuthURL: *authURL,
|
|
||||||
TokenURL: *tokenURL,
|
|
||||||
TokenCache: oauth.CacheFile(*cachefile),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up a Transport using the config.
|
|
||||||
transport := &oauth.Transport{Config: config}
|
|
||||||
|
|
||||||
// Try to pull the token from the cache; if this fails, we need to get one.
|
|
||||||
token, err := config.TokenCache.Token()
|
|
||||||
if err != nil {
|
|
||||||
if *clientId == "" || *clientSecret == "" {
|
|
||||||
flag.Usage()
|
|
||||||
fmt.Fprint(os.Stderr, usageMsg)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
if *code == "" {
|
|
||||||
// Get an authorization code from the data provider.
|
|
||||||
// ("Please ask the user if I can access this resource.")
|
|
||||||
url := config.AuthCodeURL("")
|
|
||||||
fmt.Print("Visit this URL to get a code, then run again with -code=YOUR_CODE\n\n")
|
|
||||||
fmt.Println(url)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Exchange the authorization code for an access token.
|
|
||||||
// ("Here's the code you gave the user, now give me a token!")
|
|
||||||
token, err = transport.Exchange(*code)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Exchange:", err)
|
|
||||||
}
|
|
||||||
// (The Exchange method will automatically cache the token.)
|
|
||||||
fmt.Printf("Token is cached in %v\n", config.TokenCache)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the actual request using the cached token to authenticate.
|
|
||||||
// ("Here's the token, let me in!")
|
|
||||||
transport.Token = token
|
|
||||||
|
|
||||||
// Make the request.
|
|
||||||
r, err := transport.Client().Get(*requestURL)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Get:", err)
|
|
||||||
}
|
|
||||||
defer r.Body.Close()
|
|
||||||
|
|
||||||
// Write the response to standard output.
|
|
||||||
io.Copy(os.Stdout, r.Body)
|
|
||||||
|
|
||||||
// Send final carriage return, just to be neat.
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"web":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","client_email":"XXXXXXXXXXXX@developer.gserviceaccount.com","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/XXXXXXXXXXXX@developer.gserviceaccount.com","client_id":"XXXXXXXXXXXX.apps.googleusercontent.com","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs"}}
|
|
||||||
20
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/example.pem
generated
vendored
20
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/example.pem
generated
vendored
@@ -1,20 +0,0 @@
|
|||||||
Bag Attributes
|
|
||||||
friendlyName: privatekey
|
|
||||||
localKeyID: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
|
||||||
Key Attributes: <No Attributes>
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
XXXXxyXXXXXXXxxyxxxX9y0XXYXXXXYXXxXyxxXxXxXXXyXXXXx4yx1xy1xyYxxY
|
|
||||||
1XxYy38YxXxxxyXxyyxx+xxxxyx1Y1xYx7yx2/Y1XyyXYYYxY5YXxX0xY/Y642yX
|
|
||||||
zYYxYXzXYxY0Y8y9YxyYXxxX40YyXxxXX4XXxx7XxXxxXyXxYYXxXyxX5XY0Yy2X
|
|
||||||
1YX0XXyy6YXyXx9XxXxyXX9XXYXxXxXXXXXXxYXYY3Y8Yy311XYYY81XyY14Xyyx
|
|
||||||
xXyx7xxXXXxxxxyyyX4YYYXyYyYXyxX4XYXYyxXYyx9xy23xXYyXyxYxXxx1XXXY
|
|
||||||
y98yX6yYxyyyX4Xyx1Xy/0yxxYxXxYYx2xx7yYXXXxYXXXxyXyyYYxx5XX2xxyxy
|
|
||||||
y6Yyyx0XX3YYYyx9YYXXXX7y0yxXXy+90XYz1y2xyx7yXxX+8X0xYxXXYxxyxYYy
|
|
||||||
YXx8Yy4yX0Xyxxx6yYX92yxy1YYYzyyyyxy55x/yyXXXYYXYXXzXXxYYxyXY8XXX
|
|
||||||
+y9+yXxX7XxxyYYxxXYxyY623xxXxYX59x5Y6yYyXYY4YxXXYXXXYxXYxXxXXx6x
|
|
||||||
YXX7XxXX2X0XY7YXyYy1XXxYXxXxYY1xXXxxxyy+07zXYxYxxXyyxxyxXx1XYy5X
|
|
||||||
5XYzyxYxXXYyX9XX7xX8xXxx+XXYyYXXXX5YY1x8Yxyx54Xy/1XXyyYXY5YxYyxY
|
|
||||||
XyyxXyX/YxxXXXxXXYXxyxx63xX/xxyYXXyYzx0XY+YxX5xyYyyxxxXXYX/94XXy
|
|
||||||
Xx63xYxXyXY3/XXxyyXX15XXXyz08XYY5YYXY/YXy/96x68XyyXXxYyXy4xYXx5x
|
|
||||||
7yxxyxxYxXxyx3y=
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
114
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/main.go
generated
vendored
114
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/main.go
generated
vendored
@@ -1,114 +0,0 @@
|
|||||||
// Copyright 2011 The goauth2 Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This program makes a read only call to the Google Cloud Storage API,
|
|
||||||
// authenticated with OAuth2. A list of example APIs can be found at
|
|
||||||
// https://code.google.com/oauthplayground/
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.google.com/p/goauth2/oauth/jwt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const scope = "https://www.googleapis.com/auth/devstorage.read_only"
|
|
||||||
|
|
||||||
var (
|
|
||||||
secretsFile = flag.String("s", "", "JSON encoded secrets for the service account")
|
|
||||||
pemFile = flag.String("k", "", "private pem key file for the service account")
|
|
||||||
)
|
|
||||||
|
|
||||||
const usageMsg = `
|
|
||||||
You must specify -k and -s.
|
|
||||||
|
|
||||||
To obtain client secrets and pem, see the "OAuth 2 Credentials" section under
|
|
||||||
the "API Access" tab on this page: https://code.google.com/apis/console/
|
|
||||||
|
|
||||||
Google Cloud Storage must also be turned on in the API console.
|
|
||||||
`
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *secretsFile == "" || *pemFile == "" {
|
|
||||||
flag.Usage()
|
|
||||||
fmt.Println(usageMsg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the secret file bytes into the config.
|
|
||||||
secretBytes, err := ioutil.ReadFile(*secretsFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("error reading secerets file:", err)
|
|
||||||
}
|
|
||||||
var config struct {
|
|
||||||
Web struct {
|
|
||||||
ClientEmail string `json:"client_email"`
|
|
||||||
ClientID string `json:"client_id"`
|
|
||||||
TokenURI string `json:"token_uri"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(secretBytes, &config)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("error unmarshalling secerets:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the project ID from the client ID.
|
|
||||||
projectID := strings.SplitN(config.Web.ClientID, "-", 2)[0]
|
|
||||||
|
|
||||||
// Read the pem file bytes for the private key.
|
|
||||||
keyBytes, err := ioutil.ReadFile(*pemFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("error reading private key file:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Craft the ClaimSet and JWT token.
|
|
||||||
t := jwt.NewToken(config.Web.ClientEmail, scope, keyBytes)
|
|
||||||
t.ClaimSet.Aud = config.Web.TokenURI
|
|
||||||
|
|
||||||
// We need to provide a client.
|
|
||||||
c := &http.Client{}
|
|
||||||
|
|
||||||
// Get the access token.
|
|
||||||
o, err := t.Assert(c)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("assertion error:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh token will be missing, but this access_token will be good
|
|
||||||
// for one hour.
|
|
||||||
fmt.Printf("access_token = %v\n", o.AccessToken)
|
|
||||||
fmt.Printf("refresh_token = %v\n", o.RefreshToken)
|
|
||||||
fmt.Printf("expires %v\n", o.Expiry)
|
|
||||||
|
|
||||||
// Form the request to list Google Cloud Storage buckets.
|
|
||||||
req, err := http.NewRequest("GET", "https://storage.googleapis.com/", nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("http.NewRequest:", err)
|
|
||||||
}
|
|
||||||
req.Header.Set("Authorization", "OAuth "+o.AccessToken)
|
|
||||||
req.Header.Set("x-goog-api-version", "2")
|
|
||||||
req.Header.Set("x-goog-project-id", projectID)
|
|
||||||
|
|
||||||
// Make the request.
|
|
||||||
r, err := c.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("API request error:", err)
|
|
||||||
}
|
|
||||||
defer r.Body.Close()
|
|
||||||
|
|
||||||
// Write the response to standard output.
|
|
||||||
res, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("error reading API request results:", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("\nRESULT:\n%s\n", res)
|
|
||||||
}
|
|
||||||
511
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/jwt.go
generated
vendored
511
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/jwt.go
generated
vendored
@@ -1,511 +0,0 @@
|
|||||||
// Copyright 2012 The goauth2 Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// The jwt package provides support for creating credentials for OAuth2 service
|
|
||||||
// account requests.
|
|
||||||
//
|
|
||||||
// For examples of the package usage please see jwt_test.go.
|
|
||||||
// Example usage (error handling omitted for brevity):
|
|
||||||
//
|
|
||||||
// // Craft the ClaimSet and JWT token.
|
|
||||||
// iss := "XXXXXXXXXXXX@developer.gserviceaccount.com"
|
|
||||||
// scope := "https://www.googleapis.com/auth/devstorage.read_only"
|
|
||||||
// t := jwt.NewToken(iss, scope, pemKeyBytes)
|
|
||||||
//
|
|
||||||
// // We need to provide a client.
|
|
||||||
// c := &http.Client{}
|
|
||||||
//
|
|
||||||
// // Get the access token.
|
|
||||||
// o, _ := t.Assert(c)
|
|
||||||
//
|
|
||||||
// // Form the request to the service.
|
|
||||||
// req, _ := http.NewRequest("GET", "https://storage.googleapis.com/", nil)
|
|
||||||
// req.Header.Set("Authorization", "OAuth "+o.AccessToken)
|
|
||||||
// req.Header.Set("x-goog-api-version", "2")
|
|
||||||
// req.Header.Set("x-goog-project-id", "XXXXXXXXXXXX")
|
|
||||||
//
|
|
||||||
// // Make the request.
|
|
||||||
// result, _ := c.Do(req)
|
|
||||||
//
|
|
||||||
// For info on OAuth2 service accounts please see the online documentation.
|
|
||||||
// https://developers.google.com/accounts/docs/OAuth2ServiceAccount
|
|
||||||
//
|
|
||||||
package jwt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.google.com/p/goauth2/oauth"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These are the default/standard values for this to work for Google service accounts.
|
|
||||||
const (
|
|
||||||
stdAlgorithm = "RS256"
|
|
||||||
stdType = "JWT"
|
|
||||||
stdAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer"
|
|
||||||
stdGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
|
||||||
stdAud = "https://accounts.google.com/o/oauth2/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidKey = errors.New("Invalid Key")
|
|
||||||
)
|
|
||||||
|
|
||||||
// base64Encode returns and Base64url encoded version of the input string with any
|
|
||||||
// trailing "=" stripped.
|
|
||||||
func base64Encode(b []byte) string {
|
|
||||||
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
|
|
||||||
}
|
|
||||||
|
|
||||||
// base64Decode decodes the Base64url encoded string
|
|
||||||
func base64Decode(s string) ([]byte, error) {
|
|
||||||
// add back missing padding
|
|
||||||
switch len(s) % 4 {
|
|
||||||
case 2:
|
|
||||||
s += "=="
|
|
||||||
case 3:
|
|
||||||
s += "="
|
|
||||||
}
|
|
||||||
return base64.URLEncoding.DecodeString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The JWT claim set contains information about the JWT including the
|
|
||||||
// permissions being requested (scopes), the target of the token, the issuer,
|
|
||||||
// the time the token was issued, and the lifetime of the token.
|
|
||||||
//
|
|
||||||
// Aud is usually https://accounts.google.com/o/oauth2/token
|
|
||||||
type ClaimSet struct {
|
|
||||||
Iss string `json:"iss"` // email address of the client_id of the application making the access token request
|
|
||||||
Scope string `json:"scope,omitempty"` // space-delimited list of the permissions the application requests
|
|
||||||
Aud string `json:"aud"` // descriptor of the intended target of the assertion (Optional).
|
|
||||||
Prn string `json:"prn,omitempty"` // email for which the application is requesting delegated access (Optional).
|
|
||||||
Exp int64 `json:"exp"`
|
|
||||||
Iat int64 `json:"iat"`
|
|
||||||
Typ string `json:"typ,omitempty"`
|
|
||||||
Sub string `json:"sub,omitempty"` // Add support for googleapi delegation support
|
|
||||||
|
|
||||||
// See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3
|
|
||||||
// This array is marshalled using custom code (see (c *ClaimSet) encode()).
|
|
||||||
PrivateClaims map[string]interface{} `json:"-"`
|
|
||||||
|
|
||||||
exp time.Time
|
|
||||||
iat time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// setTimes sets iat and exp to time.Now() and iat.Add(time.Hour) respectively.
|
|
||||||
//
|
|
||||||
// Note that these times have nothing to do with the expiration time for the
|
|
||||||
// access_token returned by the server. These have to do with the lifetime of
|
|
||||||
// the encoded JWT.
|
|
||||||
//
|
|
||||||
// A JWT can be re-used for up to one hour after it was encoded. The access
|
|
||||||
// token that is granted will also be good for one hour so there is little point
|
|
||||||
// in trying to use the JWT a second time.
|
|
||||||
func (c *ClaimSet) setTimes(t time.Time) {
|
|
||||||
c.iat = t
|
|
||||||
c.exp = c.iat.Add(time.Hour)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
jsonStart = []byte{'{'}
|
|
||||||
jsonEnd = []byte{'}'}
|
|
||||||
)
|
|
||||||
|
|
||||||
// encode returns the Base64url encoded form of the Signature.
|
|
||||||
func (c *ClaimSet) encode() string {
|
|
||||||
if c.exp.IsZero() || c.iat.IsZero() {
|
|
||||||
c.setTimes(time.Now())
|
|
||||||
}
|
|
||||||
if c.Aud == "" {
|
|
||||||
c.Aud = stdAud
|
|
||||||
}
|
|
||||||
c.Exp = c.exp.Unix()
|
|
||||||
c.Iat = c.iat.Unix()
|
|
||||||
|
|
||||||
b, err := json.Marshal(c)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.PrivateClaims) == 0 {
|
|
||||||
return base64Encode(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal private claim set and then append it to b.
|
|
||||||
prv, err := json.Marshal(c.PrivateClaims)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("Invalid map of private claims %v", c.PrivateClaims))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concatenate public and private claim JSON objects.
|
|
||||||
if !bytes.HasSuffix(b, jsonEnd) {
|
|
||||||
panic(fmt.Errorf("Invalid JSON %s", b))
|
|
||||||
}
|
|
||||||
if !bytes.HasPrefix(prv, jsonStart) {
|
|
||||||
panic(fmt.Errorf("Invalid JSON %s", prv))
|
|
||||||
}
|
|
||||||
b[len(b)-1] = ',' // Replace closing curly brace with a comma.
|
|
||||||
b = append(b, prv[1:]...) // Append private claims.
|
|
||||||
|
|
||||||
return base64Encode(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header describes the algorithm and type of token being generated,
|
|
||||||
// and optionally a KeyID describing additional parameters for the
|
|
||||||
// signature.
|
|
||||||
type Header struct {
|
|
||||||
Algorithm string `json:"alg"`
|
|
||||||
Type string `json:"typ"`
|
|
||||||
KeyId string `json:"kid,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Header) encode() string {
|
|
||||||
b, err := json.Marshal(h)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return base64Encode(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A JWT is composed of three parts: a header, a claim set, and a signature.
|
|
||||||
// The well formed and encoded JWT can then be exchanged for an access token.
|
|
||||||
//
|
|
||||||
// The Token is not a JWT, but is is encoded to produce a well formed JWT.
|
|
||||||
//
|
|
||||||
// When obtaining a key from the Google API console it will be downloaded in a
|
|
||||||
// PKCS12 encoding. To use this key you will need to convert it to a PEM file.
|
|
||||||
// This can be achieved with openssl.
|
|
||||||
//
|
|
||||||
// $ openssl pkcs12 -in <key.p12> -nocerts -passin pass:notasecret -nodes -out <key.pem>
|
|
||||||
//
|
|
||||||
// The contents of this file can then be used as the Key.
|
|
||||||
type Token struct {
|
|
||||||
ClaimSet *ClaimSet // claim set used to construct the JWT
|
|
||||||
Header *Header // header used to construct the JWT
|
|
||||||
Key []byte // PEM printable encoding of the private key
|
|
||||||
pKey *rsa.PrivateKey
|
|
||||||
|
|
||||||
header string
|
|
||||||
claim string
|
|
||||||
sig string
|
|
||||||
|
|
||||||
useExternalSigner bool
|
|
||||||
signer Signer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewToken returns a filled in *Token based on the standard header,
|
|
||||||
// and sets the Iat and Exp times based on when the call to Assert is
|
|
||||||
// made.
|
|
||||||
func NewToken(iss, scope string, key []byte) *Token {
|
|
||||||
c := &ClaimSet{
|
|
||||||
Iss: iss,
|
|
||||||
Scope: scope,
|
|
||||||
Aud: stdAud,
|
|
||||||
}
|
|
||||||
h := &Header{
|
|
||||||
Algorithm: stdAlgorithm,
|
|
||||||
Type: stdType,
|
|
||||||
}
|
|
||||||
t := &Token{
|
|
||||||
ClaimSet: c,
|
|
||||||
Header: h,
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signer is an interface that given a JWT token, returns the header &
|
|
||||||
// claim (serialized and urlEncoded to a byte slice), along with the
|
|
||||||
// signature and an error (if any occured). It could modify any data
|
|
||||||
// to sign (typically the KeyID).
|
|
||||||
//
|
|
||||||
// Example usage where a SHA256 hash of the original url-encoded token
|
|
||||||
// with an added KeyID and secret data is used as a signature:
|
|
||||||
//
|
|
||||||
// var privateData = "secret data added to hash, indexed by KeyID"
|
|
||||||
//
|
|
||||||
// type SigningService struct{}
|
|
||||||
//
|
|
||||||
// func (ss *SigningService) Sign(in *jwt.Token) (newTokenData, sig []byte, err error) {
|
|
||||||
// in.Header.KeyID = "signing service"
|
|
||||||
// newTokenData = in.EncodeWithoutSignature()
|
|
||||||
// dataToSign := fmt.Sprintf("%s.%s", newTokenData, privateData)
|
|
||||||
// h := sha256.New()
|
|
||||||
// _, err := h.Write([]byte(dataToSign))
|
|
||||||
// sig = h.Sum(nil)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
type Signer interface {
|
|
||||||
Sign(in *Token) (tokenData, signature []byte, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSignerToken returns a *Token, using an external signer function
|
|
||||||
func NewSignerToken(iss, scope string, signer Signer) *Token {
|
|
||||||
t := NewToken(iss, scope, nil)
|
|
||||||
t.useExternalSigner = true
|
|
||||||
t.signer = signer
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expired returns a boolean value letting us know if the token has expired.
|
|
||||||
func (t *Token) Expired() bool {
|
|
||||||
return t.ClaimSet.exp.Before(time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode constructs and signs a Token returning a JWT ready to use for
|
|
||||||
// requesting an access token.
|
|
||||||
func (t *Token) Encode() (string, error) {
|
|
||||||
var tok string
|
|
||||||
t.header = t.Header.encode()
|
|
||||||
t.claim = t.ClaimSet.encode()
|
|
||||||
err := t.sign()
|
|
||||||
if err != nil {
|
|
||||||
return tok, err
|
|
||||||
}
|
|
||||||
tok = fmt.Sprintf("%s.%s.%s", t.header, t.claim, t.sig)
|
|
||||||
return tok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeWithoutSignature returns the url-encoded value of the Token
|
|
||||||
// before signing has occured (typically for use by external signers).
|
|
||||||
func (t *Token) EncodeWithoutSignature() string {
|
|
||||||
t.header = t.Header.encode()
|
|
||||||
t.claim = t.ClaimSet.encode()
|
|
||||||
return fmt.Sprintf("%s.%s", t.header, t.claim)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sign computes the signature for a Token. The details for this can be found
|
|
||||||
// in the OAuth2 Service Account documentation.
|
|
||||||
// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#computingsignature
|
|
||||||
func (t *Token) sign() error {
|
|
||||||
if t.useExternalSigner {
|
|
||||||
fulldata, sig, err := t.signer.Sign(t)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
split := strings.Split(string(fulldata), ".")
|
|
||||||
if len(split) != 2 {
|
|
||||||
return errors.New("no token returned")
|
|
||||||
}
|
|
||||||
t.header = split[0]
|
|
||||||
t.claim = split[1]
|
|
||||||
t.sig = base64Encode(sig)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ss := fmt.Sprintf("%s.%s", t.header, t.claim)
|
|
||||||
if t.pKey == nil {
|
|
||||||
err := t.parsePrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h := sha256.New()
|
|
||||||
h.Write([]byte(ss))
|
|
||||||
b, err := rsa.SignPKCS1v15(rand.Reader, t.pKey, crypto.SHA256, h.Sum(nil))
|
|
||||||
t.sig = base64Encode(b)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// parsePrivateKey converts the Token's Key ([]byte) into a parsed
|
|
||||||
// rsa.PrivateKey. If the key is not well formed this method will return an
|
|
||||||
// ErrInvalidKey error.
|
|
||||||
func (t *Token) parsePrivateKey() error {
|
|
||||||
block, _ := pem.Decode(t.Key)
|
|
||||||
if block == nil {
|
|
||||||
return ErrInvalidKey
|
|
||||||
}
|
|
||||||
parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var ok bool
|
|
||||||
t.pKey, ok = parsedKey.(*rsa.PrivateKey)
|
|
||||||
if !ok {
|
|
||||||
return ErrInvalidKey
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert obtains an *oauth.Token from the remote server by encoding and sending
|
|
||||||
// a JWT. The access_token will expire in one hour (3600 seconds) and cannot be
|
|
||||||
// refreshed (no refresh_token is returned with the response). Once this token
|
|
||||||
// expires call this method again to get a fresh one.
|
|
||||||
func (t *Token) Assert(c *http.Client) (*oauth.Token, error) {
|
|
||||||
var o *oauth.Token
|
|
||||||
t.ClaimSet.setTimes(time.Now())
|
|
||||||
u, v, err := t.buildRequest()
|
|
||||||
if err != nil {
|
|
||||||
return o, err
|
|
||||||
}
|
|
||||||
resp, err := c.PostForm(u, v)
|
|
||||||
if err != nil {
|
|
||||||
return o, err
|
|
||||||
}
|
|
||||||
o, err = handleResponse(resp)
|
|
||||||
return o, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildRequest sets up the URL values and the proper URL string for making our
|
|
||||||
// access_token request.
|
|
||||||
func (t *Token) buildRequest() (string, url.Values, error) {
|
|
||||||
v := url.Values{}
|
|
||||||
j, err := t.Encode()
|
|
||||||
if err != nil {
|
|
||||||
return t.ClaimSet.Aud, v, err
|
|
||||||
}
|
|
||||||
v.Set("grant_type", stdGrantType)
|
|
||||||
v.Set("assertion", j)
|
|
||||||
return t.ClaimSet.Aud, v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used for decoding the response body.
|
|
||||||
type respBody struct {
|
|
||||||
IdToken string `json:"id_token"`
|
|
||||||
Access string `json:"access_token"`
|
|
||||||
Type string `json:"token_type"`
|
|
||||||
ExpiresIn time.Duration `json:"expires_in"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleResponse returns a filled in *oauth.Token given the *http.Response from
|
|
||||||
// a *http.Request created by buildRequest.
|
|
||||||
func handleResponse(r *http.Response) (*oauth.Token, error) {
|
|
||||||
o := &oauth.Token{}
|
|
||||||
defer r.Body.Close()
|
|
||||||
if r.StatusCode != 200 {
|
|
||||||
return o, errors.New("invalid response: " + r.Status)
|
|
||||||
}
|
|
||||||
b := &respBody{}
|
|
||||||
err := json.NewDecoder(r.Body).Decode(b)
|
|
||||||
if err != nil {
|
|
||||||
return o, err
|
|
||||||
}
|
|
||||||
o.AccessToken = b.Access
|
|
||||||
if b.IdToken != "" {
|
|
||||||
// decode returned id token to get expiry
|
|
||||||
o.AccessToken = b.IdToken
|
|
||||||
s := strings.Split(b.IdToken, ".")
|
|
||||||
if len(s) < 2 {
|
|
||||||
return nil, errors.New("invalid token received")
|
|
||||||
}
|
|
||||||
d, err := base64Decode(s[1])
|
|
||||||
if err != nil {
|
|
||||||
return o, err
|
|
||||||
}
|
|
||||||
c := &ClaimSet{}
|
|
||||||
err = json.NewDecoder(bytes.NewBuffer(d)).Decode(c)
|
|
||||||
if err != nil {
|
|
||||||
return o, err
|
|
||||||
}
|
|
||||||
o.Expiry = time.Unix(c.Exp, 0)
|
|
||||||
return o, nil
|
|
||||||
}
|
|
||||||
o.Expiry = time.Now().Add(b.ExpiresIn * time.Second)
|
|
||||||
return o, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transport implements http.RoundTripper. When configured with a valid
|
|
||||||
// JWT and OAuth tokens it can be used to make authenticated HTTP requests.
|
|
||||||
//
|
|
||||||
// t := &jwt.Transport{jwtToken, oauthToken}
|
|
||||||
// r, _, err := t.Client().Get("http://example.org/url/requiring/auth")
|
|
||||||
//
|
|
||||||
// It will automatically refresh the OAuth token if it can, updating in place.
|
|
||||||
type Transport struct {
|
|
||||||
JWTToken *Token
|
|
||||||
OAuthToken *oauth.Token
|
|
||||||
|
|
||||||
// Transport is the HTTP transport to use when making requests.
|
|
||||||
// It will default to http.DefaultTransport if nil.
|
|
||||||
Transport http.RoundTripper
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a new authenticated transport.
|
|
||||||
func NewTransport(token *Token) (*Transport, error) {
|
|
||||||
oa, err := token.Assert(new(http.Client))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Transport{
|
|
||||||
JWTToken: token,
|
|
||||||
OAuthToken: oa,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client returns an *http.Client that makes OAuth-authenticated requests.
|
|
||||||
func (t *Transport) Client() *http.Client {
|
|
||||||
return &http.Client{Transport: t}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetches the internal transport.
|
|
||||||
func (t *Transport) transport() http.RoundTripper {
|
|
||||||
if t.Transport != nil {
|
|
||||||
return t.Transport
|
|
||||||
}
|
|
||||||
return http.DefaultTransport
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundTrip executes a single HTTP transaction using the Transport's
|
|
||||||
// OAuthToken as authorization headers.
|
|
||||||
//
|
|
||||||
// This method will attempt to renew the token if it has expired and may return
|
|
||||||
// an error related to that token renewal before attempting the client request.
|
|
||||||
// If the token cannot be renewed a non-nil os.Error value will be returned.
|
|
||||||
// If the token is invalid callers should expect HTTP-level errors,
|
|
||||||
// as indicated by the Response's StatusCode.
|
|
||||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
// Sanity check the two tokens
|
|
||||||
if t.JWTToken == nil {
|
|
||||||
return nil, fmt.Errorf("no JWT token supplied")
|
|
||||||
}
|
|
||||||
if t.OAuthToken == nil {
|
|
||||||
return nil, fmt.Errorf("no OAuth token supplied")
|
|
||||||
}
|
|
||||||
// Refresh the OAuth token if it has expired
|
|
||||||
if t.OAuthToken.Expired() {
|
|
||||||
if oa, err := t.JWTToken.Assert(new(http.Client)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
t.OAuthToken = oa
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// To set the Authorization header, we must make a copy of the Request
|
|
||||||
// so that we don't modify the Request we were given.
|
|
||||||
// This is required by the specification of http.RoundTripper.
|
|
||||||
req = cloneRequest(req)
|
|
||||||
req.Header.Set("Authorization", "Bearer "+t.OAuthToken.AccessToken)
|
|
||||||
|
|
||||||
// Make the HTTP request.
|
|
||||||
return t.transport().RoundTrip(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// cloneRequest returns a clone of the provided *http.Request.
|
|
||||||
// The clone is a shallow copy of the struct and its Header map.
|
|
||||||
func cloneRequest(r *http.Request) *http.Request {
|
|
||||||
// shallow copy of the struct
|
|
||||||
r2 := new(http.Request)
|
|
||||||
*r2 = *r
|
|
||||||
// deep copy of the Header
|
|
||||||
r2.Header = make(http.Header)
|
|
||||||
for k, s := range r.Header {
|
|
||||||
r2.Header[k] = s
|
|
||||||
}
|
|
||||||
return r2
|
|
||||||
}
|
|
||||||
486
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/jwt_test.go
generated
vendored
486
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/jwt_test.go
generated
vendored
@@ -1,486 +0,0 @@
|
|||||||
// Copyright 2012 The goauth2 Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// For package documentation please see jwt.go.
|
|
||||||
//
|
|
||||||
package jwt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
stdHeaderStr = `{"alg":"RS256","typ":"JWT"}`
|
|
||||||
iss = "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com"
|
|
||||||
scope = "https://www.googleapis.com/auth/prediction"
|
|
||||||
exp = 1328554385
|
|
||||||
iat = 1328550785 // exp + 1 hour
|
|
||||||
)
|
|
||||||
|
|
||||||
// Base64url encoded Header
|
|
||||||
const headerEnc = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
|
|
||||||
|
|
||||||
// Base64url encoded ClaimSet
|
|
||||||
const claimSetEnc = "eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ"
|
|
||||||
|
|
||||||
// Base64url encoded Signature
|
|
||||||
const sigEnc = "olukbHreNiYrgiGCTEmY3eWGeTvYDSUHYoE84Jz3BRPBSaMdZMNOn_0CYK7UHPO7OdvUofjwft1dH59UxE9GWS02pjFti1uAQoImaqjLZoTXr8qiF6O_kDa9JNoykklWlRAIwGIZkDupCS-8cTAnM_ksSymiH1coKJrLDUX_BM0x2f4iMFQzhL5vT1ll-ZipJ0lNlxb5QsyXxDYcxtHYguF12-vpv3ItgT0STfcXoWzIGQoEbhwB9SBp9JYcQ8Ygz6pYDjm0rWX9LrchmTyDArCodpKLFtutNgcIFUP9fWxvwd1C2dNw5GjLcKr9a_SAERyoJ2WnCR1_j9N0wD2o0g"
|
|
||||||
|
|
||||||
// Base64url encoded Token
|
|
||||||
const tokEnc = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ.olukbHreNiYrgiGCTEmY3eWGeTvYDSUHYoE84Jz3BRPBSaMdZMNOn_0CYK7UHPO7OdvUofjwft1dH59UxE9GWS02pjFti1uAQoImaqjLZoTXr8qiF6O_kDa9JNoykklWlRAIwGIZkDupCS-8cTAnM_ksSymiH1coKJrLDUX_BM0x2f4iMFQzhL5vT1ll-ZipJ0lNlxb5QsyXxDYcxtHYguF12-vpv3ItgT0STfcXoWzIGQoEbhwB9SBp9JYcQ8Ygz6pYDjm0rWX9LrchmTyDArCodpKLFtutNgcIFUP9fWxvwd1C2dNw5GjLcKr9a_SAERyoJ2WnCR1_j9N0wD2o0g"
|
|
||||||
|
|
||||||
// Private key for testing
|
|
||||||
const privateKeyPem = `-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEpAIBAAKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj
|
|
||||||
7wZgkdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/
|
|
||||||
xmVU1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYs
|
|
||||||
SliS5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18
|
|
||||||
pe+zpyl4+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xk
|
|
||||||
SBc//fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABAoIBAQDGGHzQxGKX+ANk
|
|
||||||
nQi53v/c6632dJKYXVJC+PDAz4+bzU800Y+n/bOYsWf/kCp94XcG4Lgsdd0Gx+Zq
|
|
||||||
HD9CI1IcqqBRR2AFscsmmX6YzPLTuEKBGMW8twaYy3utlFxElMwoUEsrSWRcCA1y
|
|
||||||
nHSDzTt871c7nxCXHxuZ6Nm/XCL7Bg8uidRTSC1sQrQyKgTPhtQdYrPQ4WZ1A4J9
|
|
||||||
IisyDYmZodSNZe5P+LTJ6M1SCgH8KH9ZGIxv3diMwzNNpk3kxJc9yCnja4mjiGE2
|
|
||||||
YCNusSycU5IhZwVeCTlhQGcNeV/skfg64xkiJE34c2y2ttFbdwBTPixStGaF09nU
|
|
||||||
Z422D40BAoGBAPvVyRRsC3BF+qZdaSMFwI1yiXY7vQw5+JZh01tD28NuYdRFzjcJ
|
|
||||||
vzT2n8LFpj5ZfZFvSMLMVEFVMgQvWnN0O6xdXvGov6qlRUSGaH9u+TCPNnIldjMP
|
|
||||||
B8+xTwFMqI7uQr54wBB+Poq7dVRP+0oHb0NYAwUBXoEuvYo3c/nDoRcZAoGBAOWl
|
|
||||||
aLHjMv4CJbArzT8sPfic/8waSiLV9Ixs3Re5YREUTtnLq7LoymqB57UXJB3BNz/2
|
|
||||||
eCueuW71avlWlRtE/wXASj5jx6y5mIrlV4nZbVuyYff0QlcG+fgb6pcJQuO9DxMI
|
|
||||||
aqFGrWP3zye+LK87a6iR76dS9vRU+bHZpSVvGMKJAoGAFGt3TIKeQtJJyqeUWNSk
|
|
||||||
klORNdcOMymYMIlqG+JatXQD1rR6ThgqOt8sgRyJqFCVT++YFMOAqXOBBLnaObZZ
|
|
||||||
CFbh1fJ66BlSjoXff0W+SuOx5HuJJAa5+WtFHrPajwxeuRcNa8jwxUsB7n41wADu
|
|
||||||
UqWWSRedVBg4Ijbw3nWwYDECgYB0pLew4z4bVuvdt+HgnJA9n0EuYowVdadpTEJg
|
|
||||||
soBjNHV4msLzdNqbjrAqgz6M/n8Ztg8D2PNHMNDNJPVHjJwcR7duSTA6w2p/4k28
|
|
||||||
bvvk/45Ta3XmzlxZcZSOct3O31Cw0i2XDVc018IY5be8qendDYM08icNo7vQYkRH
|
|
||||||
504kQQKBgQDjx60zpz8ozvm1XAj0wVhi7GwXe+5lTxiLi9Fxq721WDxPMiHDW2XL
|
|
||||||
YXfFVy/9/GIMvEiGYdmarK1NW+VhWl1DC5xhDg0kvMfxplt4tynoq1uTsQTY31Mx
|
|
||||||
BeF5CT/JuNYk3bEBF0H/Q3VGO1/ggVS+YezdFbLWIRoMnLj6XCFEGg==
|
|
||||||
-----END RSA PRIVATE KEY-----`
|
|
||||||
|
|
||||||
// Public key to go with the private key for testing
|
|
||||||
const publicKeyPem = `-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDIzCCAgugAwIBAgIJAMfISuBQ5m+5MA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV
|
|
||||||
BAMTCnVuaXQtdGVzdHMwHhcNMTExMjA2MTYyNjAyWhcNMjExMjAzMTYyNjAyWjAV
|
|
||||||
MRMwEQYDVQQDEwp1bml0LXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
|
||||||
CgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZgkdmM
|
|
||||||
7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU1Wer
|
|
||||||
uQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYsSliS5qQp
|
|
||||||
gyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18pe+zpyl4
|
|
||||||
+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xkSBc//fy3
|
|
||||||
ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABo3YwdDAdBgNVHQ4EFgQU2RQ8yO+O
|
|
||||||
gN8oVW2SW7RLrfYd9jEwRQYDVR0jBD4wPIAU2RQ8yO+OgN8oVW2SW7RLrfYd9jGh
|
|
||||||
GaQXMBUxEzARBgNVBAMTCnVuaXQtdGVzdHOCCQDHyErgUOZvuTAMBgNVHRMEBTAD
|
|
||||||
AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBRv+M/6+FiVu7KXNjFI5pSN17OcW5QUtPr
|
|
||||||
odJMlWrJBtynn/TA1oJlYu3yV5clc/71Vr/AxuX5xGP+IXL32YDF9lTUJXG/uUGk
|
|
||||||
+JETpKmQviPbRsvzYhz4pf6ZIOZMc3/GIcNq92ECbseGO+yAgyWUVKMmZM0HqXC9
|
|
||||||
ovNslqe0M8C1sLm1zAR5z/h/litE7/8O2ietija3Q/qtl2TOXJdCA6sgjJX2WUql
|
|
||||||
ybrC55ct18NKf3qhpcEkGQvFU40rVYApJpi98DiZPYFdx1oBDp/f4uZ3ojpxRVFT
|
|
||||||
cDwcJLfNRCPUhormsY7fDS9xSyThiHsW9mjJYdcaKQkwYZ0F11yB
|
|
||||||
-----END CERTIFICATE-----`
|
|
||||||
|
|
||||||
var (
|
|
||||||
privateKeyPemBytes = []byte(privateKeyPem)
|
|
||||||
publicKeyPemBytes = []byte(publicKeyPem)
|
|
||||||
stdHeader = &Header{Algorithm: stdAlgorithm, Type: stdType}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Testing the urlEncode function.
|
|
||||||
func TestUrlEncode(t *testing.T) {
|
|
||||||
enc := base64Encode([]byte(stdHeaderStr))
|
|
||||||
b := []byte(enc)
|
|
||||||
if b[len(b)-1] == 61 {
|
|
||||||
t.Error("TestUrlEncode: last chat == \"=\"")
|
|
||||||
}
|
|
||||||
if enc != headerEnc {
|
|
||||||
t.Error("TestUrlEncode: enc != headerEnc")
|
|
||||||
t.Errorf(" enc = %s", enc)
|
|
||||||
t.Errorf(" headerEnc = %s", headerEnc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that the times are set properly.
|
|
||||||
func TestClaimSetSetTimes(t *testing.T) {
|
|
||||||
c := &ClaimSet{
|
|
||||||
Iss: iss,
|
|
||||||
Scope: scope,
|
|
||||||
}
|
|
||||||
iat := time.Unix(iat, 0)
|
|
||||||
c.setTimes(iat)
|
|
||||||
if c.exp.Unix() != exp {
|
|
||||||
t.Error("TestClaimSetSetTimes: c.exp != exp")
|
|
||||||
t.Errorf(" c.Exp = %d", c.exp.Unix())
|
|
||||||
t.Errorf(" exp = %d", exp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given a well formed ClaimSet, test for proper encoding.
|
|
||||||
func TestClaimSetEncode(t *testing.T) {
|
|
||||||
c := &ClaimSet{
|
|
||||||
Iss: iss,
|
|
||||||
Scope: scope,
|
|
||||||
exp: time.Unix(exp, 0),
|
|
||||||
iat: time.Unix(iat, 0),
|
|
||||||
}
|
|
||||||
enc := c.encode()
|
|
||||||
re, err := base64Decode(enc)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error decoding encoded claim set: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wa, err := base64Decode(claimSetEnc)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error decoding encoded expected claim set: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if enc != claimSetEnc {
|
|
||||||
t.Error("TestClaimSetEncode: enc != claimSetEnc")
|
|
||||||
t.Errorf(" enc = %s", string(re))
|
|
||||||
t.Errorf(" claimSetEnc = %s", string(wa))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that claim sets with private claim names are encoded correctly.
|
|
||||||
func TestClaimSetWithPrivateNameEncode(t *testing.T) {
|
|
||||||
iatT := time.Unix(iat, 0)
|
|
||||||
expT := time.Unix(exp, 0)
|
|
||||||
|
|
||||||
i, err := json.Marshal(iatT.Unix())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error marshaling iatT value of %v: %v", iatT.Unix(), err)
|
|
||||||
}
|
|
||||||
iatStr := string(i)
|
|
||||||
e, err := json.Marshal(expT.Unix())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error marshaling expT value of %v: %v", expT.Unix(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expStr := string(e)
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
input map[string]interface{}
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
// Test a simple int field.
|
|
||||||
{
|
|
||||||
"single simple field",
|
|
||||||
map[string]interface{}{"amount": 22},
|
|
||||||
`{` +
|
|
||||||
`"iss":"` + iss + `",` +
|
|
||||||
`"scope":"` + scope + `",` +
|
|
||||||
`"aud":"` + stdAud + `",` +
|
|
||||||
`"exp":` + expStr + `,` +
|
|
||||||
`"iat":` + iatStr + `,` +
|
|
||||||
`"amount":22` +
|
|
||||||
`}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"multiple simple fields",
|
|
||||||
map[string]interface{}{"tracking_code": "axZf", "amount": 22},
|
|
||||||
`{` +
|
|
||||||
`"iss":"` + iss + `",` +
|
|
||||||
`"scope":"` + scope + `",` +
|
|
||||||
`"aud":"` + stdAud + `",` +
|
|
||||||
`"exp":` + expStr + `,` +
|
|
||||||
`"iat":` + iatStr + `,` +
|
|
||||||
`"amount":22,` +
|
|
||||||
`"tracking_code":"axZf"` +
|
|
||||||
`}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nested struct fields",
|
|
||||||
map[string]interface{}{
|
|
||||||
"tracking_code": "axZf",
|
|
||||||
"purchase": struct {
|
|
||||||
Description string `json:"desc"`
|
|
||||||
Quantity int32 `json:"q"`
|
|
||||||
Time int64 `json:"t"`
|
|
||||||
}{
|
|
||||||
"toaster",
|
|
||||||
5,
|
|
||||||
iat,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`{` +
|
|
||||||
`"iss":"` + iss + `",` +
|
|
||||||
`"scope":"` + scope + `",` +
|
|
||||||
`"aud":"` + stdAud + `",` +
|
|
||||||
`"exp":` + expStr + `,` +
|
|
||||||
`"iat":` + iatStr + `,` +
|
|
||||||
`"purchase":{"desc":"toaster","q":5,"t":` + iatStr + `},` +
|
|
||||||
`"tracking_code":"axZf"` +
|
|
||||||
`}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
|
||||||
c := &ClaimSet{
|
|
||||||
Iss: iss,
|
|
||||||
Scope: scope,
|
|
||||||
Aud: stdAud,
|
|
||||||
iat: iatT,
|
|
||||||
exp: expT,
|
|
||||||
PrivateClaims: testCase.input,
|
|
||||||
}
|
|
||||||
cJSON, err := base64Decode(c.encode())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error decoding claim set: %v", err)
|
|
||||||
}
|
|
||||||
if string(cJSON) != testCase.want {
|
|
||||||
t.Errorf("TestClaimSetWithPrivateNameEncode: enc != want in case %s", testCase.desc)
|
|
||||||
t.Errorf(" enc = %s", cJSON)
|
|
||||||
t.Errorf(" want = %s", testCase.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the NewToken constructor.
|
|
||||||
func TestNewToken(t *testing.T) {
|
|
||||||
tok := NewToken(iss, scope, privateKeyPemBytes)
|
|
||||||
if tok.ClaimSet.Iss != iss {
|
|
||||||
t.Error("TestNewToken: tok.ClaimSet.Iss != iss")
|
|
||||||
t.Errorf(" tok.ClaimSet.Iss = %s", tok.ClaimSet.Iss)
|
|
||||||
t.Errorf(" iss = %s", iss)
|
|
||||||
}
|
|
||||||
if tok.ClaimSet.Scope != scope {
|
|
||||||
t.Error("TestNewToken: tok.ClaimSet.Scope != scope")
|
|
||||||
t.Errorf(" tok.ClaimSet.Scope = %s", tok.ClaimSet.Scope)
|
|
||||||
t.Errorf(" scope = %s", scope)
|
|
||||||
}
|
|
||||||
if tok.ClaimSet.Aud != stdAud {
|
|
||||||
t.Error("TestNewToken: tok.ClaimSet.Aud != stdAud")
|
|
||||||
t.Errorf(" tok.ClaimSet.Aud = %s", tok.ClaimSet.Aud)
|
|
||||||
t.Errorf(" stdAud = %s", stdAud)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(tok.Key, privateKeyPemBytes) {
|
|
||||||
t.Error("TestNewToken: tok.Key != privateKeyPemBytes")
|
|
||||||
t.Errorf(" tok.Key = %s", tok.Key)
|
|
||||||
t.Errorf(" privateKeyPemBytes = %s", privateKeyPemBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the private key parsing functions work.
|
|
||||||
func TestParsePrivateKey(t *testing.T) {
|
|
||||||
tok := &Token{
|
|
||||||
Key: privateKeyPemBytes,
|
|
||||||
}
|
|
||||||
err := tok.parsePrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("TestParsePrivateKey:tok.parsePrivateKey: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that the token signature generated matches the golden standard.
|
|
||||||
func TestTokenSign(t *testing.T) {
|
|
||||||
tok := &Token{
|
|
||||||
Key: privateKeyPemBytes,
|
|
||||||
claim: claimSetEnc,
|
|
||||||
header: headerEnc,
|
|
||||||
}
|
|
||||||
err := tok.parsePrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("TestTokenSign:tok.parsePrivateKey: %v", err)
|
|
||||||
}
|
|
||||||
err = tok.sign()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("TestTokenSign:tok.sign: %v", err)
|
|
||||||
}
|
|
||||||
if tok.sig != sigEnc {
|
|
||||||
t.Error("TestTokenSign: tok.sig != sigEnc")
|
|
||||||
t.Errorf(" tok.sig = %s", tok.sig)
|
|
||||||
t.Errorf(" sigEnc = %s", sigEnc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that the token expiration function is working.
|
|
||||||
func TestTokenExpired(t *testing.T) {
|
|
||||||
c := &ClaimSet{}
|
|
||||||
tok := &Token{
|
|
||||||
ClaimSet: c,
|
|
||||||
}
|
|
||||||
now := time.Now()
|
|
||||||
c.setTimes(now)
|
|
||||||
if tok.Expired() != false {
|
|
||||||
t.Error("TestTokenExpired: tok.Expired != false")
|
|
||||||
}
|
|
||||||
// Set the times as if they were set 2 hours ago.
|
|
||||||
c.setTimes(now.Add(-2 * time.Hour))
|
|
||||||
if tok.Expired() != true {
|
|
||||||
t.Error("TestTokenExpired: tok.Expired != true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given a well formed Token, test for proper encoding.
|
|
||||||
func TestTokenEncode(t *testing.T) {
|
|
||||||
c := &ClaimSet{
|
|
||||||
Iss: iss,
|
|
||||||
Scope: scope,
|
|
||||||
exp: time.Unix(exp, 0),
|
|
||||||
iat: time.Unix(iat, 0),
|
|
||||||
}
|
|
||||||
tok := &Token{
|
|
||||||
ClaimSet: c,
|
|
||||||
Header: stdHeader,
|
|
||||||
Key: privateKeyPemBytes,
|
|
||||||
}
|
|
||||||
enc, err := tok.Encode()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("TestTokenEncode:tok.Assertion: %v", err)
|
|
||||||
}
|
|
||||||
if enc != tokEnc {
|
|
||||||
t.Error("TestTokenEncode: enc != tokEnc")
|
|
||||||
t.Errorf(" enc = %s", enc)
|
|
||||||
t.Errorf(" tokEnc = %s", tokEnc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given a well formed Token we should get back a well formed request.
|
|
||||||
func TestBuildRequest(t *testing.T) {
|
|
||||||
c := &ClaimSet{
|
|
||||||
Iss: iss,
|
|
||||||
Scope: scope,
|
|
||||||
exp: time.Unix(exp, 0),
|
|
||||||
iat: time.Unix(iat, 0),
|
|
||||||
}
|
|
||||||
tok := &Token{
|
|
||||||
ClaimSet: c,
|
|
||||||
Header: stdHeader,
|
|
||||||
Key: privateKeyPemBytes,
|
|
||||||
}
|
|
||||||
u, v, err := tok.buildRequest()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("TestBuildRequest:BuildRequest: %v", err)
|
|
||||||
}
|
|
||||||
if u != c.Aud {
|
|
||||||
t.Error("TestBuildRequest: u != c.Aud")
|
|
||||||
t.Errorf(" u = %s", u)
|
|
||||||
t.Errorf(" c.Aud = %s", c.Aud)
|
|
||||||
}
|
|
||||||
if v.Get("grant_type") != stdGrantType {
|
|
||||||
t.Error("TestBuildRequest: grant_type != stdGrantType")
|
|
||||||
t.Errorf(" grant_type = %s", v.Get("grant_type"))
|
|
||||||
t.Errorf(" stdGrantType = %s", stdGrantType)
|
|
||||||
}
|
|
||||||
if v.Get("assertion") != tokEnc {
|
|
||||||
t.Error("TestBuildRequest: assertion != tokEnc")
|
|
||||||
t.Errorf(" assertion = %s", v.Get("assertion"))
|
|
||||||
t.Errorf(" tokEnc = %s", tokEnc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given a well formed access request response we should get back a oauth.Token.
|
|
||||||
func TestHandleResponse(t *testing.T) {
|
|
||||||
rb := &respBody{
|
|
||||||
Access: "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M",
|
|
||||||
Type: "Bearer",
|
|
||||||
ExpiresIn: 3600,
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(rb)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("TestHandleResponse:json.Marshal: %v", err)
|
|
||||||
}
|
|
||||||
r := &http.Response{
|
|
||||||
Status: "200 OK",
|
|
||||||
StatusCode: 200,
|
|
||||||
Body: ioutil.NopCloser(bytes.NewReader(b)),
|
|
||||||
}
|
|
||||||
o, err := handleResponse(r)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("TestHandleResponse:handleResponse: %v", err)
|
|
||||||
}
|
|
||||||
if o.AccessToken != rb.Access {
|
|
||||||
t.Error("TestHandleResponse: o.AccessToken != rb.Access")
|
|
||||||
t.Errorf(" o.AccessToken = %s", o.AccessToken)
|
|
||||||
t.Errorf(" rb.Access = %s", rb.Access)
|
|
||||||
}
|
|
||||||
if o.Expired() {
|
|
||||||
t.Error("TestHandleResponse: o.Expired == true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// passthrough signature for test
|
|
||||||
type FakeSigner struct{}
|
|
||||||
|
|
||||||
func (f FakeSigner) Sign(tok *Token) ([]byte, []byte, error) {
|
|
||||||
block, _ := pem.Decode(privateKeyPemBytes)
|
|
||||||
pKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
||||||
ss := headerEnc + "." + claimSetEnc
|
|
||||||
h := sha256.New()
|
|
||||||
h.Write([]byte(ss))
|
|
||||||
b, _ := rsa.SignPKCS1v15(rand.Reader, pKey, crypto.SHA256, h.Sum(nil))
|
|
||||||
return []byte(ss), b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given an external signer, get back a valid and signed JWT
|
|
||||||
func TestExternalSigner(t *testing.T) {
|
|
||||||
tok := NewSignerToken(iss, scope, FakeSigner{})
|
|
||||||
enc, _ := tok.Encode()
|
|
||||||
if enc != tokEnc {
|
|
||||||
t.Errorf("TestExternalSigner: enc != tokEnc")
|
|
||||||
t.Errorf(" enc = %s", enc)
|
|
||||||
t.Errorf(" tokEnc = %s", tokEnc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandleResponseWithNewExpiry(t *testing.T) {
|
|
||||||
rb := &respBody{
|
|
||||||
IdToken: tokEnc,
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(rb)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("TestHandleResponse:json.Marshal: %v", err)
|
|
||||||
}
|
|
||||||
r := &http.Response{
|
|
||||||
Status: "200 OK",
|
|
||||||
StatusCode: 200,
|
|
||||||
Body: ioutil.NopCloser(bytes.NewReader(b)),
|
|
||||||
}
|
|
||||||
o, err := handleResponse(r)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("TestHandleResponse:handleResponse: %v", err)
|
|
||||||
}
|
|
||||||
if o.Expiry != time.Unix(exp, 0) {
|
|
||||||
t.Error("TestHandleResponse: o.Expiry != exp")
|
|
||||||
t.Errorf(" o.Expiry = %s", o.Expiry)
|
|
||||||
t.Errorf(" exp = %s", time.Unix(exp, 0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Placeholder for future Assert tests.
|
|
||||||
func TestAssert(t *testing.T) {
|
|
||||||
// Since this method makes a call to BuildRequest, an htttp.Client, and
|
|
||||||
// finally HandleResponse there is not much more to test. This is here
|
|
||||||
// as a placeholder if that changes.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Benchmark for the end-to-end encoding of a well formed token.
|
|
||||||
func BenchmarkTokenEncode(b *testing.B) {
|
|
||||||
b.StopTimer()
|
|
||||||
c := &ClaimSet{
|
|
||||||
Iss: iss,
|
|
||||||
Scope: scope,
|
|
||||||
exp: time.Unix(exp, 0),
|
|
||||||
iat: time.Unix(iat, 0),
|
|
||||||
}
|
|
||||||
tok := &Token{
|
|
||||||
ClaimSet: c,
|
|
||||||
Key: privateKeyPemBytes,
|
|
||||||
}
|
|
||||||
b.StartTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
tok.Encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
405
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth.go
generated
vendored
405
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth.go
generated
vendored
@@ -1,405 +0,0 @@
|
|||||||
// Copyright 2011 The goauth2 Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// The oauth package provides support for making
|
|
||||||
// OAuth2-authenticated HTTP requests.
|
|
||||||
//
|
|
||||||
// Example usage:
|
|
||||||
//
|
|
||||||
// // Specify your configuration. (typically as a global variable)
|
|
||||||
// var config = &oauth.Config{
|
|
||||||
// ClientId: YOUR_CLIENT_ID,
|
|
||||||
// ClientSecret: YOUR_CLIENT_SECRET,
|
|
||||||
// Scope: "https://www.googleapis.com/auth/buzz",
|
|
||||||
// AuthURL: "https://accounts.google.com/o/oauth2/auth",
|
|
||||||
// TokenURL: "https://accounts.google.com/o/oauth2/token",
|
|
||||||
// RedirectURL: "http://you.example.org/handler",
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // A landing page redirects to the OAuth provider to get the auth code.
|
|
||||||
// func landing(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// http.Redirect(w, r, config.AuthCodeURL("foo"), http.StatusFound)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // The user will be redirected back to this handler, that takes the
|
|
||||||
// // "code" query parameter and Exchanges it for an access token.
|
|
||||||
// func handler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// t := &oauth.Transport{Config: config}
|
|
||||||
// t.Exchange(r.FormValue("code"))
|
|
||||||
// // The Transport now has a valid Token. Create an *http.Client
|
|
||||||
// // with which we can make authenticated API requests.
|
|
||||||
// c := t.Client()
|
|
||||||
// c.Post(...)
|
|
||||||
// // ...
|
|
||||||
// // btw, r.FormValue("state") == "foo"
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
package oauth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"mime"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type OAuthError struct {
|
|
||||||
prefix string
|
|
||||||
msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (oe OAuthError) Error() string {
|
|
||||||
return "OAuthError: " + oe.prefix + ": " + oe.msg
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache specifies the methods that implement a Token cache.
|
|
||||||
type Cache interface {
|
|
||||||
Token() (*Token, error)
|
|
||||||
PutToken(*Token) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// CacheFile implements Cache. Its value is the name of the file in which
|
|
||||||
// the Token is stored in JSON format.
|
|
||||||
type CacheFile string
|
|
||||||
|
|
||||||
func (f CacheFile) Token() (*Token, error) {
|
|
||||||
file, err := os.Open(string(f))
|
|
||||||
if err != nil {
|
|
||||||
return nil, OAuthError{"CacheFile.Token", err.Error()}
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
tok := &Token{}
|
|
||||||
if err := json.NewDecoder(file).Decode(tok); err != nil {
|
|
||||||
return nil, OAuthError{"CacheFile.Token", err.Error()}
|
|
||||||
}
|
|
||||||
return tok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f CacheFile) PutToken(tok *Token) error {
|
|
||||||
file, err := os.OpenFile(string(f), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
|
||||||
if err != nil {
|
|
||||||
return OAuthError{"CacheFile.PutToken", err.Error()}
|
|
||||||
}
|
|
||||||
if err := json.NewEncoder(file).Encode(tok); err != nil {
|
|
||||||
file.Close()
|
|
||||||
return OAuthError{"CacheFile.PutToken", err.Error()}
|
|
||||||
}
|
|
||||||
if err := file.Close(); err != nil {
|
|
||||||
return OAuthError{"CacheFile.PutToken", err.Error()}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is the configuration of an OAuth consumer.
|
|
||||||
type Config struct {
|
|
||||||
// ClientId is the OAuth client identifier used when communicating with
|
|
||||||
// the configured OAuth provider.
|
|
||||||
ClientId string
|
|
||||||
|
|
||||||
// ClientSecret is the OAuth client secret used when communicating with
|
|
||||||
// the configured OAuth provider.
|
|
||||||
ClientSecret string
|
|
||||||
|
|
||||||
// Scope identifies the level of access being requested. Multiple scope
|
|
||||||
// values should be provided as a space-delimited string.
|
|
||||||
Scope string
|
|
||||||
|
|
||||||
// AuthURL is the URL the user will be directed to in order to grant
|
|
||||||
// access.
|
|
||||||
AuthURL string
|
|
||||||
|
|
||||||
// TokenURL is the URL used to retrieve OAuth tokens.
|
|
||||||
TokenURL string
|
|
||||||
|
|
||||||
// RedirectURL is the URL to which the user will be returned after
|
|
||||||
// granting (or denying) access.
|
|
||||||
RedirectURL string
|
|
||||||
|
|
||||||
// TokenCache allows tokens to be cached for subsequent requests.
|
|
||||||
TokenCache Cache
|
|
||||||
|
|
||||||
AccessType string // Optional, "online" (default) or "offline", no refresh token if "online"
|
|
||||||
|
|
||||||
// ApprovalPrompt indicates whether the user should be
|
|
||||||
// re-prompted for consent. If set to "auto" (default) the
|
|
||||||
// user will be prompted only if they haven't previously
|
|
||||||
// granted consent and the code can only be exchanged for an
|
|
||||||
// access token.
|
|
||||||
// If set to "force" the user will always be prompted, and the
|
|
||||||
// code can be exchanged for a refresh token.
|
|
||||||
ApprovalPrompt string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Token contains an end-user's tokens.
|
|
||||||
// This is the data you must store to persist authentication.
|
|
||||||
type Token struct {
|
|
||||||
AccessToken string
|
|
||||||
RefreshToken string
|
|
||||||
Expiry time.Time // If zero the token has no (known) expiry time.
|
|
||||||
Extra map[string]string // May be nil.
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Token) Expired() bool {
|
|
||||||
if t.Expiry.IsZero() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return t.Expiry.Before(time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transport implements http.RoundTripper. When configured with a valid
|
|
||||||
// Config and Token it can be used to make authenticated HTTP requests.
|
|
||||||
//
|
|
||||||
// t := &oauth.Transport{config}
|
|
||||||
// t.Exchange(code)
|
|
||||||
// // t now contains a valid Token
|
|
||||||
// r, _, err := t.Client().Get("http://example.org/url/requiring/auth")
|
|
||||||
//
|
|
||||||
// It will automatically refresh the Token if it can,
|
|
||||||
// updating the supplied Token in place.
|
|
||||||
type Transport struct {
|
|
||||||
*Config
|
|
||||||
*Token
|
|
||||||
|
|
||||||
// Transport is the HTTP transport to use when making requests.
|
|
||||||
// It will default to http.DefaultTransport if nil.
|
|
||||||
// (It should never be an oauth.Transport.)
|
|
||||||
Transport http.RoundTripper
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client returns an *http.Client that makes OAuth-authenticated requests.
|
|
||||||
func (t *Transport) Client() *http.Client {
|
|
||||||
return &http.Client{Transport: t}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) transport() http.RoundTripper {
|
|
||||||
if t.Transport != nil {
|
|
||||||
return t.Transport
|
|
||||||
}
|
|
||||||
return http.DefaultTransport
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthCodeURL returns a URL that the end-user should be redirected to,
|
|
||||||
// so that they may obtain an authorization code.
|
|
||||||
func (c *Config) AuthCodeURL(state string) string {
|
|
||||||
url_, err := url.Parse(c.AuthURL)
|
|
||||||
if err != nil {
|
|
||||||
panic("AuthURL malformed: " + err.Error())
|
|
||||||
}
|
|
||||||
q := url.Values{
|
|
||||||
"response_type": {"code"},
|
|
||||||
"client_id": {c.ClientId},
|
|
||||||
"redirect_uri": {c.RedirectURL},
|
|
||||||
"scope": {c.Scope},
|
|
||||||
"state": {state},
|
|
||||||
"access_type": {c.AccessType},
|
|
||||||
"approval_prompt": {c.ApprovalPrompt},
|
|
||||||
}.Encode()
|
|
||||||
if url_.RawQuery == "" {
|
|
||||||
url_.RawQuery = q
|
|
||||||
} else {
|
|
||||||
url_.RawQuery += "&" + q
|
|
||||||
}
|
|
||||||
return url_.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exchange takes a code and gets access Token from the remote server.
|
|
||||||
func (t *Transport) Exchange(code string) (*Token, error) {
|
|
||||||
if t.Config == nil {
|
|
||||||
return nil, OAuthError{"Exchange", "no Config supplied"}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the transport or the cache already has a token, it is
|
|
||||||
// passed to `updateToken` to preserve existing refresh token.
|
|
||||||
tok := t.Token
|
|
||||||
if tok == nil && t.TokenCache != nil {
|
|
||||||
tok, _ = t.TokenCache.Token()
|
|
||||||
}
|
|
||||||
if tok == nil {
|
|
||||||
tok = new(Token)
|
|
||||||
}
|
|
||||||
err := t.updateToken(tok, url.Values{
|
|
||||||
"grant_type": {"authorization_code"},
|
|
||||||
"redirect_uri": {t.RedirectURL},
|
|
||||||
"scope": {t.Scope},
|
|
||||||
"code": {code},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
t.Token = tok
|
|
||||||
if t.TokenCache != nil {
|
|
||||||
return tok, t.TokenCache.PutToken(tok)
|
|
||||||
}
|
|
||||||
return tok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundTrip executes a single HTTP transaction using the Transport's
|
|
||||||
// Token as authorization headers.
|
|
||||||
//
|
|
||||||
// This method will attempt to renew the Token if it has expired and may return
|
|
||||||
// an error related to that Token renewal before attempting the client request.
|
|
||||||
// If the Token cannot be renewed a non-nil os.Error value will be returned.
|
|
||||||
// If the Token is invalid callers should expect HTTP-level errors,
|
|
||||||
// as indicated by the Response's StatusCode.
|
|
||||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
if t.Token == nil {
|
|
||||||
if t.Config == nil {
|
|
||||||
return nil, OAuthError{"RoundTrip", "no Config supplied"}
|
|
||||||
}
|
|
||||||
if t.TokenCache == nil {
|
|
||||||
return nil, OAuthError{"RoundTrip", "no Token supplied"}
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
t.Token, err = t.TokenCache.Token()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh the Token if it has expired.
|
|
||||||
if t.Expired() {
|
|
||||||
if err := t.Refresh(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// To set the Authorization header, we must make a copy of the Request
|
|
||||||
// so that we don't modify the Request we were given.
|
|
||||||
// This is required by the specification of http.RoundTripper.
|
|
||||||
req = cloneRequest(req)
|
|
||||||
req.Header.Set("Authorization", "Bearer "+t.AccessToken)
|
|
||||||
|
|
||||||
// Make the HTTP request.
|
|
||||||
return t.transport().RoundTrip(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// cloneRequest returns a clone of the provided *http.Request.
|
|
||||||
// The clone is a shallow copy of the struct and its Header map.
|
|
||||||
func cloneRequest(r *http.Request) *http.Request {
|
|
||||||
// shallow copy of the struct
|
|
||||||
r2 := new(http.Request)
|
|
||||||
*r2 = *r
|
|
||||||
// deep copy of the Header
|
|
||||||
r2.Header = make(http.Header)
|
|
||||||
for k, s := range r.Header {
|
|
||||||
r2.Header[k] = s
|
|
||||||
}
|
|
||||||
return r2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh renews the Transport's AccessToken using its RefreshToken.
|
|
||||||
func (t *Transport) Refresh() error {
|
|
||||||
if t.Token == nil {
|
|
||||||
return OAuthError{"Refresh", "no existing Token"}
|
|
||||||
}
|
|
||||||
if t.RefreshToken == "" {
|
|
||||||
return OAuthError{"Refresh", "Token expired; no Refresh Token"}
|
|
||||||
}
|
|
||||||
if t.Config == nil {
|
|
||||||
return OAuthError{"Refresh", "no Config supplied"}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := t.updateToken(t.Token, url.Values{
|
|
||||||
"grant_type": {"refresh_token"},
|
|
||||||
"refresh_token": {t.RefreshToken},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if t.TokenCache != nil {
|
|
||||||
return t.TokenCache.PutToken(t.Token)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthenticateClient gets an access Token using the client_credentials grant
|
|
||||||
// type.
|
|
||||||
func (t *Transport) AuthenticateClient() error {
|
|
||||||
if t.Config == nil {
|
|
||||||
return OAuthError{"Exchange", "no Config supplied"}
|
|
||||||
}
|
|
||||||
if t.Token == nil {
|
|
||||||
t.Token = &Token{}
|
|
||||||
}
|
|
||||||
return t.updateToken(t.Token, url.Values{"grant_type": {"client_credentials"}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) updateToken(tok *Token, v url.Values) error {
|
|
||||||
v.Set("client_id", t.ClientId)
|
|
||||||
v.Set("client_secret", t.ClientSecret)
|
|
||||||
client := &http.Client{Transport: t.transport()}
|
|
||||||
req, err := http.NewRequest("POST", t.TokenURL, strings.NewReader(v.Encode()))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
req.SetBasicAuth(t.ClientId, t.ClientSecret)
|
|
||||||
r, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer r.Body.Close()
|
|
||||||
if r.StatusCode != 200 {
|
|
||||||
return OAuthError{"updateToken", r.Status}
|
|
||||||
}
|
|
||||||
var b struct {
|
|
||||||
Access string `json:"access_token"`
|
|
||||||
Refresh string `json:"refresh_token"`
|
|
||||||
ExpiresIn time.Duration `json:"expires_in"`
|
|
||||||
Id string `json:"id_token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
||||||
switch content {
|
|
||||||
case "application/x-www-form-urlencoded", "text/plain":
|
|
||||||
vals, err := url.ParseQuery(string(body))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Access = vals.Get("access_token")
|
|
||||||
b.Refresh = vals.Get("refresh_token")
|
|
||||||
b.ExpiresIn, _ = time.ParseDuration(vals.Get("expires_in") + "s")
|
|
||||||
b.Id = vals.Get("id_token")
|
|
||||||
default:
|
|
||||||
if err = json.Unmarshal(body, &b); err != nil {
|
|
||||||
return fmt.Errorf("got bad response from server: %q", body)
|
|
||||||
}
|
|
||||||
// The JSON parser treats the unitless ExpiresIn like 'ns' instead of 's' as above,
|
|
||||||
// so compensate here.
|
|
||||||
b.ExpiresIn *= time.Second
|
|
||||||
}
|
|
||||||
if b.Access == "" {
|
|
||||||
return errors.New("received empty access token from authorization server")
|
|
||||||
}
|
|
||||||
tok.AccessToken = b.Access
|
|
||||||
// Don't overwrite `RefreshToken` with an empty value
|
|
||||||
if len(b.Refresh) > 0 {
|
|
||||||
tok.RefreshToken = b.Refresh
|
|
||||||
}
|
|
||||||
if b.ExpiresIn == 0 {
|
|
||||||
tok.Expiry = time.Time{}
|
|
||||||
} else {
|
|
||||||
tok.Expiry = time.Now().Add(b.ExpiresIn)
|
|
||||||
}
|
|
||||||
if b.Id != "" {
|
|
||||||
if tok.Extra == nil {
|
|
||||||
tok.Extra = make(map[string]string)
|
|
||||||
}
|
|
||||||
tok.Extra["id_token"] = b.Id
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
214
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth_test.go
generated
vendored
214
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth_test.go
generated
vendored
@@ -1,214 +0,0 @@
|
|||||||
// Copyright 2011 The goauth2 Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package oauth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var requests = []struct {
|
|
||||||
path, query, auth string // request
|
|
||||||
contenttype, body string // response
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
path: "/token",
|
|
||||||
query: "grant_type=authorization_code&code=c0d3&client_id=cl13nt1d&client_secret=s3cr3t",
|
|
||||||
contenttype: "application/json",
|
|
||||||
auth: "Basic Y2wxM250MWQ6czNjcjN0",
|
|
||||||
body: `
|
|
||||||
{
|
|
||||||
"access_token":"token1",
|
|
||||||
"refresh_token":"refreshtoken1",
|
|
||||||
"id_token":"idtoken1",
|
|
||||||
"expires_in":3600
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{path: "/secure", auth: "Bearer token1", body: "first payload"},
|
|
||||||
{
|
|
||||||
path: "/token",
|
|
||||||
query: "grant_type=refresh_token&refresh_token=refreshtoken1&client_id=cl13nt1d&client_secret=s3cr3t",
|
|
||||||
contenttype: "application/json",
|
|
||||||
auth: "Basic Y2wxM250MWQ6czNjcjN0",
|
|
||||||
body: `
|
|
||||||
{
|
|
||||||
"access_token":"token2",
|
|
||||||
"refresh_token":"refreshtoken2",
|
|
||||||
"id_token":"idtoken2",
|
|
||||||
"expires_in":3600
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{path: "/secure", auth: "Bearer token2", body: "second payload"},
|
|
||||||
{
|
|
||||||
path: "/token",
|
|
||||||
query: "grant_type=refresh_token&refresh_token=refreshtoken2&client_id=cl13nt1d&client_secret=s3cr3t",
|
|
||||||
contenttype: "application/x-www-form-urlencoded",
|
|
||||||
body: "access_token=token3&refresh_token=refreshtoken3&id_token=idtoken3&expires_in=3600",
|
|
||||||
auth: "Basic Y2wxM250MWQ6czNjcjN0",
|
|
||||||
},
|
|
||||||
{path: "/secure", auth: "Bearer token3", body: "third payload"},
|
|
||||||
{
|
|
||||||
path: "/token",
|
|
||||||
query: "grant_type=client_credentials&client_id=cl13nt1d&client_secret=s3cr3t",
|
|
||||||
contenttype: "application/json",
|
|
||||||
auth: "Basic Y2wxM250MWQ6czNjcjN0",
|
|
||||||
body: `
|
|
||||||
{
|
|
||||||
"access_token":"token4",
|
|
||||||
"expires_in":3600
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{path: "/secure", auth: "Bearer token4", body: "fourth payload"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOAuth(t *testing.T) {
|
|
||||||
// Set up test server.
|
|
||||||
n := 0
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if n >= len(requests) {
|
|
||||||
t.Errorf("too many requests: %d", n)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
req := requests[n]
|
|
||||||
n++
|
|
||||||
|
|
||||||
// Check request.
|
|
||||||
if g, w := r.URL.Path, req.path; g != w {
|
|
||||||
t.Errorf("request[%d] got path %s, want %s", n, g, w)
|
|
||||||
}
|
|
||||||
want, _ := url.ParseQuery(req.query)
|
|
||||||
for k := range want {
|
|
||||||
if g, w := r.FormValue(k), want.Get(k); g != w {
|
|
||||||
t.Errorf("query[%s] = %s, want %s", k, g, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if g, w := r.Header.Get("Authorization"), req.auth; w != "" && g != w {
|
|
||||||
t.Errorf("Authorization: %v, want %v", g, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send response.
|
|
||||||
w.Header().Set("Content-Type", req.contenttype)
|
|
||||||
io.WriteString(w, req.body)
|
|
||||||
}
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
config := &Config{
|
|
||||||
ClientId: "cl13nt1d",
|
|
||||||
ClientSecret: "s3cr3t",
|
|
||||||
Scope: "https://example.net/scope",
|
|
||||||
AuthURL: server.URL + "/auth",
|
|
||||||
TokenURL: server.URL + "/token",
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(adg): test AuthCodeURL
|
|
||||||
|
|
||||||
transport := &Transport{Config: config}
|
|
||||||
_, err := transport.Exchange("c0d3")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Exchange: %v", err)
|
|
||||||
}
|
|
||||||
checkToken(t, transport.Token, "token1", "refreshtoken1", "idtoken1")
|
|
||||||
|
|
||||||
c := transport.Client()
|
|
||||||
resp, err := c.Get(server.URL + "/secure")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Get: %v", err)
|
|
||||||
}
|
|
||||||
checkBody(t, resp, "first payload")
|
|
||||||
|
|
||||||
// test automatic refresh
|
|
||||||
transport.Expiry = time.Now().Add(-time.Hour)
|
|
||||||
resp, err = c.Get(server.URL + "/secure")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Get: %v", err)
|
|
||||||
}
|
|
||||||
checkBody(t, resp, "second payload")
|
|
||||||
checkToken(t, transport.Token, "token2", "refreshtoken2", "idtoken2")
|
|
||||||
|
|
||||||
// refresh one more time, but get URL-encoded token instead of JSON
|
|
||||||
transport.Expiry = time.Now().Add(-time.Hour)
|
|
||||||
resp, err = c.Get(server.URL + "/secure")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Get: %v", err)
|
|
||||||
}
|
|
||||||
checkBody(t, resp, "third payload")
|
|
||||||
checkToken(t, transport.Token, "token3", "refreshtoken3", "idtoken3")
|
|
||||||
|
|
||||||
transport.Token = &Token{}
|
|
||||||
err = transport.AuthenticateClient()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("AuthenticateClient: %v", err)
|
|
||||||
}
|
|
||||||
checkToken(t, transport.Token, "token4", "", "")
|
|
||||||
resp, err = c.Get(server.URL + "/secure")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Get: %v", err)
|
|
||||||
}
|
|
||||||
checkBody(t, resp, "fourth payload")
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkToken(t *testing.T, tok *Token, access, refresh, id string) {
|
|
||||||
if g, w := tok.AccessToken, access; g != w {
|
|
||||||
t.Errorf("AccessToken = %q, want %q", g, w)
|
|
||||||
}
|
|
||||||
if g, w := tok.RefreshToken, refresh; g != w {
|
|
||||||
t.Errorf("RefreshToken = %q, want %q", g, w)
|
|
||||||
}
|
|
||||||
if g, w := tok.Extra["id_token"], id; g != w {
|
|
||||||
t.Errorf("Extra['id_token'] = %q, want %q", g, w)
|
|
||||||
}
|
|
||||||
exp := tok.Expiry.Sub(time.Now())
|
|
||||||
if (time.Hour-time.Second) > exp || exp > time.Hour {
|
|
||||||
t.Errorf("Expiry = %v, want ~1 hour", exp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkBody(t *testing.T, r *http.Response, body string) {
|
|
||||||
b, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("reading reponse body: %v, want %q", err, body)
|
|
||||||
}
|
|
||||||
if g, w := string(b), body; g != w {
|
|
||||||
t.Errorf("request body mismatch: got %q, want %q", g, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCachePermissions(t *testing.T) {
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
// Windows doesn't support file mode bits.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
td, err := ioutil.TempDir("", "oauth-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ioutil.TempDir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(td)
|
|
||||||
tempFile := filepath.Join(td, "cache-file")
|
|
||||||
|
|
||||||
cf := CacheFile(tempFile)
|
|
||||||
if err := cf.PutToken(new(Token)); err != nil {
|
|
||||||
t.Fatalf("PutToken: %v", err)
|
|
||||||
}
|
|
||||||
fi, err := os.Stat(tempFile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("os.Stat: %v", err)
|
|
||||||
}
|
|
||||||
if fi.Mode()&0077 != 0 {
|
|
||||||
t.Errorf("Created cache file has mode %#o, want non-accessible to group+other", fi.Mode())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
43
Godeps/_workspace/src/github.com/golang/protobuf/proto/Makefile
generated
vendored
Normal file
43
Godeps/_workspace/src/github.com/golang/protobuf/proto/Makefile
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
#
|
||||||
|
# Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
# https://github.com/golang/protobuf
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
install:
|
||||||
|
go install
|
||||||
|
|
||||||
|
test: install generate-test-pbs
|
||||||
|
go test
|
||||||
|
|
||||||
|
|
||||||
|
generate-test-pbs:
|
||||||
|
make install
|
||||||
|
make -C testdata
|
||||||
|
make -C proto3_proto
|
||||||
|
make
|
||||||
2059
Godeps/_workspace/src/github.com/golang/protobuf/proto/all_test.go
generated
vendored
Normal file
2059
Godeps/_workspace/src/github.com/golang/protobuf/proto/all_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
197
Godeps/_workspace/src/github.com/golang/protobuf/proto/clone.go
generated
vendored
Normal file
197
Godeps/_workspace/src/github.com/golang/protobuf/proto/clone.go
generated
vendored
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Protocol buffer deep copy.
|
||||||
|
// TODO: MessageSet and RawMessage.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clone returns a deep copy of a protocol buffer.
|
||||||
|
func Clone(pb Message) Message {
|
||||||
|
in := reflect.ValueOf(pb)
|
||||||
|
if in.IsNil() {
|
||||||
|
return pb
|
||||||
|
}
|
||||||
|
|
||||||
|
out := reflect.New(in.Type().Elem())
|
||||||
|
// out is empty so a merge is a deep copy.
|
||||||
|
mergeStruct(out.Elem(), in.Elem())
|
||||||
|
return out.Interface().(Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges src into dst.
|
||||||
|
// Required and optional fields that are set in src will be set to that value in dst.
|
||||||
|
// Elements of repeated fields will be appended.
|
||||||
|
// Merge panics if src and dst are not the same type, or if dst is nil.
|
||||||
|
func Merge(dst, src Message) {
|
||||||
|
in := reflect.ValueOf(src)
|
||||||
|
out := reflect.ValueOf(dst)
|
||||||
|
if out.IsNil() {
|
||||||
|
panic("proto: nil destination")
|
||||||
|
}
|
||||||
|
if in.Type() != out.Type() {
|
||||||
|
// Explicit test prior to mergeStruct so that mistyped nils will fail
|
||||||
|
panic("proto: type mismatch")
|
||||||
|
}
|
||||||
|
if in.IsNil() {
|
||||||
|
// Merging nil into non-nil is a quiet no-op
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mergeStruct(out.Elem(), in.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeStruct(out, in reflect.Value) {
|
||||||
|
for i := 0; i < in.NumField(); i++ {
|
||||||
|
f := in.Type().Field(i)
|
||||||
|
if strings.HasPrefix(f.Name, "XXX_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mergeAny(out.Field(i), in.Field(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
if emIn, ok := in.Addr().Interface().(extendableProto); ok {
|
||||||
|
emOut := out.Addr().Interface().(extendableProto)
|
||||||
|
mergeExtension(emOut.ExtensionMap(), emIn.ExtensionMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
uf := in.FieldByName("XXX_unrecognized")
|
||||||
|
if !uf.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uin := uf.Bytes()
|
||||||
|
if len(uin) > 0 {
|
||||||
|
out.FieldByName("XXX_unrecognized").SetBytes(append([]byte(nil), uin...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeAny(out, in reflect.Value) {
|
||||||
|
if in.Type() == protoMessageType {
|
||||||
|
if !in.IsNil() {
|
||||||
|
if out.IsNil() {
|
||||||
|
out.Set(reflect.ValueOf(Clone(in.Interface().(Message))))
|
||||||
|
} else {
|
||||||
|
Merge(out.Interface().(Message), in.Interface().(Message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch in.Kind() {
|
||||||
|
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||||
|
out.Set(in)
|
||||||
|
case reflect.Map:
|
||||||
|
if in.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if out.IsNil() {
|
||||||
|
out.Set(reflect.MakeMap(in.Type()))
|
||||||
|
}
|
||||||
|
// For maps with value types of *T or []byte we need to deep copy each value.
|
||||||
|
elemKind := in.Type().Elem().Kind()
|
||||||
|
for _, key := range in.MapKeys() {
|
||||||
|
var val reflect.Value
|
||||||
|
switch elemKind {
|
||||||
|
case reflect.Ptr:
|
||||||
|
val = reflect.New(in.Type().Elem().Elem())
|
||||||
|
mergeAny(val, in.MapIndex(key))
|
||||||
|
case reflect.Slice:
|
||||||
|
val = in.MapIndex(key)
|
||||||
|
val = reflect.ValueOf(append([]byte{}, val.Bytes()...))
|
||||||
|
default:
|
||||||
|
val = in.MapIndex(key)
|
||||||
|
}
|
||||||
|
out.SetMapIndex(key, val)
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
if in.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if out.IsNil() {
|
||||||
|
out.Set(reflect.New(in.Elem().Type()))
|
||||||
|
}
|
||||||
|
mergeAny(out.Elem(), in.Elem())
|
||||||
|
case reflect.Slice:
|
||||||
|
if in.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if in.Type().Elem().Kind() == reflect.Uint8 {
|
||||||
|
// []byte is a scalar bytes field, not a repeated field.
|
||||||
|
// Make a deep copy.
|
||||||
|
// Append to []byte{} instead of []byte(nil) so that we never end up
|
||||||
|
// with a nil result.
|
||||||
|
out.SetBytes(append([]byte{}, in.Bytes()...))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n := in.Len()
|
||||||
|
if out.IsNil() {
|
||||||
|
out.Set(reflect.MakeSlice(in.Type(), 0, n))
|
||||||
|
}
|
||||||
|
switch in.Type().Elem().Kind() {
|
||||||
|
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||||
|
out.Set(reflect.AppendSlice(out, in))
|
||||||
|
default:
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
x := reflect.Indirect(reflect.New(in.Type().Elem()))
|
||||||
|
mergeAny(x, in.Index(i))
|
||||||
|
out.Set(reflect.Append(out, x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
mergeStruct(out, in)
|
||||||
|
default:
|
||||||
|
// unknown type, so not a protocol buffer
|
||||||
|
log.Printf("proto: don't know how to copy %v", in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeExtension(out, in map[int32]Extension) {
|
||||||
|
for extNum, eIn := range in {
|
||||||
|
eOut := Extension{desc: eIn.desc}
|
||||||
|
if eIn.value != nil {
|
||||||
|
v := reflect.New(reflect.TypeOf(eIn.value)).Elem()
|
||||||
|
mergeAny(v, reflect.ValueOf(eIn.value))
|
||||||
|
eOut.value = v.Interface()
|
||||||
|
}
|
||||||
|
if eIn.enc != nil {
|
||||||
|
eOut.enc = make([]byte, len(eIn.enc))
|
||||||
|
copy(eOut.enc, eIn.enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
out[extNum] = eOut
|
||||||
|
}
|
||||||
|
}
|
||||||
227
Godeps/_workspace/src/github.com/golang/protobuf/proto/clone_test.go
generated
vendored
Normal file
227
Godeps/_workspace/src/github.com/golang/protobuf/proto/clone_test.go
generated
vendored
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
pb "./testdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cloneTestMessage = &pb.MyMessage{
|
||||||
|
Count: proto.Int32(42),
|
||||||
|
Name: proto.String("Dave"),
|
||||||
|
Pet: []string{"bunny", "kitty", "horsey"},
|
||||||
|
Inner: &pb.InnerMessage{
|
||||||
|
Host: proto.String("niles"),
|
||||||
|
Port: proto.Int32(9099),
|
||||||
|
Connected: proto.Bool(true),
|
||||||
|
},
|
||||||
|
Others: []*pb.OtherMessage{
|
||||||
|
{
|
||||||
|
Value: []byte("some bytes"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Somegroup: &pb.MyMessage_SomeGroup{
|
||||||
|
GroupField: proto.Int32(6),
|
||||||
|
},
|
||||||
|
RepBytes: [][]byte{[]byte("sham"), []byte("wow")},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ext := &pb.Ext{
|
||||||
|
Data: proto.String("extension"),
|
||||||
|
}
|
||||||
|
if err := proto.SetExtension(cloneTestMessage, pb.E_Ext_More, ext); err != nil {
|
||||||
|
panic("SetExtension: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClone(t *testing.T) {
|
||||||
|
m := proto.Clone(cloneTestMessage).(*pb.MyMessage)
|
||||||
|
if !proto.Equal(m, cloneTestMessage) {
|
||||||
|
t.Errorf("Clone(%v) = %v", cloneTestMessage, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify it was a deep copy.
|
||||||
|
*m.Inner.Port++
|
||||||
|
if proto.Equal(m, cloneTestMessage) {
|
||||||
|
t.Error("Mutating clone changed the original")
|
||||||
|
}
|
||||||
|
// Byte fields and repeated fields should be copied.
|
||||||
|
if &m.Pet[0] == &cloneTestMessage.Pet[0] {
|
||||||
|
t.Error("Pet: repeated field not copied")
|
||||||
|
}
|
||||||
|
if &m.Others[0] == &cloneTestMessage.Others[0] {
|
||||||
|
t.Error("Others: repeated field not copied")
|
||||||
|
}
|
||||||
|
if &m.Others[0].Value[0] == &cloneTestMessage.Others[0].Value[0] {
|
||||||
|
t.Error("Others[0].Value: bytes field not copied")
|
||||||
|
}
|
||||||
|
if &m.RepBytes[0] == &cloneTestMessage.RepBytes[0] {
|
||||||
|
t.Error("RepBytes: repeated field not copied")
|
||||||
|
}
|
||||||
|
if &m.RepBytes[0][0] == &cloneTestMessage.RepBytes[0][0] {
|
||||||
|
t.Error("RepBytes[0]: bytes field not copied")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloneNil(t *testing.T) {
|
||||||
|
var m *pb.MyMessage
|
||||||
|
if c := proto.Clone(m); !proto.Equal(m, c) {
|
||||||
|
t.Errorf("Clone(%v) = %v", m, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mergeTests = []struct {
|
||||||
|
src, dst, want proto.Message
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
src: &pb.MyMessage{
|
||||||
|
Count: proto.Int32(42),
|
||||||
|
},
|
||||||
|
dst: &pb.MyMessage{
|
||||||
|
Name: proto.String("Dave"),
|
||||||
|
},
|
||||||
|
want: &pb.MyMessage{
|
||||||
|
Count: proto.Int32(42),
|
||||||
|
Name: proto.String("Dave"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: &pb.MyMessage{
|
||||||
|
Inner: &pb.InnerMessage{
|
||||||
|
Host: proto.String("hey"),
|
||||||
|
Connected: proto.Bool(true),
|
||||||
|
},
|
||||||
|
Pet: []string{"horsey"},
|
||||||
|
Others: []*pb.OtherMessage{
|
||||||
|
{
|
||||||
|
Value: []byte("some bytes"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dst: &pb.MyMessage{
|
||||||
|
Inner: &pb.InnerMessage{
|
||||||
|
Host: proto.String("niles"),
|
||||||
|
Port: proto.Int32(9099),
|
||||||
|
},
|
||||||
|
Pet: []string{"bunny", "kitty"},
|
||||||
|
Others: []*pb.OtherMessage{
|
||||||
|
{
|
||||||
|
Key: proto.Int64(31415926535),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Explicitly test a src=nil field
|
||||||
|
Inner: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &pb.MyMessage{
|
||||||
|
Inner: &pb.InnerMessage{
|
||||||
|
Host: proto.String("hey"),
|
||||||
|
Connected: proto.Bool(true),
|
||||||
|
Port: proto.Int32(9099),
|
||||||
|
},
|
||||||
|
Pet: []string{"bunny", "kitty", "horsey"},
|
||||||
|
Others: []*pb.OtherMessage{
|
||||||
|
{
|
||||||
|
Key: proto.Int64(31415926535),
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
Value: []byte("some bytes"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: &pb.MyMessage{
|
||||||
|
RepBytes: [][]byte{[]byte("wow")},
|
||||||
|
},
|
||||||
|
dst: &pb.MyMessage{
|
||||||
|
Somegroup: &pb.MyMessage_SomeGroup{
|
||||||
|
GroupField: proto.Int32(6),
|
||||||
|
},
|
||||||
|
RepBytes: [][]byte{[]byte("sham")},
|
||||||
|
},
|
||||||
|
want: &pb.MyMessage{
|
||||||
|
Somegroup: &pb.MyMessage_SomeGroup{
|
||||||
|
GroupField: proto.Int32(6),
|
||||||
|
},
|
||||||
|
RepBytes: [][]byte{[]byte("sham"), []byte("wow")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Check that a scalar bytes field replaces rather than appends.
|
||||||
|
{
|
||||||
|
src: &pb.OtherMessage{Value: []byte("foo")},
|
||||||
|
dst: &pb.OtherMessage{Value: []byte("bar")},
|
||||||
|
want: &pb.OtherMessage{Value: []byte("foo")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: &pb.MessageWithMap{
|
||||||
|
NameMapping: map[int32]string{6: "Nigel"},
|
||||||
|
MsgMapping: map[int64]*pb.FloatingPoint{
|
||||||
|
0x4001: &pb.FloatingPoint{F: proto.Float64(2.0)},
|
||||||
|
},
|
||||||
|
ByteMapping: map[bool][]byte{true: []byte("wowsa")},
|
||||||
|
},
|
||||||
|
dst: &pb.MessageWithMap{
|
||||||
|
NameMapping: map[int32]string{
|
||||||
|
6: "Bruce", // should be overwritten
|
||||||
|
7: "Andrew",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &pb.MessageWithMap{
|
||||||
|
NameMapping: map[int32]string{
|
||||||
|
6: "Nigel",
|
||||||
|
7: "Andrew",
|
||||||
|
},
|
||||||
|
MsgMapping: map[int64]*pb.FloatingPoint{
|
||||||
|
0x4001: &pb.FloatingPoint{F: proto.Float64(2.0)},
|
||||||
|
},
|
||||||
|
ByteMapping: map[bool][]byte{true: []byte("wowsa")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMerge(t *testing.T) {
|
||||||
|
for _, m := range mergeTests {
|
||||||
|
got := proto.Clone(m.dst)
|
||||||
|
proto.Merge(got, m.src)
|
||||||
|
if !proto.Equal(got, m.want) {
|
||||||
|
t.Errorf("Merge(%v, %v)\n got %v\nwant %v\n", m.dst, m.src, got, m.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
823
Godeps/_workspace/src/github.com/golang/protobuf/proto/decode.go
generated
vendored
Normal file
823
Godeps/_workspace/src/github.com/golang/protobuf/proto/decode.go
generated
vendored
Normal file
@@ -0,0 +1,823 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Routines for decoding protocol buffer data to construct in-memory representations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errOverflow is returned when an integer is too large to be represented.
|
||||||
|
var errOverflow = errors.New("proto: integer overflow")
|
||||||
|
|
||||||
|
// The fundamental decoders that interpret bytes on the wire.
|
||||||
|
// Those that take integer types all return uint64 and are
|
||||||
|
// therefore of type valueDecoder.
|
||||||
|
|
||||||
|
// DecodeVarint reads a varint-encoded integer from the slice.
|
||||||
|
// It returns the integer and the number of bytes consumed, or
|
||||||
|
// zero if there is not enough.
|
||||||
|
// This is the format for the
|
||||||
|
// int32, int64, uint32, uint64, bool, and enum
|
||||||
|
// protocol buffer types.
|
||||||
|
func DecodeVarint(buf []byte) (x uint64, n int) {
|
||||||
|
// x, n already 0
|
||||||
|
for shift := uint(0); shift < 64; shift += 7 {
|
||||||
|
if n >= len(buf) {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
b := uint64(buf[n])
|
||||||
|
n++
|
||||||
|
x |= (b & 0x7F) << shift
|
||||||
|
if (b & 0x80) == 0 {
|
||||||
|
return x, n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The number is too large to represent in a 64-bit value.
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeVarint reads a varint-encoded integer from the Buffer.
|
||||||
|
// This is the format for the
|
||||||
|
// int32, int64, uint32, uint64, bool, and enum
|
||||||
|
// protocol buffer types.
|
||||||
|
func (p *Buffer) DecodeVarint() (x uint64, err error) {
|
||||||
|
// x, err already 0
|
||||||
|
|
||||||
|
i := p.index
|
||||||
|
l := len(p.buf)
|
||||||
|
|
||||||
|
for shift := uint(0); shift < 64; shift += 7 {
|
||||||
|
if i >= l {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b := p.buf[i]
|
||||||
|
i++
|
||||||
|
x |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
p.index = i
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The number is too large to represent in a 64-bit value.
|
||||||
|
err = errOverflow
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFixed64 reads a 64-bit integer from the Buffer.
|
||||||
|
// This is the format for the
|
||||||
|
// fixed64, sfixed64, and double protocol buffer types.
|
||||||
|
func (p *Buffer) DecodeFixed64() (x uint64, err error) {
|
||||||
|
// x, err already 0
|
||||||
|
i := p.index + 8
|
||||||
|
if i < 0 || i > len(p.buf) {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.index = i
|
||||||
|
|
||||||
|
x = uint64(p.buf[i-8])
|
||||||
|
x |= uint64(p.buf[i-7]) << 8
|
||||||
|
x |= uint64(p.buf[i-6]) << 16
|
||||||
|
x |= uint64(p.buf[i-5]) << 24
|
||||||
|
x |= uint64(p.buf[i-4]) << 32
|
||||||
|
x |= uint64(p.buf[i-3]) << 40
|
||||||
|
x |= uint64(p.buf[i-2]) << 48
|
||||||
|
x |= uint64(p.buf[i-1]) << 56
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFixed32 reads a 32-bit integer from the Buffer.
|
||||||
|
// This is the format for the
|
||||||
|
// fixed32, sfixed32, and float protocol buffer types.
|
||||||
|
func (p *Buffer) DecodeFixed32() (x uint64, err error) {
|
||||||
|
// x, err already 0
|
||||||
|
i := p.index + 4
|
||||||
|
if i < 0 || i > len(p.buf) {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.index = i
|
||||||
|
|
||||||
|
x = uint64(p.buf[i-4])
|
||||||
|
x |= uint64(p.buf[i-3]) << 8
|
||||||
|
x |= uint64(p.buf[i-2]) << 16
|
||||||
|
x |= uint64(p.buf[i-1]) << 24
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeZigzag64 reads a zigzag-encoded 64-bit integer
|
||||||
|
// from the Buffer.
|
||||||
|
// This is the format used for the sint64 protocol buffer type.
|
||||||
|
func (p *Buffer) DecodeZigzag64() (x uint64, err error) {
|
||||||
|
x, err = p.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x = (x >> 1) ^ uint64((int64(x&1)<<63)>>63)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeZigzag32 reads a zigzag-encoded 32-bit integer
|
||||||
|
// from the Buffer.
|
||||||
|
// This is the format used for the sint32 protocol buffer type.
|
||||||
|
func (p *Buffer) DecodeZigzag32() (x uint64, err error) {
|
||||||
|
x, err = p.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x = uint64((uint32(x) >> 1) ^ uint32((int32(x&1)<<31)>>31))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are not ValueDecoders: they produce an array of bytes or a string.
|
||||||
|
// bytes, embedded messages
|
||||||
|
|
||||||
|
// DecodeRawBytes reads a count-delimited byte buffer from the Buffer.
|
||||||
|
// This is the format used for the bytes protocol buffer
|
||||||
|
// type and for embedded messages.
|
||||||
|
func (p *Buffer) DecodeRawBytes(alloc bool) (buf []byte, err error) {
|
||||||
|
n, err := p.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nb := int(n)
|
||||||
|
if nb < 0 {
|
||||||
|
return nil, fmt.Errorf("proto: bad byte length %d", nb)
|
||||||
|
}
|
||||||
|
end := p.index + nb
|
||||||
|
if end < p.index || end > len(p.buf) {
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alloc {
|
||||||
|
// todo: check if can get more uses of alloc=false
|
||||||
|
buf = p.buf[p.index:end]
|
||||||
|
p.index += nb
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = make([]byte, nb)
|
||||||
|
copy(buf, p.buf[p.index:])
|
||||||
|
p.index += nb
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeStringBytes reads an encoded string from the Buffer.
|
||||||
|
// This is the format used for the proto2 string type.
|
||||||
|
func (p *Buffer) DecodeStringBytes() (s string, err error) {
|
||||||
|
buf, err := p.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||||
|
// If the protocol buffer has extensions, and the field matches, add it as an extension.
|
||||||
|
// Otherwise, if the XXX_unrecognized field exists, append the skipped data there.
|
||||||
|
func (o *Buffer) skipAndSave(t reflect.Type, tag, wire int, base structPointer, unrecField field) error {
|
||||||
|
oi := o.index
|
||||||
|
|
||||||
|
err := o.skip(t, tag, wire)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !unrecField.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := structPointer_Bytes(base, unrecField)
|
||||||
|
|
||||||
|
// Add the skipped field to struct field
|
||||||
|
obuf := o.buf
|
||||||
|
|
||||||
|
o.buf = *ptr
|
||||||
|
o.EncodeVarint(uint64(tag<<3 | wire))
|
||||||
|
*ptr = append(o.buf, obuf[oi:o.index]...)
|
||||||
|
|
||||||
|
o.buf = obuf
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||||
|
func (o *Buffer) skip(t reflect.Type, tag, wire int) error {
|
||||||
|
|
||||||
|
var u uint64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch wire {
|
||||||
|
case WireVarint:
|
||||||
|
_, err = o.DecodeVarint()
|
||||||
|
case WireFixed64:
|
||||||
|
_, err = o.DecodeFixed64()
|
||||||
|
case WireBytes:
|
||||||
|
_, err = o.DecodeRawBytes(false)
|
||||||
|
case WireFixed32:
|
||||||
|
_, err = o.DecodeFixed32()
|
||||||
|
case WireStartGroup:
|
||||||
|
for {
|
||||||
|
u, err = o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fwire := int(u & 0x7)
|
||||||
|
if fwire == WireEndGroup {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ftag := int(u >> 3)
|
||||||
|
err = o.skip(t, ftag, fwire)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("proto: can't skip unknown wire type %d for %s", wire, t)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshaler is the interface representing objects that can
|
||||||
|
// unmarshal themselves. The method should reset the receiver before
|
||||||
|
// decoding starts. The argument points to data that may be
|
||||||
|
// overwritten, so implementations should not keep references to the
|
||||||
|
// buffer.
|
||||||
|
type Unmarshaler interface {
|
||||||
|
Unmarshal([]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal parses the protocol buffer representation in buf and places the
|
||||||
|
// decoded result in pb. If the struct underlying pb does not match
|
||||||
|
// the data in buf, the results can be unpredictable.
|
||||||
|
//
|
||||||
|
// Unmarshal resets pb before starting to unmarshal, so any
|
||||||
|
// existing data in pb is always removed. Use UnmarshalMerge
|
||||||
|
// to preserve and append to existing data.
|
||||||
|
func Unmarshal(buf []byte, pb Message) error {
|
||||||
|
pb.Reset()
|
||||||
|
return UnmarshalMerge(buf, pb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMerge parses the protocol buffer representation in buf and
|
||||||
|
// writes the decoded result to pb. If the struct underlying pb does not match
|
||||||
|
// the data in buf, the results can be unpredictable.
|
||||||
|
//
|
||||||
|
// UnmarshalMerge merges into existing data in pb.
|
||||||
|
// Most code should use Unmarshal instead.
|
||||||
|
func UnmarshalMerge(buf []byte, pb Message) error {
|
||||||
|
// If the object can unmarshal itself, let it.
|
||||||
|
if u, ok := pb.(Unmarshaler); ok {
|
||||||
|
return u.Unmarshal(buf)
|
||||||
|
}
|
||||||
|
return NewBuffer(buf).Unmarshal(pb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal parses the protocol buffer representation in the
|
||||||
|
// Buffer and places the decoded result in pb. If the struct
|
||||||
|
// underlying pb does not match the data in the buffer, the results can be
|
||||||
|
// unpredictable.
|
||||||
|
func (p *Buffer) Unmarshal(pb Message) error {
|
||||||
|
// If the object can unmarshal itself, let it.
|
||||||
|
if u, ok := pb.(Unmarshaler); ok {
|
||||||
|
err := u.Unmarshal(p.buf[p.index:])
|
||||||
|
p.index = len(p.buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
typ, base, err := getbase(pb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base)
|
||||||
|
|
||||||
|
if collectStats {
|
||||||
|
stats.Decode++
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshalType does the work of unmarshaling a structure.
|
||||||
|
func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group bool, base structPointer) error {
|
||||||
|
var state errorState
|
||||||
|
required, reqFields := prop.reqCount, uint64(0)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for err == nil && o.index < len(o.buf) {
|
||||||
|
oi := o.index
|
||||||
|
var u uint64
|
||||||
|
u, err = o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
wire := int(u & 0x7)
|
||||||
|
if wire == WireEndGroup {
|
||||||
|
if is_group {
|
||||||
|
return nil // input is satisfied
|
||||||
|
}
|
||||||
|
return fmt.Errorf("proto: %s: wiretype end group for non-group", st)
|
||||||
|
}
|
||||||
|
tag := int(u >> 3)
|
||||||
|
if tag <= 0 {
|
||||||
|
return fmt.Errorf("proto: %s: illegal tag %d (wire type %d)", st, tag, wire)
|
||||||
|
}
|
||||||
|
fieldnum, ok := prop.decoderTags.get(tag)
|
||||||
|
if !ok {
|
||||||
|
// Maybe it's an extension?
|
||||||
|
if prop.extendable {
|
||||||
|
if e := structPointer_Interface(base, st).(extendableProto); isExtensionField(e, int32(tag)) {
|
||||||
|
if err = o.skip(st, tag, wire); err == nil {
|
||||||
|
ext := e.ExtensionMap()[int32(tag)] // may be missing
|
||||||
|
ext.enc = append(ext.enc, o.buf[oi:o.index]...)
|
||||||
|
e.ExtensionMap()[int32(tag)] = ext
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = o.skipAndSave(st, tag, wire, base, prop.unrecField)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p := prop.Prop[fieldnum]
|
||||||
|
|
||||||
|
if p.dec == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: no protobuf decoder for %s.%s\n", st, st.Field(fieldnum).Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dec := p.dec
|
||||||
|
if wire != WireStartGroup && wire != p.WireType {
|
||||||
|
if wire == WireBytes && p.packedDec != nil {
|
||||||
|
// a packable field
|
||||||
|
dec = p.packedDec
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("proto: bad wiretype for field %s.%s: got wiretype %d, want %d", st, st.Field(fieldnum).Name, wire, p.WireType)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decErr := dec(o, p, base)
|
||||||
|
if decErr != nil && !state.shouldContinue(decErr, p) {
|
||||||
|
err = decErr
|
||||||
|
}
|
||||||
|
if err == nil && p.Required {
|
||||||
|
// Successfully decoded a required field.
|
||||||
|
if tag <= 64 {
|
||||||
|
// use bitmap for fields 1-64 to catch field reuse.
|
||||||
|
var mask uint64 = 1 << uint64(tag-1)
|
||||||
|
if reqFields&mask == 0 {
|
||||||
|
// new required field
|
||||||
|
reqFields |= mask
|
||||||
|
required--
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is imprecise. It can be fooled by a required field
|
||||||
|
// with a tag > 64 that is encoded twice; that's very rare.
|
||||||
|
// A fully correct implementation would require allocating
|
||||||
|
// a data structure, which we would like to avoid.
|
||||||
|
required--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if is_group {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if state.err != nil {
|
||||||
|
return state.err
|
||||||
|
}
|
||||||
|
if required > 0 {
|
||||||
|
// Not enough information to determine the exact field. If we use extra
|
||||||
|
// CPU, we could determine the field only if the missing required field
|
||||||
|
// has a tag <= 64 and we check reqFields.
|
||||||
|
return &RequiredNotSetError{"{Unknown}"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Individual type decoders
|
||||||
|
// For each,
|
||||||
|
// u is the decoded value,
|
||||||
|
// v is a pointer to the field (pointer) in the struct
|
||||||
|
|
||||||
|
// Sizes of the pools to allocate inside the Buffer.
|
||||||
|
// The goal is modest amortization and allocation
|
||||||
|
// on at least 16-byte boundaries.
|
||||||
|
const (
|
||||||
|
boolPoolSize = 16
|
||||||
|
uint32PoolSize = 8
|
||||||
|
uint64PoolSize = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode a bool.
|
||||||
|
func (o *Buffer) dec_bool(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(o.bools) == 0 {
|
||||||
|
o.bools = make([]bool, boolPoolSize)
|
||||||
|
}
|
||||||
|
o.bools[0] = u != 0
|
||||||
|
*structPointer_Bool(base, p.field) = &o.bools[0]
|
||||||
|
o.bools = o.bools[1:]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Buffer) dec_proto3_bool(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*structPointer_BoolVal(base, p.field) = u != 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode an int32.
|
||||||
|
func (o *Buffer) dec_int32(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
word32_Set(structPointer_Word32(base, p.field), o, uint32(u))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Buffer) dec_proto3_int32(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
word32Val_Set(structPointer_Word32Val(base, p.field), uint32(u))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode an int64.
|
||||||
|
func (o *Buffer) dec_int64(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
word64_Set(structPointer_Word64(base, p.field), o, u)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Buffer) dec_proto3_int64(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
word64Val_Set(structPointer_Word64Val(base, p.field), o, u)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a string.
|
||||||
|
func (o *Buffer) dec_string(p *Properties, base structPointer) error {
|
||||||
|
s, err := o.DecodeStringBytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sp := new(string)
|
||||||
|
*sp = s
|
||||||
|
*structPointer_String(base, p.field) = sp
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Buffer) dec_proto3_string(p *Properties, base structPointer) error {
|
||||||
|
s, err := o.DecodeStringBytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*structPointer_StringVal(base, p.field) = s
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of bytes ([]byte).
|
||||||
|
func (o *Buffer) dec_slice_byte(p *Properties, base structPointer) error {
|
||||||
|
b, err := o.DecodeRawBytes(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*structPointer_Bytes(base, p.field) = b
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of bools ([]bool).
|
||||||
|
func (o *Buffer) dec_slice_bool(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := structPointer_BoolSlice(base, p.field)
|
||||||
|
*v = append(*v, u != 0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of bools ([]bool) in packed format.
|
||||||
|
func (o *Buffer) dec_slice_packed_bool(p *Properties, base structPointer) error {
|
||||||
|
v := structPointer_BoolSlice(base, p.field)
|
||||||
|
|
||||||
|
nn, err := o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nb := int(nn) // number of bytes of encoded bools
|
||||||
|
|
||||||
|
y := *v
|
||||||
|
for i := 0; i < nb; i++ {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
y = append(y, u != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
*v = y
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of int32s ([]int32).
|
||||||
|
func (o *Buffer) dec_slice_int32(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
structPointer_Word32Slice(base, p.field).Append(uint32(u))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of int32s ([]int32) in packed format.
|
||||||
|
func (o *Buffer) dec_slice_packed_int32(p *Properties, base structPointer) error {
|
||||||
|
v := structPointer_Word32Slice(base, p.field)
|
||||||
|
|
||||||
|
nn, err := o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nb := int(nn) // number of bytes of encoded int32s
|
||||||
|
|
||||||
|
fin := o.index + nb
|
||||||
|
if fin < o.index {
|
||||||
|
return errOverflow
|
||||||
|
}
|
||||||
|
for o.index < fin {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Append(uint32(u))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of int64s ([]int64).
|
||||||
|
func (o *Buffer) dec_slice_int64(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
structPointer_Word64Slice(base, p.field).Append(u)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of int64s ([]int64) in packed format.
|
||||||
|
func (o *Buffer) dec_slice_packed_int64(p *Properties, base structPointer) error {
|
||||||
|
v := structPointer_Word64Slice(base, p.field)
|
||||||
|
|
||||||
|
nn, err := o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nb := int(nn) // number of bytes of encoded int64s
|
||||||
|
|
||||||
|
fin := o.index + nb
|
||||||
|
if fin < o.index {
|
||||||
|
return errOverflow
|
||||||
|
}
|
||||||
|
for o.index < fin {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Append(u)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of strings ([]string).
|
||||||
|
func (o *Buffer) dec_slice_string(p *Properties, base structPointer) error {
|
||||||
|
s, err := o.DecodeStringBytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := structPointer_StringSlice(base, p.field)
|
||||||
|
*v = append(*v, s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of slice of bytes ([][]byte).
|
||||||
|
func (o *Buffer) dec_slice_slice_byte(p *Properties, base structPointer) error {
|
||||||
|
b, err := o.DecodeRawBytes(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := structPointer_BytesSlice(base, p.field)
|
||||||
|
*v = append(*v, b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a map field.
|
||||||
|
func (o *Buffer) dec_new_map(p *Properties, base structPointer) error {
|
||||||
|
raw, err := o.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oi := o.index // index at the end of this map entry
|
||||||
|
o.index -= len(raw) // move buffer back to start of map entry
|
||||||
|
|
||||||
|
mptr := structPointer_Map(base, p.field, p.mtype) // *map[K]V
|
||||||
|
if mptr.Elem().IsNil() {
|
||||||
|
mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem()))
|
||||||
|
}
|
||||||
|
v := mptr.Elem() // map[K]V
|
||||||
|
|
||||||
|
// Prepare addressable doubly-indirect placeholders for the key and value types.
|
||||||
|
// See enc_new_map for why.
|
||||||
|
keyptr := reflect.New(reflect.PtrTo(p.mtype.Key())).Elem() // addressable *K
|
||||||
|
keybase := toStructPointer(keyptr.Addr()) // **K
|
||||||
|
|
||||||
|
var valbase structPointer
|
||||||
|
var valptr reflect.Value
|
||||||
|
switch p.mtype.Elem().Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
// []byte
|
||||||
|
var dummy []byte
|
||||||
|
valptr = reflect.ValueOf(&dummy) // *[]byte
|
||||||
|
valbase = toStructPointer(valptr) // *[]byte
|
||||||
|
case reflect.Ptr:
|
||||||
|
// message; valptr is **Msg; need to allocate the intermediate pointer
|
||||||
|
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||||
|
valptr.Set(reflect.New(valptr.Type().Elem()))
|
||||||
|
valbase = toStructPointer(valptr)
|
||||||
|
default:
|
||||||
|
// everything else
|
||||||
|
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||||
|
valbase = toStructPointer(valptr.Addr()) // **V
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode.
|
||||||
|
// This parses a restricted wire format, namely the encoding of a message
|
||||||
|
// with two fields. See enc_new_map for the format.
|
||||||
|
for o.index < oi {
|
||||||
|
// tagcode for key and value properties are always a single byte
|
||||||
|
// because they have tags 1 and 2.
|
||||||
|
tagcode := o.buf[o.index]
|
||||||
|
o.index++
|
||||||
|
switch tagcode {
|
||||||
|
case p.mkeyprop.tagcode[0]:
|
||||||
|
if err := p.mkeyprop.dec(o, p.mkeyprop, keybase); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case p.mvalprop.tagcode[0]:
|
||||||
|
if err := p.mvalprop.dec(o, p.mvalprop, valbase); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO: Should we silently skip this instead?
|
||||||
|
return fmt.Errorf("proto: bad map data tag %d", raw[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.SetMapIndex(keyptr.Elem(), valptr.Elem())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a group.
|
||||||
|
func (o *Buffer) dec_struct_group(p *Properties, base structPointer) error {
|
||||||
|
bas := structPointer_GetStructPointer(base, p.field)
|
||||||
|
if structPointer_IsNil(bas) {
|
||||||
|
// allocate new nested message
|
||||||
|
bas = toStructPointer(reflect.New(p.stype))
|
||||||
|
structPointer_SetStructPointer(base, p.field, bas)
|
||||||
|
}
|
||||||
|
return o.unmarshalType(p.stype, p.sprop, true, bas)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode an embedded message.
|
||||||
|
func (o *Buffer) dec_struct_message(p *Properties, base structPointer) (err error) {
|
||||||
|
raw, e := o.DecodeRawBytes(false)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
bas := structPointer_GetStructPointer(base, p.field)
|
||||||
|
if structPointer_IsNil(bas) {
|
||||||
|
// allocate new nested message
|
||||||
|
bas = toStructPointer(reflect.New(p.stype))
|
||||||
|
structPointer_SetStructPointer(base, p.field, bas)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the object can unmarshal itself, let it.
|
||||||
|
if p.isUnmarshaler {
|
||||||
|
iv := structPointer_Interface(bas, p.stype)
|
||||||
|
return iv.(Unmarshaler).Unmarshal(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
obuf := o.buf
|
||||||
|
oi := o.index
|
||||||
|
o.buf = raw
|
||||||
|
o.index = 0
|
||||||
|
|
||||||
|
err = o.unmarshalType(p.stype, p.sprop, false, bas)
|
||||||
|
o.buf = obuf
|
||||||
|
o.index = oi
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of embedded messages.
|
||||||
|
func (o *Buffer) dec_slice_struct_message(p *Properties, base structPointer) error {
|
||||||
|
return o.dec_slice_struct(p, false, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of embedded groups.
|
||||||
|
func (o *Buffer) dec_slice_struct_group(p *Properties, base structPointer) error {
|
||||||
|
return o.dec_slice_struct(p, true, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of structs ([]*struct).
|
||||||
|
func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base structPointer) error {
|
||||||
|
v := reflect.New(p.stype)
|
||||||
|
bas := toStructPointer(v)
|
||||||
|
structPointer_StructPointerSlice(base, p.field).Append(bas)
|
||||||
|
|
||||||
|
if is_group {
|
||||||
|
err := o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := o.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the object can unmarshal itself, let it.
|
||||||
|
if p.isUnmarshaler {
|
||||||
|
iv := v.Interface()
|
||||||
|
return iv.(Unmarshaler).Unmarshal(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
obuf := o.buf
|
||||||
|
oi := o.index
|
||||||
|
o.buf = raw
|
||||||
|
o.index = 0
|
||||||
|
|
||||||
|
err = o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||||
|
|
||||||
|
o.buf = obuf
|
||||||
|
o.index = oi
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
1283
Godeps/_workspace/src/github.com/golang/protobuf/proto/encode.go
generated
vendored
Normal file
1283
Godeps/_workspace/src/github.com/golang/protobuf/proto/encode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
256
Godeps/_workspace/src/github.com/golang/protobuf/proto/equal.go
generated
vendored
Normal file
256
Godeps/_workspace/src/github.com/golang/protobuf/proto/equal.go
generated
vendored
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Protocol buffer comparison.
|
||||||
|
// TODO: MessageSet.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Equal returns true iff protocol buffers a and b are equal.
|
||||||
|
The arguments must both be pointers to protocol buffer structs.
|
||||||
|
|
||||||
|
Equality is defined in this way:
|
||||||
|
- Two messages are equal iff they are the same type,
|
||||||
|
corresponding fields are equal, unknown field sets
|
||||||
|
are equal, and extensions sets are equal.
|
||||||
|
- Two set scalar fields are equal iff their values are equal.
|
||||||
|
If the fields are of a floating-point type, remember that
|
||||||
|
NaN != x for all x, including NaN.
|
||||||
|
- Two repeated fields are equal iff their lengths are the same,
|
||||||
|
and their corresponding elements are equal (a "bytes" field,
|
||||||
|
although represented by []byte, is not a repeated field)
|
||||||
|
- Two unset fields are equal.
|
||||||
|
- Two unknown field sets are equal if their current
|
||||||
|
encoded state is equal.
|
||||||
|
- Two extension sets are equal iff they have corresponding
|
||||||
|
elements that are pairwise equal.
|
||||||
|
- Every other combination of things are not equal.
|
||||||
|
|
||||||
|
The return value is undefined if a and b are not protocol buffers.
|
||||||
|
*/
|
||||||
|
func Equal(a, b Message) bool {
|
||||||
|
if a == nil || b == nil {
|
||||||
|
return a == b
|
||||||
|
}
|
||||||
|
v1, v2 := reflect.ValueOf(a), reflect.ValueOf(b)
|
||||||
|
if v1.Type() != v2.Type() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v1.Kind() == reflect.Ptr {
|
||||||
|
if v1.IsNil() {
|
||||||
|
return v2.IsNil()
|
||||||
|
}
|
||||||
|
if v2.IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
v1, v2 = v1.Elem(), v2.Elem()
|
||||||
|
}
|
||||||
|
if v1.Kind() != reflect.Struct {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return equalStruct(v1, v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1 and v2 are known to have the same type.
|
||||||
|
func equalStruct(v1, v2 reflect.Value) bool {
|
||||||
|
for i := 0; i < v1.NumField(); i++ {
|
||||||
|
f := v1.Type().Field(i)
|
||||||
|
if strings.HasPrefix(f.Name, "XXX_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f1, f2 := v1.Field(i), v2.Field(i)
|
||||||
|
if f.Type.Kind() == reflect.Ptr {
|
||||||
|
if n1, n2 := f1.IsNil(), f2.IsNil(); n1 && n2 {
|
||||||
|
// both unset
|
||||||
|
continue
|
||||||
|
} else if n1 != n2 {
|
||||||
|
// set/unset mismatch
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b1, ok := f1.Interface().(raw)
|
||||||
|
if ok {
|
||||||
|
b2 := f2.Interface().(raw)
|
||||||
|
// RawMessage
|
||||||
|
if !bytes.Equal(b1.Bytes(), b2.Bytes()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f1, f2 = f1.Elem(), f2.Elem()
|
||||||
|
}
|
||||||
|
if !equalAny(f1, f2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if em1 := v1.FieldByName("XXX_extensions"); em1.IsValid() {
|
||||||
|
em2 := v2.FieldByName("XXX_extensions")
|
||||||
|
if !equalExtensions(v1.Type(), em1.Interface().(map[int32]Extension), em2.Interface().(map[int32]Extension)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uf := v1.FieldByName("XXX_unrecognized")
|
||||||
|
if !uf.IsValid() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
u1 := uf.Bytes()
|
||||||
|
u2 := v2.FieldByName("XXX_unrecognized").Bytes()
|
||||||
|
if !bytes.Equal(u1, u2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1 and v2 are known to have the same type.
|
||||||
|
func equalAny(v1, v2 reflect.Value) bool {
|
||||||
|
if v1.Type() == protoMessageType {
|
||||||
|
m1, _ := v1.Interface().(Message)
|
||||||
|
m2, _ := v2.Interface().(Message)
|
||||||
|
return Equal(m1, m2)
|
||||||
|
}
|
||||||
|
switch v1.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return v1.Bool() == v2.Bool()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v1.Float() == v2.Float()
|
||||||
|
case reflect.Int32, reflect.Int64:
|
||||||
|
return v1.Int() == v2.Int()
|
||||||
|
case reflect.Map:
|
||||||
|
if v1.Len() != v2.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, key := range v1.MapKeys() {
|
||||||
|
val2 := v2.MapIndex(key)
|
||||||
|
if !val2.IsValid() {
|
||||||
|
// This key was not found in the second map.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !equalAny(v1.MapIndex(key), val2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case reflect.Ptr:
|
||||||
|
return equalAny(v1.Elem(), v2.Elem())
|
||||||
|
case reflect.Slice:
|
||||||
|
if v1.Type().Elem().Kind() == reflect.Uint8 {
|
||||||
|
// short circuit: []byte
|
||||||
|
if v1.IsNil() != v2.IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return bytes.Equal(v1.Interface().([]byte), v2.Interface().([]byte))
|
||||||
|
}
|
||||||
|
|
||||||
|
if v1.Len() != v2.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < v1.Len(); i++ {
|
||||||
|
if !equalAny(v1.Index(i), v2.Index(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case reflect.String:
|
||||||
|
return v1.Interface().(string) == v2.Interface().(string)
|
||||||
|
case reflect.Struct:
|
||||||
|
return equalStruct(v1, v2)
|
||||||
|
case reflect.Uint32, reflect.Uint64:
|
||||||
|
return v1.Uint() == v2.Uint()
|
||||||
|
}
|
||||||
|
|
||||||
|
// unknown type, so not a protocol buffer
|
||||||
|
log.Printf("proto: don't know how to compare %v", v1)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// base is the struct type that the extensions are based on.
|
||||||
|
// em1 and em2 are extension maps.
|
||||||
|
func equalExtensions(base reflect.Type, em1, em2 map[int32]Extension) bool {
|
||||||
|
if len(em1) != len(em2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for extNum, e1 := range em1 {
|
||||||
|
e2, ok := em2[extNum]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
m1, m2 := e1.value, e2.value
|
||||||
|
|
||||||
|
if m1 != nil && m2 != nil {
|
||||||
|
// Both are unencoded.
|
||||||
|
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// At least one is encoded. To do a semantically correct comparison
|
||||||
|
// we need to unmarshal them first.
|
||||||
|
var desc *ExtensionDesc
|
||||||
|
if m := extensionMaps[base]; m != nil {
|
||||||
|
desc = m[extNum]
|
||||||
|
}
|
||||||
|
if desc == nil {
|
||||||
|
log.Printf("proto: don't know how to compare extension %d of %v", extNum, base)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if m1 == nil {
|
||||||
|
m1, err = decodeExtension(e1.enc, desc)
|
||||||
|
}
|
||||||
|
if m2 == nil && err == nil {
|
||||||
|
m2, err = decodeExtension(e2.enc, desc)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// The encoded form is invalid.
|
||||||
|
log.Printf("proto: badly encoded extension %d of %v: %v", extNum, base, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
191
Godeps/_workspace/src/github.com/golang/protobuf/proto/equal_test.go
generated
vendored
Normal file
191
Godeps/_workspace/src/github.com/golang/protobuf/proto/equal_test.go
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
pb "./testdata"
|
||||||
|
. "github.com/golang/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Four identical base messages.
|
||||||
|
// The init function adds extensions to some of them.
|
||||||
|
var messageWithoutExtension = &pb.MyMessage{Count: Int32(7)}
|
||||||
|
var messageWithExtension1a = &pb.MyMessage{Count: Int32(7)}
|
||||||
|
var messageWithExtension1b = &pb.MyMessage{Count: Int32(7)}
|
||||||
|
var messageWithExtension2 = &pb.MyMessage{Count: Int32(7)}
|
||||||
|
|
||||||
|
// Two messages with non-message extensions.
|
||||||
|
var messageWithInt32Extension1 = &pb.MyMessage{Count: Int32(8)}
|
||||||
|
var messageWithInt32Extension2 = &pb.MyMessage{Count: Int32(8)}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ext1 := &pb.Ext{Data: String("Kirk")}
|
||||||
|
ext2 := &pb.Ext{Data: String("Picard")}
|
||||||
|
|
||||||
|
// messageWithExtension1a has ext1, but never marshals it.
|
||||||
|
if err := SetExtension(messageWithExtension1a, pb.E_Ext_More, ext1); err != nil {
|
||||||
|
panic("SetExtension on 1a failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// messageWithExtension1b is the unmarshaled form of messageWithExtension1a.
|
||||||
|
if err := SetExtension(messageWithExtension1b, pb.E_Ext_More, ext1); err != nil {
|
||||||
|
panic("SetExtension on 1b failed: " + err.Error())
|
||||||
|
}
|
||||||
|
buf, err := Marshal(messageWithExtension1b)
|
||||||
|
if err != nil {
|
||||||
|
panic("Marshal of 1b failed: " + err.Error())
|
||||||
|
}
|
||||||
|
messageWithExtension1b.Reset()
|
||||||
|
if err := Unmarshal(buf, messageWithExtension1b); err != nil {
|
||||||
|
panic("Unmarshal of 1b failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// messageWithExtension2 has ext2.
|
||||||
|
if err := SetExtension(messageWithExtension2, pb.E_Ext_More, ext2); err != nil {
|
||||||
|
panic("SetExtension on 2 failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := SetExtension(messageWithInt32Extension1, pb.E_Ext_Number, Int32(23)); err != nil {
|
||||||
|
panic("SetExtension on Int32-1 failed: " + err.Error())
|
||||||
|
}
|
||||||
|
if err := SetExtension(messageWithInt32Extension1, pb.E_Ext_Number, Int32(24)); err != nil {
|
||||||
|
panic("SetExtension on Int32-2 failed: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var EqualTests = []struct {
|
||||||
|
desc string
|
||||||
|
a, b Message
|
||||||
|
exp bool
|
||||||
|
}{
|
||||||
|
{"different types", &pb.GoEnum{}, &pb.GoTestField{}, false},
|
||||||
|
{"equal empty", &pb.GoEnum{}, &pb.GoEnum{}, true},
|
||||||
|
{"nil vs nil", nil, nil, true},
|
||||||
|
{"typed nil vs typed nil", (*pb.GoEnum)(nil), (*pb.GoEnum)(nil), true},
|
||||||
|
{"typed nil vs empty", (*pb.GoEnum)(nil), &pb.GoEnum{}, false},
|
||||||
|
{"different typed nil", (*pb.GoEnum)(nil), (*pb.GoTestField)(nil), false},
|
||||||
|
|
||||||
|
{"one set field, one unset field", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{}, false},
|
||||||
|
{"one set field zero, one unset field", &pb.GoTest{Param: Int32(0)}, &pb.GoTest{}, false},
|
||||||
|
{"different set fields", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{Label: String("bar")}, false},
|
||||||
|
{"equal set", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{Label: String("foo")}, true},
|
||||||
|
|
||||||
|
{"repeated, one set", &pb.GoTest{F_Int32Repeated: []int32{2, 3}}, &pb.GoTest{}, false},
|
||||||
|
{"repeated, different length", &pb.GoTest{F_Int32Repeated: []int32{2, 3}}, &pb.GoTest{F_Int32Repeated: []int32{2}}, false},
|
||||||
|
{"repeated, different value", &pb.GoTest{F_Int32Repeated: []int32{2}}, &pb.GoTest{F_Int32Repeated: []int32{3}}, false},
|
||||||
|
{"repeated, equal", &pb.GoTest{F_Int32Repeated: []int32{2, 4}}, &pb.GoTest{F_Int32Repeated: []int32{2, 4}}, true},
|
||||||
|
{"repeated, nil equal nil", &pb.GoTest{F_Int32Repeated: nil}, &pb.GoTest{F_Int32Repeated: nil}, true},
|
||||||
|
{"repeated, nil equal empty", &pb.GoTest{F_Int32Repeated: nil}, &pb.GoTest{F_Int32Repeated: []int32{}}, true},
|
||||||
|
{"repeated, empty equal nil", &pb.GoTest{F_Int32Repeated: []int32{}}, &pb.GoTest{F_Int32Repeated: nil}, true},
|
||||||
|
|
||||||
|
{
|
||||||
|
"nested, different",
|
||||||
|
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("foo")}},
|
||||||
|
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("bar")}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nested, equal",
|
||||||
|
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("wow")}},
|
||||||
|
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("wow")}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{"bytes", &pb.OtherMessage{Value: []byte("foo")}, &pb.OtherMessage{Value: []byte("foo")}, true},
|
||||||
|
{"bytes, empty", &pb.OtherMessage{Value: []byte{}}, &pb.OtherMessage{Value: []byte{}}, true},
|
||||||
|
{"bytes, empty vs nil", &pb.OtherMessage{Value: []byte{}}, &pb.OtherMessage{Value: nil}, false},
|
||||||
|
{
|
||||||
|
"repeated bytes",
|
||||||
|
&pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}},
|
||||||
|
&pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{"extension vs. no extension", messageWithoutExtension, messageWithExtension1a, false},
|
||||||
|
{"extension vs. same extension", messageWithExtension1a, messageWithExtension1b, true},
|
||||||
|
{"extension vs. different extension", messageWithExtension1a, messageWithExtension2, false},
|
||||||
|
|
||||||
|
{"int32 extension vs. itself", messageWithInt32Extension1, messageWithInt32Extension1, true},
|
||||||
|
{"int32 extension vs. a different int32", messageWithInt32Extension1, messageWithInt32Extension2, false},
|
||||||
|
|
||||||
|
{
|
||||||
|
"message with group",
|
||||||
|
&pb.MyMessage{
|
||||||
|
Count: Int32(1),
|
||||||
|
Somegroup: &pb.MyMessage_SomeGroup{
|
||||||
|
GroupField: Int32(5),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&pb.MyMessage{
|
||||||
|
Count: Int32(1),
|
||||||
|
Somegroup: &pb.MyMessage_SomeGroup{
|
||||||
|
GroupField: Int32(5),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"map same",
|
||||||
|
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||||
|
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"map different entry",
|
||||||
|
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||||
|
&pb.MessageWithMap{NameMapping: map[int32]string{2: "Rob"}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"map different key only",
|
||||||
|
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||||
|
&pb.MessageWithMap{NameMapping: map[int32]string{2: "Ken"}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"map different value only",
|
||||||
|
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||||
|
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Rob"}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEqual(t *testing.T) {
|
||||||
|
for _, tc := range EqualTests {
|
||||||
|
if res := Equal(tc.a, tc.b); res != tc.exp {
|
||||||
|
t.Errorf("%v: Equal(%v, %v) = %v, want %v", tc.desc, tc.a, tc.b, res, tc.exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
353
Godeps/_workspace/src/github.com/golang/protobuf/proto/extensions.go
generated
vendored
Normal file
353
Godeps/_workspace/src/github.com/golang/protobuf/proto/extensions.go
generated
vendored
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Types and routines for supporting protocol buffer extensions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrMissingExtension is the error returned by GetExtension if the named extension is not in the message.
|
||||||
|
var ErrMissingExtension = errors.New("proto: missing extension")
|
||||||
|
|
||||||
|
// ExtensionRange represents a range of message extensions for a protocol buffer.
|
||||||
|
// Used in code generated by the protocol compiler.
|
||||||
|
type ExtensionRange struct {
|
||||||
|
Start, End int32 // both inclusive
|
||||||
|
}
|
||||||
|
|
||||||
|
// extendableProto is an interface implemented by any protocol buffer that may be extended.
|
||||||
|
type extendableProto interface {
|
||||||
|
Message
|
||||||
|
ExtensionRangeArray() []ExtensionRange
|
||||||
|
ExtensionMap() map[int32]Extension
|
||||||
|
}
|
||||||
|
|
||||||
|
var extendableProtoType = reflect.TypeOf((*extendableProto)(nil)).Elem()
|
||||||
|
|
||||||
|
// ExtensionDesc represents an extension specification.
|
||||||
|
// Used in generated code from the protocol compiler.
|
||||||
|
type ExtensionDesc struct {
|
||||||
|
ExtendedType Message // nil pointer to the type that is being extended
|
||||||
|
ExtensionType interface{} // nil pointer to the extension type
|
||||||
|
Field int32 // field number
|
||||||
|
Name string // fully-qualified name of extension, for text formatting
|
||||||
|
Tag string // protobuf tag style
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ed *ExtensionDesc) repeated() bool {
|
||||||
|
t := reflect.TypeOf(ed.ExtensionType)
|
||||||
|
return t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extension represents an extension in a message.
|
||||||
|
type Extension struct {
|
||||||
|
// When an extension is stored in a message using SetExtension
|
||||||
|
// only desc and value are set. When the message is marshaled
|
||||||
|
// enc will be set to the encoded form of the message.
|
||||||
|
//
|
||||||
|
// When a message is unmarshaled and contains extensions, each
|
||||||
|
// extension will have only enc set. When such an extension is
|
||||||
|
// accessed using GetExtension (or GetExtensions) desc and value
|
||||||
|
// will be set.
|
||||||
|
desc *ExtensionDesc
|
||||||
|
value interface{}
|
||||||
|
enc []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawExtension is for testing only.
|
||||||
|
func SetRawExtension(base extendableProto, id int32, b []byte) {
|
||||||
|
base.ExtensionMap()[id] = Extension{enc: b}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isExtensionField returns true iff the given field number is in an extension range.
|
||||||
|
func isExtensionField(pb extendableProto, field int32) bool {
|
||||||
|
for _, er := range pb.ExtensionRangeArray() {
|
||||||
|
if er.Start <= field && field <= er.End {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkExtensionTypes checks that the given extension is valid for pb.
|
||||||
|
func checkExtensionTypes(pb extendableProto, extension *ExtensionDesc) error {
|
||||||
|
// Check the extended type.
|
||||||
|
if a, b := reflect.TypeOf(pb), reflect.TypeOf(extension.ExtendedType); a != b {
|
||||||
|
return errors.New("proto: bad extended type; " + b.String() + " does not extend " + a.String())
|
||||||
|
}
|
||||||
|
// Check the range.
|
||||||
|
if !isExtensionField(pb, extension.Field) {
|
||||||
|
return errors.New("proto: bad extension number; not in declared ranges")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extPropKey is sufficient to uniquely identify an extension.
|
||||||
|
type extPropKey struct {
|
||||||
|
base reflect.Type
|
||||||
|
field int32
|
||||||
|
}
|
||||||
|
|
||||||
|
var extProp = struct {
|
||||||
|
sync.RWMutex
|
||||||
|
m map[extPropKey]*Properties
|
||||||
|
}{
|
||||||
|
m: make(map[extPropKey]*Properties),
|
||||||
|
}
|
||||||
|
|
||||||
|
func extensionProperties(ed *ExtensionDesc) *Properties {
|
||||||
|
key := extPropKey{base: reflect.TypeOf(ed.ExtendedType), field: ed.Field}
|
||||||
|
|
||||||
|
extProp.RLock()
|
||||||
|
if prop, ok := extProp.m[key]; ok {
|
||||||
|
extProp.RUnlock()
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
extProp.RUnlock()
|
||||||
|
|
||||||
|
extProp.Lock()
|
||||||
|
defer extProp.Unlock()
|
||||||
|
// Check again.
|
||||||
|
if prop, ok := extProp.m[key]; ok {
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
|
||||||
|
prop := new(Properties)
|
||||||
|
prop.Init(reflect.TypeOf(ed.ExtensionType), "unknown_name", ed.Tag, nil)
|
||||||
|
extProp.m[key] = prop
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeExtensionMap encodes any unmarshaled (unencoded) extensions in m.
|
||||||
|
func encodeExtensionMap(m map[int32]Extension) error {
|
||||||
|
for k, e := range m {
|
||||||
|
if e.value == nil || e.desc == nil {
|
||||||
|
// Extension is only in its encoded form.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't skip extensions that have an encoded form set,
|
||||||
|
// because the extension value may have been mutated after
|
||||||
|
// the last time this function was called.
|
||||||
|
|
||||||
|
et := reflect.TypeOf(e.desc.ExtensionType)
|
||||||
|
props := extensionProperties(e.desc)
|
||||||
|
|
||||||
|
p := NewBuffer(nil)
|
||||||
|
// If e.value has type T, the encoder expects a *struct{ X T }.
|
||||||
|
// Pass a *T with a zero field and hope it all works out.
|
||||||
|
x := reflect.New(et)
|
||||||
|
x.Elem().Set(reflect.ValueOf(e.value))
|
||||||
|
if err := props.enc(p, props, toStructPointer(x)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.enc = p.buf
|
||||||
|
m[k] = e
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sizeExtensionMap(m map[int32]Extension) (n int) {
|
||||||
|
for _, e := range m {
|
||||||
|
if e.value == nil || e.desc == nil {
|
||||||
|
// Extension is only in its encoded form.
|
||||||
|
n += len(e.enc)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't skip extensions that have an encoded form set,
|
||||||
|
// because the extension value may have been mutated after
|
||||||
|
// the last time this function was called.
|
||||||
|
|
||||||
|
et := reflect.TypeOf(e.desc.ExtensionType)
|
||||||
|
props := extensionProperties(e.desc)
|
||||||
|
|
||||||
|
// If e.value has type T, the encoder expects a *struct{ X T }.
|
||||||
|
// Pass a *T with a zero field and hope it all works out.
|
||||||
|
x := reflect.New(et)
|
||||||
|
x.Elem().Set(reflect.ValueOf(e.value))
|
||||||
|
n += props.size(props, toStructPointer(x))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasExtension returns whether the given extension is present in pb.
|
||||||
|
func HasExtension(pb extendableProto, extension *ExtensionDesc) bool {
|
||||||
|
// TODO: Check types, field numbers, etc.?
|
||||||
|
_, ok := pb.ExtensionMap()[extension.Field]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearExtension removes the given extension from pb.
|
||||||
|
func ClearExtension(pb extendableProto, extension *ExtensionDesc) {
|
||||||
|
// TODO: Check types, field numbers, etc.?
|
||||||
|
delete(pb.ExtensionMap(), extension.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExtension parses and returns the given extension of pb.
|
||||||
|
// If the extension is not present it returns ErrMissingExtension.
|
||||||
|
func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, error) {
|
||||||
|
if err := checkExtensionTypes(pb, extension); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
emap := pb.ExtensionMap()
|
||||||
|
e, ok := emap[extension.Field]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrMissingExtension
|
||||||
|
}
|
||||||
|
if e.value != nil {
|
||||||
|
// Already decoded. Check the descriptor, though.
|
||||||
|
if e.desc != extension {
|
||||||
|
// This shouldn't happen. If it does, it means that
|
||||||
|
// GetExtension was called twice with two different
|
||||||
|
// descriptors with the same field number.
|
||||||
|
return nil, errors.New("proto: descriptor conflict")
|
||||||
|
}
|
||||||
|
return e.value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := decodeExtension(e.enc, extension)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember the decoded version and drop the encoded version.
|
||||||
|
// That way it is safe to mutate what we return.
|
||||||
|
e.value = v
|
||||||
|
e.desc = extension
|
||||||
|
e.enc = nil
|
||||||
|
emap[extension.Field] = e
|
||||||
|
return e.value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeExtension decodes an extension encoded in b.
|
||||||
|
func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) {
|
||||||
|
o := NewBuffer(b)
|
||||||
|
|
||||||
|
t := reflect.TypeOf(extension.ExtensionType)
|
||||||
|
rep := extension.repeated()
|
||||||
|
|
||||||
|
props := extensionProperties(extension)
|
||||||
|
|
||||||
|
// t is a pointer to a struct, pointer to basic type or a slice.
|
||||||
|
// Allocate a "field" to store the pointer/slice itself; the
|
||||||
|
// pointer/slice will be stored here. We pass
|
||||||
|
// the address of this field to props.dec.
|
||||||
|
// This passes a zero field and a *t and lets props.dec
|
||||||
|
// interpret it as a *struct{ x t }.
|
||||||
|
value := reflect.New(t).Elem()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Discard wire type and field number varint. It isn't needed.
|
||||||
|
if _, err := o.DecodeVarint(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := props.dec(o, props, toStructPointer(value.Addr())); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !rep || o.index >= len(o.buf) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExtensions returns a slice of the extensions present in pb that are also listed in es.
|
||||||
|
// The returned slice has the same length as es; missing extensions will appear as nil elements.
|
||||||
|
func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, err error) {
|
||||||
|
epb, ok := pb.(extendableProto)
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("proto: not an extendable proto")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
extensions = make([]interface{}, len(es))
|
||||||
|
for i, e := range es {
|
||||||
|
extensions[i], err = GetExtension(epb, e)
|
||||||
|
if err == ErrMissingExtension {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExtension sets the specified extension of pb to the specified value.
|
||||||
|
func SetExtension(pb extendableProto, extension *ExtensionDesc, value interface{}) error {
|
||||||
|
if err := checkExtensionTypes(pb, extension); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
typ := reflect.TypeOf(extension.ExtensionType)
|
||||||
|
if typ != reflect.TypeOf(value) {
|
||||||
|
return errors.New("proto: bad extension value type")
|
||||||
|
}
|
||||||
|
|
||||||
|
pb.ExtensionMap()[extension.Field] = Extension{desc: extension, value: value}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A global registry of extensions.
|
||||||
|
// The generated code will register the generated descriptors by calling RegisterExtension.
|
||||||
|
|
||||||
|
var extensionMaps = make(map[reflect.Type]map[int32]*ExtensionDesc)
|
||||||
|
|
||||||
|
// RegisterExtension is called from the generated code.
|
||||||
|
func RegisterExtension(desc *ExtensionDesc) {
|
||||||
|
st := reflect.TypeOf(desc.ExtendedType).Elem()
|
||||||
|
m := extensionMaps[st]
|
||||||
|
if m == nil {
|
||||||
|
m = make(map[int32]*ExtensionDesc)
|
||||||
|
extensionMaps[st] = m
|
||||||
|
}
|
||||||
|
if _, ok := m[desc.Field]; ok {
|
||||||
|
panic("proto: duplicate extension registered: " + st.String() + " " + strconv.Itoa(int(desc.Field)))
|
||||||
|
}
|
||||||
|
m[desc.Field] = desc
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisteredExtensions returns a map of the registered extensions of a
|
||||||
|
// protocol buffer struct, indexed by the extension number.
|
||||||
|
// The argument pb should be a nil pointer to the struct type.
|
||||||
|
func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc {
|
||||||
|
return extensionMaps[reflect.TypeOf(pb).Elem()]
|
||||||
|
}
|
||||||
137
Godeps/_workspace/src/github.com/golang/protobuf/proto/extensions_test.go
generated
vendored
Normal file
137
Godeps/_workspace/src/github.com/golang/protobuf/proto/extensions_test.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
pb "./testdata"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetExtensionsWithMissingExtensions(t *testing.T) {
|
||||||
|
msg := &pb.MyMessage{}
|
||||||
|
ext1 := &pb.Ext{}
|
||||||
|
if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil {
|
||||||
|
t.Fatalf("Could not set ext1: %s", ext1)
|
||||||
|
}
|
||||||
|
exts, err := proto.GetExtensions(msg, []*proto.ExtensionDesc{
|
||||||
|
pb.E_Ext_More,
|
||||||
|
pb.E_Ext_Text,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetExtensions() failed: %s", err)
|
||||||
|
}
|
||||||
|
if exts[0] != ext1 {
|
||||||
|
t.Errorf("ext1 not in returned extensions: %T %v", exts[0], exts[0])
|
||||||
|
}
|
||||||
|
if exts[1] != nil {
|
||||||
|
t.Errorf("ext2 in returned extensions: %T %v", exts[1], exts[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetExtensionStability(t *testing.T) {
|
||||||
|
check := func(m *pb.MyMessage) bool {
|
||||||
|
ext1, err := proto.GetExtension(m, pb.E_Ext_More)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetExtension() failed: %s", err)
|
||||||
|
}
|
||||||
|
ext2, err := proto.GetExtension(m, pb.E_Ext_More)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetExtension() failed: %s", err)
|
||||||
|
}
|
||||||
|
return ext1 == ext2
|
||||||
|
}
|
||||||
|
msg := &pb.MyMessage{Count: proto.Int32(4)}
|
||||||
|
ext0 := &pb.Ext{}
|
||||||
|
if err := proto.SetExtension(msg, pb.E_Ext_More, ext0); err != nil {
|
||||||
|
t.Fatalf("Could not set ext1: %s", ext0)
|
||||||
|
}
|
||||||
|
if !check(msg) {
|
||||||
|
t.Errorf("GetExtension() not stable before marshaling")
|
||||||
|
}
|
||||||
|
bb, err := proto.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Marshal() failed: %s", err)
|
||||||
|
}
|
||||||
|
msg1 := &pb.MyMessage{}
|
||||||
|
err = proto.Unmarshal(bb, msg1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unmarshal() failed: %s", err)
|
||||||
|
}
|
||||||
|
if !check(msg1) {
|
||||||
|
t.Errorf("GetExtension() not stable after unmarshaling")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtensionsRoundTrip(t *testing.T) {
|
||||||
|
msg := &pb.MyMessage{}
|
||||||
|
ext1 := &pb.Ext{
|
||||||
|
Data: proto.String("hi"),
|
||||||
|
}
|
||||||
|
ext2 := &pb.Ext{
|
||||||
|
Data: proto.String("there"),
|
||||||
|
}
|
||||||
|
exists := proto.HasExtension(msg, pb.E_Ext_More)
|
||||||
|
if exists {
|
||||||
|
t.Error("Extension More present unexpectedly")
|
||||||
|
}
|
||||||
|
if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := proto.SetExtension(msg, pb.E_Ext_More, ext2); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
e, err := proto.GetExtension(msg, pb.E_Ext_More)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
x, ok := e.(*pb.Ext)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("e has type %T, expected testdata.Ext", e)
|
||||||
|
} else if *x.Data != "there" {
|
||||||
|
t.Errorf("SetExtension failed to overwrite, got %+v, not 'there'", x)
|
||||||
|
}
|
||||||
|
proto.ClearExtension(msg, pb.E_Ext_More)
|
||||||
|
if _, err = proto.GetExtension(msg, pb.E_Ext_More); err != proto.ErrMissingExtension {
|
||||||
|
t.Errorf("got %v, expected ErrMissingExtension", e)
|
||||||
|
}
|
||||||
|
if _, err := proto.GetExtension(msg, pb.E_X215); err == nil {
|
||||||
|
t.Error("expected bad extension error, got nil")
|
||||||
|
}
|
||||||
|
if err := proto.SetExtension(msg, pb.E_X215, 12); err == nil {
|
||||||
|
t.Error("expected extension err")
|
||||||
|
}
|
||||||
|
if err := proto.SetExtension(msg, pb.E_Ext_More, 12); err == nil {
|
||||||
|
t.Error("expected some sort of type mismatch error, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
751
Godeps/_workspace/src/github.com/golang/protobuf/proto/lib.go
generated
vendored
Normal file
751
Godeps/_workspace/src/github.com/golang/protobuf/proto/lib.go
generated
vendored
Normal file
@@ -0,0 +1,751 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package proto converts data structures to and from the wire format of
|
||||||
|
protocol buffers. It works in concert with the Go source code generated
|
||||||
|
for .proto files by the protocol compiler.
|
||||||
|
|
||||||
|
A summary of the properties of the protocol buffer interface
|
||||||
|
for a protocol buffer variable v:
|
||||||
|
|
||||||
|
- Names are turned from camel_case to CamelCase for export.
|
||||||
|
- There are no methods on v to set fields; just treat
|
||||||
|
them as structure fields.
|
||||||
|
- There are getters that return a field's value if set,
|
||||||
|
and return the field's default value if unset.
|
||||||
|
The getters work even if the receiver is a nil message.
|
||||||
|
- The zero value for a struct is its correct initialization state.
|
||||||
|
All desired fields must be set before marshaling.
|
||||||
|
- A Reset() method will restore a protobuf struct to its zero state.
|
||||||
|
- Non-repeated fields are pointers to the values; nil means unset.
|
||||||
|
That is, optional or required field int32 f becomes F *int32.
|
||||||
|
- Repeated fields are slices.
|
||||||
|
- Helper functions are available to aid the setting of fields.
|
||||||
|
Helpers for getting values are superseded by the
|
||||||
|
GetFoo methods and their use is deprecated.
|
||||||
|
msg.Foo = proto.String("hello") // set field
|
||||||
|
- Constants are defined to hold the default values of all fields that
|
||||||
|
have them. They have the form Default_StructName_FieldName.
|
||||||
|
Because the getter methods handle defaulted values,
|
||||||
|
direct use of these constants should be rare.
|
||||||
|
- Enums are given type names and maps from names to values.
|
||||||
|
Enum values are prefixed with the enum's type name. Enum types have
|
||||||
|
a String method, and a Enum method to assist in message construction.
|
||||||
|
- Nested groups and enums have type names prefixed with the name of
|
||||||
|
the surrounding message type.
|
||||||
|
- Extensions are given descriptor names that start with E_,
|
||||||
|
followed by an underscore-delimited list of the nested messages
|
||||||
|
that contain it (if any) followed by the CamelCased name of the
|
||||||
|
extension field itself. HasExtension, ClearExtension, GetExtension
|
||||||
|
and SetExtension are functions for manipulating extensions.
|
||||||
|
- Marshal and Unmarshal are functions to encode and decode the wire format.
|
||||||
|
|
||||||
|
The simplest way to describe this is to see an example.
|
||||||
|
Given file test.proto, containing
|
||||||
|
|
||||||
|
package example;
|
||||||
|
|
||||||
|
enum FOO { X = 17; };
|
||||||
|
|
||||||
|
message Test {
|
||||||
|
required string label = 1;
|
||||||
|
optional int32 type = 2 [default=77];
|
||||||
|
repeated int64 reps = 3;
|
||||||
|
optional group OptionalGroup = 4 {
|
||||||
|
required string RequiredField = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The resulting file, test.pb.go, is:
|
||||||
|
|
||||||
|
package example
|
||||||
|
|
||||||
|
import "github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
type FOO int32
|
||||||
|
const (
|
||||||
|
FOO_X FOO = 17
|
||||||
|
)
|
||||||
|
var FOO_name = map[int32]string{
|
||||||
|
17: "X",
|
||||||
|
}
|
||||||
|
var FOO_value = map[string]int32{
|
||||||
|
"X": 17,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x FOO) Enum() *FOO {
|
||||||
|
p := new(FOO)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
func (x FOO) String() string {
|
||||||
|
return proto.EnumName(FOO_name, int32(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Test struct {
|
||||||
|
Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
|
||||||
|
Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
|
||||||
|
Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
|
||||||
|
Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
func (this *Test) Reset() { *this = Test{} }
|
||||||
|
func (this *Test) String() string { return proto.CompactTextString(this) }
|
||||||
|
const Default_Test_Type int32 = 77
|
||||||
|
|
||||||
|
func (this *Test) GetLabel() string {
|
||||||
|
if this != nil && this.Label != nil {
|
||||||
|
return *this.Label
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Test) GetType() int32 {
|
||||||
|
if this != nil && this.Type != nil {
|
||||||
|
return *this.Type
|
||||||
|
}
|
||||||
|
return Default_Test_Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Test) GetOptionalgroup() *Test_OptionalGroup {
|
||||||
|
if this != nil {
|
||||||
|
return this.Optionalgroup
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Test_OptionalGroup struct {
|
||||||
|
RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
func (this *Test_OptionalGroup) Reset() { *this = Test_OptionalGroup{} }
|
||||||
|
func (this *Test_OptionalGroup) String() string { return proto.CompactTextString(this) }
|
||||||
|
|
||||||
|
func (this *Test_OptionalGroup) GetRequiredField() string {
|
||||||
|
if this != nil && this.RequiredField != nil {
|
||||||
|
return *this.RequiredField
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterEnum("example.FOO", FOO_name, FOO_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
To create and play with a Test object:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"./example.pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
test := &example.Test{
|
||||||
|
Label: proto.String("hello"),
|
||||||
|
Type: proto.Int32(17),
|
||||||
|
Optionalgroup: &example.Test_OptionalGroup{
|
||||||
|
RequiredField: proto.String("good bye"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data, err := proto.Marshal(test)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("marshaling error: ", err)
|
||||||
|
}
|
||||||
|
newTest := new(example.Test)
|
||||||
|
err = proto.Unmarshal(data, newTest)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("unmarshaling error: ", err)
|
||||||
|
}
|
||||||
|
// Now test and newTest contain the same data.
|
||||||
|
if test.GetLabel() != newTest.GetLabel() {
|
||||||
|
log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel())
|
||||||
|
}
|
||||||
|
// etc.
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message is implemented by generated protocol buffer messages.
|
||||||
|
type Message interface {
|
||||||
|
Reset()
|
||||||
|
String() string
|
||||||
|
ProtoMessage()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats records allocation details about the protocol buffer encoders
|
||||||
|
// and decoders. Useful for tuning the library itself.
|
||||||
|
type Stats struct {
|
||||||
|
Emalloc uint64 // mallocs in encode
|
||||||
|
Dmalloc uint64 // mallocs in decode
|
||||||
|
Encode uint64 // number of encodes
|
||||||
|
Decode uint64 // number of decodes
|
||||||
|
Chit uint64 // number of cache hits
|
||||||
|
Cmiss uint64 // number of cache misses
|
||||||
|
Size uint64 // number of sizes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set to true to enable stats collection.
|
||||||
|
const collectStats = false
|
||||||
|
|
||||||
|
var stats Stats
|
||||||
|
|
||||||
|
// GetStats returns a copy of the global Stats structure.
|
||||||
|
func GetStats() Stats { return stats }
|
||||||
|
|
||||||
|
// A Buffer is a buffer manager for marshaling and unmarshaling
|
||||||
|
// protocol buffers. It may be reused between invocations to
|
||||||
|
// reduce memory usage. It is not necessary to use a Buffer;
|
||||||
|
// the global functions Marshal and Unmarshal create a
|
||||||
|
// temporary Buffer and are fine for most applications.
|
||||||
|
type Buffer struct {
|
||||||
|
buf []byte // encode/decode byte stream
|
||||||
|
index int // write point
|
||||||
|
|
||||||
|
// pools of basic types to amortize allocation.
|
||||||
|
bools []bool
|
||||||
|
uint32s []uint32
|
||||||
|
uint64s []uint64
|
||||||
|
|
||||||
|
// extra pools, only used with pointer_reflect.go
|
||||||
|
int32s []int32
|
||||||
|
int64s []int64
|
||||||
|
float32s []float32
|
||||||
|
float64s []float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuffer allocates a new Buffer and initializes its internal data to
|
||||||
|
// the contents of the argument slice.
|
||||||
|
func NewBuffer(e []byte) *Buffer {
|
||||||
|
return &Buffer{buf: e}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the Buffer, ready for marshaling a new protocol buffer.
|
||||||
|
func (p *Buffer) Reset() {
|
||||||
|
p.buf = p.buf[0:0] // for reading/writing
|
||||||
|
p.index = 0 // for reading
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBuf replaces the internal buffer with the slice,
|
||||||
|
// ready for unmarshaling the contents of the slice.
|
||||||
|
func (p *Buffer) SetBuf(s []byte) {
|
||||||
|
p.buf = s
|
||||||
|
p.index = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the contents of the Buffer.
|
||||||
|
func (p *Buffer) Bytes() []byte { return p.buf }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper routines for simplifying the creation of optional fields of basic type.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Bool is a helper routine that allocates a new bool value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Bool(v bool) *bool {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32 is a helper routine that allocates a new int32 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Int32(v int32) *int32 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int is a helper routine that allocates a new int32 value
|
||||||
|
// to store v and returns a pointer to it, but unlike Int32
|
||||||
|
// its argument value is an int.
|
||||||
|
func Int(v int) *int32 {
|
||||||
|
p := new(int32)
|
||||||
|
*p = int32(v)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 is a helper routine that allocates a new int64 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Int64(v int64) *int64 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32 is a helper routine that allocates a new float32 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Float32(v float32) *float32 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 is a helper routine that allocates a new float64 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Float64(v float64) *float64 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 is a helper routine that allocates a new uint32 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Uint32(v uint32) *uint32 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 is a helper routine that allocates a new uint64 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Uint64(v uint64) *uint64 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is a helper routine that allocates a new string value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func String(v string) *string {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumName is a helper function to simplify printing protocol buffer enums
|
||||||
|
// by name. Given an enum map and a value, it returns a useful string.
|
||||||
|
func EnumName(m map[int32]string, v int32) string {
|
||||||
|
s, ok := m[v]
|
||||||
|
if ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return strconv.Itoa(int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSONEnum is a helper function to simplify recovering enum int values
|
||||||
|
// from their JSON-encoded representation. Given a map from the enum's symbolic
|
||||||
|
// names to its int values, and a byte buffer containing the JSON-encoded
|
||||||
|
// value, it returns an int32 that can be cast to the enum type by the caller.
|
||||||
|
//
|
||||||
|
// The function can deal with both JSON representations, numeric and symbolic.
|
||||||
|
func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32, error) {
|
||||||
|
if data[0] == '"' {
|
||||||
|
// New style: enums are strings.
|
||||||
|
var repr string
|
||||||
|
if err := json.Unmarshal(data, &repr); err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
val, ok := m[repr]
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("unrecognized enum %s value %q", enumName, repr)
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
// Old style: enums are ints.
|
||||||
|
var val int32
|
||||||
|
if err := json.Unmarshal(data, &val); err != nil {
|
||||||
|
return 0, fmt.Errorf("cannot unmarshal %#q into enum %s", data, enumName)
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugPrint dumps the encoded data in b in a debugging format with a header
|
||||||
|
// including the string s. Used in testing but made available for general debugging.
|
||||||
|
func (o *Buffer) DebugPrint(s string, b []byte) {
|
||||||
|
var u uint64
|
||||||
|
|
||||||
|
obuf := o.buf
|
||||||
|
index := o.index
|
||||||
|
o.buf = b
|
||||||
|
o.index = 0
|
||||||
|
depth := 0
|
||||||
|
|
||||||
|
fmt.Printf("\n--- %s ---\n", s)
|
||||||
|
|
||||||
|
out:
|
||||||
|
for {
|
||||||
|
for i := 0; i < depth; i++ {
|
||||||
|
fmt.Print(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
index := o.index
|
||||||
|
if index == len(o.buf) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
op, err := o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%3d: fetching op err %v\n", index, err)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
tag := op >> 3
|
||||||
|
wire := op & 7
|
||||||
|
|
||||||
|
switch wire {
|
||||||
|
default:
|
||||||
|
fmt.Printf("%3d: t=%3d unknown wire=%d\n",
|
||||||
|
index, tag, wire)
|
||||||
|
break out
|
||||||
|
|
||||||
|
case WireBytes:
|
||||||
|
var r []byte
|
||||||
|
|
||||||
|
r, err = o.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
fmt.Printf("%3d: t=%3d bytes [%d]", index, tag, len(r))
|
||||||
|
if len(r) <= 6 {
|
||||||
|
for i := 0; i < len(r); i++ {
|
||||||
|
fmt.Printf(" %.2x", r[i])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
fmt.Printf(" %.2x", r[i])
|
||||||
|
}
|
||||||
|
fmt.Printf(" ..")
|
||||||
|
for i := len(r) - 3; i < len(r); i++ {
|
||||||
|
fmt.Printf(" %.2x", r[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
|
||||||
|
case WireFixed32:
|
||||||
|
u, err = o.DecodeFixed32()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%3d: t=%3d fix32 err %v\n", index, tag, err)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
fmt.Printf("%3d: t=%3d fix32 %d\n", index, tag, u)
|
||||||
|
|
||||||
|
case WireFixed64:
|
||||||
|
u, err = o.DecodeFixed64()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%3d: t=%3d fix64 err %v\n", index, tag, err)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
fmt.Printf("%3d: t=%3d fix64 %d\n", index, tag, u)
|
||||||
|
break
|
||||||
|
|
||||||
|
case WireVarint:
|
||||||
|
u, err = o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%3d: t=%3d varint err %v\n", index, tag, err)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
fmt.Printf("%3d: t=%3d varint %d\n", index, tag, u)
|
||||||
|
|
||||||
|
case WireStartGroup:
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%3d: t=%3d start err %v\n", index, tag, err)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
fmt.Printf("%3d: t=%3d start\n", index, tag)
|
||||||
|
depth++
|
||||||
|
|
||||||
|
case WireEndGroup:
|
||||||
|
depth--
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%3d: t=%3d end err %v\n", index, tag, err)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
fmt.Printf("%3d: t=%3d end\n", index, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if depth != 0 {
|
||||||
|
fmt.Printf("%3d: start-end not balanced %d\n", o.index, depth)
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
|
||||||
|
o.buf = obuf
|
||||||
|
o.index = index
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaults sets unset protocol buffer fields to their default values.
|
||||||
|
// It only modifies fields that are both unset and have defined defaults.
|
||||||
|
// It recursively sets default values in any non-nil sub-messages.
|
||||||
|
func SetDefaults(pb Message) {
|
||||||
|
setDefaults(reflect.ValueOf(pb), true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// v is a pointer to a struct.
|
||||||
|
func setDefaults(v reflect.Value, recur, zeros bool) {
|
||||||
|
v = v.Elem()
|
||||||
|
|
||||||
|
defaultMu.RLock()
|
||||||
|
dm, ok := defaults[v.Type()]
|
||||||
|
defaultMu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
dm = buildDefaultMessage(v.Type())
|
||||||
|
defaultMu.Lock()
|
||||||
|
defaults[v.Type()] = dm
|
||||||
|
defaultMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sf := range dm.scalars {
|
||||||
|
f := v.Field(sf.index)
|
||||||
|
if !f.IsNil() {
|
||||||
|
// field already set
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dv := sf.value
|
||||||
|
if dv == nil && !zeros {
|
||||||
|
// no explicit default, and don't want to set zeros
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fptr := f.Addr().Interface() // **T
|
||||||
|
// TODO: Consider batching the allocations we do here.
|
||||||
|
switch sf.kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
b := new(bool)
|
||||||
|
if dv != nil {
|
||||||
|
*b = dv.(bool)
|
||||||
|
}
|
||||||
|
*(fptr.(**bool)) = b
|
||||||
|
case reflect.Float32:
|
||||||
|
f := new(float32)
|
||||||
|
if dv != nil {
|
||||||
|
*f = dv.(float32)
|
||||||
|
}
|
||||||
|
*(fptr.(**float32)) = f
|
||||||
|
case reflect.Float64:
|
||||||
|
f := new(float64)
|
||||||
|
if dv != nil {
|
||||||
|
*f = dv.(float64)
|
||||||
|
}
|
||||||
|
*(fptr.(**float64)) = f
|
||||||
|
case reflect.Int32:
|
||||||
|
// might be an enum
|
||||||
|
if ft := f.Type(); ft != int32PtrType {
|
||||||
|
// enum
|
||||||
|
f.Set(reflect.New(ft.Elem()))
|
||||||
|
if dv != nil {
|
||||||
|
f.Elem().SetInt(int64(dv.(int32)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// int32 field
|
||||||
|
i := new(int32)
|
||||||
|
if dv != nil {
|
||||||
|
*i = dv.(int32)
|
||||||
|
}
|
||||||
|
*(fptr.(**int32)) = i
|
||||||
|
}
|
||||||
|
case reflect.Int64:
|
||||||
|
i := new(int64)
|
||||||
|
if dv != nil {
|
||||||
|
*i = dv.(int64)
|
||||||
|
}
|
||||||
|
*(fptr.(**int64)) = i
|
||||||
|
case reflect.String:
|
||||||
|
s := new(string)
|
||||||
|
if dv != nil {
|
||||||
|
*s = dv.(string)
|
||||||
|
}
|
||||||
|
*(fptr.(**string)) = s
|
||||||
|
case reflect.Uint8:
|
||||||
|
// exceptional case: []byte
|
||||||
|
var b []byte
|
||||||
|
if dv != nil {
|
||||||
|
db := dv.([]byte)
|
||||||
|
b = make([]byte, len(db))
|
||||||
|
copy(b, db)
|
||||||
|
} else {
|
||||||
|
b = []byte{}
|
||||||
|
}
|
||||||
|
*(fptr.(*[]byte)) = b
|
||||||
|
case reflect.Uint32:
|
||||||
|
u := new(uint32)
|
||||||
|
if dv != nil {
|
||||||
|
*u = dv.(uint32)
|
||||||
|
}
|
||||||
|
*(fptr.(**uint32)) = u
|
||||||
|
case reflect.Uint64:
|
||||||
|
u := new(uint64)
|
||||||
|
if dv != nil {
|
||||||
|
*u = dv.(uint64)
|
||||||
|
}
|
||||||
|
*(fptr.(**uint64)) = u
|
||||||
|
default:
|
||||||
|
log.Printf("proto: can't set default for field %v (sf.kind=%v)", f, sf.kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ni := range dm.nested {
|
||||||
|
f := v.Field(ni)
|
||||||
|
if f.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// f is *T or []*T
|
||||||
|
if f.Kind() == reflect.Ptr {
|
||||||
|
setDefaults(f, recur, zeros)
|
||||||
|
} else {
|
||||||
|
for i := 0; i < f.Len(); i++ {
|
||||||
|
e := f.Index(i)
|
||||||
|
if e.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
setDefaults(e, recur, zeros)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// defaults maps a protocol buffer struct type to a slice of the fields,
|
||||||
|
// with its scalar fields set to their proto-declared non-zero default values.
|
||||||
|
defaultMu sync.RWMutex
|
||||||
|
defaults = make(map[reflect.Type]defaultMessage)
|
||||||
|
|
||||||
|
int32PtrType = reflect.TypeOf((*int32)(nil))
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultMessage represents information about the default values of a message.
|
||||||
|
type defaultMessage struct {
|
||||||
|
scalars []scalarField
|
||||||
|
nested []int // struct field index of nested messages
|
||||||
|
}
|
||||||
|
|
||||||
|
type scalarField struct {
|
||||||
|
index int // struct field index
|
||||||
|
kind reflect.Kind // element type (the T in *T or []T)
|
||||||
|
value interface{} // the proto-declared default value, or nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptrToStruct(t reflect.Type) bool {
|
||||||
|
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
|
||||||
|
}
|
||||||
|
|
||||||
|
// t is a struct type.
|
||||||
|
func buildDefaultMessage(t reflect.Type) (dm defaultMessage) {
|
||||||
|
sprop := GetProperties(t)
|
||||||
|
for _, prop := range sprop.Prop {
|
||||||
|
fi, ok := sprop.decoderTags.get(prop.Tag)
|
||||||
|
if !ok {
|
||||||
|
// XXX_unrecognized
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ft := t.Field(fi).Type
|
||||||
|
|
||||||
|
// nested messages
|
||||||
|
if ptrToStruct(ft) || (ft.Kind() == reflect.Slice && ptrToStruct(ft.Elem())) {
|
||||||
|
dm.nested = append(dm.nested, fi)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sf := scalarField{
|
||||||
|
index: fi,
|
||||||
|
kind: ft.Elem().Kind(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// scalar fields without defaults
|
||||||
|
if !prop.HasDefault {
|
||||||
|
dm.scalars = append(dm.scalars, sf)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// a scalar field: either *T or []byte
|
||||||
|
switch ft.Elem().Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
x, err := strconv.ParseBool(prop.Default)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("proto: bad default bool %q: %v", prop.Default, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sf.value = x
|
||||||
|
case reflect.Float32:
|
||||||
|
x, err := strconv.ParseFloat(prop.Default, 32)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("proto: bad default float32 %q: %v", prop.Default, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sf.value = float32(x)
|
||||||
|
case reflect.Float64:
|
||||||
|
x, err := strconv.ParseFloat(prop.Default, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("proto: bad default float64 %q: %v", prop.Default, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sf.value = x
|
||||||
|
case reflect.Int32:
|
||||||
|
x, err := strconv.ParseInt(prop.Default, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("proto: bad default int32 %q: %v", prop.Default, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sf.value = int32(x)
|
||||||
|
case reflect.Int64:
|
||||||
|
x, err := strconv.ParseInt(prop.Default, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("proto: bad default int64 %q: %v", prop.Default, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sf.value = x
|
||||||
|
case reflect.String:
|
||||||
|
sf.value = prop.Default
|
||||||
|
case reflect.Uint8:
|
||||||
|
// []byte (not *uint8)
|
||||||
|
sf.value = []byte(prop.Default)
|
||||||
|
case reflect.Uint32:
|
||||||
|
x, err := strconv.ParseUint(prop.Default, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("proto: bad default uint32 %q: %v", prop.Default, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sf.value = uint32(x)
|
||||||
|
case reflect.Uint64:
|
||||||
|
x, err := strconv.ParseUint(prop.Default, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("proto: bad default uint64 %q: %v", prop.Default, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sf.value = x
|
||||||
|
default:
|
||||||
|
log.Printf("proto: unhandled def kind %v", ft.Elem().Kind())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dm.scalars = append(dm.scalars, sf)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map fields may have key types of non-float scalars, strings and enums.
|
||||||
|
// The easiest way to sort them in some deterministic order is to use fmt.
|
||||||
|
// If this turns out to be inefficient we can always consider other options,
|
||||||
|
// such as doing a Schwartzian transform.
|
||||||
|
|
||||||
|
type mapKeys []reflect.Value
|
||||||
|
|
||||||
|
func (s mapKeys) Len() int { return len(s) }
|
||||||
|
func (s mapKeys) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s mapKeys) Less(i, j int) bool {
|
||||||
|
return fmt.Sprint(s[i].Interface()) < fmt.Sprint(s[j].Interface())
|
||||||
|
}
|
||||||
287
Godeps/_workspace/src/github.com/golang/protobuf/proto/message_set.go
generated
vendored
Normal file
287
Godeps/_workspace/src/github.com/golang/protobuf/proto/message_set.go
generated
vendored
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Support for message sets.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNoMessageTypeId occurs when a protocol buffer does not have a message type ID.
|
||||||
|
// A message type ID is required for storing a protocol buffer in a message set.
|
||||||
|
var ErrNoMessageTypeId = errors.New("proto does not have a message type ID")
|
||||||
|
|
||||||
|
// The first two types (_MessageSet_Item and MessageSet)
|
||||||
|
// model what the protocol compiler produces for the following protocol message:
|
||||||
|
// message MessageSet {
|
||||||
|
// repeated group Item = 1 {
|
||||||
|
// required int32 type_id = 2;
|
||||||
|
// required string message = 3;
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// That is the MessageSet wire format. We can't use a proto to generate these
|
||||||
|
// because that would introduce a circular dependency between it and this package.
|
||||||
|
//
|
||||||
|
// When a proto1 proto has a field that looks like:
|
||||||
|
// optional message<MessageSet> info = 3;
|
||||||
|
// the protocol compiler produces a field in the generated struct that looks like:
|
||||||
|
// Info *_proto_.MessageSet `protobuf:"bytes,3,opt,name=info"`
|
||||||
|
// The package is automatically inserted so there is no need for that proto file to
|
||||||
|
// import this package.
|
||||||
|
|
||||||
|
type _MessageSet_Item struct {
|
||||||
|
TypeId *int32 `protobuf:"varint,2,req,name=type_id"`
|
||||||
|
Message []byte `protobuf:"bytes,3,req,name=message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageSet struct {
|
||||||
|
Item []*_MessageSet_Item `protobuf:"group,1,rep"`
|
||||||
|
XXX_unrecognized []byte
|
||||||
|
// TODO: caching?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure MessageSet is a Message.
|
||||||
|
var _ Message = (*MessageSet)(nil)
|
||||||
|
|
||||||
|
// messageTypeIder is an interface satisfied by a protocol buffer type
|
||||||
|
// that may be stored in a MessageSet.
|
||||||
|
type messageTypeIder interface {
|
||||||
|
MessageTypeId() int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MessageSet) find(pb Message) *_MessageSet_Item {
|
||||||
|
mti, ok := pb.(messageTypeIder)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
id := mti.MessageTypeId()
|
||||||
|
for _, item := range ms.Item {
|
||||||
|
if *item.TypeId == id {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MessageSet) Has(pb Message) bool {
|
||||||
|
if ms.find(pb) != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MessageSet) Unmarshal(pb Message) error {
|
||||||
|
if item := ms.find(pb); item != nil {
|
||||||
|
return Unmarshal(item.Message, pb)
|
||||||
|
}
|
||||||
|
if _, ok := pb.(messageTypeIder); !ok {
|
||||||
|
return ErrNoMessageTypeId
|
||||||
|
}
|
||||||
|
return nil // TODO: return error instead?
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MessageSet) Marshal(pb Message) error {
|
||||||
|
msg, err := Marshal(pb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if item := ms.find(pb); item != nil {
|
||||||
|
// reuse existing item
|
||||||
|
item.Message = msg
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mti, ok := pb.(messageTypeIder)
|
||||||
|
if !ok {
|
||||||
|
return ErrNoMessageTypeId
|
||||||
|
}
|
||||||
|
|
||||||
|
mtid := mti.MessageTypeId()
|
||||||
|
ms.Item = append(ms.Item, &_MessageSet_Item{
|
||||||
|
TypeId: &mtid,
|
||||||
|
Message: msg,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MessageSet) Reset() { *ms = MessageSet{} }
|
||||||
|
func (ms *MessageSet) String() string { return CompactTextString(ms) }
|
||||||
|
func (*MessageSet) ProtoMessage() {}
|
||||||
|
|
||||||
|
// Support for the message_set_wire_format message option.
|
||||||
|
|
||||||
|
func skipVarint(buf []byte) []byte {
|
||||||
|
i := 0
|
||||||
|
for ; buf[i]&0x80 != 0; i++ {
|
||||||
|
}
|
||||||
|
return buf[i+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMessageSet encodes the extension map represented by m in the message set wire format.
|
||||||
|
// It is called by generated Marshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||||
|
func MarshalMessageSet(m map[int32]Extension) ([]byte, error) {
|
||||||
|
if err := encodeExtensionMap(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort extension IDs to provide a deterministic encoding.
|
||||||
|
// See also enc_map in encode.go.
|
||||||
|
ids := make([]int, 0, len(m))
|
||||||
|
for id := range m {
|
||||||
|
ids = append(ids, int(id))
|
||||||
|
}
|
||||||
|
sort.Ints(ids)
|
||||||
|
|
||||||
|
ms := &MessageSet{Item: make([]*_MessageSet_Item, 0, len(m))}
|
||||||
|
for _, id := range ids {
|
||||||
|
e := m[int32(id)]
|
||||||
|
// Remove the wire type and field number varint, as well as the length varint.
|
||||||
|
msg := skipVarint(skipVarint(e.enc))
|
||||||
|
|
||||||
|
ms.Item = append(ms.Item, &_MessageSet_Item{
|
||||||
|
TypeId: Int32(int32(id)),
|
||||||
|
Message: msg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Marshal(ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMessageSet decodes the extension map encoded in buf in the message set wire format.
|
||||||
|
// It is called by generated Unmarshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||||
|
func UnmarshalMessageSet(buf []byte, m map[int32]Extension) error {
|
||||||
|
ms := new(MessageSet)
|
||||||
|
if err := Unmarshal(buf, ms); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, item := range ms.Item {
|
||||||
|
id := *item.TypeId
|
||||||
|
msg := item.Message
|
||||||
|
|
||||||
|
// Restore wire type and field number varint, plus length varint.
|
||||||
|
// Be careful to preserve duplicate items.
|
||||||
|
b := EncodeVarint(uint64(id)<<3 | WireBytes)
|
||||||
|
if ext, ok := m[id]; ok {
|
||||||
|
// Existing data; rip off the tag and length varint
|
||||||
|
// so we join the new data correctly.
|
||||||
|
// We can assume that ext.enc is set because we are unmarshaling.
|
||||||
|
o := ext.enc[len(b):] // skip wire type and field number
|
||||||
|
_, n := DecodeVarint(o) // calculate length of length varint
|
||||||
|
o = o[n:] // skip length varint
|
||||||
|
msg = append(o, msg...) // join old data and new data
|
||||||
|
}
|
||||||
|
b = append(b, EncodeVarint(uint64(len(msg)))...)
|
||||||
|
b = append(b, msg...)
|
||||||
|
|
||||||
|
m[id] = Extension{enc: b}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMessageSetJSON encodes the extension map represented by m in JSON format.
|
||||||
|
// It is called by generated MarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||||
|
func MarshalMessageSetJSON(m map[int32]Extension) ([]byte, error) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
b.WriteByte('{')
|
||||||
|
|
||||||
|
// Process the map in key order for deterministic output.
|
||||||
|
ids := make([]int32, 0, len(m))
|
||||||
|
for id := range m {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
sort.Sort(int32Slice(ids)) // int32Slice defined in text.go
|
||||||
|
|
||||||
|
for i, id := range ids {
|
||||||
|
ext := m[id]
|
||||||
|
if i > 0 {
|
||||||
|
b.WriteByte(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
msd, ok := messageSetMap[id]
|
||||||
|
if !ok {
|
||||||
|
// Unknown type; we can't render it, so skip it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&b, `"[%s]":`, msd.name)
|
||||||
|
|
||||||
|
x := ext.value
|
||||||
|
if x == nil {
|
||||||
|
x = reflect.New(msd.t.Elem()).Interface()
|
||||||
|
if err := Unmarshal(ext.enc, x.(Message)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d, err := json.Marshal(x)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.Write(d)
|
||||||
|
}
|
||||||
|
b.WriteByte('}')
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMessageSetJSON decodes the extension map encoded in buf in JSON format.
|
||||||
|
// It is called by generated UnmarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||||
|
func UnmarshalMessageSetJSON(buf []byte, m map[int32]Extension) error {
|
||||||
|
// Common-case fast path.
|
||||||
|
if len(buf) == 0 || bytes.Equal(buf, []byte("{}")) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is fairly tricky, and it's not clear that it is needed.
|
||||||
|
return errors.New("TODO: UnmarshalMessageSetJSON not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A global registry of types that can be used in a MessageSet.
|
||||||
|
|
||||||
|
var messageSetMap = make(map[int32]messageSetDesc)
|
||||||
|
|
||||||
|
type messageSetDesc struct {
|
||||||
|
t reflect.Type // pointer to struct
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterMessageSetType is called from the generated code.
|
||||||
|
func RegisterMessageSetType(m Message, fieldNum int32, name string) {
|
||||||
|
messageSetMap[fieldNum] = messageSetDesc{
|
||||||
|
t: reflect.TypeOf(m),
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
66
Godeps/_workspace/src/github.com/golang/protobuf/proto/message_set_test.go
generated
vendored
Normal file
66
Godeps/_workspace/src/github.com/golang/protobuf/proto/message_set_test.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnmarshalMessageSetWithDuplicate(t *testing.T) {
|
||||||
|
// Check that a repeated message set entry will be concatenated.
|
||||||
|
in := &MessageSet{
|
||||||
|
Item: []*_MessageSet_Item{
|
||||||
|
{TypeId: Int32(12345), Message: []byte("hoo")},
|
||||||
|
{TypeId: Int32(12345), Message: []byte("hah")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b, err := Marshal(in)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Marshal: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("Marshaled bytes: %q", b)
|
||||||
|
|
||||||
|
m := make(map[int32]Extension)
|
||||||
|
if err := UnmarshalMessageSet(b, m); err != nil {
|
||||||
|
t.Fatalf("UnmarshalMessageSet: %v", err)
|
||||||
|
}
|
||||||
|
ext, ok := m[12345]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Didn't retrieve extension 12345; map is %v", m)
|
||||||
|
}
|
||||||
|
// Skip wire type/field number and length varints.
|
||||||
|
got := skipVarint(skipVarint(ext.enc))
|
||||||
|
if want := []byte("hoohah"); !bytes.Equal(got, want) {
|
||||||
|
t.Errorf("Combined extension is %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
479
Godeps/_workspace/src/github.com/golang/protobuf/proto/pointer_reflect.go
generated
vendored
Normal file
479
Godeps/_workspace/src/github.com/golang/protobuf/proto/pointer_reflect.go
generated
vendored
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build appengine,!appenginevm
|
||||||
|
|
||||||
|
// This file contains an implementation of proto field accesses using package reflect.
|
||||||
|
// It is slower than the code in pointer_unsafe.go but it avoids package unsafe and can
|
||||||
|
// be used on App Engine.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A structPointer is a pointer to a struct.
|
||||||
|
type structPointer struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// toStructPointer returns a structPointer equivalent to the given reflect value.
|
||||||
|
// The reflect value must itself be a pointer to a struct.
|
||||||
|
func toStructPointer(v reflect.Value) structPointer {
|
||||||
|
return structPointer{v}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil reports whether p is nil.
|
||||||
|
func structPointer_IsNil(p structPointer) bool {
|
||||||
|
return p.v.IsNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface returns the struct pointer as an interface value.
|
||||||
|
func structPointer_Interface(p structPointer, _ reflect.Type) interface{} {
|
||||||
|
return p.v.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A field identifies a field in a struct, accessible from a structPointer.
|
||||||
|
// In this implementation, a field is identified by the sequence of field indices
|
||||||
|
// passed to reflect's FieldByIndex.
|
||||||
|
type field []int
|
||||||
|
|
||||||
|
// toField returns a field equivalent to the given reflect field.
|
||||||
|
func toField(f *reflect.StructField) field {
|
||||||
|
return f.Index
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalidField is an invalid field identifier.
|
||||||
|
var invalidField = field(nil)
|
||||||
|
|
||||||
|
// IsValid reports whether the field identifier is valid.
|
||||||
|
func (f field) IsValid() bool { return f != nil }
|
||||||
|
|
||||||
|
// field returns the given field in the struct as a reflect value.
|
||||||
|
func structPointer_field(p structPointer, f field) reflect.Value {
|
||||||
|
// Special case: an extension map entry with a value of type T
|
||||||
|
// passes a *T to the struct-handling code with a zero field,
|
||||||
|
// expecting that it will be treated as equivalent to *struct{ X T },
|
||||||
|
// which has the same memory layout. We have to handle that case
|
||||||
|
// specially, because reflect will panic if we call FieldByIndex on a
|
||||||
|
// non-struct.
|
||||||
|
if f == nil {
|
||||||
|
return p.v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.v.Elem().FieldByIndex(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ifield returns the given field in the struct as an interface value.
|
||||||
|
func structPointer_ifield(p structPointer, f field) interface{} {
|
||||||
|
return structPointer_field(p, f).Addr().Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the address of a []byte field in the struct.
|
||||||
|
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
||||||
|
return structPointer_ifield(p, f).(*[]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesSlice returns the address of a [][]byte field in the struct.
|
||||||
|
func structPointer_BytesSlice(p structPointer, f field) *[][]byte {
|
||||||
|
return structPointer_ifield(p, f).(*[][]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns the address of a *bool field in the struct.
|
||||||
|
func structPointer_Bool(p structPointer, f field) **bool {
|
||||||
|
return structPointer_ifield(p, f).(**bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolVal returns the address of a bool field in the struct.
|
||||||
|
func structPointer_BoolVal(p structPointer, f field) *bool {
|
||||||
|
return structPointer_ifield(p, f).(*bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolSlice returns the address of a []bool field in the struct.
|
||||||
|
func structPointer_BoolSlice(p structPointer, f field) *[]bool {
|
||||||
|
return structPointer_ifield(p, f).(*[]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the address of a *string field in the struct.
|
||||||
|
func structPointer_String(p structPointer, f field) **string {
|
||||||
|
return structPointer_ifield(p, f).(**string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringVal returns the address of a string field in the struct.
|
||||||
|
func structPointer_StringVal(p structPointer, f field) *string {
|
||||||
|
return structPointer_ifield(p, f).(*string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringSlice returns the address of a []string field in the struct.
|
||||||
|
func structPointer_StringSlice(p structPointer, f field) *[]string {
|
||||||
|
return structPointer_ifield(p, f).(*[]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtMap returns the address of an extension map field in the struct.
|
||||||
|
func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension {
|
||||||
|
return structPointer_ifield(p, f).(*map[int32]Extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map returns the reflect.Value for the address of a map field in the struct.
|
||||||
|
func structPointer_Map(p structPointer, f field, typ reflect.Type) reflect.Value {
|
||||||
|
return structPointer_field(p, f).Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStructPointer writes a *struct field in the struct.
|
||||||
|
func structPointer_SetStructPointer(p structPointer, f field, q structPointer) {
|
||||||
|
structPointer_field(p, f).Set(q.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStructPointer reads a *struct field in the struct.
|
||||||
|
func structPointer_GetStructPointer(p structPointer, f field) structPointer {
|
||||||
|
return structPointer{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructPointerSlice the address of a []*struct field in the struct.
|
||||||
|
func structPointer_StructPointerSlice(p structPointer, f field) structPointerSlice {
|
||||||
|
return structPointerSlice{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A structPointerSlice represents the address of a slice of pointers to structs
|
||||||
|
// (themselves messages or groups). That is, v.Type() is *[]*struct{...}.
|
||||||
|
type structPointerSlice struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p structPointerSlice) Len() int { return p.v.Len() }
|
||||||
|
func (p structPointerSlice) Index(i int) structPointer { return structPointer{p.v.Index(i)} }
|
||||||
|
func (p structPointerSlice) Append(q structPointer) {
|
||||||
|
p.v.Set(reflect.Append(p.v, q.v))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
int32Type = reflect.TypeOf(int32(0))
|
||||||
|
uint32Type = reflect.TypeOf(uint32(0))
|
||||||
|
float32Type = reflect.TypeOf(float32(0))
|
||||||
|
int64Type = reflect.TypeOf(int64(0))
|
||||||
|
uint64Type = reflect.TypeOf(uint64(0))
|
||||||
|
float64Type = reflect.TypeOf(float64(0))
|
||||||
|
)
|
||||||
|
|
||||||
|
// A word32 represents a field of type *int32, *uint32, *float32, or *enum.
|
||||||
|
// That is, v.Type() is *int32, *uint32, *float32, or *enum and v is assignable.
|
||||||
|
type word32 struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil reports whether p is nil.
|
||||||
|
func word32_IsNil(p word32) bool {
|
||||||
|
return p.v.IsNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets p to point at a newly allocated word with bits set to x.
|
||||||
|
func word32_Set(p word32, o *Buffer, x uint32) {
|
||||||
|
t := p.v.Type().Elem()
|
||||||
|
switch t {
|
||||||
|
case int32Type:
|
||||||
|
if len(o.int32s) == 0 {
|
||||||
|
o.int32s = make([]int32, uint32PoolSize)
|
||||||
|
}
|
||||||
|
o.int32s[0] = int32(x)
|
||||||
|
p.v.Set(reflect.ValueOf(&o.int32s[0]))
|
||||||
|
o.int32s = o.int32s[1:]
|
||||||
|
return
|
||||||
|
case uint32Type:
|
||||||
|
if len(o.uint32s) == 0 {
|
||||||
|
o.uint32s = make([]uint32, uint32PoolSize)
|
||||||
|
}
|
||||||
|
o.uint32s[0] = x
|
||||||
|
p.v.Set(reflect.ValueOf(&o.uint32s[0]))
|
||||||
|
o.uint32s = o.uint32s[1:]
|
||||||
|
return
|
||||||
|
case float32Type:
|
||||||
|
if len(o.float32s) == 0 {
|
||||||
|
o.float32s = make([]float32, uint32PoolSize)
|
||||||
|
}
|
||||||
|
o.float32s[0] = math.Float32frombits(x)
|
||||||
|
p.v.Set(reflect.ValueOf(&o.float32s[0]))
|
||||||
|
o.float32s = o.float32s[1:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// must be enum
|
||||||
|
p.v.Set(reflect.New(t))
|
||||||
|
p.v.Elem().SetInt(int64(int32(x)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets the bits pointed at by p, as a uint32.
|
||||||
|
func word32_Get(p word32) uint32 {
|
||||||
|
elem := p.v.Elem()
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int32:
|
||||||
|
return uint32(elem.Int())
|
||||||
|
case reflect.Uint32:
|
||||||
|
return uint32(elem.Uint())
|
||||||
|
case reflect.Float32:
|
||||||
|
return math.Float32bits(float32(elem.Float()))
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Word32 returns a reference to a *int32, *uint32, *float32, or *enum field in the struct.
|
||||||
|
func structPointer_Word32(p structPointer, f field) word32 {
|
||||||
|
return word32{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A word32Val represents a field of type int32, uint32, float32, or enum.
|
||||||
|
// That is, v.Type() is int32, uint32, float32, or enum and v is assignable.
|
||||||
|
type word32Val struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets *p to x.
|
||||||
|
func word32Val_Set(p word32Val, x uint32) {
|
||||||
|
switch p.v.Type() {
|
||||||
|
case int32Type:
|
||||||
|
p.v.SetInt(int64(x))
|
||||||
|
return
|
||||||
|
case uint32Type:
|
||||||
|
p.v.SetUint(uint64(x))
|
||||||
|
return
|
||||||
|
case float32Type:
|
||||||
|
p.v.SetFloat(float64(math.Float32frombits(x)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// must be enum
|
||||||
|
p.v.SetInt(int64(int32(x)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets the bits pointed at by p, as a uint32.
|
||||||
|
func word32Val_Get(p word32Val) uint32 {
|
||||||
|
elem := p.v
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int32:
|
||||||
|
return uint32(elem.Int())
|
||||||
|
case reflect.Uint32:
|
||||||
|
return uint32(elem.Uint())
|
||||||
|
case reflect.Float32:
|
||||||
|
return math.Float32bits(float32(elem.Float()))
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Word32Val returns a reference to a int32, uint32, float32, or enum field in the struct.
|
||||||
|
func structPointer_Word32Val(p structPointer, f field) word32Val {
|
||||||
|
return word32Val{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A word32Slice is a slice of 32-bit values.
|
||||||
|
// That is, v.Type() is []int32, []uint32, []float32, or []enum.
|
||||||
|
type word32Slice struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p word32Slice) Append(x uint32) {
|
||||||
|
n, m := p.v.Len(), p.v.Cap()
|
||||||
|
if n < m {
|
||||||
|
p.v.SetLen(n + 1)
|
||||||
|
} else {
|
||||||
|
t := p.v.Type().Elem()
|
||||||
|
p.v.Set(reflect.Append(p.v, reflect.Zero(t)))
|
||||||
|
}
|
||||||
|
elem := p.v.Index(n)
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int32:
|
||||||
|
elem.SetInt(int64(int32(x)))
|
||||||
|
case reflect.Uint32:
|
||||||
|
elem.SetUint(uint64(x))
|
||||||
|
case reflect.Float32:
|
||||||
|
elem.SetFloat(float64(math.Float32frombits(x)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p word32Slice) Len() int {
|
||||||
|
return p.v.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p word32Slice) Index(i int) uint32 {
|
||||||
|
elem := p.v.Index(i)
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int32:
|
||||||
|
return uint32(elem.Int())
|
||||||
|
case reflect.Uint32:
|
||||||
|
return uint32(elem.Uint())
|
||||||
|
case reflect.Float32:
|
||||||
|
return math.Float32bits(float32(elem.Float()))
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Word32Slice returns a reference to a []int32, []uint32, []float32, or []enum field in the struct.
|
||||||
|
func structPointer_Word32Slice(p structPointer, f field) word32Slice {
|
||||||
|
return word32Slice{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// word64 is like word32 but for 64-bit values.
|
||||||
|
type word64 struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64_Set(p word64, o *Buffer, x uint64) {
|
||||||
|
t := p.v.Type().Elem()
|
||||||
|
switch t {
|
||||||
|
case int64Type:
|
||||||
|
if len(o.int64s) == 0 {
|
||||||
|
o.int64s = make([]int64, uint64PoolSize)
|
||||||
|
}
|
||||||
|
o.int64s[0] = int64(x)
|
||||||
|
p.v.Set(reflect.ValueOf(&o.int64s[0]))
|
||||||
|
o.int64s = o.int64s[1:]
|
||||||
|
return
|
||||||
|
case uint64Type:
|
||||||
|
if len(o.uint64s) == 0 {
|
||||||
|
o.uint64s = make([]uint64, uint64PoolSize)
|
||||||
|
}
|
||||||
|
o.uint64s[0] = x
|
||||||
|
p.v.Set(reflect.ValueOf(&o.uint64s[0]))
|
||||||
|
o.uint64s = o.uint64s[1:]
|
||||||
|
return
|
||||||
|
case float64Type:
|
||||||
|
if len(o.float64s) == 0 {
|
||||||
|
o.float64s = make([]float64, uint64PoolSize)
|
||||||
|
}
|
||||||
|
o.float64s[0] = math.Float64frombits(x)
|
||||||
|
p.v.Set(reflect.ValueOf(&o.float64s[0]))
|
||||||
|
o.float64s = o.float64s[1:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64_IsNil(p word64) bool {
|
||||||
|
return p.v.IsNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64_Get(p word64) uint64 {
|
||||||
|
elem := p.v.Elem()
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int64:
|
||||||
|
return uint64(elem.Int())
|
||||||
|
case reflect.Uint64:
|
||||||
|
return elem.Uint()
|
||||||
|
case reflect.Float64:
|
||||||
|
return math.Float64bits(elem.Float())
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func structPointer_Word64(p structPointer, f field) word64 {
|
||||||
|
return word64{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// word64Val is like word32Val but for 64-bit values.
|
||||||
|
type word64Val struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64Val_Set(p word64Val, o *Buffer, x uint64) {
|
||||||
|
switch p.v.Type() {
|
||||||
|
case int64Type:
|
||||||
|
p.v.SetInt(int64(x))
|
||||||
|
return
|
||||||
|
case uint64Type:
|
||||||
|
p.v.SetUint(x)
|
||||||
|
return
|
||||||
|
case float64Type:
|
||||||
|
p.v.SetFloat(math.Float64frombits(x))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64Val_Get(p word64Val) uint64 {
|
||||||
|
elem := p.v
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int64:
|
||||||
|
return uint64(elem.Int())
|
||||||
|
case reflect.Uint64:
|
||||||
|
return elem.Uint()
|
||||||
|
case reflect.Float64:
|
||||||
|
return math.Float64bits(elem.Float())
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func structPointer_Word64Val(p structPointer, f field) word64Val {
|
||||||
|
return word64Val{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
type word64Slice struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p word64Slice) Append(x uint64) {
|
||||||
|
n, m := p.v.Len(), p.v.Cap()
|
||||||
|
if n < m {
|
||||||
|
p.v.SetLen(n + 1)
|
||||||
|
} else {
|
||||||
|
t := p.v.Type().Elem()
|
||||||
|
p.v.Set(reflect.Append(p.v, reflect.Zero(t)))
|
||||||
|
}
|
||||||
|
elem := p.v.Index(n)
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int64:
|
||||||
|
elem.SetInt(int64(int64(x)))
|
||||||
|
case reflect.Uint64:
|
||||||
|
elem.SetUint(uint64(x))
|
||||||
|
case reflect.Float64:
|
||||||
|
elem.SetFloat(float64(math.Float64frombits(x)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p word64Slice) Len() int {
|
||||||
|
return p.v.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p word64Slice) Index(i int) uint64 {
|
||||||
|
elem := p.v.Index(i)
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int64:
|
||||||
|
return uint64(elem.Int())
|
||||||
|
case reflect.Uint64:
|
||||||
|
return uint64(elem.Uint())
|
||||||
|
case reflect.Float64:
|
||||||
|
return math.Float64bits(float64(elem.Float()))
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func structPointer_Word64Slice(p structPointer, f field) word64Slice {
|
||||||
|
return word64Slice{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
266
Godeps/_workspace/src/github.com/golang/protobuf/proto/pointer_unsafe.go
generated
vendored
Normal file
266
Godeps/_workspace/src/github.com/golang/protobuf/proto/pointer_unsafe.go
generated
vendored
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build !appengine appenginevm
|
||||||
|
|
||||||
|
// This file contains the implementation of the proto field accesses using package unsafe.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE: These type_Foo functions would more idiomatically be methods,
|
||||||
|
// but Go does not allow methods on pointer types, and we must preserve
|
||||||
|
// some pointer type for the garbage collector. We use these
|
||||||
|
// funcs with clunky names as our poor approximation to methods.
|
||||||
|
//
|
||||||
|
// An alternative would be
|
||||||
|
// type structPointer struct { p unsafe.Pointer }
|
||||||
|
// but that does not registerize as well.
|
||||||
|
|
||||||
|
// A structPointer is a pointer to a struct.
|
||||||
|
type structPointer unsafe.Pointer
|
||||||
|
|
||||||
|
// toStructPointer returns a structPointer equivalent to the given reflect value.
|
||||||
|
func toStructPointer(v reflect.Value) structPointer {
|
||||||
|
return structPointer(unsafe.Pointer(v.Pointer()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil reports whether p is nil.
|
||||||
|
func structPointer_IsNil(p structPointer) bool {
|
||||||
|
return p == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface returns the struct pointer, assumed to have element type t,
|
||||||
|
// as an interface value.
|
||||||
|
func structPointer_Interface(p structPointer, t reflect.Type) interface{} {
|
||||||
|
return reflect.NewAt(t, unsafe.Pointer(p)).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A field identifies a field in a struct, accessible from a structPointer.
|
||||||
|
// In this implementation, a field is identified by its byte offset from the start of the struct.
|
||||||
|
type field uintptr
|
||||||
|
|
||||||
|
// toField returns a field equivalent to the given reflect field.
|
||||||
|
func toField(f *reflect.StructField) field {
|
||||||
|
return field(f.Offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalidField is an invalid field identifier.
|
||||||
|
const invalidField = ^field(0)
|
||||||
|
|
||||||
|
// IsValid reports whether the field identifier is valid.
|
||||||
|
func (f field) IsValid() bool {
|
||||||
|
return f != ^field(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the address of a []byte field in the struct.
|
||||||
|
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
||||||
|
return (*[]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesSlice returns the address of a [][]byte field in the struct.
|
||||||
|
func structPointer_BytesSlice(p structPointer, f field) *[][]byte {
|
||||||
|
return (*[][]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns the address of a *bool field in the struct.
|
||||||
|
func structPointer_Bool(p structPointer, f field) **bool {
|
||||||
|
return (**bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolVal returns the address of a bool field in the struct.
|
||||||
|
func structPointer_BoolVal(p structPointer, f field) *bool {
|
||||||
|
return (*bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolSlice returns the address of a []bool field in the struct.
|
||||||
|
func structPointer_BoolSlice(p structPointer, f field) *[]bool {
|
||||||
|
return (*[]bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the address of a *string field in the struct.
|
||||||
|
func structPointer_String(p structPointer, f field) **string {
|
||||||
|
return (**string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringVal returns the address of a string field in the struct.
|
||||||
|
func structPointer_StringVal(p structPointer, f field) *string {
|
||||||
|
return (*string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringSlice returns the address of a []string field in the struct.
|
||||||
|
func structPointer_StringSlice(p structPointer, f field) *[]string {
|
||||||
|
return (*[]string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtMap returns the address of an extension map field in the struct.
|
||||||
|
func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension {
|
||||||
|
return (*map[int32]Extension)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map returns the reflect.Value for the address of a map field in the struct.
|
||||||
|
func structPointer_Map(p structPointer, f field, typ reflect.Type) reflect.Value {
|
||||||
|
return reflect.NewAt(typ, unsafe.Pointer(uintptr(p)+uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStructPointer writes a *struct field in the struct.
|
||||||
|
func structPointer_SetStructPointer(p structPointer, f field, q structPointer) {
|
||||||
|
*(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f))) = q
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStructPointer reads a *struct field in the struct.
|
||||||
|
func structPointer_GetStructPointer(p structPointer, f field) structPointer {
|
||||||
|
return *(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructPointerSlice the address of a []*struct field in the struct.
|
||||||
|
func structPointer_StructPointerSlice(p structPointer, f field) *structPointerSlice {
|
||||||
|
return (*structPointerSlice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A structPointerSlice represents a slice of pointers to structs (themselves submessages or groups).
|
||||||
|
type structPointerSlice []structPointer
|
||||||
|
|
||||||
|
func (v *structPointerSlice) Len() int { return len(*v) }
|
||||||
|
func (v *structPointerSlice) Index(i int) structPointer { return (*v)[i] }
|
||||||
|
func (v *structPointerSlice) Append(p structPointer) { *v = append(*v, p) }
|
||||||
|
|
||||||
|
// A word32 is the address of a "pointer to 32-bit value" field.
|
||||||
|
type word32 **uint32
|
||||||
|
|
||||||
|
// IsNil reports whether *v is nil.
|
||||||
|
func word32_IsNil(p word32) bool {
|
||||||
|
return *p == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets *v to point at a newly allocated word set to x.
|
||||||
|
func word32_Set(p word32, o *Buffer, x uint32) {
|
||||||
|
if len(o.uint32s) == 0 {
|
||||||
|
o.uint32s = make([]uint32, uint32PoolSize)
|
||||||
|
}
|
||||||
|
o.uint32s[0] = x
|
||||||
|
*p = &o.uint32s[0]
|
||||||
|
o.uint32s = o.uint32s[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets the value pointed at by *v.
|
||||||
|
func word32_Get(p word32) uint32 {
|
||||||
|
return **p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Word32 returns the address of a *int32, *uint32, *float32, or *enum field in the struct.
|
||||||
|
func structPointer_Word32(p structPointer, f field) word32 {
|
||||||
|
return word32((**uint32)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A word32Val is the address of a 32-bit value field.
|
||||||
|
type word32Val *uint32
|
||||||
|
|
||||||
|
// Set sets *p to x.
|
||||||
|
func word32Val_Set(p word32Val, x uint32) {
|
||||||
|
*p = x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets the value pointed at by p.
|
||||||
|
func word32Val_Get(p word32Val) uint32 {
|
||||||
|
return *p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Word32Val returns the address of a *int32, *uint32, *float32, or *enum field in the struct.
|
||||||
|
func structPointer_Word32Val(p structPointer, f field) word32Val {
|
||||||
|
return word32Val((*uint32)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A word32Slice is a slice of 32-bit values.
|
||||||
|
type word32Slice []uint32
|
||||||
|
|
||||||
|
func (v *word32Slice) Append(x uint32) { *v = append(*v, x) }
|
||||||
|
func (v *word32Slice) Len() int { return len(*v) }
|
||||||
|
func (v *word32Slice) Index(i int) uint32 { return (*v)[i] }
|
||||||
|
|
||||||
|
// Word32Slice returns the address of a []int32, []uint32, []float32, or []enum field in the struct.
|
||||||
|
func structPointer_Word32Slice(p structPointer, f field) *word32Slice {
|
||||||
|
return (*word32Slice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// word64 is like word32 but for 64-bit values.
|
||||||
|
type word64 **uint64
|
||||||
|
|
||||||
|
func word64_Set(p word64, o *Buffer, x uint64) {
|
||||||
|
if len(o.uint64s) == 0 {
|
||||||
|
o.uint64s = make([]uint64, uint64PoolSize)
|
||||||
|
}
|
||||||
|
o.uint64s[0] = x
|
||||||
|
*p = &o.uint64s[0]
|
||||||
|
o.uint64s = o.uint64s[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64_IsNil(p word64) bool {
|
||||||
|
return *p == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64_Get(p word64) uint64 {
|
||||||
|
return **p
|
||||||
|
}
|
||||||
|
|
||||||
|
func structPointer_Word64(p structPointer, f field) word64 {
|
||||||
|
return word64((**uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// word64Val is like word32Val but for 64-bit values.
|
||||||
|
type word64Val *uint64
|
||||||
|
|
||||||
|
func word64Val_Set(p word64Val, o *Buffer, x uint64) {
|
||||||
|
*p = x
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64Val_Get(p word64Val) uint64 {
|
||||||
|
return *p
|
||||||
|
}
|
||||||
|
|
||||||
|
func structPointer_Word64Val(p structPointer, f field) word64Val {
|
||||||
|
return word64Val((*uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// word64Slice is like word32Slice but for 64-bit values.
|
||||||
|
type word64Slice []uint64
|
||||||
|
|
||||||
|
func (v *word64Slice) Append(x uint64) { *v = append(*v, x) }
|
||||||
|
func (v *word64Slice) Len() int { return len(*v) }
|
||||||
|
func (v *word64Slice) Index(i int) uint64 { return (*v)[i] }
|
||||||
|
|
||||||
|
func structPointer_Word64Slice(p structPointer, f field) *word64Slice {
|
||||||
|
return (*word64Slice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
724
Godeps/_workspace/src/github.com/golang/protobuf/proto/properties.go
generated
vendored
Normal file
724
Godeps/_workspace/src/github.com/golang/protobuf/proto/properties.go
generated
vendored
Normal file
@@ -0,0 +1,724 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Routines for encoding data into the wire format for protocol buffers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const debug bool = false
|
||||||
|
|
||||||
|
// Constants that identify the encoding of a value on the wire.
|
||||||
|
const (
|
||||||
|
WireVarint = 0
|
||||||
|
WireFixed64 = 1
|
||||||
|
WireBytes = 2
|
||||||
|
WireStartGroup = 3
|
||||||
|
WireEndGroup = 4
|
||||||
|
WireFixed32 = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
const startSize = 10 // initial slice/string sizes
|
||||||
|
|
||||||
|
// Encoders are defined in encode.go
|
||||||
|
// An encoder outputs the full representation of a field, including its
|
||||||
|
// tag and encoder type.
|
||||||
|
type encoder func(p *Buffer, prop *Properties, base structPointer) error
|
||||||
|
|
||||||
|
// A valueEncoder encodes a single integer in a particular encoding.
|
||||||
|
type valueEncoder func(o *Buffer, x uint64) error
|
||||||
|
|
||||||
|
// Sizers are defined in encode.go
|
||||||
|
// A sizer returns the encoded size of a field, including its tag and encoder
|
||||||
|
// type.
|
||||||
|
type sizer func(prop *Properties, base structPointer) int
|
||||||
|
|
||||||
|
// A valueSizer returns the encoded size of a single integer in a particular
|
||||||
|
// encoding.
|
||||||
|
type valueSizer func(x uint64) int
|
||||||
|
|
||||||
|
// Decoders are defined in decode.go
|
||||||
|
// A decoder creates a value from its wire representation.
|
||||||
|
// Unrecognized subelements are saved in unrec.
|
||||||
|
type decoder func(p *Buffer, prop *Properties, base structPointer) error
|
||||||
|
|
||||||
|
// A valueDecoder decodes a single integer in a particular encoding.
|
||||||
|
type valueDecoder func(o *Buffer) (x uint64, err error)
|
||||||
|
|
||||||
|
// tagMap is an optimization over map[int]int for typical protocol buffer
|
||||||
|
// use-cases. Encoded protocol buffers are often in tag order with small tag
|
||||||
|
// numbers.
|
||||||
|
type tagMap struct {
|
||||||
|
fastTags []int
|
||||||
|
slowTags map[int]int
|
||||||
|
}
|
||||||
|
|
||||||
|
// tagMapFastLimit is the upper bound on the tag number that will be stored in
|
||||||
|
// the tagMap slice rather than its map.
|
||||||
|
const tagMapFastLimit = 1024
|
||||||
|
|
||||||
|
func (p *tagMap) get(t int) (int, bool) {
|
||||||
|
if t > 0 && t < tagMapFastLimit {
|
||||||
|
if t >= len(p.fastTags) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
fi := p.fastTags[t]
|
||||||
|
return fi, fi >= 0
|
||||||
|
}
|
||||||
|
fi, ok := p.slowTags[t]
|
||||||
|
return fi, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *tagMap) put(t int, fi int) {
|
||||||
|
if t > 0 && t < tagMapFastLimit {
|
||||||
|
for len(p.fastTags) < t+1 {
|
||||||
|
p.fastTags = append(p.fastTags, -1)
|
||||||
|
}
|
||||||
|
p.fastTags[t] = fi
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.slowTags == nil {
|
||||||
|
p.slowTags = make(map[int]int)
|
||||||
|
}
|
||||||
|
p.slowTags[t] = fi
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructProperties represents properties for all the fields of a struct.
|
||||||
|
// decoderTags and decoderOrigNames should only be used by the decoder.
|
||||||
|
type StructProperties struct {
|
||||||
|
Prop []*Properties // properties for each field
|
||||||
|
reqCount int // required count
|
||||||
|
decoderTags tagMap // map from proto tag to struct field number
|
||||||
|
decoderOrigNames map[string]int // map from original name to struct field number
|
||||||
|
order []int // list of struct field numbers in tag order
|
||||||
|
unrecField field // field id of the XXX_unrecognized []byte field
|
||||||
|
extendable bool // is this an extendable proto
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the sorting interface so we can sort the fields in tag order, as recommended by the spec.
|
||||||
|
// See encode.go, (*Buffer).enc_struct.
|
||||||
|
|
||||||
|
func (sp *StructProperties) Len() int { return len(sp.order) }
|
||||||
|
func (sp *StructProperties) Less(i, j int) bool {
|
||||||
|
return sp.Prop[sp.order[i]].Tag < sp.Prop[sp.order[j]].Tag
|
||||||
|
}
|
||||||
|
func (sp *StructProperties) Swap(i, j int) { sp.order[i], sp.order[j] = sp.order[j], sp.order[i] }
|
||||||
|
|
||||||
|
// Properties represents the protocol-specific behavior of a single struct field.
|
||||||
|
type Properties struct {
|
||||||
|
Name string // name of the field, for error messages
|
||||||
|
OrigName string // original name before protocol compiler (always set)
|
||||||
|
Wire string
|
||||||
|
WireType int
|
||||||
|
Tag int
|
||||||
|
Required bool
|
||||||
|
Optional bool
|
||||||
|
Repeated bool
|
||||||
|
Packed bool // relevant for repeated primitives only
|
||||||
|
Enum string // set for enum types only
|
||||||
|
proto3 bool // whether this is known to be a proto3 field; set for []byte only
|
||||||
|
|
||||||
|
Default string // default value
|
||||||
|
HasDefault bool // whether an explicit default was provided
|
||||||
|
def_uint64 uint64
|
||||||
|
|
||||||
|
enc encoder
|
||||||
|
valEnc valueEncoder // set for bool and numeric types only
|
||||||
|
field field
|
||||||
|
tagcode []byte // encoding of EncodeVarint((Tag<<3)|WireType)
|
||||||
|
tagbuf [8]byte
|
||||||
|
stype reflect.Type // set for struct types only
|
||||||
|
sprop *StructProperties // set for struct types only
|
||||||
|
isMarshaler bool
|
||||||
|
isUnmarshaler bool
|
||||||
|
|
||||||
|
mtype reflect.Type // set for map types only
|
||||||
|
mkeyprop *Properties // set for map types only
|
||||||
|
mvalprop *Properties // set for map types only
|
||||||
|
|
||||||
|
size sizer
|
||||||
|
valSize valueSizer // set for bool and numeric types only
|
||||||
|
|
||||||
|
dec decoder
|
||||||
|
valDec valueDecoder // set for bool and numeric types only
|
||||||
|
|
||||||
|
// If this is a packable field, this will be the decoder for the packed version of the field.
|
||||||
|
packedDec decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// String formats the properties in the protobuf struct field tag style.
|
||||||
|
func (p *Properties) String() string {
|
||||||
|
s := p.Wire
|
||||||
|
s = ","
|
||||||
|
s += strconv.Itoa(p.Tag)
|
||||||
|
if p.Required {
|
||||||
|
s += ",req"
|
||||||
|
}
|
||||||
|
if p.Optional {
|
||||||
|
s += ",opt"
|
||||||
|
}
|
||||||
|
if p.Repeated {
|
||||||
|
s += ",rep"
|
||||||
|
}
|
||||||
|
if p.Packed {
|
||||||
|
s += ",packed"
|
||||||
|
}
|
||||||
|
if p.OrigName != p.Name {
|
||||||
|
s += ",name=" + p.OrigName
|
||||||
|
}
|
||||||
|
if p.proto3 {
|
||||||
|
s += ",proto3"
|
||||||
|
}
|
||||||
|
if len(p.Enum) > 0 {
|
||||||
|
s += ",enum=" + p.Enum
|
||||||
|
}
|
||||||
|
if p.HasDefault {
|
||||||
|
s += ",def=" + p.Default
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse populates p by parsing a string in the protobuf struct field tag style.
|
||||||
|
func (p *Properties) Parse(s string) {
|
||||||
|
// "bytes,49,opt,name=foo,def=hello!"
|
||||||
|
fields := strings.Split(s, ",") // breaks def=, but handled below.
|
||||||
|
if len(fields) < 2 {
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: tag has too few fields: %q\n", s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Wire = fields[0]
|
||||||
|
switch p.Wire {
|
||||||
|
case "varint":
|
||||||
|
p.WireType = WireVarint
|
||||||
|
p.valEnc = (*Buffer).EncodeVarint
|
||||||
|
p.valDec = (*Buffer).DecodeVarint
|
||||||
|
p.valSize = sizeVarint
|
||||||
|
case "fixed32":
|
||||||
|
p.WireType = WireFixed32
|
||||||
|
p.valEnc = (*Buffer).EncodeFixed32
|
||||||
|
p.valDec = (*Buffer).DecodeFixed32
|
||||||
|
p.valSize = sizeFixed32
|
||||||
|
case "fixed64":
|
||||||
|
p.WireType = WireFixed64
|
||||||
|
p.valEnc = (*Buffer).EncodeFixed64
|
||||||
|
p.valDec = (*Buffer).DecodeFixed64
|
||||||
|
p.valSize = sizeFixed64
|
||||||
|
case "zigzag32":
|
||||||
|
p.WireType = WireVarint
|
||||||
|
p.valEnc = (*Buffer).EncodeZigzag32
|
||||||
|
p.valDec = (*Buffer).DecodeZigzag32
|
||||||
|
p.valSize = sizeZigzag32
|
||||||
|
case "zigzag64":
|
||||||
|
p.WireType = WireVarint
|
||||||
|
p.valEnc = (*Buffer).EncodeZigzag64
|
||||||
|
p.valDec = (*Buffer).DecodeZigzag64
|
||||||
|
p.valSize = sizeZigzag64
|
||||||
|
case "bytes", "group":
|
||||||
|
p.WireType = WireBytes
|
||||||
|
// no numeric converter for non-numeric types
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: tag has unknown wire type: %q\n", s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
p.Tag, err = strconv.Atoi(fields[1])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 2; i < len(fields); i++ {
|
||||||
|
f := fields[i]
|
||||||
|
switch {
|
||||||
|
case f == "req":
|
||||||
|
p.Required = true
|
||||||
|
case f == "opt":
|
||||||
|
p.Optional = true
|
||||||
|
case f == "rep":
|
||||||
|
p.Repeated = true
|
||||||
|
case f == "packed":
|
||||||
|
p.Packed = true
|
||||||
|
case strings.HasPrefix(f, "name="):
|
||||||
|
p.OrigName = f[5:]
|
||||||
|
case strings.HasPrefix(f, "enum="):
|
||||||
|
p.Enum = f[5:]
|
||||||
|
case f == "proto3":
|
||||||
|
p.proto3 = true
|
||||||
|
case strings.HasPrefix(f, "def="):
|
||||||
|
p.HasDefault = true
|
||||||
|
p.Default = f[4:] // rest of string
|
||||||
|
if i+1 < len(fields) {
|
||||||
|
// Commas aren't escaped, and def is always last.
|
||||||
|
p.Default += "," + strings.Join(fields[i+1:], ",")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logNoSliceEnc(t1, t2 reflect.Type) {
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: no slice oenc for %T = []%T\n", t1, t2)
|
||||||
|
}
|
||||||
|
|
||||||
|
var protoMessageType = reflect.TypeOf((*Message)(nil)).Elem()
|
||||||
|
|
||||||
|
// Initialize the fields for encoding and decoding.
|
||||||
|
func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lockGetProp bool) {
|
||||||
|
p.enc = nil
|
||||||
|
p.dec = nil
|
||||||
|
p.size = nil
|
||||||
|
|
||||||
|
switch t1 := typ; t1.Kind() {
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: no coders for %v\n", t1)
|
||||||
|
|
||||||
|
// proto3 scalar types
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
p.enc = (*Buffer).enc_proto3_bool
|
||||||
|
p.dec = (*Buffer).dec_proto3_bool
|
||||||
|
p.size = size_proto3_bool
|
||||||
|
case reflect.Int32:
|
||||||
|
p.enc = (*Buffer).enc_proto3_int32
|
||||||
|
p.dec = (*Buffer).dec_proto3_int32
|
||||||
|
p.size = size_proto3_int32
|
||||||
|
case reflect.Uint32:
|
||||||
|
p.enc = (*Buffer).enc_proto3_uint32
|
||||||
|
p.dec = (*Buffer).dec_proto3_int32 // can reuse
|
||||||
|
p.size = size_proto3_uint32
|
||||||
|
case reflect.Int64, reflect.Uint64:
|
||||||
|
p.enc = (*Buffer).enc_proto3_int64
|
||||||
|
p.dec = (*Buffer).dec_proto3_int64
|
||||||
|
p.size = size_proto3_int64
|
||||||
|
case reflect.Float32:
|
||||||
|
p.enc = (*Buffer).enc_proto3_uint32 // can just treat them as bits
|
||||||
|
p.dec = (*Buffer).dec_proto3_int32
|
||||||
|
p.size = size_proto3_uint32
|
||||||
|
case reflect.Float64:
|
||||||
|
p.enc = (*Buffer).enc_proto3_int64 // can just treat them as bits
|
||||||
|
p.dec = (*Buffer).dec_proto3_int64
|
||||||
|
p.size = size_proto3_int64
|
||||||
|
case reflect.String:
|
||||||
|
p.enc = (*Buffer).enc_proto3_string
|
||||||
|
p.dec = (*Buffer).dec_proto3_string
|
||||||
|
p.size = size_proto3_string
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
switch t2 := t1.Elem(); t2.Kind() {
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: no encoder function for %v -> %v\n", t1, t2)
|
||||||
|
break
|
||||||
|
case reflect.Bool:
|
||||||
|
p.enc = (*Buffer).enc_bool
|
||||||
|
p.dec = (*Buffer).dec_bool
|
||||||
|
p.size = size_bool
|
||||||
|
case reflect.Int32:
|
||||||
|
p.enc = (*Buffer).enc_int32
|
||||||
|
p.dec = (*Buffer).dec_int32
|
||||||
|
p.size = size_int32
|
||||||
|
case reflect.Uint32:
|
||||||
|
p.enc = (*Buffer).enc_uint32
|
||||||
|
p.dec = (*Buffer).dec_int32 // can reuse
|
||||||
|
p.size = size_uint32
|
||||||
|
case reflect.Int64, reflect.Uint64:
|
||||||
|
p.enc = (*Buffer).enc_int64
|
||||||
|
p.dec = (*Buffer).dec_int64
|
||||||
|
p.size = size_int64
|
||||||
|
case reflect.Float32:
|
||||||
|
p.enc = (*Buffer).enc_uint32 // can just treat them as bits
|
||||||
|
p.dec = (*Buffer).dec_int32
|
||||||
|
p.size = size_uint32
|
||||||
|
case reflect.Float64:
|
||||||
|
p.enc = (*Buffer).enc_int64 // can just treat them as bits
|
||||||
|
p.dec = (*Buffer).dec_int64
|
||||||
|
p.size = size_int64
|
||||||
|
case reflect.String:
|
||||||
|
p.enc = (*Buffer).enc_string
|
||||||
|
p.dec = (*Buffer).dec_string
|
||||||
|
p.size = size_string
|
||||||
|
case reflect.Struct:
|
||||||
|
p.stype = t1.Elem()
|
||||||
|
p.isMarshaler = isMarshaler(t1)
|
||||||
|
p.isUnmarshaler = isUnmarshaler(t1)
|
||||||
|
if p.Wire == "bytes" {
|
||||||
|
p.enc = (*Buffer).enc_struct_message
|
||||||
|
p.dec = (*Buffer).dec_struct_message
|
||||||
|
p.size = size_struct_message
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_struct_group
|
||||||
|
p.dec = (*Buffer).dec_struct_group
|
||||||
|
p.size = size_struct_group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
switch t2 := t1.Elem(); t2.Kind() {
|
||||||
|
default:
|
||||||
|
logNoSliceEnc(t1, t2)
|
||||||
|
break
|
||||||
|
case reflect.Bool:
|
||||||
|
if p.Packed {
|
||||||
|
p.enc = (*Buffer).enc_slice_packed_bool
|
||||||
|
p.size = size_slice_packed_bool
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_bool
|
||||||
|
p.size = size_slice_bool
|
||||||
|
}
|
||||||
|
p.dec = (*Buffer).dec_slice_bool
|
||||||
|
p.packedDec = (*Buffer).dec_slice_packed_bool
|
||||||
|
case reflect.Int32:
|
||||||
|
if p.Packed {
|
||||||
|
p.enc = (*Buffer).enc_slice_packed_int32
|
||||||
|
p.size = size_slice_packed_int32
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_int32
|
||||||
|
p.size = size_slice_int32
|
||||||
|
}
|
||||||
|
p.dec = (*Buffer).dec_slice_int32
|
||||||
|
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||||
|
case reflect.Uint32:
|
||||||
|
if p.Packed {
|
||||||
|
p.enc = (*Buffer).enc_slice_packed_uint32
|
||||||
|
p.size = size_slice_packed_uint32
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_uint32
|
||||||
|
p.size = size_slice_uint32
|
||||||
|
}
|
||||||
|
p.dec = (*Buffer).dec_slice_int32
|
||||||
|
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||||
|
case reflect.Int64, reflect.Uint64:
|
||||||
|
if p.Packed {
|
||||||
|
p.enc = (*Buffer).enc_slice_packed_int64
|
||||||
|
p.size = size_slice_packed_int64
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_int64
|
||||||
|
p.size = size_slice_int64
|
||||||
|
}
|
||||||
|
p.dec = (*Buffer).dec_slice_int64
|
||||||
|
p.packedDec = (*Buffer).dec_slice_packed_int64
|
||||||
|
case reflect.Uint8:
|
||||||
|
p.enc = (*Buffer).enc_slice_byte
|
||||||
|
p.dec = (*Buffer).dec_slice_byte
|
||||||
|
p.size = size_slice_byte
|
||||||
|
if p.proto3 {
|
||||||
|
p.enc = (*Buffer).enc_proto3_slice_byte
|
||||||
|
p.size = size_proto3_slice_byte
|
||||||
|
}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
switch t2.Bits() {
|
||||||
|
case 32:
|
||||||
|
// can just treat them as bits
|
||||||
|
if p.Packed {
|
||||||
|
p.enc = (*Buffer).enc_slice_packed_uint32
|
||||||
|
p.size = size_slice_packed_uint32
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_uint32
|
||||||
|
p.size = size_slice_uint32
|
||||||
|
}
|
||||||
|
p.dec = (*Buffer).dec_slice_int32
|
||||||
|
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||||
|
case 64:
|
||||||
|
// can just treat them as bits
|
||||||
|
if p.Packed {
|
||||||
|
p.enc = (*Buffer).enc_slice_packed_int64
|
||||||
|
p.size = size_slice_packed_int64
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_int64
|
||||||
|
p.size = size_slice_int64
|
||||||
|
}
|
||||||
|
p.dec = (*Buffer).dec_slice_int64
|
||||||
|
p.packedDec = (*Buffer).dec_slice_packed_int64
|
||||||
|
default:
|
||||||
|
logNoSliceEnc(t1, t2)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
p.enc = (*Buffer).enc_slice_string
|
||||||
|
p.dec = (*Buffer).dec_slice_string
|
||||||
|
p.size = size_slice_string
|
||||||
|
case reflect.Ptr:
|
||||||
|
switch t3 := t2.Elem(); t3.Kind() {
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: no ptr oenc for %T -> %T -> %T\n", t1, t2, t3)
|
||||||
|
break
|
||||||
|
case reflect.Struct:
|
||||||
|
p.stype = t2.Elem()
|
||||||
|
p.isMarshaler = isMarshaler(t2)
|
||||||
|
p.isUnmarshaler = isUnmarshaler(t2)
|
||||||
|
if p.Wire == "bytes" {
|
||||||
|
p.enc = (*Buffer).enc_slice_struct_message
|
||||||
|
p.dec = (*Buffer).dec_slice_struct_message
|
||||||
|
p.size = size_slice_struct_message
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_struct_group
|
||||||
|
p.dec = (*Buffer).dec_slice_struct_group
|
||||||
|
p.size = size_slice_struct_group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
switch t2.Elem().Kind() {
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: no slice elem oenc for %T -> %T -> %T\n", t1, t2, t2.Elem())
|
||||||
|
break
|
||||||
|
case reflect.Uint8:
|
||||||
|
p.enc = (*Buffer).enc_slice_slice_byte
|
||||||
|
p.dec = (*Buffer).dec_slice_slice_byte
|
||||||
|
p.size = size_slice_slice_byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
p.enc = (*Buffer).enc_new_map
|
||||||
|
p.dec = (*Buffer).dec_new_map
|
||||||
|
p.size = size_new_map
|
||||||
|
|
||||||
|
p.mtype = t1
|
||||||
|
p.mkeyprop = &Properties{}
|
||||||
|
p.mkeyprop.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp)
|
||||||
|
p.mvalprop = &Properties{}
|
||||||
|
vtype := p.mtype.Elem()
|
||||||
|
if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice {
|
||||||
|
// The value type is not a message (*T) or bytes ([]byte),
|
||||||
|
// so we need encoders for the pointer to this type.
|
||||||
|
vtype = reflect.PtrTo(vtype)
|
||||||
|
}
|
||||||
|
p.mvalprop.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// precalculate tag code
|
||||||
|
wire := p.WireType
|
||||||
|
if p.Packed {
|
||||||
|
wire = WireBytes
|
||||||
|
}
|
||||||
|
x := uint32(p.Tag)<<3 | uint32(wire)
|
||||||
|
i := 0
|
||||||
|
for i = 0; x > 127; i++ {
|
||||||
|
p.tagbuf[i] = 0x80 | uint8(x&0x7F)
|
||||||
|
x >>= 7
|
||||||
|
}
|
||||||
|
p.tagbuf[i] = uint8(x)
|
||||||
|
p.tagcode = p.tagbuf[0 : i+1]
|
||||||
|
|
||||||
|
if p.stype != nil {
|
||||||
|
if lockGetProp {
|
||||||
|
p.sprop = GetProperties(p.stype)
|
||||||
|
} else {
|
||||||
|
p.sprop = getPropertiesLocked(p.stype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||||
|
unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||||
|
)
|
||||||
|
|
||||||
|
// isMarshaler reports whether type t implements Marshaler.
|
||||||
|
func isMarshaler(t reflect.Type) bool {
|
||||||
|
// We're checking for (likely) pointer-receiver methods
|
||||||
|
// so if t is not a pointer, something is very wrong.
|
||||||
|
// The calls above only invoke isMarshaler on pointer types.
|
||||||
|
if t.Kind() != reflect.Ptr {
|
||||||
|
panic("proto: misuse of isMarshaler")
|
||||||
|
}
|
||||||
|
return t.Implements(marshalerType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isUnmarshaler reports whether type t implements Unmarshaler.
|
||||||
|
func isUnmarshaler(t reflect.Type) bool {
|
||||||
|
// We're checking for (likely) pointer-receiver methods
|
||||||
|
// so if t is not a pointer, something is very wrong.
|
||||||
|
// The calls above only invoke isUnmarshaler on pointer types.
|
||||||
|
if t.Kind() != reflect.Ptr {
|
||||||
|
panic("proto: misuse of isUnmarshaler")
|
||||||
|
}
|
||||||
|
return t.Implements(unmarshalerType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init populates the properties from a protocol buffer struct tag.
|
||||||
|
func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) {
|
||||||
|
p.init(typ, name, tag, f, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Properties) init(typ reflect.Type, name, tag string, f *reflect.StructField, lockGetProp bool) {
|
||||||
|
// "bytes,49,opt,def=hello!"
|
||||||
|
p.Name = name
|
||||||
|
p.OrigName = name
|
||||||
|
if f != nil {
|
||||||
|
p.field = toField(f)
|
||||||
|
}
|
||||||
|
if tag == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Parse(tag)
|
||||||
|
p.setEncAndDec(typ, f, lockGetProp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mutex sync.Mutex
|
||||||
|
propertiesMap = make(map[reflect.Type]*StructProperties)
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetProperties returns the list of properties for the type represented by t.
|
||||||
|
// t must represent a generated struct type of a protocol message.
|
||||||
|
func GetProperties(t reflect.Type) *StructProperties {
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
panic("proto: type must have kind struct")
|
||||||
|
}
|
||||||
|
mutex.Lock()
|
||||||
|
sprop := getPropertiesLocked(t)
|
||||||
|
mutex.Unlock()
|
||||||
|
return sprop
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPropertiesLocked requires that mutex is held.
|
||||||
|
func getPropertiesLocked(t reflect.Type) *StructProperties {
|
||||||
|
if prop, ok := propertiesMap[t]; ok {
|
||||||
|
if collectStats {
|
||||||
|
stats.Chit++
|
||||||
|
}
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
if collectStats {
|
||||||
|
stats.Cmiss++
|
||||||
|
}
|
||||||
|
|
||||||
|
prop := new(StructProperties)
|
||||||
|
// in case of recursive protos, fill this in now.
|
||||||
|
propertiesMap[t] = prop
|
||||||
|
|
||||||
|
// build properties
|
||||||
|
prop.extendable = reflect.PtrTo(t).Implements(extendableProtoType)
|
||||||
|
prop.unrecField = invalidField
|
||||||
|
prop.Prop = make([]*Properties, t.NumField())
|
||||||
|
prop.order = make([]int, t.NumField())
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
p := new(Properties)
|
||||||
|
name := f.Name
|
||||||
|
p.init(f.Type, name, f.Tag.Get("protobuf"), &f, false)
|
||||||
|
|
||||||
|
if f.Name == "XXX_extensions" { // special case
|
||||||
|
p.enc = (*Buffer).enc_map
|
||||||
|
p.dec = nil // not needed
|
||||||
|
p.size = size_map
|
||||||
|
}
|
||||||
|
if f.Name == "XXX_unrecognized" { // special case
|
||||||
|
prop.unrecField = toField(&f)
|
||||||
|
}
|
||||||
|
prop.Prop[i] = p
|
||||||
|
prop.order[i] = i
|
||||||
|
if debug {
|
||||||
|
print(i, " ", f.Name, " ", t.String(), " ")
|
||||||
|
if p.Tag > 0 {
|
||||||
|
print(p.String())
|
||||||
|
}
|
||||||
|
print("\n")
|
||||||
|
}
|
||||||
|
if p.enc == nil && !strings.HasPrefix(f.Name, "XXX_") {
|
||||||
|
fmt.Fprintln(os.Stderr, "proto: no encoder for", f.Name, f.Type.String(), "[GetProperties]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-order prop.order.
|
||||||
|
sort.Sort(prop)
|
||||||
|
|
||||||
|
// build required counts
|
||||||
|
// build tags
|
||||||
|
reqCount := 0
|
||||||
|
prop.decoderOrigNames = make(map[string]int)
|
||||||
|
for i, p := range prop.Prop {
|
||||||
|
if strings.HasPrefix(p.Name, "XXX_") {
|
||||||
|
// Internal fields should not appear in tags/origNames maps.
|
||||||
|
// They are handled specially when encoding and decoding.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if p.Required {
|
||||||
|
reqCount++
|
||||||
|
}
|
||||||
|
prop.decoderTags.put(p.Tag, i)
|
||||||
|
prop.decoderOrigNames[p.OrigName] = i
|
||||||
|
}
|
||||||
|
prop.reqCount = reqCount
|
||||||
|
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the Properties object for the x[0]'th field of the structure.
|
||||||
|
func propByIndex(t reflect.Type, x []int) *Properties {
|
||||||
|
if len(x) != 1 {
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: field index dimension %d (not 1) for type %s\n", len(x), t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
prop := GetProperties(t)
|
||||||
|
return prop.Prop[x[0]]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the address and type of a pointer to a struct from an interface.
|
||||||
|
func getbase(pb Message) (t reflect.Type, b structPointer, err error) {
|
||||||
|
if pb == nil {
|
||||||
|
err = ErrNil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// get the reflect type of the pointer to the struct.
|
||||||
|
t = reflect.TypeOf(pb)
|
||||||
|
// get the address of the struct.
|
||||||
|
value := reflect.ValueOf(pb)
|
||||||
|
b = toStructPointer(value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// A global registry of enum types.
|
||||||
|
// The generated code will register the generated maps by calling RegisterEnum.
|
||||||
|
|
||||||
|
var enumValueMaps = make(map[string]map[string]int32)
|
||||||
|
|
||||||
|
// RegisterEnum is called from the generated code to install the enum descriptor
|
||||||
|
// maps into the global table to aid parsing text format protocol buffers.
|
||||||
|
func RegisterEnum(typeName string, unusedNameMap map[int32]string, valueMap map[string]int32) {
|
||||||
|
if _, ok := enumValueMaps[typeName]; ok {
|
||||||
|
panic("proto: duplicate enum registered: " + typeName)
|
||||||
|
}
|
||||||
|
enumValueMaps[typeName] = valueMap
|
||||||
|
}
|
||||||
44
Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_proto/Makefile
generated
vendored
Normal file
44
Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_proto/Makefile
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
#
|
||||||
|
# Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
# https://github.com/golang/protobuf
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
include ../../Make.protobuf
|
||||||
|
|
||||||
|
all: regenerate
|
||||||
|
|
||||||
|
regenerate:
|
||||||
|
rm -f proto3.pb.go
|
||||||
|
make proto3.pb.go
|
||||||
|
|
||||||
|
# The following rules are just aids to development. Not needed for typical testing.
|
||||||
|
|
||||||
|
diff: regenerate
|
||||||
|
git diff proto3.pb.go
|
||||||
58
Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_proto/proto3.proto
generated
vendored
Normal file
58
Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_proto/proto3.proto
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package proto3_proto;
|
||||||
|
|
||||||
|
message Message {
|
||||||
|
enum Humour {
|
||||||
|
UNKNOWN = 0;
|
||||||
|
PUNS = 1;
|
||||||
|
SLAPSTICK = 2;
|
||||||
|
BILL_BAILEY = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
string name = 1;
|
||||||
|
Humour hilarity = 2;
|
||||||
|
uint32 height_in_cm = 3;
|
||||||
|
bytes data = 4;
|
||||||
|
int64 result_count = 7;
|
||||||
|
bool true_scotsman = 8;
|
||||||
|
float score = 9;
|
||||||
|
|
||||||
|
repeated uint64 key = 5;
|
||||||
|
Nested nested = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Nested {
|
||||||
|
string bunny = 1;
|
||||||
|
}
|
||||||
93
Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_test.go
generated
vendored
Normal file
93
Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_test.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
pb "./proto3_proto"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProto3ZeroValues(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
m proto.Message
|
||||||
|
}{
|
||||||
|
{"zero message", &pb.Message{}},
|
||||||
|
{"empty bytes field", &pb.Message{Data: []byte{}}},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
b, err := proto.Marshal(test.m)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: proto.Marshal: %v", test.desc, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(b) > 0 {
|
||||||
|
t.Errorf("%s: Encoding is non-empty: %q", test.desc, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoundTripProto3(t *testing.T) {
|
||||||
|
m := &pb.Message{
|
||||||
|
Name: "David", // (2 | 1<<3): 0x0a 0x05 "David"
|
||||||
|
Hilarity: pb.Message_PUNS, // (0 | 2<<3): 0x10 0x01
|
||||||
|
HeightInCm: 178, // (0 | 3<<3): 0x18 0xb2 0x01
|
||||||
|
Data: []byte("roboto"), // (2 | 4<<3): 0x20 0x06 "roboto"
|
||||||
|
ResultCount: 47, // (0 | 7<<3): 0x38 0x2f
|
||||||
|
TrueScotsman: true, // (0 | 8<<3): 0x40 0x01
|
||||||
|
Score: 8.1, // (5 | 9<<3): 0x4d <8.1>
|
||||||
|
|
||||||
|
Key: []uint64{1, 0xdeadbeef},
|
||||||
|
Nested: &pb.Nested{
|
||||||
|
Bunny: "Monty",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
t.Logf(" m: %v", m)
|
||||||
|
|
||||||
|
b, err := proto.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("proto.Marshal: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf(" b: %q", b)
|
||||||
|
|
||||||
|
m2 := new(pb.Message)
|
||||||
|
if err := proto.Unmarshal(b, m2); err != nil {
|
||||||
|
t.Fatalf("proto.Unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("m2: %v", m2)
|
||||||
|
|
||||||
|
if !proto.Equal(m, m2) {
|
||||||
|
t.Errorf("proto.Equal returned false:\n m: %v\nm2: %v", m, m2)
|
||||||
|
}
|
||||||
|
}
|
||||||
63
Godeps/_workspace/src/github.com/golang/protobuf/proto/size2_test.go
generated
vendored
Normal file
63
Godeps/_workspace/src/github.com/golang/protobuf/proto/size2_test.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a separate file and package from size_test.go because that one uses
|
||||||
|
// generated messages and thus may not be in package proto without having a circular
|
||||||
|
// dependency, whereas this file tests unexported details of size.go.
|
||||||
|
|
||||||
|
func TestVarintSize(t *testing.T) {
|
||||||
|
// Check the edge cases carefully.
|
||||||
|
testCases := []struct {
|
||||||
|
n uint64
|
||||||
|
size int
|
||||||
|
}{
|
||||||
|
{0, 1},
|
||||||
|
{1, 1},
|
||||||
|
{127, 1},
|
||||||
|
{128, 2},
|
||||||
|
{16383, 2},
|
||||||
|
{16384, 3},
|
||||||
|
{1<<63 - 1, 9},
|
||||||
|
{1 << 63, 10},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
size := sizeVarint(tc.n)
|
||||||
|
if size != tc.size {
|
||||||
|
t.Errorf("sizeVarint(%d) = %d, want %d", tc.n, size, tc.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
135
Godeps/_workspace/src/github.com/golang/protobuf/proto/size_test.go
generated
vendored
Normal file
135
Godeps/_workspace/src/github.com/golang/protobuf/proto/size_test.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
proto3pb "./proto3_proto"
|
||||||
|
pb "./testdata"
|
||||||
|
. "github.com/golang/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var messageWithExtension1 = &pb.MyMessage{Count: Int32(7)}
|
||||||
|
|
||||||
|
// messageWithExtension2 is in equal_test.go.
|
||||||
|
var messageWithExtension3 = &pb.MyMessage{Count: Int32(8)}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := SetExtension(messageWithExtension1, pb.E_Ext_More, &pb.Ext{Data: String("Abbott")}); err != nil {
|
||||||
|
log.Panicf("SetExtension: %v", err)
|
||||||
|
}
|
||||||
|
if err := SetExtension(messageWithExtension3, pb.E_Ext_More, &pb.Ext{Data: String("Costello")}); err != nil {
|
||||||
|
log.Panicf("SetExtension: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force messageWithExtension3 to have the extension encoded.
|
||||||
|
Marshal(messageWithExtension3)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var SizeTests = []struct {
|
||||||
|
desc string
|
||||||
|
pb Message
|
||||||
|
}{
|
||||||
|
{"empty", &pb.OtherMessage{}},
|
||||||
|
// Basic types.
|
||||||
|
{"bool", &pb.Defaults{F_Bool: Bool(true)}},
|
||||||
|
{"int32", &pb.Defaults{F_Int32: Int32(12)}},
|
||||||
|
{"negative int32", &pb.Defaults{F_Int32: Int32(-1)}},
|
||||||
|
{"small int64", &pb.Defaults{F_Int64: Int64(1)}},
|
||||||
|
{"big int64", &pb.Defaults{F_Int64: Int64(1 << 20)}},
|
||||||
|
{"negative int64", &pb.Defaults{F_Int64: Int64(-1)}},
|
||||||
|
{"fixed32", &pb.Defaults{F_Fixed32: Uint32(71)}},
|
||||||
|
{"fixed64", &pb.Defaults{F_Fixed64: Uint64(72)}},
|
||||||
|
{"uint32", &pb.Defaults{F_Uint32: Uint32(123)}},
|
||||||
|
{"uint64", &pb.Defaults{F_Uint64: Uint64(124)}},
|
||||||
|
{"float", &pb.Defaults{F_Float: Float32(12.6)}},
|
||||||
|
{"double", &pb.Defaults{F_Double: Float64(13.9)}},
|
||||||
|
{"string", &pb.Defaults{F_String: String("niles")}},
|
||||||
|
{"bytes", &pb.Defaults{F_Bytes: []byte("wowsa")}},
|
||||||
|
{"bytes, empty", &pb.Defaults{F_Bytes: []byte{}}},
|
||||||
|
{"sint32", &pb.Defaults{F_Sint32: Int32(65)}},
|
||||||
|
{"sint64", &pb.Defaults{F_Sint64: Int64(67)}},
|
||||||
|
{"enum", &pb.Defaults{F_Enum: pb.Defaults_BLUE.Enum()}},
|
||||||
|
// Repeated.
|
||||||
|
{"empty repeated bool", &pb.MoreRepeated{Bools: []bool{}}},
|
||||||
|
{"repeated bool", &pb.MoreRepeated{Bools: []bool{false, true, true, false}}},
|
||||||
|
{"packed repeated bool", &pb.MoreRepeated{BoolsPacked: []bool{false, true, true, false, true, true, true}}},
|
||||||
|
{"repeated int32", &pb.MoreRepeated{Ints: []int32{1, 12203, 1729, -1}}},
|
||||||
|
{"repeated int32 packed", &pb.MoreRepeated{IntsPacked: []int32{1, 12203, 1729}}},
|
||||||
|
{"repeated int64 packed", &pb.MoreRepeated{Int64SPacked: []int64{
|
||||||
|
// Need enough large numbers to verify that the header is counting the number of bytes
|
||||||
|
// for the field, not the number of elements.
|
||||||
|
1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62,
|
||||||
|
1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62,
|
||||||
|
}}},
|
||||||
|
{"repeated string", &pb.MoreRepeated{Strings: []string{"r", "ken", "gri"}}},
|
||||||
|
{"repeated fixed", &pb.MoreRepeated{Fixeds: []uint32{1, 2, 3, 4}}},
|
||||||
|
// Nested.
|
||||||
|
{"nested", &pb.OldMessage{Nested: &pb.OldMessage_Nested{Name: String("whatever")}}},
|
||||||
|
{"group", &pb.GroupOld{G: &pb.GroupOld_G{X: Int32(12345)}}},
|
||||||
|
// Other things.
|
||||||
|
{"unrecognized", &pb.MoreRepeated{XXX_unrecognized: []byte{13<<3 | 0, 4}}},
|
||||||
|
{"extension (unencoded)", messageWithExtension1},
|
||||||
|
{"extension (encoded)", messageWithExtension3},
|
||||||
|
// proto3 message
|
||||||
|
{"proto3 empty", &proto3pb.Message{}},
|
||||||
|
{"proto3 bool", &proto3pb.Message{TrueScotsman: true}},
|
||||||
|
{"proto3 int64", &proto3pb.Message{ResultCount: 1}},
|
||||||
|
{"proto3 uint32", &proto3pb.Message{HeightInCm: 123}},
|
||||||
|
{"proto3 float", &proto3pb.Message{Score: 12.6}},
|
||||||
|
{"proto3 string", &proto3pb.Message{Name: "Snezana"}},
|
||||||
|
{"proto3 bytes", &proto3pb.Message{Data: []byte("wowsa")}},
|
||||||
|
{"proto3 bytes, empty", &proto3pb.Message{Data: []byte{}}},
|
||||||
|
{"proto3 enum", &proto3pb.Message{Hilarity: proto3pb.Message_PUNS}},
|
||||||
|
|
||||||
|
{"map field", &pb.MessageWithMap{NameMapping: map[int32]string{1: "Rob", 7: "Andrew"}}},
|
||||||
|
{"map field with message", &pb.MessageWithMap{MsgMapping: map[int64]*pb.FloatingPoint{0x7001: &pb.FloatingPoint{F: Float64(2.0)}}}},
|
||||||
|
{"map field with bytes", &pb.MessageWithMap{ByteMapping: map[bool][]byte{true: []byte("this time for sure")}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSize(t *testing.T) {
|
||||||
|
for _, tc := range SizeTests {
|
||||||
|
size := Size(tc.pb)
|
||||||
|
b, err := Marshal(tc.pb)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: Marshal failed: %v", tc.desc, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if size != len(b) {
|
||||||
|
t.Errorf("%v: Size(%v) = %d, want %d", tc.desc, tc.pb, size, len(b))
|
||||||
|
t.Logf("%v: bytes: %#v", tc.desc, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/Makefile
generated
vendored
Normal file
50
Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/Makefile
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
#
|
||||||
|
# Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
# https://github.com/golang/protobuf
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
|
include ../../Make.protobuf
|
||||||
|
|
||||||
|
all: regenerate
|
||||||
|
|
||||||
|
regenerate:
|
||||||
|
rm -f test.pb.go
|
||||||
|
make test.pb.go
|
||||||
|
|
||||||
|
# The following rules are just aids to development. Not needed for typical testing.
|
||||||
|
|
||||||
|
diff: regenerate
|
||||||
|
git diff test.pb.go
|
||||||
|
|
||||||
|
restore:
|
||||||
|
cp test.pb.go.golden test.pb.go
|
||||||
|
|
||||||
|
preserve:
|
||||||
|
cp test.pb.go test.pb.go.golden
|
||||||
86
Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/golden_test.go
generated
vendored
Normal file
86
Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/golden_test.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Verify that the compiler output for test.proto is unchanged.
|
||||||
|
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sum returns in string form (for easy comparison) the SHA-1 hash of the named file.
|
||||||
|
func sum(t *testing.T, name string) string {
|
||||||
|
data, err := ioutil.ReadFile(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("sum(%q): length is %d", name, len(data))
|
||||||
|
hash := sha1.New()
|
||||||
|
_, err = hash.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("% x", hash.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(t *testing.T, name string, args ...string) {
|
||||||
|
cmd := exec.Command(name, args...)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGolden(t *testing.T) {
|
||||||
|
// Compute the original checksum.
|
||||||
|
goldenSum := sum(t, "test.pb.go")
|
||||||
|
// Run the proto compiler.
|
||||||
|
run(t, "protoc", "--go_out="+os.TempDir(), "test.proto")
|
||||||
|
newFile := filepath.Join(os.TempDir(), "test.pb.go")
|
||||||
|
defer os.Remove(newFile)
|
||||||
|
// Compute the new checksum.
|
||||||
|
newSum := sum(t, newFile)
|
||||||
|
// Verify
|
||||||
|
if newSum != goldenSum {
|
||||||
|
run(t, "diff", "-u", "test.pb.go", newFile)
|
||||||
|
t.Fatal("Code generated by protoc-gen-go has changed; update test.pb.go")
|
||||||
|
}
|
||||||
|
}
|
||||||
2389
Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/test.pb.go
generated
vendored
Normal file
2389
Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/test.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
434
Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/test.proto
generated
vendored
Normal file
434
Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/test.proto
generated
vendored
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// A feature-rich test file for the protocol compiler and libraries.
|
||||||
|
|
||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
|
package testdata;
|
||||||
|
|
||||||
|
enum FOO { FOO1 = 1; };
|
||||||
|
|
||||||
|
message GoEnum {
|
||||||
|
required FOO foo = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GoTestField {
|
||||||
|
required string Label = 1;
|
||||||
|
required string Type = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GoTest {
|
||||||
|
// An enum, for completeness.
|
||||||
|
enum KIND {
|
||||||
|
VOID = 0;
|
||||||
|
|
||||||
|
// Basic types
|
||||||
|
BOOL = 1;
|
||||||
|
BYTES = 2;
|
||||||
|
FINGERPRINT = 3;
|
||||||
|
FLOAT = 4;
|
||||||
|
INT = 5;
|
||||||
|
STRING = 6;
|
||||||
|
TIME = 7;
|
||||||
|
|
||||||
|
// Groupings
|
||||||
|
TUPLE = 8;
|
||||||
|
ARRAY = 9;
|
||||||
|
MAP = 10;
|
||||||
|
|
||||||
|
// Table types
|
||||||
|
TABLE = 11;
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
FUNCTION = 12; // last tag
|
||||||
|
};
|
||||||
|
|
||||||
|
// Some typical parameters
|
||||||
|
required KIND Kind = 1;
|
||||||
|
optional string Table = 2;
|
||||||
|
optional int32 Param = 3;
|
||||||
|
|
||||||
|
// Required, repeated and optional foreign fields.
|
||||||
|
required GoTestField RequiredField = 4;
|
||||||
|
repeated GoTestField RepeatedField = 5;
|
||||||
|
optional GoTestField OptionalField = 6;
|
||||||
|
|
||||||
|
// Required fields of all basic types
|
||||||
|
required bool F_Bool_required = 10;
|
||||||
|
required int32 F_Int32_required = 11;
|
||||||
|
required int64 F_Int64_required = 12;
|
||||||
|
required fixed32 F_Fixed32_required = 13;
|
||||||
|
required fixed64 F_Fixed64_required = 14;
|
||||||
|
required uint32 F_Uint32_required = 15;
|
||||||
|
required uint64 F_Uint64_required = 16;
|
||||||
|
required float F_Float_required = 17;
|
||||||
|
required double F_Double_required = 18;
|
||||||
|
required string F_String_required = 19;
|
||||||
|
required bytes F_Bytes_required = 101;
|
||||||
|
required sint32 F_Sint32_required = 102;
|
||||||
|
required sint64 F_Sint64_required = 103;
|
||||||
|
|
||||||
|
// Repeated fields of all basic types
|
||||||
|
repeated bool F_Bool_repeated = 20;
|
||||||
|
repeated int32 F_Int32_repeated = 21;
|
||||||
|
repeated int64 F_Int64_repeated = 22;
|
||||||
|
repeated fixed32 F_Fixed32_repeated = 23;
|
||||||
|
repeated fixed64 F_Fixed64_repeated = 24;
|
||||||
|
repeated uint32 F_Uint32_repeated = 25;
|
||||||
|
repeated uint64 F_Uint64_repeated = 26;
|
||||||
|
repeated float F_Float_repeated = 27;
|
||||||
|
repeated double F_Double_repeated = 28;
|
||||||
|
repeated string F_String_repeated = 29;
|
||||||
|
repeated bytes F_Bytes_repeated = 201;
|
||||||
|
repeated sint32 F_Sint32_repeated = 202;
|
||||||
|
repeated sint64 F_Sint64_repeated = 203;
|
||||||
|
|
||||||
|
// Optional fields of all basic types
|
||||||
|
optional bool F_Bool_optional = 30;
|
||||||
|
optional int32 F_Int32_optional = 31;
|
||||||
|
optional int64 F_Int64_optional = 32;
|
||||||
|
optional fixed32 F_Fixed32_optional = 33;
|
||||||
|
optional fixed64 F_Fixed64_optional = 34;
|
||||||
|
optional uint32 F_Uint32_optional = 35;
|
||||||
|
optional uint64 F_Uint64_optional = 36;
|
||||||
|
optional float F_Float_optional = 37;
|
||||||
|
optional double F_Double_optional = 38;
|
||||||
|
optional string F_String_optional = 39;
|
||||||
|
optional bytes F_Bytes_optional = 301;
|
||||||
|
optional sint32 F_Sint32_optional = 302;
|
||||||
|
optional sint64 F_Sint64_optional = 303;
|
||||||
|
|
||||||
|
// Default-valued fields of all basic types
|
||||||
|
optional bool F_Bool_defaulted = 40 [default=true];
|
||||||
|
optional int32 F_Int32_defaulted = 41 [default=32];
|
||||||
|
optional int64 F_Int64_defaulted = 42 [default=64];
|
||||||
|
optional fixed32 F_Fixed32_defaulted = 43 [default=320];
|
||||||
|
optional fixed64 F_Fixed64_defaulted = 44 [default=640];
|
||||||
|
optional uint32 F_Uint32_defaulted = 45 [default=3200];
|
||||||
|
optional uint64 F_Uint64_defaulted = 46 [default=6400];
|
||||||
|
optional float F_Float_defaulted = 47 [default=314159.];
|
||||||
|
optional double F_Double_defaulted = 48 [default=271828.];
|
||||||
|
optional string F_String_defaulted = 49 [default="hello, \"world!\"\n"];
|
||||||
|
optional bytes F_Bytes_defaulted = 401 [default="Bignose"];
|
||||||
|
optional sint32 F_Sint32_defaulted = 402 [default = -32];
|
||||||
|
optional sint64 F_Sint64_defaulted = 403 [default = -64];
|
||||||
|
|
||||||
|
// Packed repeated fields (no string or bytes).
|
||||||
|
repeated bool F_Bool_repeated_packed = 50 [packed=true];
|
||||||
|
repeated int32 F_Int32_repeated_packed = 51 [packed=true];
|
||||||
|
repeated int64 F_Int64_repeated_packed = 52 [packed=true];
|
||||||
|
repeated fixed32 F_Fixed32_repeated_packed = 53 [packed=true];
|
||||||
|
repeated fixed64 F_Fixed64_repeated_packed = 54 [packed=true];
|
||||||
|
repeated uint32 F_Uint32_repeated_packed = 55 [packed=true];
|
||||||
|
repeated uint64 F_Uint64_repeated_packed = 56 [packed=true];
|
||||||
|
repeated float F_Float_repeated_packed = 57 [packed=true];
|
||||||
|
repeated double F_Double_repeated_packed = 58 [packed=true];
|
||||||
|
repeated sint32 F_Sint32_repeated_packed = 502 [packed=true];
|
||||||
|
repeated sint64 F_Sint64_repeated_packed = 503 [packed=true];
|
||||||
|
|
||||||
|
// Required, repeated, and optional groups.
|
||||||
|
required group RequiredGroup = 70 {
|
||||||
|
required string RequiredField = 71;
|
||||||
|
};
|
||||||
|
|
||||||
|
repeated group RepeatedGroup = 80 {
|
||||||
|
required string RequiredField = 81;
|
||||||
|
};
|
||||||
|
|
||||||
|
optional group OptionalGroup = 90 {
|
||||||
|
required string RequiredField = 91;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// For testing skipping of unrecognized fields.
|
||||||
|
// Numbers are all big, larger than tag numbers in GoTestField,
|
||||||
|
// the message used in the corresponding test.
|
||||||
|
message GoSkipTest {
|
||||||
|
required int32 skip_int32 = 11;
|
||||||
|
required fixed32 skip_fixed32 = 12;
|
||||||
|
required fixed64 skip_fixed64 = 13;
|
||||||
|
required string skip_string = 14;
|
||||||
|
required group SkipGroup = 15 {
|
||||||
|
required int32 group_int32 = 16;
|
||||||
|
required string group_string = 17;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For testing packed/non-packed decoder switching.
|
||||||
|
// A serialized instance of one should be deserializable as the other.
|
||||||
|
message NonPackedTest {
|
||||||
|
repeated int32 a = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PackedTest {
|
||||||
|
repeated int32 b = 1 [packed=true];
|
||||||
|
}
|
||||||
|
|
||||||
|
message MaxTag {
|
||||||
|
// Maximum possible tag number.
|
||||||
|
optional string last_field = 536870911;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OldMessage {
|
||||||
|
message Nested {
|
||||||
|
optional string name = 1;
|
||||||
|
}
|
||||||
|
optional Nested nested = 1;
|
||||||
|
|
||||||
|
optional int32 num = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMessage is wire compatible with OldMessage;
|
||||||
|
// imagine it as a future version.
|
||||||
|
message NewMessage {
|
||||||
|
message Nested {
|
||||||
|
optional string name = 1;
|
||||||
|
optional string food_group = 2;
|
||||||
|
}
|
||||||
|
optional Nested nested = 1;
|
||||||
|
|
||||||
|
// This is an int32 in OldMessage.
|
||||||
|
optional int64 num = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smaller tests for ASCII formatting.
|
||||||
|
|
||||||
|
message InnerMessage {
|
||||||
|
required string host = 1;
|
||||||
|
optional int32 port = 2 [default=4000];
|
||||||
|
optional bool connected = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OtherMessage {
|
||||||
|
optional int64 key = 1;
|
||||||
|
optional bytes value = 2;
|
||||||
|
optional float weight = 3;
|
||||||
|
optional InnerMessage inner = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MyMessage {
|
||||||
|
required int32 count = 1;
|
||||||
|
optional string name = 2;
|
||||||
|
optional string quote = 3;
|
||||||
|
repeated string pet = 4;
|
||||||
|
optional InnerMessage inner = 5;
|
||||||
|
repeated OtherMessage others = 6;
|
||||||
|
repeated InnerMessage rep_inner = 12;
|
||||||
|
|
||||||
|
enum Color {
|
||||||
|
RED = 0;
|
||||||
|
GREEN = 1;
|
||||||
|
BLUE = 2;
|
||||||
|
};
|
||||||
|
optional Color bikeshed = 7;
|
||||||
|
|
||||||
|
optional group SomeGroup = 8 {
|
||||||
|
optional int32 group_field = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This field becomes [][]byte in the generated code.
|
||||||
|
repeated bytes rep_bytes = 10;
|
||||||
|
|
||||||
|
optional double bigfloat = 11;
|
||||||
|
|
||||||
|
extensions 100 to max;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Ext {
|
||||||
|
extend MyMessage {
|
||||||
|
optional Ext more = 103;
|
||||||
|
optional string text = 104;
|
||||||
|
optional int32 number = 105;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional string data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
extend MyMessage {
|
||||||
|
repeated string greeting = 106;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MyMessageSet {
|
||||||
|
option message_set_wire_format = true;
|
||||||
|
extensions 100 to max;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Empty {
|
||||||
|
}
|
||||||
|
|
||||||
|
extend MyMessageSet {
|
||||||
|
optional Empty x201 = 201;
|
||||||
|
optional Empty x202 = 202;
|
||||||
|
optional Empty x203 = 203;
|
||||||
|
optional Empty x204 = 204;
|
||||||
|
optional Empty x205 = 205;
|
||||||
|
optional Empty x206 = 206;
|
||||||
|
optional Empty x207 = 207;
|
||||||
|
optional Empty x208 = 208;
|
||||||
|
optional Empty x209 = 209;
|
||||||
|
optional Empty x210 = 210;
|
||||||
|
optional Empty x211 = 211;
|
||||||
|
optional Empty x212 = 212;
|
||||||
|
optional Empty x213 = 213;
|
||||||
|
optional Empty x214 = 214;
|
||||||
|
optional Empty x215 = 215;
|
||||||
|
optional Empty x216 = 216;
|
||||||
|
optional Empty x217 = 217;
|
||||||
|
optional Empty x218 = 218;
|
||||||
|
optional Empty x219 = 219;
|
||||||
|
optional Empty x220 = 220;
|
||||||
|
optional Empty x221 = 221;
|
||||||
|
optional Empty x222 = 222;
|
||||||
|
optional Empty x223 = 223;
|
||||||
|
optional Empty x224 = 224;
|
||||||
|
optional Empty x225 = 225;
|
||||||
|
optional Empty x226 = 226;
|
||||||
|
optional Empty x227 = 227;
|
||||||
|
optional Empty x228 = 228;
|
||||||
|
optional Empty x229 = 229;
|
||||||
|
optional Empty x230 = 230;
|
||||||
|
optional Empty x231 = 231;
|
||||||
|
optional Empty x232 = 232;
|
||||||
|
optional Empty x233 = 233;
|
||||||
|
optional Empty x234 = 234;
|
||||||
|
optional Empty x235 = 235;
|
||||||
|
optional Empty x236 = 236;
|
||||||
|
optional Empty x237 = 237;
|
||||||
|
optional Empty x238 = 238;
|
||||||
|
optional Empty x239 = 239;
|
||||||
|
optional Empty x240 = 240;
|
||||||
|
optional Empty x241 = 241;
|
||||||
|
optional Empty x242 = 242;
|
||||||
|
optional Empty x243 = 243;
|
||||||
|
optional Empty x244 = 244;
|
||||||
|
optional Empty x245 = 245;
|
||||||
|
optional Empty x246 = 246;
|
||||||
|
optional Empty x247 = 247;
|
||||||
|
optional Empty x248 = 248;
|
||||||
|
optional Empty x249 = 249;
|
||||||
|
optional Empty x250 = 250;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MessageList {
|
||||||
|
repeated group Message = 1 {
|
||||||
|
required string name = 2;
|
||||||
|
required int32 count = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Strings {
|
||||||
|
optional string string_field = 1;
|
||||||
|
optional bytes bytes_field = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Defaults {
|
||||||
|
enum Color {
|
||||||
|
RED = 0;
|
||||||
|
GREEN = 1;
|
||||||
|
BLUE = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default-valued fields of all basic types.
|
||||||
|
// Same as GoTest, but copied here to make testing easier.
|
||||||
|
optional bool F_Bool = 1 [default=true];
|
||||||
|
optional int32 F_Int32 = 2 [default=32];
|
||||||
|
optional int64 F_Int64 = 3 [default=64];
|
||||||
|
optional fixed32 F_Fixed32 = 4 [default=320];
|
||||||
|
optional fixed64 F_Fixed64 = 5 [default=640];
|
||||||
|
optional uint32 F_Uint32 = 6 [default=3200];
|
||||||
|
optional uint64 F_Uint64 = 7 [default=6400];
|
||||||
|
optional float F_Float = 8 [default=314159.];
|
||||||
|
optional double F_Double = 9 [default=271828.];
|
||||||
|
optional string F_String = 10 [default="hello, \"world!\"\n"];
|
||||||
|
optional bytes F_Bytes = 11 [default="Bignose"];
|
||||||
|
optional sint32 F_Sint32 = 12 [default=-32];
|
||||||
|
optional sint64 F_Sint64 = 13 [default=-64];
|
||||||
|
optional Color F_Enum = 14 [default=GREEN];
|
||||||
|
|
||||||
|
// More fields with crazy defaults.
|
||||||
|
optional float F_Pinf = 15 [default=inf];
|
||||||
|
optional float F_Ninf = 16 [default=-inf];
|
||||||
|
optional float F_Nan = 17 [default=nan];
|
||||||
|
|
||||||
|
// Sub-message.
|
||||||
|
optional SubDefaults sub = 18;
|
||||||
|
|
||||||
|
// Redundant but explicit defaults.
|
||||||
|
optional string str_zero = 19 [default=""];
|
||||||
|
}
|
||||||
|
|
||||||
|
message SubDefaults {
|
||||||
|
optional int64 n = 1 [default=7];
|
||||||
|
}
|
||||||
|
|
||||||
|
message RepeatedEnum {
|
||||||
|
enum Color {
|
||||||
|
RED = 1;
|
||||||
|
}
|
||||||
|
repeated Color color = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MoreRepeated {
|
||||||
|
repeated bool bools = 1;
|
||||||
|
repeated bool bools_packed = 2 [packed=true];
|
||||||
|
repeated int32 ints = 3;
|
||||||
|
repeated int32 ints_packed = 4 [packed=true];
|
||||||
|
repeated int64 int64s_packed = 7 [packed=true];
|
||||||
|
repeated string strings = 5;
|
||||||
|
repeated fixed32 fixeds = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupOld and GroupNew have the same wire format.
|
||||||
|
// GroupNew has a new field inside a group.
|
||||||
|
|
||||||
|
message GroupOld {
|
||||||
|
optional group G = 101 {
|
||||||
|
optional int32 x = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message GroupNew {
|
||||||
|
optional group G = 101 {
|
||||||
|
optional int32 x = 2;
|
||||||
|
optional int32 y = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message FloatingPoint {
|
||||||
|
required double f = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MessageWithMap {
|
||||||
|
map<int32, string> name_mapping = 1;
|
||||||
|
map<sint64, FloatingPoint> msg_mapping = 2;
|
||||||
|
map<bool, bytes> byte_mapping = 3;
|
||||||
|
}
|
||||||
789
Godeps/_workspace/src/github.com/golang/protobuf/proto/text.go
generated
vendored
Normal file
789
Godeps/_workspace/src/github.com/golang/protobuf/proto/text.go
generated
vendored
Normal file
@@ -0,0 +1,789 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
// Functions for writing the text protocol buffer format.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
newline = []byte("\n")
|
||||||
|
spaces = []byte(" ")
|
||||||
|
gtNewline = []byte(">\n")
|
||||||
|
endBraceNewline = []byte("}\n")
|
||||||
|
backslashN = []byte{'\\', 'n'}
|
||||||
|
backslashR = []byte{'\\', 'r'}
|
||||||
|
backslashT = []byte{'\\', 't'}
|
||||||
|
backslashDQ = []byte{'\\', '"'}
|
||||||
|
backslashBS = []byte{'\\', '\\'}
|
||||||
|
posInf = []byte("inf")
|
||||||
|
negInf = []byte("-inf")
|
||||||
|
nan = []byte("nan")
|
||||||
|
)
|
||||||
|
|
||||||
|
type writer interface {
|
||||||
|
io.Writer
|
||||||
|
WriteByte(byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// textWriter is an io.Writer that tracks its indentation level.
|
||||||
|
type textWriter struct {
|
||||||
|
ind int
|
||||||
|
complete bool // if the current position is a complete line
|
||||||
|
compact bool // whether to write out as a one-liner
|
||||||
|
w writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) WriteString(s string) (n int, err error) {
|
||||||
|
if !strings.Contains(s, "\n") {
|
||||||
|
if !w.compact && w.complete {
|
||||||
|
w.writeIndent()
|
||||||
|
}
|
||||||
|
w.complete = false
|
||||||
|
return io.WriteString(w.w, s)
|
||||||
|
}
|
||||||
|
// WriteString is typically called without newlines, so this
|
||||||
|
// codepath and its copy are rare. We copy to avoid
|
||||||
|
// duplicating all of Write's logic here.
|
||||||
|
return w.Write([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) Write(p []byte) (n int, err error) {
|
||||||
|
newlines := bytes.Count(p, newline)
|
||||||
|
if newlines == 0 {
|
||||||
|
if !w.compact && w.complete {
|
||||||
|
w.writeIndent()
|
||||||
|
}
|
||||||
|
n, err = w.w.Write(p)
|
||||||
|
w.complete = false
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
frags := bytes.SplitN(p, newline, newlines+1)
|
||||||
|
if w.compact {
|
||||||
|
for i, frag := range frags {
|
||||||
|
if i > 0 {
|
||||||
|
if err := w.w.WriteByte(' '); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
nn, err := w.w.Write(frag)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, frag := range frags {
|
||||||
|
if w.complete {
|
||||||
|
w.writeIndent()
|
||||||
|
}
|
||||||
|
nn, err := w.w.Write(frag)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if i+1 < len(frags) {
|
||||||
|
if err := w.w.WriteByte('\n'); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.complete = len(frags[len(frags)-1]) == 0
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) WriteByte(c byte) error {
|
||||||
|
if w.compact && c == '\n' {
|
||||||
|
c = ' '
|
||||||
|
}
|
||||||
|
if !w.compact && w.complete {
|
||||||
|
w.writeIndent()
|
||||||
|
}
|
||||||
|
err := w.w.WriteByte(c)
|
||||||
|
w.complete = c == '\n'
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) indent() { w.ind++ }
|
||||||
|
|
||||||
|
func (w *textWriter) unindent() {
|
||||||
|
if w.ind == 0 {
|
||||||
|
log.Printf("proto: textWriter unindented too far")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.ind--
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeName(w *textWriter, props *Properties) error {
|
||||||
|
if _, err := w.WriteString(props.OrigName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if props.Wire != "group" {
|
||||||
|
return w.WriteByte(':')
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
messageSetType = reflect.TypeOf((*MessageSet)(nil)).Elem()
|
||||||
|
)
|
||||||
|
|
||||||
|
// raw is the interface satisfied by RawMessage.
|
||||||
|
type raw interface {
|
||||||
|
Bytes() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeStruct(w *textWriter, sv reflect.Value) error {
|
||||||
|
if sv.Type() == messageSetType {
|
||||||
|
return writeMessageSet(w, sv.Addr().Interface().(*MessageSet))
|
||||||
|
}
|
||||||
|
|
||||||
|
st := sv.Type()
|
||||||
|
sprops := GetProperties(st)
|
||||||
|
for i := 0; i < sv.NumField(); i++ {
|
||||||
|
fv := sv.Field(i)
|
||||||
|
props := sprops.Prop[i]
|
||||||
|
name := st.Field(i).Name
|
||||||
|
|
||||||
|
if strings.HasPrefix(name, "XXX_") {
|
||||||
|
// There are two XXX_ fields:
|
||||||
|
// XXX_unrecognized []byte
|
||||||
|
// XXX_extensions map[int32]proto.Extension
|
||||||
|
// The first is handled here;
|
||||||
|
// the second is handled at the bottom of this function.
|
||||||
|
if name == "XXX_unrecognized" && !fv.IsNil() {
|
||||||
|
if err := writeUnknownStruct(w, fv.Interface().([]byte)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fv.Kind() == reflect.Ptr && fv.IsNil() {
|
||||||
|
// Field not filled in. This could be an optional field or
|
||||||
|
// a required field that wasn't filled in. Either way, there
|
||||||
|
// isn't anything we can show for it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fv.Kind() == reflect.Slice && fv.IsNil() {
|
||||||
|
// Repeated field that is empty, or a bytes field that is unused.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if props.Repeated && fv.Kind() == reflect.Slice {
|
||||||
|
// Repeated field.
|
||||||
|
for j := 0; j < fv.Len(); j++ {
|
||||||
|
if err := writeName(w, props); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v := fv.Index(j)
|
||||||
|
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||||
|
// A nil message in a repeated field is not valid,
|
||||||
|
// but we can handle that more gracefully than panicking.
|
||||||
|
if _, err := w.Write([]byte("<nil>\n")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := writeAny(w, v, props); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fv.Kind() == reflect.Map {
|
||||||
|
// Map fields are rendered as a repeated struct with key/value fields.
|
||||||
|
keys := fv.MapKeys() // TODO: should we sort these for deterministic output?
|
||||||
|
sort.Sort(mapKeys(keys))
|
||||||
|
for _, key := range keys {
|
||||||
|
val := fv.MapIndex(key)
|
||||||
|
if err := writeName(w, props); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// open struct
|
||||||
|
if err := w.WriteByte('<'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.indent()
|
||||||
|
// key
|
||||||
|
if _, err := w.WriteString("key:"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := writeAny(w, key, props.mkeyprop); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// value
|
||||||
|
if _, err := w.WriteString("value:"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := writeAny(w, val, props.mvalprop); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// close struct
|
||||||
|
w.unindent()
|
||||||
|
if err := w.WriteByte('>'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if props.proto3 && fv.Kind() == reflect.Slice && fv.Len() == 0 {
|
||||||
|
// empty bytes field
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fv.Kind() != reflect.Ptr && fv.Kind() != reflect.Slice {
|
||||||
|
// proto3 non-repeated scalar field; skip if zero value
|
||||||
|
switch fv.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
if !fv.Bool() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case reflect.Int32, reflect.Int64:
|
||||||
|
if fv.Int() == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case reflect.Uint32, reflect.Uint64:
|
||||||
|
if fv.Uint() == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
if fv.Float() == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
if fv.String() == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writeName(w, props); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b, ok := fv.Interface().(raw); ok {
|
||||||
|
if err := writeRaw(w, b.Bytes()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enums have a String method, so writeAny will work fine.
|
||||||
|
if err := writeAny(w, fv, props); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extensions (the XXX_extensions field).
|
||||||
|
pv := sv.Addr()
|
||||||
|
if pv.Type().Implements(extendableProtoType) {
|
||||||
|
if err := writeExtensions(w, pv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeRaw writes an uninterpreted raw message.
|
||||||
|
func writeRaw(w *textWriter, b []byte) error {
|
||||||
|
if err := w.WriteByte('<'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.indent()
|
||||||
|
if err := writeUnknownStruct(w, b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.unindent()
|
||||||
|
if err := w.WriteByte('>'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeAny writes an arbitrary field.
|
||||||
|
func writeAny(w *textWriter, v reflect.Value, props *Properties) error {
|
||||||
|
v = reflect.Indirect(v)
|
||||||
|
|
||||||
|
// Floats have special cases.
|
||||||
|
if v.Kind() == reflect.Float32 || v.Kind() == reflect.Float64 {
|
||||||
|
x := v.Float()
|
||||||
|
var b []byte
|
||||||
|
switch {
|
||||||
|
case math.IsInf(x, 1):
|
||||||
|
b = posInf
|
||||||
|
case math.IsInf(x, -1):
|
||||||
|
b = negInf
|
||||||
|
case math.IsNaN(x):
|
||||||
|
b = nan
|
||||||
|
}
|
||||||
|
if b != nil {
|
||||||
|
_, err := w.Write(b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Other values are handled below.
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't attempt to serialise every possible value type; only those
|
||||||
|
// that can occur in protocol buffers.
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
// Should only be a []byte; repeated fields are handled in writeStruct.
|
||||||
|
if err := writeString(w, string(v.Interface().([]byte))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
if err := writeString(w, v.String()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
// Required/optional group/message.
|
||||||
|
var bra, ket byte = '<', '>'
|
||||||
|
if props != nil && props.Wire == "group" {
|
||||||
|
bra, ket = '{', '}'
|
||||||
|
}
|
||||||
|
if err := w.WriteByte(bra); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.indent()
|
||||||
|
if tm, ok := v.Interface().(encoding.TextMarshaler); ok {
|
||||||
|
text, err := tm.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = w.Write(text); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err := writeStruct(w, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.unindent()
|
||||||
|
if err := w.WriteByte(ket); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
_, err := fmt.Fprint(w, v.Interface())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// equivalent to C's isprint.
|
||||||
|
func isprint(c byte) bool {
|
||||||
|
return c >= 0x20 && c < 0x7f
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeString writes a string in the protocol buffer text format.
|
||||||
|
// It is similar to strconv.Quote except we don't use Go escape sequences,
|
||||||
|
// we treat the string as a byte sequence, and we use octal escapes.
|
||||||
|
// These differences are to maintain interoperability with the other
|
||||||
|
// languages' implementations of the text format.
|
||||||
|
func writeString(w *textWriter, s string) error {
|
||||||
|
// use WriteByte here to get any needed indent
|
||||||
|
if err := w.WriteByte('"'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Loop over the bytes, not the runes.
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
var err error
|
||||||
|
// Divergence from C++: we don't escape apostrophes.
|
||||||
|
// There's no need to escape them, and the C++ parser
|
||||||
|
// copes with a naked apostrophe.
|
||||||
|
switch c := s[i]; c {
|
||||||
|
case '\n':
|
||||||
|
_, err = w.w.Write(backslashN)
|
||||||
|
case '\r':
|
||||||
|
_, err = w.w.Write(backslashR)
|
||||||
|
case '\t':
|
||||||
|
_, err = w.w.Write(backslashT)
|
||||||
|
case '"':
|
||||||
|
_, err = w.w.Write(backslashDQ)
|
||||||
|
case '\\':
|
||||||
|
_, err = w.w.Write(backslashBS)
|
||||||
|
default:
|
||||||
|
if isprint(c) {
|
||||||
|
err = w.w.WriteByte(c)
|
||||||
|
} else {
|
||||||
|
_, err = fmt.Fprintf(w.w, "\\%03o", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return w.WriteByte('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeMessageSet(w *textWriter, ms *MessageSet) error {
|
||||||
|
for _, item := range ms.Item {
|
||||||
|
id := *item.TypeId
|
||||||
|
if msd, ok := messageSetMap[id]; ok {
|
||||||
|
// Known message set type.
|
||||||
|
if _, err := fmt.Fprintf(w, "[%s]: <\n", msd.name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.indent()
|
||||||
|
|
||||||
|
pb := reflect.New(msd.t.Elem())
|
||||||
|
if err := Unmarshal(item.Message, pb.Interface().(Message)); err != nil {
|
||||||
|
if _, err := fmt.Fprintf(w, "/* bad message: %v */\n", err); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := writeStruct(w, pb.Elem()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unknown type.
|
||||||
|
if _, err := fmt.Fprintf(w, "[%d]: <\n", id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.indent()
|
||||||
|
if err := writeUnknownStruct(w, item.Message); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.unindent()
|
||||||
|
if _, err := w.Write(gtNewline); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeUnknownStruct(w *textWriter, data []byte) (err error) {
|
||||||
|
if !w.compact {
|
||||||
|
if _, err := fmt.Fprintf(w, "/* %d unknown bytes */\n", len(data)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b := NewBuffer(data)
|
||||||
|
for b.index < len(b.buf) {
|
||||||
|
x, err := b.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
_, err := fmt.Fprintf(w, "/* %v */\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wire, tag := x&7, x>>3
|
||||||
|
if wire == WireEndGroup {
|
||||||
|
w.unindent()
|
||||||
|
if _, err := w.Write(endBraceNewline); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprint(w, tag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if wire != WireStartGroup {
|
||||||
|
if err := w.WriteByte(':'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !w.compact || wire == WireStartGroup {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch wire {
|
||||||
|
case WireBytes:
|
||||||
|
buf, e := b.DecodeRawBytes(false)
|
||||||
|
if e == nil {
|
||||||
|
_, err = fmt.Fprintf(w, "%q", buf)
|
||||||
|
} else {
|
||||||
|
_, err = fmt.Fprintf(w, "/* %v */", e)
|
||||||
|
}
|
||||||
|
case WireFixed32:
|
||||||
|
x, err = b.DecodeFixed32()
|
||||||
|
err = writeUnknownInt(w, x, err)
|
||||||
|
case WireFixed64:
|
||||||
|
x, err = b.DecodeFixed64()
|
||||||
|
err = writeUnknownInt(w, x, err)
|
||||||
|
case WireStartGroup:
|
||||||
|
err = w.WriteByte('{')
|
||||||
|
w.indent()
|
||||||
|
case WireVarint:
|
||||||
|
x, err = b.DecodeVarint()
|
||||||
|
err = writeUnknownInt(w, x, err)
|
||||||
|
default:
|
||||||
|
_, err = fmt.Fprintf(w, "/* unknown wire type %d */", wire)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeUnknownInt(w *textWriter, x uint64, err error) error {
|
||||||
|
if err == nil {
|
||||||
|
_, err = fmt.Fprint(w, x)
|
||||||
|
} else {
|
||||||
|
_, err = fmt.Fprintf(w, "/* %v */", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type int32Slice []int32
|
||||||
|
|
||||||
|
func (s int32Slice) Len() int { return len(s) }
|
||||||
|
func (s int32Slice) Less(i, j int) bool { return s[i] < s[j] }
|
||||||
|
func (s int32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
|
// writeExtensions writes all the extensions in pv.
|
||||||
|
// pv is assumed to be a pointer to a protocol message struct that is extendable.
|
||||||
|
func writeExtensions(w *textWriter, pv reflect.Value) error {
|
||||||
|
emap := extensionMaps[pv.Type().Elem()]
|
||||||
|
ep := pv.Interface().(extendableProto)
|
||||||
|
|
||||||
|
// Order the extensions by ID.
|
||||||
|
// This isn't strictly necessary, but it will give us
|
||||||
|
// canonical output, which will also make testing easier.
|
||||||
|
m := ep.ExtensionMap()
|
||||||
|
ids := make([]int32, 0, len(m))
|
||||||
|
for id := range m {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
sort.Sort(int32Slice(ids))
|
||||||
|
|
||||||
|
for _, extNum := range ids {
|
||||||
|
ext := m[extNum]
|
||||||
|
var desc *ExtensionDesc
|
||||||
|
if emap != nil {
|
||||||
|
desc = emap[extNum]
|
||||||
|
}
|
||||||
|
if desc == nil {
|
||||||
|
// Unknown extension.
|
||||||
|
if err := writeUnknownStruct(w, ext.enc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pb, err := GetExtension(ep, desc)
|
||||||
|
if err != nil {
|
||||||
|
if _, err := fmt.Fprintln(os.Stderr, "proto: failed getting extension: ", err); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repeated extensions will appear as a slice.
|
||||||
|
if !desc.repeated() {
|
||||||
|
if err := writeExtension(w, desc.Name, pb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v := reflect.ValueOf(pb)
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
if err := writeExtension(w, desc.Name, v.Index(i).Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeExtension(w *textWriter, name string, pb interface{}) error {
|
||||||
|
if _, err := fmt.Fprintf(w, "[%s]:", name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := writeAny(w, reflect.ValueOf(pb), nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) writeIndent() {
|
||||||
|
if !w.complete {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remain := w.ind * 2
|
||||||
|
for remain > 0 {
|
||||||
|
n := remain
|
||||||
|
if n > len(spaces) {
|
||||||
|
n = len(spaces)
|
||||||
|
}
|
||||||
|
w.w.Write(spaces[:n])
|
||||||
|
remain -= n
|
||||||
|
}
|
||||||
|
w.complete = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalText(w io.Writer, pb Message, compact bool) error {
|
||||||
|
val := reflect.ValueOf(pb)
|
||||||
|
if pb == nil || val.IsNil() {
|
||||||
|
w.Write([]byte("<nil>"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var bw *bufio.Writer
|
||||||
|
ww, ok := w.(writer)
|
||||||
|
if !ok {
|
||||||
|
bw = bufio.NewWriter(w)
|
||||||
|
ww = bw
|
||||||
|
}
|
||||||
|
aw := &textWriter{
|
||||||
|
w: ww,
|
||||||
|
complete: true,
|
||||||
|
compact: compact,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tm, ok := pb.(encoding.TextMarshaler); ok {
|
||||||
|
text, err := tm.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = aw.Write(text); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bw != nil {
|
||||||
|
return bw.Flush()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Dereference the received pointer so we don't have outer < and >.
|
||||||
|
v := reflect.Indirect(val)
|
||||||
|
if err := writeStruct(aw, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bw != nil {
|
||||||
|
return bw.Flush()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText writes a given protocol buffer in text format.
|
||||||
|
// The only errors returned are from w.
|
||||||
|
func MarshalText(w io.Writer, pb Message) error {
|
||||||
|
return marshalText(w, pb, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalTextString is the same as MarshalText, but returns the string directly.
|
||||||
|
func MarshalTextString(pb Message) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
marshalText(&buf, pb, false)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompactText writes a given protocol buffer in compact text format (one line).
|
||||||
|
func CompactText(w io.Writer, pb Message) error { return marshalText(w, pb, true) }
|
||||||
|
|
||||||
|
// CompactTextString is the same as CompactText, but returns the string directly.
|
||||||
|
func CompactTextString(pb Message) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
marshalText(&buf, pb, true)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
757
Godeps/_workspace/src/github.com/golang/protobuf/proto/text_parser.go
generated
vendored
Normal file
757
Godeps/_workspace/src/github.com/golang/protobuf/proto/text_parser.go
generated
vendored
Normal file
@@ -0,0 +1,757 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
// Functions for parsing the Text protocol buffer format.
|
||||||
|
// TODO: message sets.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ParseError struct {
|
||||||
|
Message string
|
||||||
|
Line int // 1-based line number
|
||||||
|
Offset int // 0-based byte offset from start of input
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseError) Error() string {
|
||||||
|
if p.Line == 1 {
|
||||||
|
// show offset only for first line
|
||||||
|
return fmt.Sprintf("line 1.%d: %v", p.Offset, p.Message)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("line %d: %v", p.Line, p.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
type token struct {
|
||||||
|
value string
|
||||||
|
err *ParseError
|
||||||
|
line int // line number
|
||||||
|
offset int // byte number from start of input, not start of line
|
||||||
|
unquoted string // the unquoted version of value, if it was a quoted string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *token) String() string {
|
||||||
|
if t.err == nil {
|
||||||
|
return fmt.Sprintf("%q (line=%d, offset=%d)", t.value, t.line, t.offset)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("parse error: %v", t.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type textParser struct {
|
||||||
|
s string // remaining input
|
||||||
|
done bool // whether the parsing is finished (success or error)
|
||||||
|
backed bool // whether back() was called
|
||||||
|
offset, line int
|
||||||
|
cur token
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTextParser(s string) *textParser {
|
||||||
|
p := new(textParser)
|
||||||
|
p.s = s
|
||||||
|
p.line = 1
|
||||||
|
p.cur.line = 1
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) errorf(format string, a ...interface{}) *ParseError {
|
||||||
|
pe := &ParseError{fmt.Sprintf(format, a...), p.cur.line, p.cur.offset}
|
||||||
|
p.cur.err = pe
|
||||||
|
p.done = true
|
||||||
|
return pe
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numbers and identifiers are matched by [-+._A-Za-z0-9]
|
||||||
|
func isIdentOrNumberChar(c byte) bool {
|
||||||
|
switch {
|
||||||
|
case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z':
|
||||||
|
return true
|
||||||
|
case '0' <= c && c <= '9':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch c {
|
||||||
|
case '-', '+', '.', '_':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWhitespace(c byte) bool {
|
||||||
|
switch c {
|
||||||
|
case ' ', '\t', '\n', '\r':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) skipWhitespace() {
|
||||||
|
i := 0
|
||||||
|
for i < len(p.s) && (isWhitespace(p.s[i]) || p.s[i] == '#') {
|
||||||
|
if p.s[i] == '#' {
|
||||||
|
// comment; skip to end of line or input
|
||||||
|
for i < len(p.s) && p.s[i] != '\n' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i == len(p.s) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.s[i] == '\n' {
|
||||||
|
p.line++
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
p.offset += i
|
||||||
|
p.s = p.s[i:len(p.s)]
|
||||||
|
if len(p.s) == 0 {
|
||||||
|
p.done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) advance() {
|
||||||
|
// Skip whitespace
|
||||||
|
p.skipWhitespace()
|
||||||
|
if p.done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start of non-whitespace
|
||||||
|
p.cur.err = nil
|
||||||
|
p.cur.offset, p.cur.line = p.offset, p.line
|
||||||
|
p.cur.unquoted = ""
|
||||||
|
switch p.s[0] {
|
||||||
|
case '<', '>', '{', '}', ':', '[', ']', ';', ',':
|
||||||
|
// Single symbol
|
||||||
|
p.cur.value, p.s = p.s[0:1], p.s[1:len(p.s)]
|
||||||
|
case '"', '\'':
|
||||||
|
// Quoted string
|
||||||
|
i := 1
|
||||||
|
for i < len(p.s) && p.s[i] != p.s[0] && p.s[i] != '\n' {
|
||||||
|
if p.s[i] == '\\' && i+1 < len(p.s) {
|
||||||
|
// skip escaped char
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i >= len(p.s) || p.s[i] != p.s[0] {
|
||||||
|
p.errorf("unmatched quote")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unq, err := unquoteC(p.s[1:i], rune(p.s[0]))
|
||||||
|
if err != nil {
|
||||||
|
p.errorf("invalid quoted string %v", p.s[0:i+1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.cur.value, p.s = p.s[0:i+1], p.s[i+1:len(p.s)]
|
||||||
|
p.cur.unquoted = unq
|
||||||
|
default:
|
||||||
|
i := 0
|
||||||
|
for i < len(p.s) && isIdentOrNumberChar(p.s[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
p.errorf("unexpected byte %#x", p.s[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.cur.value, p.s = p.s[0:i], p.s[i:len(p.s)]
|
||||||
|
}
|
||||||
|
p.offset += len(p.cur.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errBadUTF8 = errors.New("proto: bad UTF-8")
|
||||||
|
errBadHex = errors.New("proto: bad hexadecimal")
|
||||||
|
)
|
||||||
|
|
||||||
|
func unquoteC(s string, quote rune) (string, error) {
|
||||||
|
// This is based on C++'s tokenizer.cc.
|
||||||
|
// Despite its name, this is *not* parsing C syntax.
|
||||||
|
// For instance, "\0" is an invalid quoted string.
|
||||||
|
|
||||||
|
// Avoid allocation in trivial cases.
|
||||||
|
simple := true
|
||||||
|
for _, r := range s {
|
||||||
|
if r == '\\' || r == quote {
|
||||||
|
simple = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if simple {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 0, 3*len(s)/2)
|
||||||
|
for len(s) > 0 {
|
||||||
|
r, n := utf8.DecodeRuneInString(s)
|
||||||
|
if r == utf8.RuneError && n == 1 {
|
||||||
|
return "", errBadUTF8
|
||||||
|
}
|
||||||
|
s = s[n:]
|
||||||
|
if r != '\\' {
|
||||||
|
if r < utf8.RuneSelf {
|
||||||
|
buf = append(buf, byte(r))
|
||||||
|
} else {
|
||||||
|
buf = append(buf, string(r)...)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, tail, err := unescape(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buf = append(buf, ch...)
|
||||||
|
s = tail
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unescape(s string) (ch string, tail string, err error) {
|
||||||
|
r, n := utf8.DecodeRuneInString(s)
|
||||||
|
if r == utf8.RuneError && n == 1 {
|
||||||
|
return "", "", errBadUTF8
|
||||||
|
}
|
||||||
|
s = s[n:]
|
||||||
|
switch r {
|
||||||
|
case 'a':
|
||||||
|
return "\a", s, nil
|
||||||
|
case 'b':
|
||||||
|
return "\b", s, nil
|
||||||
|
case 'f':
|
||||||
|
return "\f", s, nil
|
||||||
|
case 'n':
|
||||||
|
return "\n", s, nil
|
||||||
|
case 'r':
|
||||||
|
return "\r", s, nil
|
||||||
|
case 't':
|
||||||
|
return "\t", s, nil
|
||||||
|
case 'v':
|
||||||
|
return "\v", s, nil
|
||||||
|
case '?':
|
||||||
|
return "?", s, nil // trigraph workaround
|
||||||
|
case '\'', '"', '\\':
|
||||||
|
return string(r), s, nil
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', 'x', 'X':
|
||||||
|
if len(s) < 2 {
|
||||||
|
return "", "", fmt.Errorf(`\%c requires 2 following digits`, r)
|
||||||
|
}
|
||||||
|
base := 8
|
||||||
|
ss := s[:2]
|
||||||
|
s = s[2:]
|
||||||
|
if r == 'x' || r == 'X' {
|
||||||
|
base = 16
|
||||||
|
} else {
|
||||||
|
ss = string(r) + ss
|
||||||
|
}
|
||||||
|
i, err := strconv.ParseUint(ss, base, 8)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return string([]byte{byte(i)}), s, nil
|
||||||
|
case 'u', 'U':
|
||||||
|
n := 4
|
||||||
|
if r == 'U' {
|
||||||
|
n = 8
|
||||||
|
}
|
||||||
|
if len(s) < n {
|
||||||
|
return "", "", fmt.Errorf(`\%c requires %d digits`, r, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := make([]byte, n/2)
|
||||||
|
for i := 0; i < n; i += 2 {
|
||||||
|
a, ok1 := unhex(s[i])
|
||||||
|
b, ok2 := unhex(s[i+1])
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
return "", "", errBadHex
|
||||||
|
}
|
||||||
|
bs[i/2] = a<<4 | b
|
||||||
|
}
|
||||||
|
s = s[n:]
|
||||||
|
return string(bs), s, nil
|
||||||
|
}
|
||||||
|
return "", "", fmt.Errorf(`unknown escape \%c`, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from src/pkg/strconv/quote.go.
|
||||||
|
func unhex(b byte) (v byte, ok bool) {
|
||||||
|
switch {
|
||||||
|
case '0' <= b && b <= '9':
|
||||||
|
return b - '0', true
|
||||||
|
case 'a' <= b && b <= 'f':
|
||||||
|
return b - 'a' + 10, true
|
||||||
|
case 'A' <= b && b <= 'F':
|
||||||
|
return b - 'A' + 10, true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back off the parser by one token. Can only be done between calls to next().
|
||||||
|
// It makes the next advance() a no-op.
|
||||||
|
func (p *textParser) back() { p.backed = true }
|
||||||
|
|
||||||
|
// Advances the parser and returns the new current token.
|
||||||
|
func (p *textParser) next() *token {
|
||||||
|
if p.backed || p.done {
|
||||||
|
p.backed = false
|
||||||
|
return &p.cur
|
||||||
|
}
|
||||||
|
p.advance()
|
||||||
|
if p.done {
|
||||||
|
p.cur.value = ""
|
||||||
|
} else if len(p.cur.value) > 0 && p.cur.value[0] == '"' {
|
||||||
|
// Look for multiple quoted strings separated by whitespace,
|
||||||
|
// and concatenate them.
|
||||||
|
cat := p.cur
|
||||||
|
for {
|
||||||
|
p.skipWhitespace()
|
||||||
|
if p.done || p.s[0] != '"' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
p.advance()
|
||||||
|
if p.cur.err != nil {
|
||||||
|
return &p.cur
|
||||||
|
}
|
||||||
|
cat.value += " " + p.cur.value
|
||||||
|
cat.unquoted += p.cur.unquoted
|
||||||
|
}
|
||||||
|
p.done = false // parser may have seen EOF, but we want to return cat
|
||||||
|
p.cur = cat
|
||||||
|
}
|
||||||
|
return &p.cur
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) consumeToken(s string) error {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value != s {
|
||||||
|
p.back()
|
||||||
|
return p.errorf("expected %q, found %q", s, tok.value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a RequiredNotSetError indicating which required field was not set.
|
||||||
|
func (p *textParser) missingRequiredFieldError(sv reflect.Value) *RequiredNotSetError {
|
||||||
|
st := sv.Type()
|
||||||
|
sprops := GetProperties(st)
|
||||||
|
for i := 0; i < st.NumField(); i++ {
|
||||||
|
if !isNil(sv.Field(i)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
props := sprops.Prop[i]
|
||||||
|
if props.Required {
|
||||||
|
return &RequiredNotSetError{fmt.Sprintf("%v.%v", st, props.OrigName)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &RequiredNotSetError{fmt.Sprintf("%v.<unknown field name>", st)} // should not happen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the index in the struct for the named field, as well as the parsed tag properties.
|
||||||
|
func structFieldByName(st reflect.Type, name string) (int, *Properties, bool) {
|
||||||
|
sprops := GetProperties(st)
|
||||||
|
i, ok := sprops.decoderOrigNames[name]
|
||||||
|
if ok {
|
||||||
|
return i, sprops.Prop[i], true
|
||||||
|
}
|
||||||
|
return -1, nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume a ':' from the input stream (if the next token is a colon),
|
||||||
|
// returning an error if a colon is needed but not present.
|
||||||
|
func (p *textParser) checkForColon(props *Properties, typ reflect.Type) *ParseError {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value != ":" {
|
||||||
|
// Colon is optional when the field is a group or message.
|
||||||
|
needColon := true
|
||||||
|
switch props.Wire {
|
||||||
|
case "group":
|
||||||
|
needColon = false
|
||||||
|
case "bytes":
|
||||||
|
// A "bytes" field is either a message, a string, or a repeated field;
|
||||||
|
// those three become *T, *string and []T respectively, so we can check for
|
||||||
|
// this field being a pointer to a non-string.
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
// *T or *string
|
||||||
|
if typ.Elem().Kind() == reflect.String {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if typ.Kind() == reflect.Slice {
|
||||||
|
// []T or []*T
|
||||||
|
if typ.Elem().Kind() != reflect.Ptr {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if typ.Kind() == reflect.String {
|
||||||
|
// The proto3 exception is for a string field,
|
||||||
|
// which requires a colon.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
needColon = false
|
||||||
|
}
|
||||||
|
if needColon {
|
||||||
|
return p.errorf("expected ':', found %q", tok.value)
|
||||||
|
}
|
||||||
|
p.back()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) readStruct(sv reflect.Value, terminator string) error {
|
||||||
|
st := sv.Type()
|
||||||
|
reqCount := GetProperties(st).reqCount
|
||||||
|
var reqFieldErr error
|
||||||
|
fieldSet := make(map[string]bool)
|
||||||
|
// A struct is a sequence of "name: value", terminated by one of
|
||||||
|
// '>' or '}', or the end of the input. A name may also be
|
||||||
|
// "[extension]".
|
||||||
|
for {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value == terminator {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if tok.value == "[" {
|
||||||
|
// Looks like an extension.
|
||||||
|
//
|
||||||
|
// TODO: Check whether we need to handle
|
||||||
|
// namespace rooted names (e.g. ".something.Foo").
|
||||||
|
tok = p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
var desc *ExtensionDesc
|
||||||
|
// This could be faster, but it's functional.
|
||||||
|
// TODO: Do something smarter than a linear scan.
|
||||||
|
for _, d := range RegisteredExtensions(reflect.New(st).Interface().(Message)) {
|
||||||
|
if d.Name == tok.value {
|
||||||
|
desc = d
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if desc == nil {
|
||||||
|
return p.errorf("unrecognized extension %q", tok.value)
|
||||||
|
}
|
||||||
|
// Check the extension terminator.
|
||||||
|
tok = p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value != "]" {
|
||||||
|
return p.errorf("unrecognized extension terminator %q", tok.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
props := &Properties{}
|
||||||
|
props.Parse(desc.Tag)
|
||||||
|
|
||||||
|
typ := reflect.TypeOf(desc.ExtensionType)
|
||||||
|
if err := p.checkForColon(props, typ); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rep := desc.repeated()
|
||||||
|
|
||||||
|
// Read the extension structure, and set it in
|
||||||
|
// the value we're constructing.
|
||||||
|
var ext reflect.Value
|
||||||
|
if !rep {
|
||||||
|
ext = reflect.New(typ).Elem()
|
||||||
|
} else {
|
||||||
|
ext = reflect.New(typ.Elem()).Elem()
|
||||||
|
}
|
||||||
|
if err := p.readAny(ext, props); err != nil {
|
||||||
|
if _, ok := err.(*RequiredNotSetError); !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reqFieldErr = err
|
||||||
|
}
|
||||||
|
ep := sv.Addr().Interface().(extendableProto)
|
||||||
|
if !rep {
|
||||||
|
SetExtension(ep, desc, ext.Interface())
|
||||||
|
} else {
|
||||||
|
old, err := GetExtension(ep, desc)
|
||||||
|
var sl reflect.Value
|
||||||
|
if err == nil {
|
||||||
|
sl = reflect.ValueOf(old) // existing slice
|
||||||
|
} else {
|
||||||
|
sl = reflect.MakeSlice(typ, 0, 1)
|
||||||
|
}
|
||||||
|
sl = reflect.Append(sl, ext)
|
||||||
|
SetExtension(ep, desc, sl.Interface())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is a normal, non-extension field.
|
||||||
|
name := tok.value
|
||||||
|
fi, props, ok := structFieldByName(st, name)
|
||||||
|
if !ok {
|
||||||
|
return p.errorf("unknown field name %q in %v", name, st)
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := sv.Field(fi)
|
||||||
|
|
||||||
|
if dst.Kind() == reflect.Map {
|
||||||
|
// Consume any colon.
|
||||||
|
if err := p.checkForColon(props, dst.Type()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the map if it doesn't already exist.
|
||||||
|
if dst.IsNil() {
|
||||||
|
dst.Set(reflect.MakeMap(dst.Type()))
|
||||||
|
}
|
||||||
|
key := reflect.New(dst.Type().Key()).Elem()
|
||||||
|
val := reflect.New(dst.Type().Elem()).Elem()
|
||||||
|
|
||||||
|
// The map entry should be this sequence of tokens:
|
||||||
|
// < key : KEY value : VALUE >
|
||||||
|
// Technically the "key" and "value" could come in any order,
|
||||||
|
// but in practice they won't.
|
||||||
|
|
||||||
|
tok := p.next()
|
||||||
|
var terminator string
|
||||||
|
switch tok.value {
|
||||||
|
case "<":
|
||||||
|
terminator = ">"
|
||||||
|
case "{":
|
||||||
|
terminator = "}"
|
||||||
|
default:
|
||||||
|
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||||
|
}
|
||||||
|
if err := p.consumeToken("key"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.consumeToken(":"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.readAny(key, props.mkeyprop); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.consumeToken("value"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.consumeToken(":"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.readAny(val, props.mvalprop); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.consumeToken(terminator); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.SetMapIndex(key, val)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that it's not already set if it's not a repeated field.
|
||||||
|
if !props.Repeated && fieldSet[name] {
|
||||||
|
return p.errorf("non-repeated field %q was repeated", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.checkForColon(props, st.Field(fi).Type); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse into the field.
|
||||||
|
fieldSet[name] = true
|
||||||
|
if err := p.readAny(dst, props); err != nil {
|
||||||
|
if _, ok := err.(*RequiredNotSetError); !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reqFieldErr = err
|
||||||
|
} else if props.Required {
|
||||||
|
reqCount--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For backward compatibility, permit a semicolon or comma after a field.
|
||||||
|
tok = p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value != ";" && tok.value != "," {
|
||||||
|
p.back()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqCount > 0 {
|
||||||
|
return p.missingRequiredFieldError(sv)
|
||||||
|
}
|
||||||
|
return reqFieldErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) readAny(v reflect.Value, props *Properties) error {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value == "" {
|
||||||
|
return p.errorf("unexpected EOF")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fv := v; fv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
at := v.Type()
|
||||||
|
if at.Elem().Kind() == reflect.Uint8 {
|
||||||
|
// Special case for []byte
|
||||||
|
if tok.value[0] != '"' && tok.value[0] != '\'' {
|
||||||
|
// Deliberately written out here, as the error after
|
||||||
|
// this switch statement would write "invalid []byte: ...",
|
||||||
|
// which is not as user-friendly.
|
||||||
|
return p.errorf("invalid string: %v", tok.value)
|
||||||
|
}
|
||||||
|
bytes := []byte(tok.unquoted)
|
||||||
|
fv.Set(reflect.ValueOf(bytes))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Repeated field. May already exist.
|
||||||
|
flen := fv.Len()
|
||||||
|
if flen == fv.Cap() {
|
||||||
|
nav := reflect.MakeSlice(at, flen, 2*flen+1)
|
||||||
|
reflect.Copy(nav, fv)
|
||||||
|
fv.Set(nav)
|
||||||
|
}
|
||||||
|
fv.SetLen(flen + 1)
|
||||||
|
|
||||||
|
// Read one.
|
||||||
|
p.back()
|
||||||
|
return p.readAny(fv.Index(flen), props)
|
||||||
|
case reflect.Bool:
|
||||||
|
// Either "true", "false", 1 or 0.
|
||||||
|
switch tok.value {
|
||||||
|
case "true", "1":
|
||||||
|
fv.SetBool(true)
|
||||||
|
return nil
|
||||||
|
case "false", "0":
|
||||||
|
fv.SetBool(false)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
v := tok.value
|
||||||
|
// Ignore 'f' for compatibility with output generated by C++, but don't
|
||||||
|
// remove 'f' when the value is "-inf" or "inf".
|
||||||
|
if strings.HasSuffix(v, "f") && tok.value != "-inf" && tok.value != "inf" {
|
||||||
|
v = v[:len(v)-1]
|
||||||
|
}
|
||||||
|
if f, err := strconv.ParseFloat(v, fv.Type().Bits()); err == nil {
|
||||||
|
fv.SetFloat(f)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case reflect.Int32:
|
||||||
|
if x, err := strconv.ParseInt(tok.value, 0, 32); err == nil {
|
||||||
|
fv.SetInt(x)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(props.Enum) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m, ok := enumValueMaps[props.Enum]
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
x, ok := m[tok.value]
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fv.SetInt(int64(x))
|
||||||
|
return nil
|
||||||
|
case reflect.Int64:
|
||||||
|
if x, err := strconv.ParseInt(tok.value, 0, 64); err == nil {
|
||||||
|
fv.SetInt(x)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// A basic field (indirected through pointer), or a repeated message/group
|
||||||
|
p.back()
|
||||||
|
fv.Set(reflect.New(fv.Type().Elem()))
|
||||||
|
return p.readAny(fv.Elem(), props)
|
||||||
|
case reflect.String:
|
||||||
|
if tok.value[0] == '"' || tok.value[0] == '\'' {
|
||||||
|
fv.SetString(tok.unquoted)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
var terminator string
|
||||||
|
switch tok.value {
|
||||||
|
case "{":
|
||||||
|
terminator = "}"
|
||||||
|
case "<":
|
||||||
|
terminator = ">"
|
||||||
|
default:
|
||||||
|
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||||
|
}
|
||||||
|
// TODO: Handle nested messages which implement encoding.TextUnmarshaler.
|
||||||
|
return p.readStruct(fv, terminator)
|
||||||
|
case reflect.Uint32:
|
||||||
|
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
||||||
|
fv.SetUint(uint64(x))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case reflect.Uint64:
|
||||||
|
if x, err := strconv.ParseUint(tok.value, 0, 64); err == nil {
|
||||||
|
fv.SetUint(x)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.errorf("invalid %v: %v", v.Type(), tok.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText reads a protocol buffer in Text format. UnmarshalText resets pb
|
||||||
|
// before starting to unmarshal, so any existing data in pb is always removed.
|
||||||
|
// If a required field is not set and no other error occurs,
|
||||||
|
// UnmarshalText returns *RequiredNotSetError.
|
||||||
|
func UnmarshalText(s string, pb Message) error {
|
||||||
|
if um, ok := pb.(encoding.TextUnmarshaler); ok {
|
||||||
|
err := um.UnmarshalText([]byte(s))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pb.Reset()
|
||||||
|
v := reflect.ValueOf(pb)
|
||||||
|
if pe := newTextParser(s).readStruct(v.Elem(), ""); pe != nil {
|
||||||
|
return pe
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
509
Godeps/_workspace/src/github.com/golang/protobuf/proto/text_parser_test.go
generated
vendored
Normal file
509
Godeps/_workspace/src/github.com/golang/protobuf/proto/text_parser_test.go
generated
vendored
Normal file
@@ -0,0 +1,509 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
proto3pb "./proto3_proto"
|
||||||
|
. "./testdata"
|
||||||
|
. "github.com/golang/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UnmarshalTextTest struct {
|
||||||
|
in string
|
||||||
|
err string // if "", no error expected
|
||||||
|
out *MyMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildExtStructTest(text string) UnmarshalTextTest {
|
||||||
|
msg := &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
}
|
||||||
|
SetExtension(msg, E_Ext_More, &Ext{
|
||||||
|
Data: String("Hello, world!"),
|
||||||
|
})
|
||||||
|
return UnmarshalTextTest{in: text, out: msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildExtDataTest(text string) UnmarshalTextTest {
|
||||||
|
msg := &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
}
|
||||||
|
SetExtension(msg, E_Ext_Text, String("Hello, world!"))
|
||||||
|
SetExtension(msg, E_Ext_Number, Int32(1729))
|
||||||
|
return UnmarshalTextTest{in: text, out: msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildExtRepStringTest(text string) UnmarshalTextTest {
|
||||||
|
msg := &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
}
|
||||||
|
if err := SetExtension(msg, E_Greeting, []string{"bula", "hola"}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return UnmarshalTextTest{in: text, out: msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
var unMarshalTextTests = []UnmarshalTextTest{
|
||||||
|
// Basic
|
||||||
|
{
|
||||||
|
in: " count:42\n name:\"Dave\" ",
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
Name: String("Dave"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Empty quoted string
|
||||||
|
{
|
||||||
|
in: `count:42 name:""`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
Name: String(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quoted string concatenation
|
||||||
|
{
|
||||||
|
in: `count:42 name: "My name is "` + "\n" + `"elsewhere"`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
Name: String("My name is elsewhere"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quoted string with escaped apostrophe
|
||||||
|
{
|
||||||
|
in: `count:42 name: "HOLIDAY - New Year\'s Day"`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
Name: String("HOLIDAY - New Year's Day"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quoted string with single quote
|
||||||
|
{
|
||||||
|
in: `count:42 name: 'Roger "The Ramster" Ramjet'`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
Name: String(`Roger "The Ramster" Ramjet`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quoted string with all the accepted special characters from the C++ test
|
||||||
|
{
|
||||||
|
in: `count:42 name: ` + "\"\\\"A string with \\' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"",
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
Name: String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quoted string with quoted backslash
|
||||||
|
{
|
||||||
|
in: `count:42 name: "\\'xyz"`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
Name: String(`\'xyz`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quoted string with UTF-8 bytes.
|
||||||
|
{
|
||||||
|
in: "count:42 name: '\303\277\302\201\xAB'",
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
Name: String("\303\277\302\201\xAB"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Bad quoted string
|
||||||
|
{
|
||||||
|
in: `inner: < host: "\0" >` + "\n",
|
||||||
|
err: `line 1.15: invalid quoted string "\0"`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Number too large for int64
|
||||||
|
{
|
||||||
|
in: "count: 1 others { key: 123456789012345678901 }",
|
||||||
|
err: "line 1.23: invalid int64: 123456789012345678901",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Number too large for int32
|
||||||
|
{
|
||||||
|
in: "count: 1234567890123",
|
||||||
|
err: "line 1.7: invalid int32: 1234567890123",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Number in hexadecimal
|
||||||
|
{
|
||||||
|
in: "count: 0x2beef",
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(0x2beef),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Number in octal
|
||||||
|
{
|
||||||
|
in: "count: 024601",
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(024601),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Floating point number with "f" suffix
|
||||||
|
{
|
||||||
|
in: "count: 4 others:< weight: 17.0f >",
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(4),
|
||||||
|
Others: []*OtherMessage{
|
||||||
|
{
|
||||||
|
Weight: Float32(17),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Floating point positive infinity
|
||||||
|
{
|
||||||
|
in: "count: 4 bigfloat: inf",
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(4),
|
||||||
|
Bigfloat: Float64(math.Inf(1)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Floating point negative infinity
|
||||||
|
{
|
||||||
|
in: "count: 4 bigfloat: -inf",
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(4),
|
||||||
|
Bigfloat: Float64(math.Inf(-1)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Number too large for float32
|
||||||
|
{
|
||||||
|
in: "others:< weight: 12345678901234567890123456789012345678901234567890 >",
|
||||||
|
err: "line 1.17: invalid float32: 12345678901234567890123456789012345678901234567890",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Number posing as a quoted string
|
||||||
|
{
|
||||||
|
in: `inner: < host: 12 >` + "\n",
|
||||||
|
err: `line 1.15: invalid string: 12`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quoted string posing as int32
|
||||||
|
{
|
||||||
|
in: `count: "12"`,
|
||||||
|
err: `line 1.7: invalid int32: "12"`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quoted string posing a float32
|
||||||
|
{
|
||||||
|
in: `others:< weight: "17.4" >`,
|
||||||
|
err: `line 1.17: invalid float32: "17.4"`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Enum
|
||||||
|
{
|
||||||
|
in: `count:42 bikeshed: BLUE`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
Bikeshed: MyMessage_BLUE.Enum(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Repeated field
|
||||||
|
{
|
||||||
|
in: `count:42 pet: "horsey" pet:"bunny"`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
Pet: []string{"horsey", "bunny"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Repeated message with/without colon and <>/{}
|
||||||
|
{
|
||||||
|
in: `count:42 others:{} others{} others:<> others:{}`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
Others: []*OtherMessage{
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Missing colon for inner message
|
||||||
|
{
|
||||||
|
in: `count:42 inner < host: "cauchy.syd" >`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
Inner: &InnerMessage{
|
||||||
|
Host: String("cauchy.syd"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Missing colon for string field
|
||||||
|
{
|
||||||
|
in: `name "Dave"`,
|
||||||
|
err: `line 1.5: expected ':', found "\"Dave\""`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Missing colon for int32 field
|
||||||
|
{
|
||||||
|
in: `count 42`,
|
||||||
|
err: `line 1.6: expected ':', found "42"`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Missing required field
|
||||||
|
{
|
||||||
|
in: `name: "Pawel"`,
|
||||||
|
err: `proto: required field "testdata.MyMessage.count" not set`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Name: String("Pawel"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Repeated non-repeated field
|
||||||
|
{
|
||||||
|
in: `name: "Rob" name: "Russ"`,
|
||||||
|
err: `line 1.12: non-repeated field "name" was repeated`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Group
|
||||||
|
{
|
||||||
|
in: `count: 17 SomeGroup { group_field: 12 }`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(17),
|
||||||
|
Somegroup: &MyMessage_SomeGroup{
|
||||||
|
GroupField: Int32(12),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Semicolon between fields
|
||||||
|
{
|
||||||
|
in: `count:3;name:"Calvin"`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(3),
|
||||||
|
Name: String("Calvin"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Comma between fields
|
||||||
|
{
|
||||||
|
in: `count:4,name:"Ezekiel"`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(4),
|
||||||
|
Name: String("Ezekiel"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Extension
|
||||||
|
buildExtStructTest(`count: 42 [testdata.Ext.more]:<data:"Hello, world!" >`),
|
||||||
|
buildExtStructTest(`count: 42 [testdata.Ext.more] {data:"Hello, world!"}`),
|
||||||
|
buildExtDataTest(`count: 42 [testdata.Ext.text]:"Hello, world!" [testdata.Ext.number]:1729`),
|
||||||
|
buildExtRepStringTest(`count: 42 [testdata.greeting]:"bula" [testdata.greeting]:"hola"`),
|
||||||
|
|
||||||
|
// Big all-in-one
|
||||||
|
{
|
||||||
|
in: "count:42 # Meaning\n" +
|
||||||
|
`name:"Dave" ` +
|
||||||
|
`quote:"\"I didn't want to go.\"" ` +
|
||||||
|
`pet:"bunny" ` +
|
||||||
|
`pet:"kitty" ` +
|
||||||
|
`pet:"horsey" ` +
|
||||||
|
`inner:<` +
|
||||||
|
` host:"footrest.syd" ` +
|
||||||
|
` port:7001 ` +
|
||||||
|
` connected:true ` +
|
||||||
|
`> ` +
|
||||||
|
`others:<` +
|
||||||
|
` key:3735928559 ` +
|
||||||
|
` value:"\x01A\a\f" ` +
|
||||||
|
`> ` +
|
||||||
|
`others:<` +
|
||||||
|
" weight:58.9 # Atomic weight of Co\n" +
|
||||||
|
` inner:<` +
|
||||||
|
` host:"lesha.mtv" ` +
|
||||||
|
` port:8002 ` +
|
||||||
|
` >` +
|
||||||
|
`>`,
|
||||||
|
out: &MyMessage{
|
||||||
|
Count: Int32(42),
|
||||||
|
Name: String("Dave"),
|
||||||
|
Quote: String(`"I didn't want to go."`),
|
||||||
|
Pet: []string{"bunny", "kitty", "horsey"},
|
||||||
|
Inner: &InnerMessage{
|
||||||
|
Host: String("footrest.syd"),
|
||||||
|
Port: Int32(7001),
|
||||||
|
Connected: Bool(true),
|
||||||
|
},
|
||||||
|
Others: []*OtherMessage{
|
||||||
|
{
|
||||||
|
Key: Int64(3735928559),
|
||||||
|
Value: []byte{0x1, 'A', '\a', '\f'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Weight: Float32(58.9),
|
||||||
|
Inner: &InnerMessage{
|
||||||
|
Host: String("lesha.mtv"),
|
||||||
|
Port: Int32(8002),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalText(t *testing.T) {
|
||||||
|
for i, test := range unMarshalTextTests {
|
||||||
|
pb := new(MyMessage)
|
||||||
|
err := UnmarshalText(test.in, pb)
|
||||||
|
if test.err == "" {
|
||||||
|
// We don't expect failure.
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Test %d: Unexpected error: %v", i, err)
|
||||||
|
} else if !reflect.DeepEqual(pb, test.out) {
|
||||||
|
t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v",
|
||||||
|
i, pb, test.out)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We do expect failure.
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Test %d: Didn't get expected error: %v", i, test.err)
|
||||||
|
} else if err.Error() != test.err {
|
||||||
|
t.Errorf("Test %d: Incorrect error.\nHave: %v\nWant: %v",
|
||||||
|
i, err.Error(), test.err)
|
||||||
|
} else if _, ok := err.(*RequiredNotSetError); ok && test.out != nil && !reflect.DeepEqual(pb, test.out) {
|
||||||
|
t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v",
|
||||||
|
i, pb, test.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalTextCustomMessage(t *testing.T) {
|
||||||
|
msg := &textMessage{}
|
||||||
|
if err := UnmarshalText("custom", msg); err != nil {
|
||||||
|
t.Errorf("Unexpected error from custom unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
if UnmarshalText("not custom", msg) == nil {
|
||||||
|
t.Errorf("Didn't get expected error from custom unmarshal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regression test; this caused a panic.
|
||||||
|
func TestRepeatedEnum(t *testing.T) {
|
||||||
|
pb := new(RepeatedEnum)
|
||||||
|
if err := UnmarshalText("color: RED", pb); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
exp := &RepeatedEnum{
|
||||||
|
Color: []RepeatedEnum_Color{RepeatedEnum_RED},
|
||||||
|
}
|
||||||
|
if !Equal(pb, exp) {
|
||||||
|
t.Errorf("Incorrect populated \nHave: %v\nWant: %v", pb, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProto3TextParsing(t *testing.T) {
|
||||||
|
m := new(proto3pb.Message)
|
||||||
|
const in = `name: "Wallace" true_scotsman: true`
|
||||||
|
want := &proto3pb.Message{
|
||||||
|
Name: "Wallace",
|
||||||
|
TrueScotsman: true,
|
||||||
|
}
|
||||||
|
if err := UnmarshalText(in, m); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !Equal(m, want) {
|
||||||
|
t.Errorf("\n got %v\nwant %v", m, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapParsing(t *testing.T) {
|
||||||
|
m := new(MessageWithMap)
|
||||||
|
const in = `name_mapping:<key:1234 value:"Feist"> name_mapping:<key:1 value:"Beatles">` +
|
||||||
|
`msg_mapping:<key:-4 value:<f: 2.0>>` +
|
||||||
|
`byte_mapping:<key:true value:"so be it">`
|
||||||
|
want := &MessageWithMap{
|
||||||
|
NameMapping: map[int32]string{
|
||||||
|
1: "Beatles",
|
||||||
|
1234: "Feist",
|
||||||
|
},
|
||||||
|
MsgMapping: map[int64]*FloatingPoint{
|
||||||
|
-4: {F: Float64(2.0)},
|
||||||
|
},
|
||||||
|
ByteMapping: map[bool][]byte{
|
||||||
|
true: []byte("so be it"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := UnmarshalText(in, m); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !Equal(m, want) {
|
||||||
|
t.Errorf("\n got %v\nwant %v", m, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var benchInput string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
benchInput = "count: 4\n"
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
benchInput += "pet: \"fido\"\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check it is valid input.
|
||||||
|
pb := new(MyMessage)
|
||||||
|
err := UnmarshalText(benchInput, pb)
|
||||||
|
if err != nil {
|
||||||
|
panic("Bad benchmark input: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalText(b *testing.B) {
|
||||||
|
pb := new(MyMessage)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
UnmarshalText(benchInput, pb)
|
||||||
|
}
|
||||||
|
b.SetBytes(int64(len(benchInput)))
|
||||||
|
}
|
||||||
436
Godeps/_workspace/src/github.com/golang/protobuf/proto/text_test.go
generated
vendored
Normal file
436
Godeps/_workspace/src/github.com/golang/protobuf/proto/text_test.go
generated
vendored
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
package proto_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
proto3pb "./proto3_proto"
|
||||||
|
pb "./testdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// textMessage implements the methods that allow it to marshal and unmarshal
|
||||||
|
// itself as text.
|
||||||
|
type textMessage struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*textMessage) MarshalText() ([]byte, error) {
|
||||||
|
return []byte("custom"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*textMessage) UnmarshalText(bytes []byte) error {
|
||||||
|
if string(bytes) != "custom" {
|
||||||
|
return errors.New("expected 'custom'")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*textMessage) Reset() {}
|
||||||
|
func (*textMessage) String() string { return "" }
|
||||||
|
func (*textMessage) ProtoMessage() {}
|
||||||
|
|
||||||
|
func newTestMessage() *pb.MyMessage {
|
||||||
|
msg := &pb.MyMessage{
|
||||||
|
Count: proto.Int32(42),
|
||||||
|
Name: proto.String("Dave"),
|
||||||
|
Quote: proto.String(`"I didn't want to go."`),
|
||||||
|
Pet: []string{"bunny", "kitty", "horsey"},
|
||||||
|
Inner: &pb.InnerMessage{
|
||||||
|
Host: proto.String("footrest.syd"),
|
||||||
|
Port: proto.Int32(7001),
|
||||||
|
Connected: proto.Bool(true),
|
||||||
|
},
|
||||||
|
Others: []*pb.OtherMessage{
|
||||||
|
{
|
||||||
|
Key: proto.Int64(0xdeadbeef),
|
||||||
|
Value: []byte{1, 65, 7, 12},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Weight: proto.Float32(6.022),
|
||||||
|
Inner: &pb.InnerMessage{
|
||||||
|
Host: proto.String("lesha.mtv"),
|
||||||
|
Port: proto.Int32(8002),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bikeshed: pb.MyMessage_BLUE.Enum(),
|
||||||
|
Somegroup: &pb.MyMessage_SomeGroup{
|
||||||
|
GroupField: proto.Int32(8),
|
||||||
|
},
|
||||||
|
// One normally wouldn't do this.
|
||||||
|
// This is an undeclared tag 13, as a varint (wire type 0) with value 4.
|
||||||
|
XXX_unrecognized: []byte{13<<3 | 0, 4},
|
||||||
|
}
|
||||||
|
ext := &pb.Ext{
|
||||||
|
Data: proto.String("Big gobs for big rats"),
|
||||||
|
}
|
||||||
|
if err := proto.SetExtension(msg, pb.E_Ext_More, ext); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
greetings := []string{"adg", "easy", "cow"}
|
||||||
|
if err := proto.SetExtension(msg, pb.E_Greeting, greetings); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an unknown extension. We marshal a pb.Ext, and fake the ID.
|
||||||
|
b, err := proto.Marshal(&pb.Ext{Data: proto.String("3G skiing")})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...)
|
||||||
|
proto.SetRawExtension(msg, 201, b)
|
||||||
|
|
||||||
|
// Extensions can be plain fields, too, so let's test that.
|
||||||
|
b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19)
|
||||||
|
proto.SetRawExtension(msg, 202, b)
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = `count: 42
|
||||||
|
name: "Dave"
|
||||||
|
quote: "\"I didn't want to go.\""
|
||||||
|
pet: "bunny"
|
||||||
|
pet: "kitty"
|
||||||
|
pet: "horsey"
|
||||||
|
inner: <
|
||||||
|
host: "footrest.syd"
|
||||||
|
port: 7001
|
||||||
|
connected: true
|
||||||
|
>
|
||||||
|
others: <
|
||||||
|
key: 3735928559
|
||||||
|
value: "\001A\007\014"
|
||||||
|
>
|
||||||
|
others: <
|
||||||
|
weight: 6.022
|
||||||
|
inner: <
|
||||||
|
host: "lesha.mtv"
|
||||||
|
port: 8002
|
||||||
|
>
|
||||||
|
>
|
||||||
|
bikeshed: BLUE
|
||||||
|
SomeGroup {
|
||||||
|
group_field: 8
|
||||||
|
}
|
||||||
|
/* 2 unknown bytes */
|
||||||
|
13: 4
|
||||||
|
[testdata.Ext.more]: <
|
||||||
|
data: "Big gobs for big rats"
|
||||||
|
>
|
||||||
|
[testdata.greeting]: "adg"
|
||||||
|
[testdata.greeting]: "easy"
|
||||||
|
[testdata.greeting]: "cow"
|
||||||
|
/* 13 unknown bytes */
|
||||||
|
201: "\t3G skiing"
|
||||||
|
/* 3 unknown bytes */
|
||||||
|
202: 19
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestMarshalText(t *testing.T) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := proto.MarshalText(buf, newTestMessage()); err != nil {
|
||||||
|
t.Fatalf("proto.MarshalText: %v", err)
|
||||||
|
}
|
||||||
|
s := buf.String()
|
||||||
|
if s != text {
|
||||||
|
t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalTextCustomMessage(t *testing.T) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := proto.MarshalText(buf, &textMessage{}); err != nil {
|
||||||
|
t.Fatalf("proto.MarshalText: %v", err)
|
||||||
|
}
|
||||||
|
s := buf.String()
|
||||||
|
if s != "custom" {
|
||||||
|
t.Errorf("Got %q, expected %q", s, "custom")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestMarshalTextNil(t *testing.T) {
|
||||||
|
want := "<nil>"
|
||||||
|
tests := []proto.Message{nil, (*pb.MyMessage)(nil)}
|
||||||
|
for i, test := range tests {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := proto.MarshalText(buf, test); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := buf.String(); got != want {
|
||||||
|
t.Errorf("%d: got %q want %q", i, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalTextUnknownEnum(t *testing.T) {
|
||||||
|
// The Color enum only specifies values 0-2.
|
||||||
|
m := &pb.MyMessage{Bikeshed: pb.MyMessage_Color(3).Enum()}
|
||||||
|
got := m.String()
|
||||||
|
const want = `bikeshed:3 `
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("\n got %q\nwant %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalTextBuffered(b *testing.B) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
m := newTestMessage()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
buf.Reset()
|
||||||
|
proto.MarshalText(buf, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalTextUnbuffered(b *testing.B) {
|
||||||
|
w := ioutil.Discard
|
||||||
|
m := newTestMessage()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
proto.MarshalText(w, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compact(src string) string {
|
||||||
|
// s/[ \n]+/ /g; s/ $//;
|
||||||
|
dst := make([]byte, len(src))
|
||||||
|
space, comment := false, false
|
||||||
|
j := 0
|
||||||
|
for i := 0; i < len(src); i++ {
|
||||||
|
if strings.HasPrefix(src[i:], "/*") {
|
||||||
|
comment = true
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if comment && strings.HasPrefix(src[i:], "*/") {
|
||||||
|
comment = false
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if comment {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c := src[i]
|
||||||
|
if c == ' ' || c == '\n' {
|
||||||
|
space = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') {
|
||||||
|
space = false
|
||||||
|
}
|
||||||
|
if c == '{' {
|
||||||
|
space = false
|
||||||
|
}
|
||||||
|
if space {
|
||||||
|
dst[j] = ' '
|
||||||
|
j++
|
||||||
|
space = false
|
||||||
|
}
|
||||||
|
dst[j] = c
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
if space {
|
||||||
|
dst[j] = ' '
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
return string(dst[0:j])
|
||||||
|
}
|
||||||
|
|
||||||
|
var compactText = compact(text)
|
||||||
|
|
||||||
|
func TestCompactText(t *testing.T) {
|
||||||
|
s := proto.CompactTextString(newTestMessage())
|
||||||
|
if s != compactText {
|
||||||
|
t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v\n===\n", s, compactText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringEscaping(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
in *pb.Strings
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// Test data from C++ test (TextFormatTest.StringEscape).
|
||||||
|
// Single divergence: we don't escape apostrophes.
|
||||||
|
&pb.Strings{StringField: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces")},
|
||||||
|
"string_field: \"\\\"A string with ' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Test data from the same C++ test.
|
||||||
|
&pb.Strings{StringField: proto.String("\350\260\267\346\255\214")},
|
||||||
|
"string_field: \"\\350\\260\\267\\346\\255\\214\"\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Some UTF-8.
|
||||||
|
&pb.Strings{StringField: proto.String("\x00\x01\xff\x81")},
|
||||||
|
`string_field: "\000\001\377\201"` + "\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := proto.MarshalText(&buf, tc.in); err != nil {
|
||||||
|
t.Errorf("proto.MarsalText: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s := buf.String()
|
||||||
|
if s != tc.out {
|
||||||
|
t.Errorf("#%d: Got:\n%s\nExpected:\n%s\n", i, s, tc.out)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check round-trip.
|
||||||
|
pb := new(pb.Strings)
|
||||||
|
if err := proto.UnmarshalText(s, pb); err != nil {
|
||||||
|
t.Errorf("#%d: UnmarshalText: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !proto.Equal(pb, tc.in) {
|
||||||
|
t.Errorf("#%d: Round-trip failed:\nstart: %v\n end: %v", i, tc.in, pb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A limitedWriter accepts some output before it fails.
|
||||||
|
// This is a proxy for something like a nearly-full or imminently-failing disk,
|
||||||
|
// or a network connection that is about to die.
|
||||||
|
type limitedWriter struct {
|
||||||
|
b bytes.Buffer
|
||||||
|
limit int
|
||||||
|
}
|
||||||
|
|
||||||
|
var outOfSpace = errors.New("proto: insufficient space")
|
||||||
|
|
||||||
|
func (w *limitedWriter) Write(p []byte) (n int, err error) {
|
||||||
|
var avail = w.limit - w.b.Len()
|
||||||
|
if avail <= 0 {
|
||||||
|
return 0, outOfSpace
|
||||||
|
}
|
||||||
|
if len(p) <= avail {
|
||||||
|
return w.b.Write(p)
|
||||||
|
}
|
||||||
|
n, _ = w.b.Write(p[:avail])
|
||||||
|
return n, outOfSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalTextFailing(t *testing.T) {
|
||||||
|
// Try lots of different sizes to exercise more error code-paths.
|
||||||
|
for lim := 0; lim < len(text); lim++ {
|
||||||
|
buf := new(limitedWriter)
|
||||||
|
buf.limit = lim
|
||||||
|
err := proto.MarshalText(buf, newTestMessage())
|
||||||
|
// We expect a certain error, but also some partial results in the buffer.
|
||||||
|
if err != outOfSpace {
|
||||||
|
t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", err, outOfSpace)
|
||||||
|
}
|
||||||
|
s := buf.b.String()
|
||||||
|
x := text[:buf.limit]
|
||||||
|
if s != x {
|
||||||
|
t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloats(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
f float64
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{0, "0"},
|
||||||
|
{4.7, "4.7"},
|
||||||
|
{math.Inf(1), "inf"},
|
||||||
|
{math.Inf(-1), "-inf"},
|
||||||
|
{math.NaN(), "nan"},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
msg := &pb.FloatingPoint{F: &test.f}
|
||||||
|
got := strings.TrimSpace(msg.String())
|
||||||
|
want := `f:` + test.want
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("f=%f: got %q, want %q", test.f, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRepeatedNilText(t *testing.T) {
|
||||||
|
m := &pb.MessageList{
|
||||||
|
Message: []*pb.MessageList_Message{
|
||||||
|
nil,
|
||||||
|
&pb.MessageList_Message{
|
||||||
|
Name: proto.String("Horse"),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
want := `Message <nil>
|
||||||
|
Message {
|
||||||
|
name: "Horse"
|
||||||
|
}
|
||||||
|
Message <nil>
|
||||||
|
`
|
||||||
|
if s := proto.MarshalTextString(m); s != want {
|
||||||
|
t.Errorf(" got: %s\nwant: %s", s, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProto3Text(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
m proto.Message
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// zero message
|
||||||
|
{&proto3pb.Message{}, ``},
|
||||||
|
// zero message except for an empty byte slice
|
||||||
|
{&proto3pb.Message{Data: []byte{}}, ``},
|
||||||
|
// trivial case
|
||||||
|
{&proto3pb.Message{Name: "Rob", HeightInCm: 175}, `name:"Rob" height_in_cm:175`},
|
||||||
|
// empty map
|
||||||
|
{&pb.MessageWithMap{}, ``},
|
||||||
|
// non-empty map; current map format is the same as a repeated struct
|
||||||
|
{
|
||||||
|
&pb.MessageWithMap{NameMapping: map[int32]string{1234: "Feist"}},
|
||||||
|
`name_mapping:<key:1234 value:"Feist" >`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
got := strings.TrimSpace(test.m.String())
|
||||||
|
if got != test.want {
|
||||||
|
t.Errorf("\n got %s\nwant %s", got, test.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
575
Godeps/_workspace/src/github.com/mitchellh/goamz/elb/elb.go
generated
vendored
575
Godeps/_workspace/src/github.com/mitchellh/goamz/elb/elb.go
generated
vendored
@@ -1,575 +0,0 @@
|
|||||||
// The elb package provides types and functions for interaction with the AWS
|
|
||||||
// Elastic Load Balancing service (ELB)
|
|
||||||
package elb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mitchellh/goamz/aws"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The ELB type encapsulates operations operations with the elb endpoint.
|
|
||||||
type ELB struct {
|
|
||||||
aws.Auth
|
|
||||||
aws.Region
|
|
||||||
httpClient *http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
const APIVersion = "2012-06-01"
|
|
||||||
|
|
||||||
// New creates a new ELB instance.
|
|
||||||
func New(auth aws.Auth, region aws.Region) *ELB {
|
|
||||||
return NewWithClient(auth, region, aws.RetryingClient)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWithClient(auth aws.Auth, region aws.Region, httpClient *http.Client) *ELB {
|
|
||||||
return &ELB{auth, region, httpClient}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (elb *ELB) query(params map[string]string, resp interface{}) error {
|
|
||||||
params["Version"] = APIVersion
|
|
||||||
params["Timestamp"] = time.Now().In(time.UTC).Format(time.RFC3339)
|
|
||||||
|
|
||||||
endpoint, err := url.Parse(elb.Region.ELBEndpoint)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sign(elb.Auth, "GET", "/", params, endpoint.Host)
|
|
||||||
endpoint.RawQuery = multimap(params).Encode()
|
|
||||||
r, err := elb.httpClient.Get(endpoint.String())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer r.Body.Close()
|
|
||||||
if r.StatusCode > 200 {
|
|
||||||
return buildError(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder := xml.NewDecoder(r.Body)
|
|
||||||
decodedBody := decoder.Decode(resp)
|
|
||||||
|
|
||||||
return decodedBody
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildError(r *http.Response) error {
|
|
||||||
var (
|
|
||||||
err Error
|
|
||||||
errors xmlErrors
|
|
||||||
)
|
|
||||||
xml.NewDecoder(r.Body).Decode(&errors)
|
|
||||||
if len(errors.Errors) > 0 {
|
|
||||||
err = errors.Errors[0]
|
|
||||||
}
|
|
||||||
err.StatusCode = r.StatusCode
|
|
||||||
if err.Message == "" {
|
|
||||||
err.Message = r.Status
|
|
||||||
}
|
|
||||||
return &err
|
|
||||||
}
|
|
||||||
|
|
||||||
func multimap(p map[string]string) url.Values {
|
|
||||||
q := make(url.Values, len(p))
|
|
||||||
for k, v := range p {
|
|
||||||
q[k] = []string{v}
|
|
||||||
}
|
|
||||||
return q
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeParams(action string) map[string]string {
|
|
||||||
params := make(map[string]string)
|
|
||||||
params["Action"] = action
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// ELB objects
|
|
||||||
|
|
||||||
// A listener attaches to an elb
|
|
||||||
type Listener struct {
|
|
||||||
InstancePort int64 `xml:"Listener>InstancePort"`
|
|
||||||
InstanceProtocol string `xml:"Listener>InstanceProtocol"`
|
|
||||||
SSLCertificateId string `xml:"Listener>SSLCertificateId"`
|
|
||||||
LoadBalancerPort int64 `xml:"Listener>LoadBalancerPort"`
|
|
||||||
Protocol string `xml:"Listener>Protocol"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// An Instance attaches to an elb
|
|
||||||
type Instance struct {
|
|
||||||
InstanceId string `xml:"InstanceId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// A tag attached to an elb
|
|
||||||
type Tag struct {
|
|
||||||
Key string `xml:"Key"`
|
|
||||||
Value string `xml:"Value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// An InstanceState from an elb health query
|
|
||||||
type InstanceState struct {
|
|
||||||
InstanceId string `xml:"InstanceId"`
|
|
||||||
Description string `xml:"Description"`
|
|
||||||
State string `xml:"State"`
|
|
||||||
ReasonCode string `xml:"ReasonCode"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// AddTags
|
|
||||||
|
|
||||||
type AddTags struct {
|
|
||||||
LoadBalancerNames []string
|
|
||||||
Tags []Tag
|
|
||||||
}
|
|
||||||
|
|
||||||
type AddTagsResp struct {
|
|
||||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (elb *ELB) AddTags(options *AddTags) (resp *AddTagsResp, err error) {
|
|
||||||
params := makeParams("AddTags")
|
|
||||||
|
|
||||||
for i, v := range options.LoadBalancerNames {
|
|
||||||
params["LoadBalancerNames.member."+strconv.Itoa(i+1)] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, v := range options.Tags {
|
|
||||||
params["Tags.member."+strconv.Itoa(i+1)+".Key"] = v.Key
|
|
||||||
params["Tags.member."+strconv.Itoa(i+1)+".Value"] = v.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = &AddTagsResp{}
|
|
||||||
|
|
||||||
err = elb.query(params, resp)
|
|
||||||
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// RemoveTags
|
|
||||||
|
|
||||||
type RemoveTags struct {
|
|
||||||
LoadBalancerNames []string
|
|
||||||
TagKeys []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type RemoveTagsResp struct {
|
|
||||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (elb *ELB) RemoveTags(options *RemoveTags) (resp *RemoveTagsResp, err error) {
|
|
||||||
params := makeParams("RemoveTags")
|
|
||||||
|
|
||||||
for i, v := range options.LoadBalancerNames {
|
|
||||||
params["LoadBalancerNames.member."+strconv.Itoa(i+1)] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, v := range options.TagKeys {
|
|
||||||
params["Tags.member."+strconv.Itoa(i+1)+".Key"] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = &RemoveTagsResp{}
|
|
||||||
|
|
||||||
err = elb.query(params, resp)
|
|
||||||
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Create
|
|
||||||
|
|
||||||
// The CreateLoadBalancer request parameters
|
|
||||||
type CreateLoadBalancer struct {
|
|
||||||
AvailZone []string
|
|
||||||
Listeners []Listener
|
|
||||||
LoadBalancerName string
|
|
||||||
Internal bool // true for vpc elbs
|
|
||||||
SecurityGroups []string
|
|
||||||
Subnets []string
|
|
||||||
Tags []Tag
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateLoadBalancerResp struct {
|
|
||||||
DNSName string `xml:"CreateLoadBalancerResult>DNSName"`
|
|
||||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (elb *ELB) CreateLoadBalancer(options *CreateLoadBalancer) (resp *CreateLoadBalancerResp, err error) {
|
|
||||||
params := makeParams("CreateLoadBalancer")
|
|
||||||
|
|
||||||
params["LoadBalancerName"] = options.LoadBalancerName
|
|
||||||
|
|
||||||
for i, v := range options.AvailZone {
|
|
||||||
params["AvailabilityZones.member."+strconv.Itoa(i+1)] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, v := range options.SecurityGroups {
|
|
||||||
params["SecurityGroups.member."+strconv.Itoa(i+1)] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, v := range options.Subnets {
|
|
||||||
params["Subnets.member."+strconv.Itoa(i+1)] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, v := range options.Listeners {
|
|
||||||
params["Listeners.member."+strconv.Itoa(i+1)+".LoadBalancerPort"] = strconv.FormatInt(v.LoadBalancerPort, 10)
|
|
||||||
params["Listeners.member."+strconv.Itoa(i+1)+".InstancePort"] = strconv.FormatInt(v.InstancePort, 10)
|
|
||||||
params["Listeners.member."+strconv.Itoa(i+1)+".Protocol"] = v.Protocol
|
|
||||||
params["Listeners.member."+strconv.Itoa(i+1)+".InstanceProtocol"] = v.InstanceProtocol
|
|
||||||
params["Listeners.member."+strconv.Itoa(i+1)+".SSLCertificateId"] = v.SSLCertificateId
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, v := range options.Tags {
|
|
||||||
params["Tags.member."+strconv.Itoa(i+1)+".Key"] = v.Key
|
|
||||||
params["Tags.member."+strconv.Itoa(i+1)+".Value"] = v.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Internal {
|
|
||||||
params["Scheme"] = "internal"
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = &CreateLoadBalancerResp{}
|
|
||||||
|
|
||||||
err = elb.query(params, resp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
resp = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Destroy
|
|
||||||
|
|
||||||
// The DestroyLoadBalancer request parameters
|
|
||||||
type DeleteLoadBalancer struct {
|
|
||||||
LoadBalancerName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (elb *ELB) DeleteLoadBalancer(options *DeleteLoadBalancer) (resp *SimpleResp, err error) {
|
|
||||||
params := makeParams("DeleteLoadBalancer")
|
|
||||||
|
|
||||||
params["LoadBalancerName"] = options.LoadBalancerName
|
|
||||||
|
|
||||||
resp = &SimpleResp{}
|
|
||||||
|
|
||||||
err = elb.query(params, resp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
resp = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Describe
|
|
||||||
|
|
||||||
// An individual load balancer
|
|
||||||
type LoadBalancer struct {
|
|
||||||
LoadBalancerName string `xml:"LoadBalancerName"`
|
|
||||||
Listeners []Listener `xml:"ListenerDescriptions>member"`
|
|
||||||
Instances []Instance `xml:"Instances>member"`
|
|
||||||
HealthCheck HealthCheck `xml:"HealthCheck"`
|
|
||||||
AvailabilityZones []string `xml:"AvailabilityZones>member"`
|
|
||||||
HostedZoneNameID string `xml:"CanonicalHostedZoneNameID"`
|
|
||||||
DNSName string `xml:"DNSName"`
|
|
||||||
SecurityGroups []string `xml:"SecurityGroups>member"`
|
|
||||||
Scheme string `xml:"Scheme"`
|
|
||||||
Subnets []string `xml:"Subnets>member"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DescribeLoadBalancer request params
|
|
||||||
type DescribeLoadBalancer struct {
|
|
||||||
Names []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DescribeLoadBalancersResp struct {
|
|
||||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
|
||||||
LoadBalancers []LoadBalancer `xml:"DescribeLoadBalancersResult>LoadBalancerDescriptions>member"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (elb *ELB) DescribeLoadBalancers(options *DescribeLoadBalancer) (resp *DescribeLoadBalancersResp, err error) {
|
|
||||||
params := makeParams("DescribeLoadBalancers")
|
|
||||||
|
|
||||||
for i, v := range options.Names {
|
|
||||||
params["LoadBalancerNames.member."+strconv.Itoa(i+1)] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = &DescribeLoadBalancersResp{}
|
|
||||||
|
|
||||||
err = elb.query(params, resp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
resp = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Attributes
|
|
||||||
|
|
||||||
type AccessLog struct {
|
|
||||||
EmitInterval int64
|
|
||||||
Enabled bool
|
|
||||||
S3BucketName string
|
|
||||||
S3BucketPrefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectionDraining struct {
|
|
||||||
Enabled bool
|
|
||||||
Timeout int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type LoadBalancerAttributes struct {
|
|
||||||
CrossZoneLoadBalancingEnabled bool
|
|
||||||
ConnectionSettingsIdleTimeout int64
|
|
||||||
ConnectionDraining ConnectionDraining
|
|
||||||
AccessLog AccessLog
|
|
||||||
}
|
|
||||||
|
|
||||||
type ModifyLoadBalancerAttributes struct {
|
|
||||||
LoadBalancerName string
|
|
||||||
LoadBalancerAttributes LoadBalancerAttributes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (elb *ELB) ModifyLoadBalancerAttributes(options *ModifyLoadBalancerAttributes) (resp *SimpleResp, err error) {
|
|
||||||
params := makeParams("ModifyLoadBalancerAttributes")
|
|
||||||
|
|
||||||
params["LoadBalancerName"] = options.LoadBalancerName
|
|
||||||
params["LoadBalancerAttributes.CrossZoneLoadBalancing.Enabled"] = strconv.FormatBool(options.LoadBalancerAttributes.CrossZoneLoadBalancingEnabled)
|
|
||||||
if options.LoadBalancerAttributes.ConnectionSettingsIdleTimeout > 0 {
|
|
||||||
params["LoadBalancerAttributes.ConnectionSettings.IdleTimeout"] = strconv.Itoa(int(options.LoadBalancerAttributes.ConnectionSettingsIdleTimeout))
|
|
||||||
}
|
|
||||||
if options.LoadBalancerAttributes.ConnectionDraining.Timeout > 0 {
|
|
||||||
params["LoadBalancerAttributes.ConnectionDraining.Timeout"] = strconv.Itoa(int(options.LoadBalancerAttributes.ConnectionDraining.Timeout))
|
|
||||||
}
|
|
||||||
params["LoadBalancerAttributes.ConnectionDraining.Enabled"] = strconv.FormatBool(options.LoadBalancerAttributes.ConnectionDraining.Enabled)
|
|
||||||
params["LoadBalancerAttributes.AccessLog.Enabled"] = strconv.FormatBool(options.LoadBalancerAttributes.AccessLog.Enabled)
|
|
||||||
if options.LoadBalancerAttributes.AccessLog.Enabled {
|
|
||||||
params["LoadBalancerAttributes.AccessLog.EmitInterval"] = strconv.Itoa(int(options.LoadBalancerAttributes.AccessLog.EmitInterval))
|
|
||||||
params["LoadBalancerAttributes.AccessLog.S3BucketName"] = options.LoadBalancerAttributes.AccessLog.S3BucketName
|
|
||||||
params["LoadBalancerAttributes.AccessLog.S3BucketPrefix"] = options.LoadBalancerAttributes.AccessLog.S3BucketPrefix
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = &SimpleResp{}
|
|
||||||
|
|
||||||
err = elb.query(params, resp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
resp = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Instance Registration / deregistration
|
|
||||||
|
|
||||||
// The RegisterInstancesWithLoadBalancer request parameters
|
|
||||||
type RegisterInstancesWithLoadBalancer struct {
|
|
||||||
LoadBalancerName string
|
|
||||||
Instances []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type RegisterInstancesWithLoadBalancerResp struct {
|
|
||||||
Instances []Instance `xml:"RegisterInstancesWithLoadBalancerResult>Instances>member"`
|
|
||||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (elb *ELB) RegisterInstancesWithLoadBalancer(options *RegisterInstancesWithLoadBalancer) (resp *RegisterInstancesWithLoadBalancerResp, err error) {
|
|
||||||
params := makeParams("RegisterInstancesWithLoadBalancer")
|
|
||||||
|
|
||||||
params["LoadBalancerName"] = options.LoadBalancerName
|
|
||||||
|
|
||||||
for i, v := range options.Instances {
|
|
||||||
params["Instances.member."+strconv.Itoa(i+1)+".InstanceId"] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = &RegisterInstancesWithLoadBalancerResp{}
|
|
||||||
|
|
||||||
err = elb.query(params, resp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
resp = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// The DeregisterInstancesFromLoadBalancer request parameters
|
|
||||||
type DeregisterInstancesFromLoadBalancer struct {
|
|
||||||
LoadBalancerName string
|
|
||||||
Instances []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeregisterInstancesFromLoadBalancerResp struct {
|
|
||||||
Instances []Instance `xml:"DeregisterInstancesFromLoadBalancerResult>Instances>member"`
|
|
||||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (elb *ELB) DeregisterInstancesFromLoadBalancer(options *DeregisterInstancesFromLoadBalancer) (resp *DeregisterInstancesFromLoadBalancerResp, err error) {
|
|
||||||
params := makeParams("DeregisterInstancesFromLoadBalancer")
|
|
||||||
|
|
||||||
params["LoadBalancerName"] = options.LoadBalancerName
|
|
||||||
|
|
||||||
for i, v := range options.Instances {
|
|
||||||
params["Instances.member."+strconv.Itoa(i+1)+".InstanceId"] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = &DeregisterInstancesFromLoadBalancerResp{}
|
|
||||||
|
|
||||||
err = elb.query(params, resp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
resp = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// DescribeTags
|
|
||||||
|
|
||||||
type DescribeTags struct {
|
|
||||||
LoadBalancerNames []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type LoadBalancerTag struct {
|
|
||||||
Tags []Tag `xml:"Tags>member"`
|
|
||||||
LoadBalancerName string `xml:"LoadBalancerName"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DescribeTagsResp struct {
|
|
||||||
LoadBalancerTags []LoadBalancerTag `xml:"DescribeTagsResult>TagDescriptions>member"`
|
|
||||||
NextToken string `xml:"DescribeTagsResult>NextToken"`
|
|
||||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (elb *ELB) DescribeTags(options *DescribeTags) (resp *DescribeTagsResp, err error) {
|
|
||||||
params := makeParams("DescribeTags")
|
|
||||||
|
|
||||||
for i, v := range options.LoadBalancerNames {
|
|
||||||
params["LoadBalancerNames.member."+strconv.Itoa(i+1)] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = &DescribeTagsResp{}
|
|
||||||
|
|
||||||
err = elb.query(params, resp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
resp = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Health Checks
|
|
||||||
|
|
||||||
type HealthCheck struct {
|
|
||||||
HealthyThreshold int64 `xml:"HealthyThreshold"`
|
|
||||||
UnhealthyThreshold int64 `xml:"UnhealthyThreshold"`
|
|
||||||
Interval int64 `xml:"Interval"`
|
|
||||||
Target string `xml:"Target"`
|
|
||||||
Timeout int64 `xml:"Timeout"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigureHealthCheck struct {
|
|
||||||
LoadBalancerName string
|
|
||||||
Check HealthCheck
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigureHealthCheckResp struct {
|
|
||||||
Check HealthCheck `xml:"ConfigureHealthCheckResult>HealthCheck"`
|
|
||||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (elb *ELB) ConfigureHealthCheck(options *ConfigureHealthCheck) (resp *ConfigureHealthCheckResp, err error) {
|
|
||||||
params := makeParams("ConfigureHealthCheck")
|
|
||||||
|
|
||||||
params["LoadBalancerName"] = options.LoadBalancerName
|
|
||||||
params["HealthCheck.HealthyThreshold"] = strconv.Itoa(int(options.Check.HealthyThreshold))
|
|
||||||
params["HealthCheck.UnhealthyThreshold"] = strconv.Itoa(int(options.Check.UnhealthyThreshold))
|
|
||||||
params["HealthCheck.Interval"] = strconv.Itoa(int(options.Check.Interval))
|
|
||||||
params["HealthCheck.Target"] = options.Check.Target
|
|
||||||
params["HealthCheck.Timeout"] = strconv.Itoa(int(options.Check.Timeout))
|
|
||||||
|
|
||||||
resp = &ConfigureHealthCheckResp{}
|
|
||||||
|
|
||||||
err = elb.query(params, resp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
resp = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Instance Health
|
|
||||||
|
|
||||||
// The DescribeInstanceHealth request parameters
|
|
||||||
type DescribeInstanceHealth struct {
|
|
||||||
LoadBalancerName string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DescribeInstanceHealthResp struct {
|
|
||||||
InstanceStates []InstanceState `xml:"DescribeInstanceHealthResult>InstanceStates>member"`
|
|
||||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (elb *ELB) DescribeInstanceHealth(options *DescribeInstanceHealth) (resp *DescribeInstanceHealthResp, err error) {
|
|
||||||
params := makeParams("DescribeInstanceHealth")
|
|
||||||
|
|
||||||
params["LoadBalancerName"] = options.LoadBalancerName
|
|
||||||
|
|
||||||
resp = &DescribeInstanceHealthResp{}
|
|
||||||
|
|
||||||
err = elb.query(params, resp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
resp = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Responses
|
|
||||||
|
|
||||||
type SimpleResp struct {
|
|
||||||
RequestId string `xml:"ResponseMetadata>RequestId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type xmlErrors struct {
|
|
||||||
Errors []Error `xml:"Error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error encapsulates an elb error.
|
|
||||||
type Error struct {
|
|
||||||
// HTTP status code of the error.
|
|
||||||
StatusCode int
|
|
||||||
|
|
||||||
// AWS code of the error.
|
|
||||||
Code string
|
|
||||||
|
|
||||||
// Message explaining the error.
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
|
||||||
var prefix string
|
|
||||||
if e.Code != "" {
|
|
||||||
prefix = e.Code + ": "
|
|
||||||
}
|
|
||||||
if prefix == "" && e.StatusCode > 0 {
|
|
||||||
prefix = strconv.Itoa(e.StatusCode) + ": "
|
|
||||||
}
|
|
||||||
return prefix + e.Message
|
|
||||||
}
|
|
||||||
235
Godeps/_workspace/src/github.com/mitchellh/goamz/elb/elb_test.go
generated
vendored
235
Godeps/_workspace/src/github.com/mitchellh/goamz/elb/elb_test.go
generated
vendored
@@ -1,235 +0,0 @@
|
|||||||
package elb_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mitchellh/goamz/aws"
|
|
||||||
"github.com/mitchellh/goamz/elb"
|
|
||||||
"github.com/mitchellh/goamz/testutil"
|
|
||||||
. "github.com/motain/gocheck"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
|
||||||
TestingT(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
type S struct {
|
|
||||||
elb *elb.ELB
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = Suite(&S{})
|
|
||||||
|
|
||||||
var testServer = testutil.NewHTTPServer()
|
|
||||||
|
|
||||||
func (s *S) SetUpSuite(c *C) {
|
|
||||||
testServer.Start()
|
|
||||||
auth := aws.Auth{"abc", "123", ""}
|
|
||||||
s.elb = elb.NewWithClient(auth, aws.Region{ELBEndpoint: testServer.URL}, testutil.DefaultClient)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TearDownTest(c *C) {
|
|
||||||
testServer.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestAddTags(c *C) {
|
|
||||||
testServer.Response(200, nil, AddTagsExample)
|
|
||||||
|
|
||||||
options := elb.AddTags{
|
|
||||||
LoadBalancerNames: []string{"foobar"},
|
|
||||||
Tags: []elb.Tag{
|
|
||||||
{
|
|
||||||
Key: "hello",
|
|
||||||
Value: "world",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := s.elb.AddTags(&options)
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
|
|
||||||
c.Assert(req.Form["Action"], DeepEquals, []string{"AddTags"})
|
|
||||||
c.Assert(req.Form["LoadBalancerNames.member.1"], DeepEquals, []string{"foobar"})
|
|
||||||
c.Assert(req.Form["Tags.member.1.Key"], DeepEquals, []string{"hello"})
|
|
||||||
c.Assert(req.Form["Tags.member.1.Value"], DeepEquals, []string{"world"})
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(resp.RequestId, Equals, "360e81f7-1100-11e4-b6ed-0f30EXAMPLE")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestRemoveTags(c *C) {
|
|
||||||
testServer.Response(200, nil, RemoveTagsExample)
|
|
||||||
|
|
||||||
options := elb.RemoveTags{
|
|
||||||
LoadBalancerNames: []string{"foobar"},
|
|
||||||
TagKeys: []string{"hello"},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := s.elb.RemoveTags(&options)
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
|
|
||||||
c.Assert(req.Form["Action"], DeepEquals, []string{"RemoveTags"})
|
|
||||||
c.Assert(req.Form["LoadBalancerNames.member.1"], DeepEquals, []string{"foobar"})
|
|
||||||
c.Assert(req.Form["Tags.member.1.Key"], DeepEquals, []string{"hello"})
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(resp.RequestId, Equals, "83c88b9d-12b7-11e3-8b82-87b12EXAMPLE")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestCreateLoadBalancer(c *C) {
|
|
||||||
testServer.Response(200, nil, CreateLoadBalancerExample)
|
|
||||||
|
|
||||||
options := elb.CreateLoadBalancer{
|
|
||||||
AvailZone: []string{"us-east-1a"},
|
|
||||||
Listeners: []elb.Listener{elb.Listener{
|
|
||||||
InstancePort: 80,
|
|
||||||
InstanceProtocol: "http",
|
|
||||||
SSLCertificateId: "needToAddASSLCertToYourAWSAccount",
|
|
||||||
LoadBalancerPort: 80,
|
|
||||||
Protocol: "http",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
LoadBalancerName: "foobar",
|
|
||||||
Internal: false,
|
|
||||||
SecurityGroups: []string{"sg1"},
|
|
||||||
Subnets: []string{"sn1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := s.elb.CreateLoadBalancer(&options)
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
|
|
||||||
c.Assert(req.Form["Action"], DeepEquals, []string{"CreateLoadBalancer"})
|
|
||||||
c.Assert(req.Form["LoadBalancerName"], DeepEquals, []string{"foobar"})
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(resp.RequestId, Equals, "1549581b-12b7-11e3-895e-1334aEXAMPLE")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestDeleteLoadBalancer(c *C) {
|
|
||||||
testServer.Response(200, nil, DeleteLoadBalancerExample)
|
|
||||||
|
|
||||||
options := elb.DeleteLoadBalancer{
|
|
||||||
LoadBalancerName: "foobar",
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := s.elb.DeleteLoadBalancer(&options)
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
|
|
||||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteLoadBalancer"})
|
|
||||||
c.Assert(req.Form["LoadBalancerName"], DeepEquals, []string{"foobar"})
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(resp.RequestId, Equals, "1549581b-12b7-11e3-895e-1334aEXAMPLE")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestDescribeLoadBalancers(c *C) {
|
|
||||||
testServer.Response(200, nil, DescribeLoadBalancersExample)
|
|
||||||
|
|
||||||
options := elb.DescribeLoadBalancer{
|
|
||||||
Names: []string{"foobar"},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := s.elb.DescribeLoadBalancers(&options)
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
|
|
||||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeLoadBalancers"})
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(resp.RequestId, Equals, "83c88b9d-12b7-11e3-8b82-87b12EXAMPLE")
|
|
||||||
c.Assert(resp.LoadBalancers[0].LoadBalancerName, Equals, "MyLoadBalancer")
|
|
||||||
c.Assert(resp.LoadBalancers[0].Listeners[0].Protocol, Equals, "HTTP")
|
|
||||||
c.Assert(resp.LoadBalancers[0].Instances[0].InstanceId, Equals, "i-e4cbe38d")
|
|
||||||
c.Assert(resp.LoadBalancers[0].AvailabilityZones[0].AvailabilityZone, Equals, "us-east-1a")
|
|
||||||
c.Assert(resp.LoadBalancers[0].Scheme, Equals, "internet-facing")
|
|
||||||
c.Assert(resp.LoadBalancers[0].DNSName, Equals, "MyLoadBalancer-123456789.us-east-1.elb.amazonaws.com")
|
|
||||||
c.Assert(resp.LoadBalancers[0].HealthCheck.HealthyThreshold, Equals, int64(2))
|
|
||||||
c.Assert(resp.LoadBalancers[0].HealthCheck.UnhealthyThreshold, Equals, int64(10))
|
|
||||||
c.Assert(resp.LoadBalancers[0].HealthCheck.Interval, Equals, int64(90))
|
|
||||||
c.Assert(resp.LoadBalancers[0].HealthCheck.Target, Equals, "HTTP:80/")
|
|
||||||
c.Assert(resp.LoadBalancers[0].HealthCheck.Timeout, Equals, int64(60))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestRegisterInstancesWithLoadBalancer(c *C) {
|
|
||||||
testServer.Response(200, nil, RegisterInstancesWithLoadBalancerExample)
|
|
||||||
|
|
||||||
options := elb.RegisterInstancesWithLoadBalancer{
|
|
||||||
LoadBalancerName: "foobar",
|
|
||||||
Instances: []string{"instance-1", "instance-2"},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := s.elb.RegisterInstancesWithLoadBalancer(&options)
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
|
|
||||||
c.Assert(req.Form["Action"], DeepEquals, []string{"RegisterInstancesWithLoadBalancer"})
|
|
||||||
c.Assert(req.Form["LoadBalancerName"], DeepEquals, []string{"foobar"})
|
|
||||||
c.Assert(req.Form["Instances.member.1.InstanceId"], DeepEquals, []string{"instance-1"})
|
|
||||||
c.Assert(req.Form["Instances.member.2.InstanceId"], DeepEquals, []string{"instance-2"})
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
c.Assert(resp.Instances[0].InstanceId, Equals, "i-315b7e51")
|
|
||||||
c.Assert(resp.RequestId, Equals, "83c88b9d-12b7-11e3-8b82-87b12EXAMPLE")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestDeregisterInstancesFromLoadBalancer(c *C) {
|
|
||||||
testServer.Response(200, nil, DeregisterInstancesFromLoadBalancerExample)
|
|
||||||
|
|
||||||
options := elb.DeregisterInstancesFromLoadBalancer{
|
|
||||||
LoadBalancerName: "foobar",
|
|
||||||
Instances: []string{"instance-1", "instance-2"},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := s.elb.DeregisterInstancesFromLoadBalancer(&options)
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
|
|
||||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DeregisterInstancesFromLoadBalancer"})
|
|
||||||
c.Assert(req.Form["LoadBalancerName"], DeepEquals, []string{"foobar"})
|
|
||||||
c.Assert(req.Form["Instances.member.1.InstanceId"], DeepEquals, []string{"instance-1"})
|
|
||||||
c.Assert(req.Form["Instances.member.2.InstanceId"], DeepEquals, []string{"instance-2"})
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
c.Assert(resp.Instances[0].InstanceId, Equals, "i-6ec63d59")
|
|
||||||
c.Assert(resp.RequestId, Equals, "83c88b9d-12b7-11e3-8b82-87b12EXAMPLE")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestConfigureHealthCheck(c *C) {
|
|
||||||
testServer.Response(200, nil, ConfigureHealthCheckExample)
|
|
||||||
|
|
||||||
options := elb.ConfigureHealthCheck{
|
|
||||||
LoadBalancerName: "foobar",
|
|
||||||
Check: elb.HealthCheck{
|
|
||||||
HealthyThreshold: 2,
|
|
||||||
UnhealthyThreshold: 2,
|
|
||||||
Interval: 30,
|
|
||||||
Target: "HTTP:80/ping",
|
|
||||||
Timeout: 3,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := s.elb.ConfigureHealthCheck(&options)
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
|
|
||||||
c.Assert(req.Form["Action"], DeepEquals, []string{"ConfigureHealthCheck"})
|
|
||||||
c.Assert(req.Form["LoadBalancerName"], DeepEquals, []string{"foobar"})
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
c.Assert(resp.Check.HealthyThreshold, Equals, int64(2))
|
|
||||||
c.Assert(resp.Check.UnhealthyThreshold, Equals, int64(2))
|
|
||||||
c.Assert(resp.Check.Interval, Equals, int64(30))
|
|
||||||
c.Assert(resp.Check.Target, Equals, "HTTP:80/ping")
|
|
||||||
c.Assert(resp.Check.Timeout, Equals, int64(3))
|
|
||||||
c.Assert(resp.RequestId, Equals, "83c88b9d-12b7-11e3-8b82-87b12EXAMPLE")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestDescribeInstanceHealth(c *C) {
|
|
||||||
testServer.Response(200, nil, DescribeInstanceHealthExample)
|
|
||||||
|
|
||||||
options := elb.DescribeInstanceHealth{
|
|
||||||
LoadBalancerName: "foobar",
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := s.elb.DescribeInstanceHealth(&options)
|
|
||||||
req := testServer.WaitRequest()
|
|
||||||
|
|
||||||
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeInstanceHealth"})
|
|
||||||
c.Assert(req.Form["LoadBalancerName"], DeepEquals, []string{"foobar"})
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
c.Assert(resp.InstanceStates[0].InstanceId, Equals, "i-90d8c2a5")
|
|
||||||
c.Assert(resp.InstanceStates[0].State, Equals, "InService")
|
|
||||||
c.Assert(resp.InstanceStates[1].InstanceId, Equals, "i-06ea3e60")
|
|
||||||
c.Assert(resp.InstanceStates[1].State, Equals, "OutOfService")
|
|
||||||
c.Assert(resp.RequestId, Equals, "1549581b-12b7-11e3-895e-1334aEXAMPLE")
|
|
||||||
}
|
|
||||||
182
Godeps/_workspace/src/github.com/mitchellh/goamz/elb/responses_test.go
generated
vendored
182
Godeps/_workspace/src/github.com/mitchellh/goamz/elb/responses_test.go
generated
vendored
@@ -1,182 +0,0 @@
|
|||||||
package elb_test
|
|
||||||
|
|
||||||
var ErrorDump = `
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Response><Errors><Error><Code>UnsupportedOperation</Code>
|
|
||||||
<Message></Message>
|
|
||||||
</Error></Errors><RequestID>0503f4e9-bbd6-483c-b54f-c4ae9f3b30f4</RequestID></Response>
|
|
||||||
`
|
|
||||||
|
|
||||||
// http://goo.gl/OkMdtJ
|
|
||||||
var AddTagsExample = `
|
|
||||||
<AddTagsResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
|
||||||
<AddTagsResult/>
|
|
||||||
<ResponseMetadata>
|
|
||||||
<RequestId>360e81f7-1100-11e4-b6ed-0f30EXAMPLE</RequestId>
|
|
||||||
</ResponseMetadata>
|
|
||||||
</AddTagsResponse>
|
|
||||||
`
|
|
||||||
|
|
||||||
// http://goo.gl/nT2E89
|
|
||||||
var RemoveTagsExample = `
|
|
||||||
<RemoveTagsResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
|
||||||
<RemoveTagsResult/>
|
|
||||||
<ResponseMetadata>
|
|
||||||
<RequestId>83c88b9d-12b7-11e3-8b82-87b12EXAMPLE</RequestId>
|
|
||||||
</ResponseMetadata>
|
|
||||||
</RemoveTagsResponse>
|
|
||||||
`
|
|
||||||
|
|
||||||
// http://goo.gl/gQRD2H
|
|
||||||
var CreateLoadBalancerExample = `
|
|
||||||
<CreateLoadBalancerResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
|
||||||
<CreateLoadBalancerResult>
|
|
||||||
<DNSName>MyLoadBalancer-1234567890.us-east-1.elb.amazonaws.com</DNSName>
|
|
||||||
</CreateLoadBalancerResult>
|
|
||||||
<ResponseMetadata>
|
|
||||||
<RequestId>1549581b-12b7-11e3-895e-1334aEXAMPLE</RequestId>
|
|
||||||
</ResponseMetadata>
|
|
||||||
</CreateLoadBalancerResponse>
|
|
||||||
`
|
|
||||||
|
|
||||||
// http://goo.gl/GLZeBN
|
|
||||||
var DeleteLoadBalancerExample = `
|
|
||||||
<DeleteLoadBalancerResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
|
||||||
<ResponseMetadata>
|
|
||||||
<RequestId>1549581b-12b7-11e3-895e-1334aEXAMPLE</RequestId>
|
|
||||||
</ResponseMetadata>
|
|
||||||
</DeleteLoadBalancerResponse>
|
|
||||||
`
|
|
||||||
|
|
||||||
// http://goo.gl/8UgpQ8
|
|
||||||
var DescribeLoadBalancersExample = `
|
|
||||||
<DescribeLoadBalancersResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
|
||||||
<DescribeLoadBalancersResult>
|
|
||||||
<LoadBalancerDescriptions>
|
|
||||||
<member>
|
|
||||||
<SecurityGroups/>
|
|
||||||
<LoadBalancerName>MyLoadBalancer</LoadBalancerName>
|
|
||||||
<CreatedTime>2013-05-24T21:15:31.280Z</CreatedTime>
|
|
||||||
<HealthCheck>
|
|
||||||
<Interval>90</Interval>
|
|
||||||
<Target>HTTP:80/</Target>
|
|
||||||
<HealthyThreshold>2</HealthyThreshold>
|
|
||||||
<Timeout>60</Timeout>
|
|
||||||
<UnhealthyThreshold>10</UnhealthyThreshold>
|
|
||||||
</HealthCheck>
|
|
||||||
<ListenerDescriptions>
|
|
||||||
<member>
|
|
||||||
<PolicyNames/>
|
|
||||||
<Listener>
|
|
||||||
<Protocol>HTTP</Protocol>
|
|
||||||
<LoadBalancerPort>80</LoadBalancerPort>
|
|
||||||
<InstanceProtocol>HTTP</InstanceProtocol>
|
|
||||||
<SSLCertificateId>needToAddASSLCertToYourAWSAccount</SSLCertificateId>
|
|
||||||
<InstancePort>80</InstancePort>
|
|
||||||
</Listener>
|
|
||||||
</member>
|
|
||||||
</ListenerDescriptions>
|
|
||||||
<Instances>
|
|
||||||
<member>
|
|
||||||
<InstanceId>i-e4cbe38d</InstanceId>
|
|
||||||
</member>
|
|
||||||
</Instances>
|
|
||||||
<Policies>
|
|
||||||
<AppCookieStickinessPolicies/>
|
|
||||||
<OtherPolicies/>
|
|
||||||
<LBCookieStickinessPolicies/>
|
|
||||||
</Policies>
|
|
||||||
<AvailabilityZones>
|
|
||||||
<member>us-east-1a</member>
|
|
||||||
</AvailabilityZones>
|
|
||||||
<CanonicalHostedZoneNameID>ZZZZZZZZZZZ123X</CanonicalHostedZoneNameID>
|
|
||||||
<CanonicalHostedZoneName>MyLoadBalancer-123456789.us-east-1.elb.amazonaws.com</CanonicalHostedZoneName>
|
|
||||||
<Scheme>internet-facing</Scheme>
|
|
||||||
<SourceSecurityGroup>
|
|
||||||
<OwnerAlias>amazon-elb</OwnerAlias>
|
|
||||||
<GroupName>amazon-elb-sg</GroupName>
|
|
||||||
</SourceSecurityGroup>
|
|
||||||
<DNSName>MyLoadBalancer-123456789.us-east-1.elb.amazonaws.com</DNSName>
|
|
||||||
<BackendServerDescriptions/>
|
|
||||||
<Subnets/>
|
|
||||||
</member>
|
|
||||||
</LoadBalancerDescriptions>
|
|
||||||
</DescribeLoadBalancersResult>
|
|
||||||
<ResponseMetadata>
|
|
||||||
<RequestId>83c88b9d-12b7-11e3-8b82-87b12EXAMPLE</RequestId>
|
|
||||||
</ResponseMetadata>
|
|
||||||
</DescribeLoadBalancersResponse>
|
|
||||||
`
|
|
||||||
|
|
||||||
// http://goo.gl/Uz1N66
|
|
||||||
var RegisterInstancesWithLoadBalancerExample = `
|
|
||||||
<RegisterInstancesWithLoadBalancerResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
|
||||||
<RegisterInstancesWithLoadBalancerResult>
|
|
||||||
<Instances>
|
|
||||||
<member>
|
|
||||||
<InstanceId>i-315b7e51</InstanceId>
|
|
||||||
</member>
|
|
||||||
</Instances>
|
|
||||||
</RegisterInstancesWithLoadBalancerResult>
|
|
||||||
<ResponseMetadata>
|
|
||||||
<RequestId>83c88b9d-12b7-11e3-8b82-87b12EXAMPLE</RequestId>
|
|
||||||
</ResponseMetadata>
|
|
||||||
</RegisterInstancesWithLoadBalancerResponse>
|
|
||||||
`
|
|
||||||
|
|
||||||
// http://goo.gl/5OMv62
|
|
||||||
var DeregisterInstancesFromLoadBalancerExample = `
|
|
||||||
<DeregisterInstancesFromLoadBalancerResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
|
||||||
<DeregisterInstancesFromLoadBalancerResult>
|
|
||||||
<Instances>
|
|
||||||
<member>
|
|
||||||
<InstanceId>i-6ec63d59</InstanceId>
|
|
||||||
</member>
|
|
||||||
</Instances>
|
|
||||||
</DeregisterInstancesFromLoadBalancerResult>
|
|
||||||
<ResponseMetadata>
|
|
||||||
<RequestId>83c88b9d-12b7-11e3-8b82-87b12EXAMPLE</RequestId>
|
|
||||||
</ResponseMetadata>
|
|
||||||
</DeregisterInstancesFromLoadBalancerResponse>
|
|
||||||
`
|
|
||||||
|
|
||||||
// http://docs.aws.amazon.com/ElasticLoadBalancing/latest/APIReference/API_ConfigureHealthCheck.html
|
|
||||||
var ConfigureHealthCheckExample = `
|
|
||||||
<ConfigureHealthCheckResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
|
||||||
<ConfigureHealthCheckResult>
|
|
||||||
<HealthCheck>
|
|
||||||
<Interval>30</Interval>
|
|
||||||
<Target>HTTP:80/ping</Target>
|
|
||||||
<HealthyThreshold>2</HealthyThreshold>
|
|
||||||
<Timeout>3</Timeout>
|
|
||||||
<UnhealthyThreshold>2</UnhealthyThreshold>
|
|
||||||
</HealthCheck>
|
|
||||||
</ConfigureHealthCheckResult>
|
|
||||||
<ResponseMetadata>
|
|
||||||
<RequestId>83c88b9d-12b7-11e3-8b82-87b12EXAMPLE</RequestId>
|
|
||||||
</ResponseMetadata>
|
|
||||||
</ConfigureHealthCheckResponse>`
|
|
||||||
|
|
||||||
// http://goo.gl/cGNxfj
|
|
||||||
var DescribeInstanceHealthExample = `
|
|
||||||
<DescribeInstanceHealthResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2012-06-01/">
|
|
||||||
<DescribeInstanceHealthResult>
|
|
||||||
<InstanceStates>
|
|
||||||
<member>
|
|
||||||
<Description>N/A</Description>
|
|
||||||
<InstanceId>i-90d8c2a5</InstanceId>
|
|
||||||
<State>InService</State>
|
|
||||||
<ReasonCode>N/A</ReasonCode>
|
|
||||||
</member>
|
|
||||||
<member>
|
|
||||||
<Description>N/A</Description>
|
|
||||||
<InstanceId>i-06ea3e60</InstanceId>
|
|
||||||
<State>OutOfService</State>
|
|
||||||
<ReasonCode>N/A</ReasonCode>
|
|
||||||
</member>
|
|
||||||
</InstanceStates>
|
|
||||||
</DescribeInstanceHealthResult>
|
|
||||||
<ResponseMetadata>
|
|
||||||
<RequestId>1549581b-12b7-11e3-895e-1334aEXAMPLE</RequestId>
|
|
||||||
</ResponseMetadata>
|
|
||||||
</DescribeInstanceHealthResponse>`
|
|
||||||
38
Godeps/_workspace/src/github.com/mitchellh/goamz/elb/sign.go
generated
vendored
38
Godeps/_workspace/src/github.com/mitchellh/goamz/elb/sign.go
generated
vendored
@@ -1,38 +0,0 @@
|
|||||||
package elb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
|
||||||
"github.com/mitchellh/goamz/aws"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Version 2 signing (http://goo.gl/RSRp5)
|
|
||||||
|
|
||||||
var b64 = base64.StdEncoding
|
|
||||||
|
|
||||||
func sign(auth aws.Auth, method, path string, params map[string]string, host string) {
|
|
||||||
params["AWSAccessKeyId"] = auth.AccessKey
|
|
||||||
params["SignatureVersion"] = "2"
|
|
||||||
params["SignatureMethod"] = "HmacSHA256"
|
|
||||||
if auth.Token != "" {
|
|
||||||
params["SecurityToken"] = auth.Token
|
|
||||||
}
|
|
||||||
|
|
||||||
var sarray []string
|
|
||||||
for k, v := range params {
|
|
||||||
sarray = append(sarray, aws.Encode(k)+"="+aws.Encode(v))
|
|
||||||
}
|
|
||||||
sort.StringSlice(sarray).Sort()
|
|
||||||
joined := strings.Join(sarray, "&")
|
|
||||||
payload := method + "\n" + host + "\n" + path + "\n" + joined
|
|
||||||
hash := hmac.New(sha256.New, []byte(auth.SecretKey))
|
|
||||||
hash.Write([]byte(payload))
|
|
||||||
signature := make([]byte, b64.EncodedLen(hash.Size()))
|
|
||||||
b64.Encode(signature, hash.Sum(nil))
|
|
||||||
|
|
||||||
params["Signature"] = string(signature)
|
|
||||||
}
|
|
||||||
14
Godeps/_workspace/src/golang.org/x/oauth2/.travis.yml
generated
vendored
Normal file
14
Godeps/_workspace/src/golang.org/x/oauth2/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.3
|
||||||
|
- 1.4
|
||||||
|
|
||||||
|
install:
|
||||||
|
- export GOPATH="$HOME/gopath"
|
||||||
|
- mkdir -p "$GOPATH/src/golang.org/x"
|
||||||
|
- mv "$TRAVIS_BUILD_DIR" "$GOPATH/src/golang.org/x/oauth2"
|
||||||
|
- go get -v -t -d -tags='appengine appenginevm' golang.org/x/oauth2/...
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v -tags='appengine appenginevm' golang.org/x/oauth2/...
|
||||||
3
Godeps/_workspace/src/golang.org/x/oauth2/AUTHORS
generated
vendored
Normal file
3
Godeps/_workspace/src/golang.org/x/oauth2/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# This source code refers to The Go Authors for copyright purposes.
|
||||||
|
# The master list of authors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/AUTHORS.
|
||||||
25
Godeps/_workspace/src/golang.org/x/oauth2/CONTRIBUTING.md
generated
vendored
Normal file
25
Godeps/_workspace/src/golang.org/x/oauth2/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
We don't use GitHub pull requests but use Gerrit for code reviews,
|
||||||
|
similar to the Go project.
|
||||||
|
|
||||||
|
1. Sign one of the contributor license agreements below.
|
||||||
|
2. `go get golang.org/x/review/git-codereview` to install the code reviewing tool.
|
||||||
|
3. Get the package by running `go get -d golang.org/x/oauth2`.
|
||||||
|
Make changes and create a change by running `git codereview change <name>`, provide a command message, and use `git codereview mail` to create a Gerrit CL.
|
||||||
|
Keep amending to the change and mail as your recieve feedback.
|
||||||
|
|
||||||
|
For more information about the workflow, see Go's [Contribution Guidelines](https://golang.org/doc/contribute.html).
|
||||||
|
|
||||||
|
Before we can accept any pull requests
|
||||||
|
we have to jump through a couple of legal hurdles,
|
||||||
|
primarily a Contributor License Agreement (CLA):
|
||||||
|
|
||||||
|
- **If you are an individual writing original source code**
|
||||||
|
and you're sure you own the intellectual property,
|
||||||
|
then you'll need to sign an [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html).
|
||||||
|
- **If you work for a company that wants to allow you to contribute your work**,
|
||||||
|
then you'll need to sign a [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html).
|
||||||
|
|
||||||
|
You can sign these electronically (just scroll to the bottom).
|
||||||
|
After that, we'll be able to accept your pull requests.
|
||||||
3
Godeps/_workspace/src/golang.org/x/oauth2/CONTRIBUTORS
generated
vendored
Normal file
3
Godeps/_workspace/src/golang.org/x/oauth2/CONTRIBUTORS
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# This source code was written by the Go contributors.
|
||||||
|
# The master list of contributors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/CONTRIBUTORS.
|
||||||
27
Godeps/_workspace/src/golang.org/x/oauth2/LICENSE
generated
vendored
Normal file
27
Godeps/_workspace/src/golang.org/x/oauth2/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The oauth2 Authors. 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.
|
||||||
18
Godeps/_workspace/src/golang.org/x/oauth2/README.md
generated
vendored
Normal file
18
Godeps/_workspace/src/golang.org/x/oauth2/README.md
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# OAuth2 for Go
|
||||||
|
|
||||||
|
[](https://travis-ci.org/golang/oauth2)
|
||||||
|
|
||||||
|
oauth2 package contains a client implementation for OAuth 2.0 spec.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
~~~~
|
||||||
|
go get golang.org/x/oauth2
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
See godoc for further documentation and examples.
|
||||||
|
|
||||||
|
* [godoc.org/golang.org/x/oauth2](http://godoc.org/golang.org/x/oauth2)
|
||||||
|
* [godoc.org/golang.org/x/oauth2/google](http://godoc.org/golang.org/x/oauth2/google)
|
||||||
|
|
||||||
|
|
||||||
39
Godeps/_workspace/src/golang.org/x/oauth2/client_appengine.go
generated
vendored
Normal file
39
Godeps/_workspace/src/golang.org/x/oauth2/client_appengine.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build appengine,!appenginevm
|
||||||
|
|
||||||
|
// App Engine hooks.
|
||||||
|
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"appengine"
|
||||||
|
"appengine/urlfetch"
|
||||||
|
)
|
||||||
|
|
||||||
|
var warnOnce sync.Once
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerContextClientFunc(contextClientAppEngine)
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextClientAppEngine(ctx Context) (*http.Client, error) {
|
||||||
|
if actx, ok := ctx.(appengine.Context); ok {
|
||||||
|
return urlfetch.Client(actx), nil
|
||||||
|
}
|
||||||
|
// The user did it wrong. We'll log once (and hope they see it
|
||||||
|
// in dev_appserver), but stil return (nil, nil) in case some
|
||||||
|
// other contextClientFunc hook finds a way to proceed.
|
||||||
|
warnOnce.Do(gaeDoingItWrongHelp)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gaeDoingItWrongHelp() {
|
||||||
|
log.Printf("WARNING: you attempted to use the oauth2 package without passing a valid appengine.Context or *http.Request as the oauth2.Context. App Engine requires that all service RPCs (including urlfetch) be associated with an *http.Request/appengine.Context.")
|
||||||
|
}
|
||||||
50
Godeps/_workspace/src/golang.org/x/oauth2/example_test.go
generated
vendored
Normal file
50
Godeps/_workspace/src/golang.org/x/oauth2/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package oauth2_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(jbd): Remove after Go 1.4.
|
||||||
|
// Related to https://codereview.appspot.com/107320046
|
||||||
|
func TestA(t *testing.T) {}
|
||||||
|
|
||||||
|
func ExampleConfig() {
|
||||||
|
conf := &oauth2.Config{
|
||||||
|
ClientID: "YOUR_CLIENT_ID",
|
||||||
|
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||||
|
Scopes: []string{"SCOPE1", "SCOPE2"},
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
AuthURL: "https://provider.com/o/oauth2/auth",
|
||||||
|
TokenURL: "https://provider.com/o/oauth2/token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect user to consent page to ask for permission
|
||||||
|
// for the scopes specified above.
|
||||||
|
url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
|
||||||
|
fmt.Printf("Visit the URL for the auth dialog: %v", url)
|
||||||
|
|
||||||
|
// Use the authorization code that is pushed to the redirect URL.
|
||||||
|
// NewTransportWithCode will do the handshake to retrieve
|
||||||
|
// an access token and initiate a Transport that is
|
||||||
|
// authorized and authenticated by the retrieved token.
|
||||||
|
var code string
|
||||||
|
if _, err := fmt.Scan(&code); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
tok, err := conf.Exchange(oauth2.NoContext, code)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := conf.Client(oauth2.NoContext, tok)
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
16
Godeps/_workspace/src/golang.org/x/oauth2/github/github.go
generated
vendored
Normal file
16
Godeps/_workspace/src/golang.org/x/oauth2/github/github.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package github provides constants for using OAuth2 to access Github.
|
||||||
|
package github
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint is Github's OAuth 2.0 endpoint.
|
||||||
|
var Endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://github.com/login/oauth/authorize",
|
||||||
|
TokenURL: "https://github.com/login/oauth/access_token",
|
||||||
|
}
|
||||||
37
Godeps/_workspace/src/golang.org/x/oauth2/google/appengine.go
generated
vendored
Normal file
37
Godeps/_workspace/src/golang.org/x/oauth2/google/appengine.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build appengine,!appenginevm
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"appengine"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AppEngineTokenSource returns a token source that fetches tokens
|
||||||
|
// issued to the current App Engine application's service account.
|
||||||
|
// If you are implementing a 3-legged OAuth 2.0 flow on App Engine
|
||||||
|
// that involves user accounts, see oauth2.Config instead.
|
||||||
|
//
|
||||||
|
// You are required to provide a valid appengine.Context as context.
|
||||||
|
func AppEngineTokenSource(ctx appengine.Context, scope ...string) oauth2.TokenSource {
|
||||||
|
return &appEngineTokenSource{
|
||||||
|
ctx: ctx,
|
||||||
|
scopes: scope,
|
||||||
|
fetcherFunc: aeFetcherFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var aeFetcherFunc = func(ctx oauth2.Context, scope ...string) (string, time.Time, error) {
|
||||||
|
c, ok := ctx.(appengine.Context)
|
||||||
|
if !ok {
|
||||||
|
return "", time.Time{}, errInvalidContext
|
||||||
|
}
|
||||||
|
return appengine.AccessToken(c, scope...)
|
||||||
|
}
|
||||||
36
Godeps/_workspace/src/golang.org/x/oauth2/google/appenginevm.go
generated
vendored
Normal file
36
Godeps/_workspace/src/golang.org/x/oauth2/google/appenginevm.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build appenginevm !appengine
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AppEngineTokenSource returns a token source that fetches tokens
|
||||||
|
// issued to the current App Engine application's service account.
|
||||||
|
// If you are implementing a 3-legged OAuth 2.0 flow on App Engine
|
||||||
|
// that involves user accounts, see oauth2.Config instead.
|
||||||
|
//
|
||||||
|
// You are required to provide a valid appengine.Context as context.
|
||||||
|
func AppEngineTokenSource(ctx appengine.Context, scope ...string) oauth2.TokenSource {
|
||||||
|
return &appEngineTokenSource{
|
||||||
|
ctx: ctx,
|
||||||
|
scopes: scope,
|
||||||
|
fetcherFunc: aeVMFetcherFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var aeVMFetcherFunc = func(ctx oauth2.Context, scope ...string) (string, time.Time, error) {
|
||||||
|
c, ok := ctx.(appengine.Context)
|
||||||
|
if !ok {
|
||||||
|
return "", time.Time{}, errInvalidContext
|
||||||
|
}
|
||||||
|
return appengine.AccessToken(c, scope...)
|
||||||
|
}
|
||||||
133
Godeps/_workspace/src/golang.org/x/oauth2/google/example_test.go
generated
vendored
Normal file
133
Godeps/_workspace/src/golang.org/x/oauth2/google/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build appenginevm !appengine
|
||||||
|
|
||||||
|
package google_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/google"
|
||||||
|
"golang.org/x/oauth2/jwt"
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
"google.golang.org/appengine/urlfetch"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Remove after Go 1.4.
|
||||||
|
// Related to https://codereview.appspot.com/107320046
|
||||||
|
func TestA(t *testing.T) {}
|
||||||
|
|
||||||
|
func Example_webServer() {
|
||||||
|
// Your credentials should be obtained from the Google
|
||||||
|
// Developer Console (https://console.developers.google.com).
|
||||||
|
conf := &oauth2.Config{
|
||||||
|
ClientID: "YOUR_CLIENT_ID",
|
||||||
|
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||||
|
RedirectURL: "YOUR_REDIRECT_URL",
|
||||||
|
Scopes: []string{
|
||||||
|
"https://www.googleapis.com/auth/bigquery",
|
||||||
|
"https://www.googleapis.com/auth/blogger",
|
||||||
|
},
|
||||||
|
Endpoint: google.Endpoint,
|
||||||
|
}
|
||||||
|
// Redirect user to Google's consent page to ask for permission
|
||||||
|
// for the scopes specified above.
|
||||||
|
url := conf.AuthCodeURL("state")
|
||||||
|
fmt.Printf("Visit the URL for the auth dialog: %v", url)
|
||||||
|
|
||||||
|
// Handle the exchange code to initiate a transport.
|
||||||
|
tok, err := conf.Exchange(oauth2.NoContext, "authorization-code")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
client := conf.Client(oauth2.NoContext, tok)
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleJWTConfigFromJSON() {
|
||||||
|
// Your credentials should be obtained from the Google
|
||||||
|
// Developer Console (https://console.developers.google.com).
|
||||||
|
// Navigate to your project, then see the "Credentials" page
|
||||||
|
// under "APIs & Auth".
|
||||||
|
// To create a service account client, click "Create new Client ID",
|
||||||
|
// select "Service Account", and click "Create Client ID". A JSON
|
||||||
|
// key file will then be downloaded to your computer.
|
||||||
|
data, err := ioutil.ReadFile("/path/to/your-project-key.json")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/bigquery")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// Initiate an http.Client. The following GET request will be
|
||||||
|
// authorized and authenticated on the behalf of
|
||||||
|
// your service account.
|
||||||
|
client := conf.Client(oauth2.NoContext)
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_serviceAccount() {
|
||||||
|
// Your credentials should be obtained from the Google
|
||||||
|
// Developer Console (https://console.developers.google.com).
|
||||||
|
conf := &jwt.Config{
|
||||||
|
Email: "xxx@developer.gserviceaccount.com",
|
||||||
|
// The contents of your RSA private key or your PEM file
|
||||||
|
// that contains a private key.
|
||||||
|
// If you have a p12 file instead, you
|
||||||
|
// can use `openssl` to export the private key into a pem file.
|
||||||
|
//
|
||||||
|
// $ openssl pkcs12 -in key.p12 -passin pass:notasecret -out key.pem -nodes
|
||||||
|
//
|
||||||
|
// The field only supports PEM containers with no passphrase.
|
||||||
|
// The openssl command will convert p12 keys to passphrase-less PEM containers.
|
||||||
|
PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."),
|
||||||
|
Scopes: []string{
|
||||||
|
"https://www.googleapis.com/auth/bigquery",
|
||||||
|
"https://www.googleapis.com/auth/blogger",
|
||||||
|
},
|
||||||
|
TokenURL: google.JWTTokenURL,
|
||||||
|
// If you would like to impersonate a user, you can
|
||||||
|
// create a transport with a subject. The following GET
|
||||||
|
// request will be made on the behalf of user@example.com.
|
||||||
|
// Optional.
|
||||||
|
Subject: "user@example.com",
|
||||||
|
}
|
||||||
|
// Initiate an http.Client, the following GET request will be
|
||||||
|
// authorized and authenticated on the behalf of user@example.com.
|
||||||
|
client := conf.Client(oauth2.NoContext)
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAppEngineTokenSource() {
|
||||||
|
var req *http.Request // from the ServeHTTP handler
|
||||||
|
ctx := appengine.NewContext(req)
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &oauth2.Transport{
|
||||||
|
Source: google.AppEngineTokenSource(ctx, "https://www.googleapis.com/auth/bigquery"),
|
||||||
|
Base: &urlfetch.Transport{
|
||||||
|
Context: ctx,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleComputeTokenSource() {
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &oauth2.Transport{
|
||||||
|
// Fetch from Google Compute Engine's metadata server to retrieve
|
||||||
|
// an access token for the provided account.
|
||||||
|
// If no account is specified, "default" is used.
|
||||||
|
Source: google.ComputeTokenSource(""),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
103
Godeps/_workspace/src/golang.org/x/oauth2/google/google.go
generated
vendored
Normal file
103
Godeps/_workspace/src/golang.org/x/oauth2/google/google.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package google provides support for making
|
||||||
|
// OAuth2 authorized and authenticated HTTP requests
|
||||||
|
// to Google APIs. It supports Web server, client-side,
|
||||||
|
// service accounts, Google Compute Engine service accounts,
|
||||||
|
// and Google App Engine service accounts authorization
|
||||||
|
// and authentications flows:
|
||||||
|
//
|
||||||
|
// For more information, please read
|
||||||
|
// https://developers.google.com/accounts/docs/OAuth2.
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/jwt"
|
||||||
|
"google.golang.org/cloud/compute/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(bradfitz,jbd): import "google.golang.org/cloud/compute/metadata" instead of
|
||||||
|
// the metaClient and metadata.google.internal stuff below.
|
||||||
|
|
||||||
|
// Endpoint is Google's OAuth 2.0 endpoint.
|
||||||
|
var Endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
TokenURL: "https://accounts.google.com/o/oauth2/token",
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow.
|
||||||
|
const JWTTokenURL = "https://accounts.google.com/o/oauth2/token"
|
||||||
|
|
||||||
|
// JWTConfigFromJSON uses a Google Developers service account JSON key file to read
|
||||||
|
// the credentials that authorize and authenticate the requests.
|
||||||
|
// Create a service account on "Credentials" page under "APIs & Auth" for your
|
||||||
|
// project at https://console.developers.google.com to download a JSON key file.
|
||||||
|
func JWTConfigFromJSON(jsonKey []byte, scope ...string) (*jwt.Config, error) {
|
||||||
|
var key struct {
|
||||||
|
Email string `json:"client_email"`
|
||||||
|
PrivateKey string `json:"private_key"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(jsonKey, &key); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &jwt.Config{
|
||||||
|
Email: key.Email,
|
||||||
|
PrivateKey: []byte(key.PrivateKey),
|
||||||
|
Scopes: scope,
|
||||||
|
TokenURL: JWTTokenURL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComputeTokenSource returns a token source that fetches access tokens
|
||||||
|
// from Google Compute Engine (GCE)'s metadata server. It's only valid to use
|
||||||
|
// this token source if your program is running on a GCE instance.
|
||||||
|
// If no account is specified, "default" is used.
|
||||||
|
// Further information about retrieving access tokens from the GCE metadata
|
||||||
|
// server can be found at https://cloud.google.com/compute/docs/authentication.
|
||||||
|
func ComputeTokenSource(account string) oauth2.TokenSource {
|
||||||
|
return oauth2.ReuseTokenSource(nil, computeSource{account: account})
|
||||||
|
}
|
||||||
|
|
||||||
|
type computeSource struct {
|
||||||
|
account string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs computeSource) Token() (*oauth2.Token, error) {
|
||||||
|
if !metadata.OnGCE() {
|
||||||
|
return nil, errors.New("oauth2/google: can't get a token from the metadata service; not running on GCE")
|
||||||
|
}
|
||||||
|
acct := cs.account
|
||||||
|
if acct == "" {
|
||||||
|
acct = "default"
|
||||||
|
}
|
||||||
|
tokenJSON, err := metadata.Get("instance/service-accounts/" + acct + "/token")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var res struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresInSec int `json:"expires_in"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: invalid token JSON from metadata: %v", err)
|
||||||
|
}
|
||||||
|
if res.ExpiresInSec == 0 || res.AccessToken == "" {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: incomplete token received from metadata")
|
||||||
|
}
|
||||||
|
return &oauth2.Token{
|
||||||
|
AccessToken: res.AccessToken,
|
||||||
|
TokenType: res.TokenType,
|
||||||
|
Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
71
Godeps/_workspace/src/golang.org/x/oauth2/google/source_appengine.go
generated
vendored
Normal file
71
Godeps/_workspace/src/golang.org/x/oauth2/google/source_appengine.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
aeTokensMu sync.Mutex // guards aeTokens and appEngineTokenSource.key
|
||||||
|
|
||||||
|
// aeTokens helps the fetched tokens to be reused until their expiration.
|
||||||
|
aeTokens = make(map[string]*tokenLock) // key is '\0'-separated scopes
|
||||||
|
)
|
||||||
|
|
||||||
|
var errInvalidContext = errors.New("oauth2: a valid appengine.Context is required")
|
||||||
|
|
||||||
|
type tokenLock struct {
|
||||||
|
mu sync.Mutex // guards t; held while updating t
|
||||||
|
t *oauth2.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type appEngineTokenSource struct {
|
||||||
|
ctx oauth2.Context
|
||||||
|
|
||||||
|
// fetcherFunc makes the actual RPC to fetch a new access
|
||||||
|
// token with an expiry time. Provider of this function is
|
||||||
|
// responsible to assert that the given context is valid.
|
||||||
|
fetcherFunc func(ctx oauth2.Context, scope ...string) (accessToken string, expiry time.Time, err error)
|
||||||
|
|
||||||
|
// scopes and key are guarded by the package-level mutex aeTokensMu
|
||||||
|
scopes []string
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *appEngineTokenSource) Token() (*oauth2.Token, error) {
|
||||||
|
aeTokensMu.Lock()
|
||||||
|
if ts.key == "" {
|
||||||
|
sort.Sort(sort.StringSlice(ts.scopes))
|
||||||
|
ts.key = strings.Join(ts.scopes, string(0))
|
||||||
|
}
|
||||||
|
tok, ok := aeTokens[ts.key]
|
||||||
|
if !ok {
|
||||||
|
tok = &tokenLock{}
|
||||||
|
aeTokens[ts.key] = tok
|
||||||
|
}
|
||||||
|
aeTokensMu.Unlock()
|
||||||
|
|
||||||
|
tok.mu.Lock()
|
||||||
|
defer tok.mu.Unlock()
|
||||||
|
if tok.t.Valid() {
|
||||||
|
return tok.t, nil
|
||||||
|
}
|
||||||
|
access, exp, err := ts.fetcherFunc(ts.ctx, ts.scopes...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tok.t = &oauth2.Token{
|
||||||
|
AccessToken: access,
|
||||||
|
Expiry: exp,
|
||||||
|
}
|
||||||
|
return tok.t, nil
|
||||||
|
}
|
||||||
37
Godeps/_workspace/src/golang.org/x/oauth2/internal/oauth2.go
generated
vendored
Normal file
37
Godeps/_workspace/src/golang.org/x/oauth2/internal/oauth2.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package internal contains support packages for oauth2 package.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseKey converts the binary contents of a private key file
|
||||||
|
// to an *rsa.PrivateKey. It detects whether the private key is in a
|
||||||
|
// PEM container or not. If so, it extracts the the private key
|
||||||
|
// from PEM container before conversion. It only supports PEM
|
||||||
|
// containers with no passphrase.
|
||||||
|
func ParseKey(key []byte) (*rsa.PrivateKey, error) {
|
||||||
|
block, _ := pem.Decode(key)
|
||||||
|
if block != nil {
|
||||||
|
key = block.Bytes
|
||||||
|
}
|
||||||
|
parsedKey, err := x509.ParsePKCS8PrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
parsedKey, err = x509.ParsePKCS1PrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsed, ok := parsedKey.(*rsa.PrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("oauth2: private key is invalid")
|
||||||
|
}
|
||||||
|
return parsed, nil
|
||||||
|
}
|
||||||
160
Godeps/_workspace/src/golang.org/x/oauth2/jws/jws.go
generated
vendored
Normal file
160
Godeps/_workspace/src/golang.org/x/oauth2/jws/jws.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package jws provides encoding and decoding utilities for
|
||||||
|
// signed JWS messages.
|
||||||
|
package jws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClaimSet contains information about the JWT signature including the
|
||||||
|
// permissions being requested (scopes), the target of the token, the issuer,
|
||||||
|
// the time the token was issued, and the lifetime of the token.
|
||||||
|
type ClaimSet struct {
|
||||||
|
Iss string `json:"iss"` // email address of the client_id of the application making the access token request
|
||||||
|
Scope string `json:"scope,omitempty"` // space-delimited list of the permissions the application requests
|
||||||
|
Aud string `json:"aud"` // descriptor of the intended target of the assertion (Optional).
|
||||||
|
Exp int64 `json:"exp"` // the expiration time of the assertion
|
||||||
|
Iat int64 `json:"iat"` // the time the assertion was issued.
|
||||||
|
Typ string `json:"typ,omitempty"` // token type (Optional).
|
||||||
|
|
||||||
|
// Email for which the application is requesting delegated access (Optional).
|
||||||
|
Sub string `json:"sub,omitempty"`
|
||||||
|
|
||||||
|
// The old name of Sub. Client keeps setting Prn to be
|
||||||
|
// complaint with legacy OAuth 2.0 providers. (Optional)
|
||||||
|
Prn string `json:"prn,omitempty"`
|
||||||
|
|
||||||
|
// See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3
|
||||||
|
// This array is marshalled using custom code (see (c *ClaimSet) encode()).
|
||||||
|
PrivateClaims map[string]interface{} `json:"-"`
|
||||||
|
|
||||||
|
exp time.Time
|
||||||
|
iat time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClaimSet) encode() (string, error) {
|
||||||
|
if c.exp.IsZero() || c.iat.IsZero() {
|
||||||
|
// Reverting time back for machines whose time is not perfectly in sync.
|
||||||
|
// If client machine's time is in the future according
|
||||||
|
// to Google servers, an access token will not be issued.
|
||||||
|
now := time.Now().Add(-10 * time.Second)
|
||||||
|
c.iat = now
|
||||||
|
c.exp = now.Add(time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Exp = c.exp.Unix()
|
||||||
|
c.Iat = c.iat.Unix()
|
||||||
|
|
||||||
|
b, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.PrivateClaims) == 0 {
|
||||||
|
return base64Encode(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal private claim set and then append it to b.
|
||||||
|
prv, err := json.Marshal(c.PrivateClaims)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("jws: invalid map of private claims %v", c.PrivateClaims)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concatenate public and private claim JSON objects.
|
||||||
|
if !bytes.HasSuffix(b, []byte{'}'}) {
|
||||||
|
return "", fmt.Errorf("jws: invalid JSON %s", b)
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(prv, []byte{'{'}) {
|
||||||
|
return "", fmt.Errorf("jws: invalid JSON %s", prv)
|
||||||
|
}
|
||||||
|
b[len(b)-1] = ',' // Replace closing curly brace with a comma.
|
||||||
|
b = append(b, prv[1:]...) // Append private claims.
|
||||||
|
return base64Encode(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header represents the header for the signed JWS payloads.
|
||||||
|
type Header struct {
|
||||||
|
// The algorithm used for signature.
|
||||||
|
Algorithm string `json:"alg"`
|
||||||
|
|
||||||
|
// Represents the token type.
|
||||||
|
Typ string `json:"typ"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Header) encode() (string, error) {
|
||||||
|
b, err := json.Marshal(h)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64Encode(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes a claim set from a JWS payload.
|
||||||
|
func Decode(payload string) (*ClaimSet, error) {
|
||||||
|
// decode returned id token to get expiry
|
||||||
|
s := strings.Split(payload, ".")
|
||||||
|
if len(s) < 2 {
|
||||||
|
// TODO(jbd): Provide more context about the error.
|
||||||
|
return nil, errors.New("jws: invalid token received")
|
||||||
|
}
|
||||||
|
decoded, err := base64Decode(s[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c := &ClaimSet{}
|
||||||
|
err = json.NewDecoder(bytes.NewBuffer(decoded)).Decode(c)
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encodes a signed JWS with provided header and claim set.
|
||||||
|
func Encode(header *Header, c *ClaimSet, signature *rsa.PrivateKey) (string, error) {
|
||||||
|
head, err := header.encode()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cs, err := c.encode()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
ss := fmt.Sprintf("%s.%s", head, cs)
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write([]byte(ss))
|
||||||
|
b, err := rsa.SignPKCS1v15(rand.Reader, signature, crypto.SHA256, h.Sum(nil))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
sig := base64Encode(b)
|
||||||
|
return fmt.Sprintf("%s.%s", ss, sig), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// base64Encode returns and Base64url encoded version of the input string with any
|
||||||
|
// trailing "=" stripped.
|
||||||
|
func base64Encode(b []byte) string {
|
||||||
|
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
|
||||||
|
}
|
||||||
|
|
||||||
|
// base64Decode decodes the Base64url encoded string
|
||||||
|
func base64Decode(s string) ([]byte, error) {
|
||||||
|
// add back missing padding
|
||||||
|
switch len(s) % 4 {
|
||||||
|
case 2:
|
||||||
|
s += "=="
|
||||||
|
case 3:
|
||||||
|
s += "="
|
||||||
|
}
|
||||||
|
return base64.URLEncoding.DecodeString(s)
|
||||||
|
}
|
||||||
31
Godeps/_workspace/src/golang.org/x/oauth2/jwt/example_test.go
generated
vendored
Normal file
31
Godeps/_workspace/src/golang.org/x/oauth2/jwt/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package jwt_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/jwt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleJWTConfig() {
|
||||||
|
conf := &jwt.Config{
|
||||||
|
Email: "xxx@developer.com",
|
||||||
|
// The contents of your RSA private key or your PEM file
|
||||||
|
// that contains a private key.
|
||||||
|
// If you have a p12 file instead, you
|
||||||
|
// can use `openssl` to export the private key into a pem file.
|
||||||
|
//
|
||||||
|
// $ openssl pkcs12 -in key.p12 -out key.pem -nodes
|
||||||
|
//
|
||||||
|
// It only supports PEM containers with no passphrase.
|
||||||
|
PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."),
|
||||||
|
Subject: "user@example.com",
|
||||||
|
TokenURL: "https://provider.com/o/oauth2/token",
|
||||||
|
}
|
||||||
|
// Initiate an http.Client, the following GET request will be
|
||||||
|
// authorized and authenticated on the behalf of user@example.com.
|
||||||
|
client := conf.Client(oauth2.NoContext)
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
146
Godeps/_workspace/src/golang.org/x/oauth2/jwt/jwt.go
generated
vendored
Normal file
146
Godeps/_workspace/src/golang.org/x/oauth2/jwt/jwt.go
generated
vendored
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package jwt implements the OAuth 2.0 JSON Web Token flow, commonly
|
||||||
|
// known as "two-legged OAuth 2.0".
|
||||||
|
//
|
||||||
|
// See: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/internal"
|
||||||
|
"golang.org/x/oauth2/jws"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||||
|
defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is the configuration for using JWT to fetch tokens,
|
||||||
|
// commonly known as "two-legged OAuth 2.0".
|
||||||
|
type Config struct {
|
||||||
|
// Email is the OAuth client identifier used when communicating with
|
||||||
|
// the configured OAuth provider.
|
||||||
|
Email string
|
||||||
|
|
||||||
|
// PrivateKey contains the contents of an RSA private key or the
|
||||||
|
// contents of a PEM file that contains a private key. The provided
|
||||||
|
// private key is used to sign JWT payloads.
|
||||||
|
// PEM containers with a passphrase are not supported.
|
||||||
|
// Use the following command to convert a PKCS 12 file into a PEM.
|
||||||
|
//
|
||||||
|
// $ openssl pkcs12 -in key.p12 -out key.pem -nodes
|
||||||
|
//
|
||||||
|
PrivateKey []byte
|
||||||
|
|
||||||
|
// Subject is the optional user to impersonate.
|
||||||
|
Subject string
|
||||||
|
|
||||||
|
// Scopes optionally specifies a list of requested permission scopes.
|
||||||
|
Scopes []string
|
||||||
|
|
||||||
|
// TokenURL is the endpoint required to complete the 2-legged JWT flow.
|
||||||
|
TokenURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenSource returns a JWT TokenSource using the configuration
|
||||||
|
// in c and the HTTP client from the provided context.
|
||||||
|
func (c *Config) TokenSource(ctx oauth2.Context) oauth2.TokenSource {
|
||||||
|
return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client returns an HTTP client wrapping the context's
|
||||||
|
// HTTP transport and adding Authorization headers with tokens
|
||||||
|
// obtained from c.
|
||||||
|
//
|
||||||
|
// The returned client and its Transport should not be modified.
|
||||||
|
func (c *Config) Client(ctx oauth2.Context) *http.Client {
|
||||||
|
return oauth2.NewClient(ctx, c.TokenSource(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// jwtSource is a source that always does a signed JWT request for a token.
|
||||||
|
// It should typically be wrapped with a reuseTokenSource.
|
||||||
|
type jwtSource struct {
|
||||||
|
ctx oauth2.Context
|
||||||
|
conf *Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (js jwtSource) Token() (*oauth2.Token, error) {
|
||||||
|
pk, err := internal.ParseKey(js.conf.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hc := oauth2.NewClient(js.ctx, nil)
|
||||||
|
claimSet := &jws.ClaimSet{
|
||||||
|
Iss: js.conf.Email,
|
||||||
|
Scope: strings.Join(js.conf.Scopes, " "),
|
||||||
|
Aud: js.conf.TokenURL,
|
||||||
|
}
|
||||||
|
if subject := js.conf.Subject; subject != "" {
|
||||||
|
claimSet.Sub = subject
|
||||||
|
// prn is the old name of sub. Keep setting it
|
||||||
|
// to be compatible with legacy OAuth 2.0 providers.
|
||||||
|
claimSet.Prn = subject
|
||||||
|
}
|
||||||
|
payload, err := jws.Encode(defaultHeader, claimSet, pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("grant_type", defaultGrantType)
|
||||||
|
v.Set("assertion", payload)
|
||||||
|
resp, err := hc.PostForm(js.conf.TokenURL, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||||
|
}
|
||||||
|
if c := resp.StatusCode; c < 200 || c > 299 {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", resp.Status, body)
|
||||||
|
}
|
||||||
|
// tokenRes is the JSON response body.
|
||||||
|
var tokenRes struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
IDToken string `json:"id_token"`
|
||||||
|
ExpiresIn int64 `json:"expires_in"` // relative seconds from now
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &tokenRes); err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||||
|
}
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: tokenRes.AccessToken,
|
||||||
|
TokenType: tokenRes.TokenType,
|
||||||
|
}
|
||||||
|
raw := make(map[string]interface{})
|
||||||
|
json.Unmarshal(body, &raw) // no error checks for optional fields
|
||||||
|
token = token.WithExtra(raw)
|
||||||
|
|
||||||
|
if secs := tokenRes.ExpiresIn; secs > 0 {
|
||||||
|
token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
|
||||||
|
}
|
||||||
|
if v := tokenRes.IDToken; v != "" {
|
||||||
|
// decode returned id token to get expiry
|
||||||
|
claimSet, err := jws.Decode(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: error decoding JWT token: %v", err)
|
||||||
|
}
|
||||||
|
token.Expiry = time.Unix(claimSet.Exp, 0)
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
134
Godeps/_workspace/src/golang.org/x/oauth2/jwt/jwt_test.go
generated
vendored
Normal file
134
Godeps/_workspace/src/golang.org/x/oauth2/jwt/jwt_test.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dummyPrivateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEAx4fm7dngEmOULNmAs1IGZ9Apfzh+BkaQ1dzkmbUgpcoghucE
|
||||||
|
DZRnAGd2aPyB6skGMXUytWQvNYav0WTR00wFtX1ohWTfv68HGXJ8QXCpyoSKSSFY
|
||||||
|
fuP9X36wBSkSX9J5DVgiuzD5VBdzUISSmapjKm+DcbRALjz6OUIPEWi1Tjl6p5RK
|
||||||
|
1w41qdbmt7E5/kGhKLDuT7+M83g4VWhgIvaAXtnhklDAggilPPa8ZJ1IFe31lNlr
|
||||||
|
k4DRk38nc6sEutdf3RL7QoH7FBusI7uXV03DC6dwN1kP4GE7bjJhcRb/7jYt7CQ9
|
||||||
|
/E9Exz3c0yAp0yrTg0Fwh+qxfH9dKwN52S7SBwIDAQABAoIBAQCaCs26K07WY5Jt
|
||||||
|
3a2Cw3y2gPrIgTCqX6hJs7O5ByEhXZ8nBwsWANBUe4vrGaajQHdLj5OKfsIDrOvn
|
||||||
|
2NI1MqflqeAbu/kR32q3tq8/Rl+PPiwUsW3E6Pcf1orGMSNCXxeducF2iySySzh3
|
||||||
|
nSIhCG5uwJDWI7a4+9KiieFgK1pt/Iv30q1SQS8IEntTfXYwANQrfKUVMmVF9aIK
|
||||||
|
6/WZE2yd5+q3wVVIJ6jsmTzoDCX6QQkkJICIYwCkglmVy5AeTckOVwcXL0jqw5Kf
|
||||||
|
5/soZJQwLEyBoQq7Kbpa26QHq+CJONetPP8Ssy8MJJXBT+u/bSseMb3Zsr5cr43e
|
||||||
|
DJOhwsThAoGBAPY6rPKl2NT/K7XfRCGm1sbWjUQyDShscwuWJ5+kD0yudnT/ZEJ1
|
||||||
|
M3+KS/iOOAoHDdEDi9crRvMl0UfNa8MAcDKHflzxg2jg/QI+fTBjPP5GOX0lkZ9g
|
||||||
|
z6VePoVoQw2gpPFVNPPTxKfk27tEzbaffvOLGBEih0Kb7HTINkW8rIlzAoGBAM9y
|
||||||
|
1yr+jvfS1cGFtNU+Gotoihw2eMKtIqR03Yn3n0PK1nVCDKqwdUqCypz4+ml6cxRK
|
||||||
|
J8+Pfdh7D+ZJd4LEG6Y4QRDLuv5OA700tUoSHxMSNn3q9As4+T3MUyYxWKvTeu3U
|
||||||
|
f2NWP9ePU0lV8ttk7YlpVRaPQmc1qwooBA/z/8AdAoGAW9x0HWqmRICWTBnpjyxx
|
||||||
|
QGlW9rQ9mHEtUotIaRSJ6K/F3cxSGUEkX1a3FRnp6kPLcckC6NlqdNgNBd6rb2rA
|
||||||
|
cPl/uSkZP42Als+9YMoFPU/xrrDPbUhu72EDrj3Bllnyb168jKLa4VBOccUvggxr
|
||||||
|
Dm08I1hgYgdN5huzs7y6GeUCgYEAj+AZJSOJ6o1aXS6rfV3mMRve9bQ9yt8jcKXw
|
||||||
|
5HhOCEmMtaSKfnOF1Ziih34Sxsb7O2428DiX0mV/YHtBnPsAJidL0SdLWIapBzeg
|
||||||
|
KHArByIRkwE6IvJvwpGMdaex1PIGhx5i/3VZL9qiq/ElT05PhIb+UXgoWMabCp84
|
||||||
|
OgxDK20CgYAeaFo8BdQ7FmVX2+EEejF+8xSge6WVLtkaon8bqcn6P0O8lLypoOhd
|
||||||
|
mJAYH8WU+UAy9pecUnDZj14LAGNVmYcse8HFX71MoshnvCTFEPVo4rZxIAGwMpeJ
|
||||||
|
5jgQ3slYLpqrGlcbLgUXBUgzEO684Wk/UV9DFPlHALVqCfXQ9dpJPg==
|
||||||
|
-----END RSA PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
func TestJWTFetch_JSONResponse(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{
|
||||||
|
"access_token": "90d64460d14870c08c81352a05dedd3465940a7c",
|
||||||
|
"scope": "user",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"expires_in": 3600
|
||||||
|
}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
conf := &Config{
|
||||||
|
Email: "aaa@xxx.com",
|
||||||
|
PrivateKey: dummyPrivateKey,
|
||||||
|
TokenURL: ts.URL,
|
||||||
|
}
|
||||||
|
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !tok.Valid() {
|
||||||
|
t.Errorf("Token invalid")
|
||||||
|
}
|
||||||
|
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
|
||||||
|
t.Errorf("Unexpected access token, %#v", tok.AccessToken)
|
||||||
|
}
|
||||||
|
if tok.TokenType != "bearer" {
|
||||||
|
t.Errorf("Unexpected token type, %#v", tok.TokenType)
|
||||||
|
}
|
||||||
|
if tok.Expiry.IsZero() {
|
||||||
|
t.Errorf("Unexpected token expiry, %#v", tok.Expiry)
|
||||||
|
}
|
||||||
|
scope := tok.Extra("scope")
|
||||||
|
if scope != "user" {
|
||||||
|
t.Errorf("Unexpected value for scope: %v", scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJWTFetch_BadResponse(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"scope": "user", "token_type": "bearer"}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
conf := &Config{
|
||||||
|
Email: "aaa@xxx.com",
|
||||||
|
PrivateKey: dummyPrivateKey,
|
||||||
|
TokenURL: ts.URL,
|
||||||
|
}
|
||||||
|
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if tok == nil {
|
||||||
|
t.Fatalf("token is nil")
|
||||||
|
}
|
||||||
|
if tok.Valid() {
|
||||||
|
t.Errorf("token is valid. want invalid.")
|
||||||
|
}
|
||||||
|
if tok.AccessToken != "" {
|
||||||
|
t.Errorf("Unexpected non-empty access token %q.", tok.AccessToken)
|
||||||
|
}
|
||||||
|
if want := "bearer"; tok.TokenType != want {
|
||||||
|
t.Errorf("TokenType = %q; want %q", tok.TokenType, want)
|
||||||
|
}
|
||||||
|
scope := tok.Extra("scope")
|
||||||
|
if want := "user"; scope != want {
|
||||||
|
t.Errorf("token scope = %q; want %q", scope, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJWTFetch_BadResponseType(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := &Config{
|
||||||
|
Email: "aaa@xxx.com",
|
||||||
|
PrivateKey: dummyPrivateKey,
|
||||||
|
TokenURL: ts.URL,
|
||||||
|
}
|
||||||
|
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("got a token; expected error")
|
||||||
|
if tok.AccessToken != "" {
|
||||||
|
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
462
Godeps/_workspace/src/golang.org/x/oauth2/oauth2.go
generated
vendored
Normal file
462
Godeps/_workspace/src/golang.org/x/oauth2/oauth2.go
generated
vendored
Normal file
@@ -0,0 +1,462 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package oauth2 provides support for making
|
||||||
|
// OAuth2 authorized and authenticated HTTP requests.
|
||||||
|
// It can additionally grant authorization with Bearer JWT.
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Context can be an golang.org/x/net.Context, or an App Engine Context.
|
||||||
|
// If you don't care and aren't running on App Engine, you may use NoContext.
|
||||||
|
type Context interface{}
|
||||||
|
|
||||||
|
// NoContext is the default context. If you're not running this code
|
||||||
|
// on App Engine or not using golang.org/x/net.Context to provide a custom
|
||||||
|
// HTTP client, you should use NoContext.
|
||||||
|
var NoContext Context = nil
|
||||||
|
|
||||||
|
// Config describes a typical 3-legged OAuth2 flow, with both the
|
||||||
|
// client application information and the server's endpoint URLs.
|
||||||
|
type Config struct {
|
||||||
|
// ClientID is the application's ID.
|
||||||
|
ClientID string
|
||||||
|
|
||||||
|
// ClientSecret is the application's secret.
|
||||||
|
ClientSecret string
|
||||||
|
|
||||||
|
// Endpoint contains the resource server's token endpoint
|
||||||
|
// URLs. These are constants specific to each server and are
|
||||||
|
// often available via site-specific packages, such as
|
||||||
|
// google.Endpoint or github.Endpoint.
|
||||||
|
Endpoint Endpoint
|
||||||
|
|
||||||
|
// RedirectURL is the URL to redirect users going through
|
||||||
|
// the OAuth flow, after the resource owner's URLs.
|
||||||
|
RedirectURL string
|
||||||
|
|
||||||
|
// Scope specifies optional requested permissions.
|
||||||
|
Scopes []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// A TokenSource is anything that can return a token.
|
||||||
|
type TokenSource interface {
|
||||||
|
// Token returns a token or an error.
|
||||||
|
// Token must be safe for concurrent use by multiple goroutines.
|
||||||
|
// The returned Token must not be modified.
|
||||||
|
Token() (*Token, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint contains the OAuth 2.0 provider's authorization and token
|
||||||
|
// endpoint URLs.
|
||||||
|
type Endpoint struct {
|
||||||
|
AuthURL string
|
||||||
|
TokenURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// AccessTypeOnline and AccessTypeOffline are options passed
|
||||||
|
// to the Options.AuthCodeURL method. They modify the
|
||||||
|
// "access_type" field that gets sent in the URL returned by
|
||||||
|
// AuthCodeURL.
|
||||||
|
//
|
||||||
|
// Online (the default if neither is specified) is the default.
|
||||||
|
// If your application needs to refresh access tokens when the
|
||||||
|
// user is not present at the browser, then use offline. This
|
||||||
|
// will result in your application obtaining a refresh token
|
||||||
|
// the first time your application exchanges an authorization
|
||||||
|
// code for a user.
|
||||||
|
AccessTypeOnline AuthCodeOption = setParam{"access_type", "online"}
|
||||||
|
AccessTypeOffline AuthCodeOption = setParam{"access_type", "offline"}
|
||||||
|
|
||||||
|
// ApprovalForce forces the users to view the consent dialog
|
||||||
|
// and confirm the permissions request at the URL returned
|
||||||
|
// from AuthCodeURL, even if they've already done so.
|
||||||
|
ApprovalForce AuthCodeOption = setParam{"approval_prompt", "force"}
|
||||||
|
)
|
||||||
|
|
||||||
|
type setParam struct{ k, v string }
|
||||||
|
|
||||||
|
func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) }
|
||||||
|
|
||||||
|
// An AuthCodeOption is passed to Config.AuthCodeURL.
|
||||||
|
type AuthCodeOption interface {
|
||||||
|
setValue(url.Values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthCodeURL returns a URL to OAuth 2.0 provider's consent page
|
||||||
|
// that asks for permissions for the required scopes explicitly.
|
||||||
|
//
|
||||||
|
// State is a token to protect the user from CSRF attacks. You must
|
||||||
|
// always provide a non-zero string and validate that it matches the
|
||||||
|
// the state query parameter on your redirect callback.
|
||||||
|
// See http://tools.ietf.org/html/rfc6749#section-10.12 for more info.
|
||||||
|
//
|
||||||
|
// Opts may include AccessTypeOnline or AccessTypeOffline, as well
|
||||||
|
// as ApprovalForce.
|
||||||
|
func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString(c.Endpoint.AuthURL)
|
||||||
|
v := url.Values{
|
||||||
|
"response_type": {"code"},
|
||||||
|
"client_id": {c.ClientID},
|
||||||
|
"redirect_uri": condVal(c.RedirectURL),
|
||||||
|
"scope": condVal(strings.Join(c.Scopes, " ")),
|
||||||
|
"state": condVal(state),
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt.setValue(v)
|
||||||
|
}
|
||||||
|
if strings.Contains(c.Endpoint.AuthURL, "?") {
|
||||||
|
buf.WriteByte('&')
|
||||||
|
} else {
|
||||||
|
buf.WriteByte('?')
|
||||||
|
}
|
||||||
|
buf.WriteString(v.Encode())
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange converts an authorization code into a token.
|
||||||
|
//
|
||||||
|
// It is used after a resource provider redirects the user back
|
||||||
|
// to the Redirect URI (the URL obtained from AuthCodeURL).
|
||||||
|
//
|
||||||
|
// The HTTP client to use is derived from the context. If nil,
|
||||||
|
// http.DefaultClient is used. See the Context type's documentation.
|
||||||
|
//
|
||||||
|
// The code will be in the *http.Request.FormValue("code"). Before
|
||||||
|
// calling Exchange, be sure to validate FormValue("state").
|
||||||
|
func (c *Config) Exchange(ctx Context, code string) (*Token, error) {
|
||||||
|
return retrieveToken(ctx, c, url.Values{
|
||||||
|
"grant_type": {"authorization_code"},
|
||||||
|
"code": {code},
|
||||||
|
"redirect_uri": condVal(c.RedirectURL),
|
||||||
|
"scope": condVal(strings.Join(c.Scopes, " ")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// contextClientFunc is a func which tries to return an *http.Client
|
||||||
|
// given a Context value. If it returns an error, the search stops
|
||||||
|
// with that error. If it returns (nil, nil), the search continues
|
||||||
|
// down the list of registered funcs.
|
||||||
|
type contextClientFunc func(Context) (*http.Client, error)
|
||||||
|
|
||||||
|
var contextClientFuncs []contextClientFunc
|
||||||
|
|
||||||
|
func registerContextClientFunc(fn contextClientFunc) {
|
||||||
|
contextClientFuncs = append(contextClientFuncs, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextClient(ctx Context) (*http.Client, error) {
|
||||||
|
for _, fn := range contextClientFuncs {
|
||||||
|
c, err := fn(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if xc, ok := ctx.(context.Context); ok {
|
||||||
|
if hc, ok := xc.Value(HTTPClient).(*http.Client); ok {
|
||||||
|
return hc, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http.DefaultClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextTransport(ctx Context) http.RoundTripper {
|
||||||
|
hc, err := contextClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
// This is a rare error case (somebody using nil on App Engine),
|
||||||
|
// so I'd rather not everybody do an error check on this Client
|
||||||
|
// method. They can get the error that they're doing it wrong
|
||||||
|
// later, at client.Get/PostForm time.
|
||||||
|
return errorTransport{err}
|
||||||
|
}
|
||||||
|
return hc.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client returns an HTTP client using the provided token.
|
||||||
|
// The token will auto-refresh as necessary. The underlying
|
||||||
|
// HTTP transport will be obtained using the provided context.
|
||||||
|
// The returned client and its Transport should not be modified.
|
||||||
|
func (c *Config) Client(ctx Context, t *Token) *http.Client {
|
||||||
|
return NewClient(ctx, c.TokenSource(ctx, t))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenSource returns a TokenSource that returns t until t expires,
|
||||||
|
// automatically refreshing it as necessary using the provided context.
|
||||||
|
// See the the Context documentation.
|
||||||
|
//
|
||||||
|
// Most users will use Config.Client instead.
|
||||||
|
func (c *Config) TokenSource(ctx Context, t *Token) TokenSource {
|
||||||
|
nwn := &reuseTokenSource{t: t}
|
||||||
|
nwn.new = tokenRefresher{
|
||||||
|
ctx: ctx,
|
||||||
|
conf: c,
|
||||||
|
oldToken: &nwn.t,
|
||||||
|
}
|
||||||
|
return nwn
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenRefresher is a TokenSource that makes "grant_type"=="refresh_token"
|
||||||
|
// HTTP requests to renew a token using a RefreshToken.
|
||||||
|
type tokenRefresher struct {
|
||||||
|
ctx Context // used to get HTTP requests
|
||||||
|
conf *Config
|
||||||
|
oldToken **Token // pointer to old *Token w/ RefreshToken
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tf tokenRefresher) Token() (*Token, error) {
|
||||||
|
t := *tf.oldToken
|
||||||
|
if t == nil {
|
||||||
|
return nil, errors.New("oauth2: attempted use of nil Token")
|
||||||
|
}
|
||||||
|
if t.RefreshToken == "" {
|
||||||
|
return nil, errors.New("oauth2: token expired and refresh token is not set")
|
||||||
|
}
|
||||||
|
return retrieveToken(tf.ctx, tf.conf, url.Values{
|
||||||
|
"grant_type": {"refresh_token"},
|
||||||
|
"refresh_token": {t.RefreshToken},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// reuseTokenSource is a TokenSource that holds a single token in memory
|
||||||
|
// and validates its expiry before each call to retrieve it with
|
||||||
|
// Token. If it's expired, it will be auto-refreshed using the
|
||||||
|
// new TokenSource.
|
||||||
|
//
|
||||||
|
// The first call to TokenRefresher must be SetToken.
|
||||||
|
type reuseTokenSource struct {
|
||||||
|
new TokenSource // called when t is expired.
|
||||||
|
|
||||||
|
mu sync.Mutex // guards t
|
||||||
|
t *Token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token returns the current token if it's still valid, else will
|
||||||
|
// refresh the current token (using r.Context for HTTP client
|
||||||
|
// information) and return the new one.
|
||||||
|
func (s *reuseTokenSource) Token() (*Token, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if s.t.Valid() {
|
||||||
|
return s.t, nil
|
||||||
|
}
|
||||||
|
t, err := s.new.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.t = t
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieveToken(ctx Context, c *Config, v url.Values) (*Token, error) {
|
||||||
|
hc, err := contextClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v.Set("client_id", c.ClientID)
|
||||||
|
bustedAuth := !providerAuthHeaderWorks(c.Endpoint.TokenURL)
|
||||||
|
if bustedAuth && c.ClientSecret != "" {
|
||||||
|
v.Set("client_secret", c.ClientSecret)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", c.Endpoint.TokenURL, strings.NewReader(v.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
if !bustedAuth && c.ClientSecret != "" {
|
||||||
|
req.SetBasicAuth(c.ClientID, c.ClientSecret)
|
||||||
|
}
|
||||||
|
r, err := hc.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||||
|
}
|
||||||
|
if code := r.StatusCode; code < 200 || code > 299 {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var token *Token
|
||||||
|
content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||||
|
switch content {
|
||||||
|
case "application/x-www-form-urlencoded", "text/plain":
|
||||||
|
vals, err := url.ParseQuery(string(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token = &Token{
|
||||||
|
AccessToken: vals.Get("access_token"),
|
||||||
|
TokenType: vals.Get("token_type"),
|
||||||
|
RefreshToken: vals.Get("refresh_token"),
|
||||||
|
raw: vals,
|
||||||
|
}
|
||||||
|
e := vals.Get("expires_in")
|
||||||
|
if e == "" {
|
||||||
|
// TODO(jbd): Facebook's OAuth2 implementation is broken and
|
||||||
|
// returns expires_in field in expires. Remove the fallback to expires,
|
||||||
|
// when Facebook fixes their implementation.
|
||||||
|
e = vals.Get("expires")
|
||||||
|
}
|
||||||
|
expires, _ := strconv.Atoi(e)
|
||||||
|
if expires != 0 {
|
||||||
|
token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
var tj tokenJSON
|
||||||
|
if err = json.Unmarshal(body, &tj); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token = &Token{
|
||||||
|
AccessToken: tj.AccessToken,
|
||||||
|
TokenType: tj.TokenType,
|
||||||
|
RefreshToken: tj.RefreshToken,
|
||||||
|
Expiry: tj.expiry(),
|
||||||
|
raw: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
json.Unmarshal(body, &token.raw) // no error checks for optional fields
|
||||||
|
}
|
||||||
|
// Don't overwrite `RefreshToken` with an empty value
|
||||||
|
// if this was a token refreshing request.
|
||||||
|
if token.RefreshToken == "" {
|
||||||
|
token.RefreshToken = v.Get("refresh_token")
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenJSON is the struct representing the HTTP response from OAuth2
|
||||||
|
// providers returning a token in JSON form.
|
||||||
|
type tokenJSON struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
ExpiresIn int32 `json:"expires_in"`
|
||||||
|
Expires int32 `json:"expires"` // broken Facebook spelling of expires_in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *tokenJSON) expiry() (t time.Time) {
|
||||||
|
if v := e.ExpiresIn; v != 0 {
|
||||||
|
return time.Now().Add(time.Duration(v) * time.Second)
|
||||||
|
}
|
||||||
|
if v := e.Expires; v != 0 {
|
||||||
|
return time.Now().Add(time.Duration(v) * time.Second)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func condVal(v string) []string {
|
||||||
|
if v == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []string{v}
|
||||||
|
}
|
||||||
|
|
||||||
|
// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
|
||||||
|
// implements the OAuth2 spec correctly
|
||||||
|
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
|
||||||
|
// In summary:
|
||||||
|
// - Reddit only accepts client secret in the Authorization header
|
||||||
|
// - Dropbox accepts either it in URL param or Auth header, but not both.
|
||||||
|
// - Google only accepts URL param (not spec compliant?), not Auth header
|
||||||
|
func providerAuthHeaderWorks(tokenURL string) bool {
|
||||||
|
if strings.HasPrefix(tokenURL, "https://accounts.google.com/") ||
|
||||||
|
strings.HasPrefix(tokenURL, "https://github.com/") ||
|
||||||
|
strings.HasPrefix(tokenURL, "https://api.instagram.com/") ||
|
||||||
|
strings.HasPrefix(tokenURL, "https://www.douban.com/") ||
|
||||||
|
strings.HasPrefix(tokenURL, "https://api.dropbox.com/") ||
|
||||||
|
strings.HasPrefix(tokenURL, "https://api.soundcloud.com/") ||
|
||||||
|
strings.HasPrefix(tokenURL, "https://www.linkedin.com/") {
|
||||||
|
// Some sites fail to implement the OAuth2 spec fully.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume the provider implements the spec properly
|
||||||
|
// otherwise. We can add more exceptions as they're
|
||||||
|
// discovered. We will _not_ be adding configurable hooks
|
||||||
|
// to this package to let users select server bugs.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPClient is the context key to use with golang.org/x/net/context's
|
||||||
|
// WithValue function to associate an *http.Client value with a context.
|
||||||
|
var HTTPClient contextKey
|
||||||
|
|
||||||
|
// contextKey is just an empty struct. It exists so HTTPClient can be
|
||||||
|
// an immutable public variable with a unique type. It's immutable
|
||||||
|
// because nobody else can create a contextKey, being unexported.
|
||||||
|
type contextKey struct{}
|
||||||
|
|
||||||
|
// NewClient creates an *http.Client from a Context and TokenSource.
|
||||||
|
// The returned client is not valid beyond the lifetime of the context.
|
||||||
|
//
|
||||||
|
// As a special case, if src is nil, a non-OAuth2 client is returned
|
||||||
|
// using the provided context. This exists to support related OAuth2
|
||||||
|
// packages.
|
||||||
|
func NewClient(ctx Context, src TokenSource) *http.Client {
|
||||||
|
if src == nil {
|
||||||
|
c, err := contextClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return &http.Client{Transport: errorTransport{err}}
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &Transport{
|
||||||
|
Base: contextTransport(ctx),
|
||||||
|
Source: ReuseTokenSource(nil, src),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReuseTokenSource returns a TokenSource which repeatedly returns the
|
||||||
|
// same token as long as it's valid, starting with t.
|
||||||
|
// When its cached token is invalid, a new token is obtained from src.
|
||||||
|
//
|
||||||
|
// ReuseTokenSource is typically used to reuse tokens from a cache
|
||||||
|
// (such as a file on disk) between runs of a program, rather than
|
||||||
|
// obtaining new tokens unnecessarily.
|
||||||
|
//
|
||||||
|
// The initial token t may be nil, in which case the TokenSource is
|
||||||
|
// wrapped in a caching version if it isn't one already. This also
|
||||||
|
// means it's always safe to wrap ReuseTokenSource around any other
|
||||||
|
// TokenSource without adverse effects.
|
||||||
|
func ReuseTokenSource(t *Token, src TokenSource) TokenSource {
|
||||||
|
// Don't wrap a reuseTokenSource in itself. That would work,
|
||||||
|
// but cause an unnecessary number of mutex operations.
|
||||||
|
// Just build the equivalent one.
|
||||||
|
if rt, ok := src.(*reuseTokenSource); ok {
|
||||||
|
if t == nil {
|
||||||
|
// Just use it directly.
|
||||||
|
return rt
|
||||||
|
}
|
||||||
|
src = rt.new
|
||||||
|
}
|
||||||
|
return &reuseTokenSource{
|
||||||
|
t: t,
|
||||||
|
new: src,
|
||||||
|
}
|
||||||
|
}
|
||||||
260
Godeps/_workspace/src/golang.org/x/oauth2/oauth2_test.go
generated
vendored
Normal file
260
Godeps/_workspace/src/golang.org/x/oauth2/oauth2_test.go
generated
vendored
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockTransport struct {
|
||||||
|
rt func(req *http.Request) (resp *http.Response, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mockTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||||
|
return t.rt(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockCache struct {
|
||||||
|
token *Token
|
||||||
|
readErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockCache) ReadToken() (*Token, error) {
|
||||||
|
return c.token, c.readErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockCache) WriteToken(*Token) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConf(url string) *Config {
|
||||||
|
return &Config{
|
||||||
|
ClientID: "CLIENT_ID",
|
||||||
|
ClientSecret: "CLIENT_SECRET",
|
||||||
|
RedirectURL: "REDIRECT_URL",
|
||||||
|
Scopes: []string{"scope1", "scope2"},
|
||||||
|
Endpoint: Endpoint{
|
||||||
|
AuthURL: url + "/auth",
|
||||||
|
TokenURL: url + "/token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthCodeURL(t *testing.T) {
|
||||||
|
conf := newConf("server")
|
||||||
|
url := conf.AuthCodeURL("foo", AccessTypeOffline, ApprovalForce)
|
||||||
|
if url != "server/auth?access_type=offline&approval_prompt=force&client_id=CLIENT_ID&redirect_uri=REDIRECT_URL&response_type=code&scope=scope1+scope2&state=foo" {
|
||||||
|
t.Errorf("Auth code URL doesn't match the expected, found: %v", url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthCodeURL_Optional(t *testing.T) {
|
||||||
|
conf := &Config{
|
||||||
|
ClientID: "CLIENT_ID",
|
||||||
|
Endpoint: Endpoint{
|
||||||
|
AuthURL: "/auth-url",
|
||||||
|
TokenURL: "/token-url",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
url := conf.AuthCodeURL("")
|
||||||
|
if url != "/auth-url?client_id=CLIENT_ID&response_type=code" {
|
||||||
|
t.Fatalf("Auth code URL doesn't match the expected, found: %v", url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExchangeRequest(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.String() != "/token" {
|
||||||
|
t.Errorf("Unexpected exchange request URL, %v is found.", r.URL)
|
||||||
|
}
|
||||||
|
headerAuth := r.Header.Get("Authorization")
|
||||||
|
if headerAuth != "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" {
|
||||||
|
t.Errorf("Unexpected authorization header, %v is found.", headerAuth)
|
||||||
|
}
|
||||||
|
headerContentType := r.Header.Get("Content-Type")
|
||||||
|
if headerContentType != "application/x-www-form-urlencoded" {
|
||||||
|
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed reading request body: %s.", err)
|
||||||
|
}
|
||||||
|
if string(body) != "client_id=CLIENT_ID&code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL&scope=scope1+scope2" {
|
||||||
|
t.Errorf("Unexpected exchange payload, %v is found.", string(body))
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer"))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
tok, err := conf.Exchange(NoContext, "exchange-code")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if !tok.Valid() {
|
||||||
|
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||||
|
}
|
||||||
|
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
|
||||||
|
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||||
|
}
|
||||||
|
if tok.TokenType != "bearer" {
|
||||||
|
t.Errorf("Unexpected token type, %#v.", tok.TokenType)
|
||||||
|
}
|
||||||
|
scope := tok.Extra("scope")
|
||||||
|
if scope != "user" {
|
||||||
|
t.Errorf("Unexpected value for scope: %v", scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExchangeRequest_JSONResponse(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.String() != "/token" {
|
||||||
|
t.Errorf("Unexpected exchange request URL, %v is found.", r.URL)
|
||||||
|
}
|
||||||
|
headerAuth := r.Header.Get("Authorization")
|
||||||
|
if headerAuth != "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" {
|
||||||
|
t.Errorf("Unexpected authorization header, %v is found.", headerAuth)
|
||||||
|
}
|
||||||
|
headerContentType := r.Header.Get("Content-Type")
|
||||||
|
if headerContentType != "application/x-www-form-urlencoded" {
|
||||||
|
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed reading request body: %s.", err)
|
||||||
|
}
|
||||||
|
if string(body) != "client_id=CLIENT_ID&code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL&scope=scope1+scope2" {
|
||||||
|
t.Errorf("Unexpected exchange payload, %v is found.", string(body))
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"access_token": "90d64460d14870c08c81352a05dedd3465940a7c", "scope": "user", "token_type": "bearer", "expires_in": 86400}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
tok, err := conf.Exchange(NoContext, "exchange-code")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if !tok.Valid() {
|
||||||
|
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||||
|
}
|
||||||
|
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
|
||||||
|
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||||
|
}
|
||||||
|
if tok.TokenType != "bearer" {
|
||||||
|
t.Errorf("Unexpected token type, %#v.", tok.TokenType)
|
||||||
|
}
|
||||||
|
scope := tok.Extra("scope")
|
||||||
|
if scope != "user" {
|
||||||
|
t.Errorf("Unexpected value for scope: %v", scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExchangeRequest_BadResponse(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"scope": "user", "token_type": "bearer"}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
tok, err := conf.Exchange(NoContext, "code")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if tok.AccessToken != "" {
|
||||||
|
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExchangeRequest_BadResponseType(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
_, err := conf.Exchange(NoContext, "exchange-code")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error from invalid access_token type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExchangeRequest_NonBasicAuth(t *testing.T) {
|
||||||
|
tr := &mockTransport{
|
||||||
|
rt: func(r *http.Request) (w *http.Response, err error) {
|
||||||
|
headerAuth := r.Header.Get("Authorization")
|
||||||
|
if headerAuth != "" {
|
||||||
|
t.Errorf("Unexpected authorization header, %v is found.", headerAuth)
|
||||||
|
}
|
||||||
|
return nil, errors.New("no response")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c := &http.Client{Transport: tr}
|
||||||
|
conf := &Config{
|
||||||
|
ClientID: "CLIENT_ID",
|
||||||
|
Endpoint: Endpoint{
|
||||||
|
AuthURL: "https://accounts.google.com/auth",
|
||||||
|
TokenURL: "https://accounts.google.com/token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(context.Background(), HTTPClient, c)
|
||||||
|
conf.Exchange(ctx, "code")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenRefreshRequest(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.String() == "/somethingelse" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.String() != "/token" {
|
||||||
|
t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL)
|
||||||
|
}
|
||||||
|
headerContentType := r.Header.Get("Content-Type")
|
||||||
|
if headerContentType != "application/x-www-form-urlencoded" {
|
||||||
|
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||||
|
}
|
||||||
|
body, _ := ioutil.ReadAll(r.Body)
|
||||||
|
if string(body) != "client_id=CLIENT_ID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN" {
|
||||||
|
t.Errorf("Unexpected refresh token payload, %v is found.", string(body))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
c := conf.Client(NoContext, &Token{RefreshToken: "REFRESH_TOKEN"})
|
||||||
|
c.Get(ts.URL + "/somethingelse")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchWithNoRefreshToken(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.String() == "/somethingelse" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.String() != "/token" {
|
||||||
|
t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL)
|
||||||
|
}
|
||||||
|
headerContentType := r.Header.Get("Content-Type")
|
||||||
|
if headerContentType != "application/x-www-form-urlencoded" {
|
||||||
|
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||||
|
}
|
||||||
|
body, _ := ioutil.ReadAll(r.Body)
|
||||||
|
if string(body) != "client_id=CLIENT_ID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN" {
|
||||||
|
t.Errorf("Unexpected refresh token payload, %v is found.", string(body))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
c := conf.Client(NoContext, nil)
|
||||||
|
_, err := c.Get(ts.URL + "/somethingelse")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Fetch should return an error if no refresh token is set")
|
||||||
|
}
|
||||||
|
}
|
||||||
99
Godeps/_workspace/src/golang.org/x/oauth2/token.go
generated
vendored
Normal file
99
Godeps/_workspace/src/golang.org/x/oauth2/token.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token represents the crendentials used to authorize
|
||||||
|
// the requests to access protected resources on the OAuth 2.0
|
||||||
|
// provider's backend.
|
||||||
|
//
|
||||||
|
// Most users of this package should not access fields of Token
|
||||||
|
// directly. They're exported mostly for use by related packages
|
||||||
|
// implementing derivative OAuth2 flows.
|
||||||
|
type Token struct {
|
||||||
|
// AccessToken is the token that authorizes and authenticates
|
||||||
|
// the requests.
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
|
||||||
|
// TokenType is the type of token.
|
||||||
|
// The Type method returns either this or "Bearer", the default.
|
||||||
|
TokenType string `json:"token_type,omitempty"`
|
||||||
|
|
||||||
|
// RefreshToken is a token that's used by the application
|
||||||
|
// (as opposed to the user) to refresh the access token
|
||||||
|
// if it expires.
|
||||||
|
RefreshToken string `json:"refresh_token,omitempty"`
|
||||||
|
|
||||||
|
// Expiry is the optional expiration time of the access token.
|
||||||
|
//
|
||||||
|
// If zero, TokenSource implementations will reuse the same
|
||||||
|
// token forever and RefreshToken or equivalent
|
||||||
|
// mechanisms for that TokenSource will not be used.
|
||||||
|
Expiry time.Time `json:"expiry,omitempty"`
|
||||||
|
|
||||||
|
// raw optionally contains extra metadata from the server
|
||||||
|
// when updating a token.
|
||||||
|
raw interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns t.TokenType if non-empty, else "Bearer".
|
||||||
|
func (t *Token) Type() string {
|
||||||
|
if t.TokenType != "" {
|
||||||
|
return t.TokenType
|
||||||
|
}
|
||||||
|
return "Bearer"
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAuthHeader sets the Authorization header to r using the access
|
||||||
|
// token in t.
|
||||||
|
//
|
||||||
|
// This method is unnecessary when using Transport or an HTTP Client
|
||||||
|
// returned by this package.
|
||||||
|
func (t *Token) SetAuthHeader(r *http.Request) {
|
||||||
|
r.Header.Set("Authorization", t.Type()+" "+t.AccessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithExtra returns a new Token that's a clone of t, but using the
|
||||||
|
// provided raw extra map. This is only intended for use by packages
|
||||||
|
// implementing derivative OAuth2 flows.
|
||||||
|
func (t *Token) WithExtra(extra interface{}) *Token {
|
||||||
|
t2 := new(Token)
|
||||||
|
*t2 = *t
|
||||||
|
t2.raw = extra
|
||||||
|
return t2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra returns an extra field.
|
||||||
|
// Extra fields are key-value pairs returned by the server as a
|
||||||
|
// part of the token retrieval response.
|
||||||
|
func (t *Token) Extra(key string) interface{} {
|
||||||
|
if vals, ok := t.raw.(url.Values); ok {
|
||||||
|
// TODO(jbd): Cast numeric values to int64 or float64.
|
||||||
|
return vals.Get(key)
|
||||||
|
}
|
||||||
|
if raw, ok := t.raw.(map[string]interface{}); ok {
|
||||||
|
return raw[key]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// expired reports whether the token is expired.
|
||||||
|
// t must be non-nil.
|
||||||
|
func (t *Token) expired() bool {
|
||||||
|
if t.Expiry.IsZero() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.Expiry.Before(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid reports whether t is non-nil, has an AccessToken, and is not expired.
|
||||||
|
func (t *Token) Valid() bool {
|
||||||
|
return t != nil && t.AccessToken != "" && !t.expired()
|
||||||
|
}
|
||||||
30
Godeps/_workspace/src/golang.org/x/oauth2/token_test.go
generated
vendored
Normal file
30
Godeps/_workspace/src/golang.org/x/oauth2/token_test.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestTokenExtra(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
key string
|
||||||
|
val interface{}
|
||||||
|
want interface{}
|
||||||
|
}
|
||||||
|
const key = "extra-key"
|
||||||
|
cases := []testCase{
|
||||||
|
{key: key, val: "abc", want: "abc"},
|
||||||
|
{key: key, val: 123, want: 123},
|
||||||
|
{key: key, val: "", want: ""},
|
||||||
|
{key: "other-key", val: "def", want: nil},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
extra := make(map[string]interface{})
|
||||||
|
extra[tc.key] = tc.val
|
||||||
|
tok := &Token{raw: extra}
|
||||||
|
if got, want := tok.Extra(key), tc.want; got != want {
|
||||||
|
t.Errorf("Extra(%q) = %q; want %q", key, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
138
Godeps/_workspace/src/golang.org/x/oauth2/transport.go
generated
vendored
Normal file
138
Godeps/_workspace/src/golang.org/x/oauth2/transport.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Transport is an http.RoundTripper that makes OAuth 2.0 HTTP requests,
|
||||||
|
// wrapping a base RoundTripper and adding an Authorization header
|
||||||
|
// with a token from the supplied Sources.
|
||||||
|
//
|
||||||
|
// Transport is a low-level mechanism. Most code will use the
|
||||||
|
// higher-level Config.Client method instead.
|
||||||
|
type Transport struct {
|
||||||
|
// Source supplies the token to add to outgoing requests'
|
||||||
|
// Authorization headers.
|
||||||
|
Source TokenSource
|
||||||
|
|
||||||
|
// Base is the base RoundTripper used to make HTTP requests.
|
||||||
|
// If nil, http.DefaultTransport is used.
|
||||||
|
Base http.RoundTripper
|
||||||
|
|
||||||
|
mu sync.Mutex // guards modReq
|
||||||
|
modReq map[*http.Request]*http.Request // original -> modified
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip authorizes and authenticates the request with an
|
||||||
|
// access token. If no token exists or token is expired,
|
||||||
|
// tries to refresh/fetch a new token.
|
||||||
|
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
if t.Source == nil {
|
||||||
|
return nil, errors.New("oauth2: Transport's Source is nil")
|
||||||
|
}
|
||||||
|
token, err := t.Source.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req2 := cloneRequest(req) // per RoundTripper contract
|
||||||
|
token.SetAuthHeader(req2)
|
||||||
|
t.setModReq(req, req2)
|
||||||
|
res, err := t.base().RoundTrip(req2)
|
||||||
|
if err != nil {
|
||||||
|
t.setModReq(req, nil)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res.Body = &onEOFReader{
|
||||||
|
rc: res.Body,
|
||||||
|
fn: func() { t.setModReq(req, nil) },
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelRequest cancels an in-flight request by closing its connection.
|
||||||
|
func (t *Transport) CancelRequest(req *http.Request) {
|
||||||
|
type canceler interface {
|
||||||
|
CancelRequest(*http.Request)
|
||||||
|
}
|
||||||
|
if cr, ok := t.base().(canceler); ok {
|
||||||
|
t.mu.Lock()
|
||||||
|
modReq := t.modReq[req]
|
||||||
|
delete(t.modReq, req)
|
||||||
|
t.mu.Unlock()
|
||||||
|
cr.CancelRequest(modReq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) base() http.RoundTripper {
|
||||||
|
if t.Base != nil {
|
||||||
|
return t.Base
|
||||||
|
}
|
||||||
|
return http.DefaultTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) setModReq(orig, mod *http.Request) {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
if t.modReq == nil {
|
||||||
|
t.modReq = make(map[*http.Request]*http.Request)
|
||||||
|
}
|
||||||
|
if mod == nil {
|
||||||
|
delete(t.modReq, orig)
|
||||||
|
} else {
|
||||||
|
t.modReq[orig] = mod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cloneRequest returns a clone of the provided *http.Request.
|
||||||
|
// The clone is a shallow copy of the struct and its Header map.
|
||||||
|
func cloneRequest(r *http.Request) *http.Request {
|
||||||
|
// shallow copy of the struct
|
||||||
|
r2 := new(http.Request)
|
||||||
|
*r2 = *r
|
||||||
|
// deep copy of the Header
|
||||||
|
r2.Header = make(http.Header, len(r.Header))
|
||||||
|
for k, s := range r.Header {
|
||||||
|
r2.Header[k] = append([]string(nil), s...)
|
||||||
|
}
|
||||||
|
return r2
|
||||||
|
}
|
||||||
|
|
||||||
|
type onEOFReader struct {
|
||||||
|
rc io.ReadCloser
|
||||||
|
fn func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *onEOFReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = r.rc.Read(p)
|
||||||
|
if err == io.EOF {
|
||||||
|
r.runFunc()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *onEOFReader) Close() error {
|
||||||
|
err := r.rc.Close()
|
||||||
|
r.runFunc()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *onEOFReader) runFunc() {
|
||||||
|
if fn := r.fn; fn != nil {
|
||||||
|
fn()
|
||||||
|
r.fn = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorTransport struct{ err error }
|
||||||
|
|
||||||
|
func (t errorTransport) RoundTrip(*http.Request) (*http.Response, error) {
|
||||||
|
return nil, t.err
|
||||||
|
}
|
||||||
53
Godeps/_workspace/src/golang.org/x/oauth2/transport_test.go
generated
vendored
Normal file
53
Godeps/_workspace/src/golang.org/x/oauth2/transport_test.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tokenSource struct{ token *Token }
|
||||||
|
|
||||||
|
func (t *tokenSource) Token() (*Token, error) {
|
||||||
|
return t.token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransportTokenSource(t *testing.T) {
|
||||||
|
ts := &tokenSource{
|
||||||
|
token: &Token{
|
||||||
|
AccessToken: "abc",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tr := &Transport{
|
||||||
|
Source: ts,
|
||||||
|
}
|
||||||
|
server := newMockServer(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Header.Get("Authorization") != "Bearer abc" {
|
||||||
|
t.Errorf("Transport doesn't set the Authorization header from the fetched token")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
client := http.Client{Transport: tr}
|
||||||
|
client.Get(server.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenValidNoAccessToken(t *testing.T) {
|
||||||
|
token := &Token{}
|
||||||
|
if token.Valid() {
|
||||||
|
t.Errorf("Token should not be valid with no access token")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpiredWithExpiry(t *testing.T) {
|
||||||
|
token := &Token{
|
||||||
|
Expiry: time.Now().Add(-5 * time.Hour),
|
||||||
|
}
|
||||||
|
if token.Valid() {
|
||||||
|
t.Errorf("Token should not be valid if it expired in the past")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockServer(handler func(w http.ResponseWriter, r *http.Request)) *httptest.Server {
|
||||||
|
return httptest.NewServer(http.HandlerFunc(handler))
|
||||||
|
}
|
||||||
14
Godeps/_workspace/src/google.golang.org/appengine/.travis.yml
generated
vendored
Normal file
14
Godeps/_workspace/src/google.golang.org/appengine/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.4
|
||||||
|
|
||||||
|
install:
|
||||||
|
- export GOPATH="$HOME/gopath"
|
||||||
|
- mkdir -p "$GOPATH/src/google.golang.org"
|
||||||
|
- mv "$TRAVIS_BUILD_DIR" "$GOPATH/src/google.golang.org/appengine"
|
||||||
|
- go get -v -t -d google.golang.org/appengine/...
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v google.golang.org/appengine/...
|
||||||
|
- go test -v -race google.golang.org/appengine/...
|
||||||
202
Godeps/_workspace/src/google.golang.org/appengine/LICENSE
generated
vendored
Normal file
202
Godeps/_workspace/src/google.golang.org/appengine/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
65
Godeps/_workspace/src/google.golang.org/appengine/README.md
generated
vendored
Normal file
65
Godeps/_workspace/src/google.golang.org/appengine/README.md
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Go App Engine for Managed VMs
|
||||||
|
|
||||||
|
[](https://travis-ci.org/golang/appengine)
|
||||||
|
|
||||||
|
This repository supports the Go runtime for Managed VMs on App Engine.
|
||||||
|
It provides APIs for interacting with App Engine services.
|
||||||
|
Its canonical import path is `google.golang.org/appengine`.
|
||||||
|
|
||||||
|
See https://cloud.google.com/appengine/docs/go/managed-vms/
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
## Directory structure
|
||||||
|
The top level directory of this repository is the `appengine` package. It
|
||||||
|
contains the
|
||||||
|
basic types (e.g. `appengine.Context`) that are used across APIs. Specific API
|
||||||
|
packages are in subdirectories (e.g. `datastore`).
|
||||||
|
|
||||||
|
There is an `internal` subdirectory that contains service protocol buffers,
|
||||||
|
plus packages required for connectivity to make API calls. App Engine apps
|
||||||
|
should not directly import any package under `internal`.
|
||||||
|
|
||||||
|
## Updating a Go App Engine app
|
||||||
|
|
||||||
|
This section describes how to update a traditional Go App Engine app to run on Managed VMs.
|
||||||
|
|
||||||
|
### 1. Update YAML files
|
||||||
|
|
||||||
|
The `app.yaml` file (and YAML files for modules) should have these new lines added:
|
||||||
|
```
|
||||||
|
vm: true
|
||||||
|
manual_scaling:
|
||||||
|
instances: 1
|
||||||
|
```
|
||||||
|
See https://cloud.google.com/appengine/docs/go/modules/#Go_Instance_scaling_and_class for details.
|
||||||
|
|
||||||
|
### 2. Update import paths
|
||||||
|
|
||||||
|
The import paths for App Engine packages are now fully qualified, based at `google.golang.org/appengine`.
|
||||||
|
You will need to update your code to use import paths starting with that; for instance,
|
||||||
|
code importing `appengine/datastore` will now need to import `google.golang.org/appengine/datastore`.
|
||||||
|
You can do that manually, or by running this command to recursively update all Go source files in the current directory:
|
||||||
|
(may require GNU sed)
|
||||||
|
```
|
||||||
|
sed -i '/"appengine/{s,"appengine,"google.golang.org/appengine,;s,appengine_,appengine/,}' \
|
||||||
|
$(find . -name '*.go')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Update code using deprecated, removed or modified APIs
|
||||||
|
|
||||||
|
Most App Engine services are available with exactly the same API.
|
||||||
|
A few APIs were cleaned up, and some are not available yet.
|
||||||
|
This list summarises the differences:
|
||||||
|
|
||||||
|
* `appengine.Datacenter` now takes an `appengine.Context` argument.
|
||||||
|
* `datastore.PropertyLoadSaver` has been simplified to use slices in place of channels.
|
||||||
|
* `search.FieldLoadSaver` now handles document metadata.
|
||||||
|
* `taskqueue.QueueStats` no longer takes a maxTasks argument. That argument has been
|
||||||
|
deprecated and unused for a long time.
|
||||||
|
* `appengine/aetest`, `appengine/blobstore`, `appengine/cloudsql`
|
||||||
|
and `appengine/runtime` have not been ported yet.
|
||||||
|
* `appengine.BackendHostname` and `appengine.BackendInstance` were for the deprecated backends feature.
|
||||||
|
Use `appengine.ModuleHostname`and `appengine.ModuleName` instead.
|
||||||
|
* `appengine.IsCapabilityDisabled` and `appengine/capability` are obsolete.
|
||||||
|
* Most of `appengine/file` is deprecated. Use [Google Cloud Storage](https://godoc.org/google.golang.org/cloud/storage) instead.
|
||||||
|
* `appengine/socket` is deprecated. Use the standard `net` package instead.
|
||||||
78
Godeps/_workspace/src/google.golang.org/appengine/appengine.go
generated
vendored
Normal file
78
Godeps/_workspace/src/google.golang.org/appengine/appengine.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package appengine provides basic functionality for Google App Engine.
|
||||||
|
//
|
||||||
|
// For more information on how to write Go apps for Google App Engine, see:
|
||||||
|
// https://cloud.google.com/appengine/docs/go/
|
||||||
|
package appengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsDevAppServer reports whether the App Engine app is running in the
|
||||||
|
// development App Server.
|
||||||
|
func IsDevAppServer() bool {
|
||||||
|
// TODO(dsymonds): Detect this.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context represents the context of an in-flight HTTP request.
|
||||||
|
type Context interface {
|
||||||
|
// Debugf formats its arguments according to the format, analogous to fmt.Printf,
|
||||||
|
// and records the text as a log message at Debug level.
|
||||||
|
Debugf(format string, args ...interface{})
|
||||||
|
|
||||||
|
// Infof is like Debugf, but at Info level.
|
||||||
|
Infof(format string, args ...interface{})
|
||||||
|
|
||||||
|
// Warningf is like Debugf, but at Warning level.
|
||||||
|
Warningf(format string, args ...interface{})
|
||||||
|
|
||||||
|
// Errorf is like Debugf, but at Error level.
|
||||||
|
Errorf(format string, args ...interface{})
|
||||||
|
|
||||||
|
// Criticalf is like Debugf, but at Critical level.
|
||||||
|
Criticalf(format string, args ...interface{})
|
||||||
|
|
||||||
|
// The remaining methods are for internal use only.
|
||||||
|
// Developer-facing APIs wrap these methods to provide a more friendly API.
|
||||||
|
|
||||||
|
// Internal use only.
|
||||||
|
Call(service, method string, in, out proto.Message, opts *internal.CallOptions) error
|
||||||
|
// Internal use only. Use AppID instead.
|
||||||
|
FullyQualifiedAppID() string
|
||||||
|
// Internal use only.
|
||||||
|
Request() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext returns a context for an in-flight HTTP request.
|
||||||
|
// Repeated calls will return the same value.
|
||||||
|
func NewContext(req *http.Request) Context {
|
||||||
|
return internal.NewContext(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(dsymonds): Add BackgroundContext function?
|
||||||
|
|
||||||
|
// BlobKey is a key for a blobstore blob.
|
||||||
|
//
|
||||||
|
// Conceptually, this type belongs in the blobstore package, but it lives in
|
||||||
|
// the appengine package to avoid a circular dependency: blobstore depends on
|
||||||
|
// datastore, and datastore needs to refer to the BlobKey type.
|
||||||
|
type BlobKey string
|
||||||
|
|
||||||
|
// GeoPoint represents a location as latitude/longitude in degrees.
|
||||||
|
type GeoPoint struct {
|
||||||
|
Lat, Lng float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns whether a GeoPoint is within [-90, 90] latitude and [-180, 180] longitude.
|
||||||
|
func (g GeoPoint) Valid() bool {
|
||||||
|
return -90 <= g.Lat && g.Lat <= 90 && -180 <= g.Lng && g.Lng <= 180
|
||||||
|
}
|
||||||
45
Godeps/_workspace/src/google.golang.org/appengine/appengine_test.go
generated
vendored
Normal file
45
Godeps/_workspace/src/google.golang.org/appengine/appengine_test.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package appengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidGeoPoint(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
pt GeoPoint
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"valid",
|
||||||
|
GeoPoint{67.21, 13.37},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"high lat",
|
||||||
|
GeoPoint{-90.01, 13.37},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"low lat",
|
||||||
|
GeoPoint{90.01, 13.37},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"high lng",
|
||||||
|
GeoPoint{67.21, 182},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"low lng",
|
||||||
|
GeoPoint{67.21, -181},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
if got := tc.pt.Valid(); got != tc.want {
|
||||||
|
t.Errorf("%s: got %v, want %v", tc.desc, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
Godeps/_workspace/src/google.golang.org/appengine/channel/channel.go
generated
vendored
Normal file
81
Godeps/_workspace/src/google.golang.org/appengine/channel/channel.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package channel implements the server side of App Engine's Channel API.
|
||||||
|
|
||||||
|
Create creates a new channel associated with the given clientID,
|
||||||
|
which must be unique to the client that will use the returned token.
|
||||||
|
|
||||||
|
token, err := channel.Create(c, "player1")
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
// return token to the client in an HTTP response
|
||||||
|
|
||||||
|
Send sends a message to the client over the channel identified by clientID.
|
||||||
|
|
||||||
|
channel.Send(c, "player1", "Game over!")
|
||||||
|
*/
|
||||||
|
package channel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
basepb "google.golang.org/appengine/internal/base"
|
||||||
|
pb "google.golang.org/appengine/internal/channel"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create creates a channel and returns a token for use by the client.
|
||||||
|
// The clientID is an application-provided string used to identify the client.
|
||||||
|
func Create(c appengine.Context, clientID string) (token string, err error) {
|
||||||
|
req := &pb.CreateChannelRequest{
|
||||||
|
ApplicationKey: &clientID,
|
||||||
|
}
|
||||||
|
resp := &pb.CreateChannelResponse{}
|
||||||
|
err = c.Call(service, "CreateChannel", req, resp, nil)
|
||||||
|
token = resp.GetToken()
|
||||||
|
return token, remapError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends a message on the channel associated with clientID.
|
||||||
|
func Send(c appengine.Context, clientID, message string) error {
|
||||||
|
req := &pb.SendMessageRequest{
|
||||||
|
ApplicationKey: &clientID,
|
||||||
|
Message: &message,
|
||||||
|
}
|
||||||
|
resp := &basepb.VoidProto{}
|
||||||
|
return remapError(c.Call(service, "SendChannelMessage", req, resp, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendJSON is a helper function that sends a JSON-encoded value
|
||||||
|
// on the channel associated with clientID.
|
||||||
|
func SendJSON(c appengine.Context, clientID string, value interface{}) error {
|
||||||
|
m, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return Send(c, clientID, string(m))
|
||||||
|
}
|
||||||
|
|
||||||
|
// remapError fixes any APIError referencing "xmpp" into one referencing "channel".
|
||||||
|
func remapError(err error) error {
|
||||||
|
if e, ok := err.(*internal.APIError); ok {
|
||||||
|
if e.Service == "xmpp" {
|
||||||
|
e.Service = "channel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var service = "xmpp" // prod
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if appengine.IsDevAppServer() {
|
||||||
|
service = "channel" // dev
|
||||||
|
}
|
||||||
|
internal.RegisterErrorCodeMap("channel", pb.ChannelServiceError_ErrorCode_name)
|
||||||
|
}
|
||||||
17
Godeps/_workspace/src/google.golang.org/appengine/channel/channel_test.go
generated
vendored
Normal file
17
Godeps/_workspace/src/google.golang.org/appengine/channel/channel_test.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package channel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemapError(t *testing.T) {
|
||||||
|
err := &internal.APIError{
|
||||||
|
Service: "xmpp",
|
||||||
|
}
|
||||||
|
err = remapError(err).(*internal.APIError)
|
||||||
|
if err.Service != "channel" {
|
||||||
|
t.Errorf("err.Service = %q, want %q", err.Service, "channel")
|
||||||
|
}
|
||||||
|
}
|
||||||
405
Godeps/_workspace/src/google.golang.org/appengine/datastore/datastore.go
generated
vendored
Normal file
405
Godeps/_workspace/src/google.golang.org/appengine/datastore/datastore.go
generated
vendored
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package datastore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
pb "google.golang.org/appengine/internal/datastore"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidEntityType is returned when functions like Get or Next are
|
||||||
|
// passed a dst or src argument of invalid type.
|
||||||
|
ErrInvalidEntityType = errors.New("datastore: invalid entity type")
|
||||||
|
// ErrInvalidKey is returned when an invalid key is presented.
|
||||||
|
ErrInvalidKey = errors.New("datastore: invalid key")
|
||||||
|
// ErrNoSuchEntity is returned when no entity was found for a given key.
|
||||||
|
ErrNoSuchEntity = errors.New("datastore: no such entity")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrFieldMismatch is returned when a field is to be loaded into a different
|
||||||
|
// type than the one it was stored from, or when a field is missing or
|
||||||
|
// unexported in the destination struct.
|
||||||
|
// StructType is the type of the struct pointed to by the destination argument
|
||||||
|
// passed to Get or to Iterator.Next.
|
||||||
|
type ErrFieldMismatch struct {
|
||||||
|
StructType reflect.Type
|
||||||
|
FieldName string
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrFieldMismatch) Error() string {
|
||||||
|
return fmt.Sprintf("datastore: cannot load field %q into a %q: %s",
|
||||||
|
e.FieldName, e.StructType, e.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
// protoToKey converts a Reference proto to a *Key.
|
||||||
|
func protoToKey(r *pb.Reference) (k *Key, err error) {
|
||||||
|
appID := r.GetApp()
|
||||||
|
namespace := r.GetNameSpace()
|
||||||
|
for _, e := range r.Path.Element {
|
||||||
|
k = &Key{
|
||||||
|
kind: e.GetType(),
|
||||||
|
stringID: e.GetName(),
|
||||||
|
intID: e.GetId(),
|
||||||
|
parent: k,
|
||||||
|
appID: appID,
|
||||||
|
namespace: namespace,
|
||||||
|
}
|
||||||
|
if !k.valid() {
|
||||||
|
return nil, ErrInvalidKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyToProto converts a *Key to a Reference proto.
|
||||||
|
func keyToProto(defaultAppID string, k *Key) *pb.Reference {
|
||||||
|
appID := k.appID
|
||||||
|
if appID == "" {
|
||||||
|
appID = defaultAppID
|
||||||
|
}
|
||||||
|
n := 0
|
||||||
|
for i := k; i != nil; i = i.parent {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
e := make([]*pb.Path_Element, n)
|
||||||
|
for i := k; i != nil; i = i.parent {
|
||||||
|
n--
|
||||||
|
e[n] = &pb.Path_Element{
|
||||||
|
Type: &i.kind,
|
||||||
|
}
|
||||||
|
// At most one of {Name,Id} should be set.
|
||||||
|
// Neither will be set for incomplete keys.
|
||||||
|
if i.stringID != "" {
|
||||||
|
e[n].Name = &i.stringID
|
||||||
|
} else if i.intID != 0 {
|
||||||
|
e[n].Id = &i.intID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var namespace *string
|
||||||
|
if k.namespace != "" {
|
||||||
|
namespace = proto.String(k.namespace)
|
||||||
|
}
|
||||||
|
return &pb.Reference{
|
||||||
|
App: proto.String(appID),
|
||||||
|
NameSpace: namespace,
|
||||||
|
Path: &pb.Path{
|
||||||
|
Element: e,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiKeyToProto is a batch version of keyToProto.
|
||||||
|
func multiKeyToProto(appID string, key []*Key) []*pb.Reference {
|
||||||
|
ret := make([]*pb.Reference, len(key))
|
||||||
|
for i, k := range key {
|
||||||
|
ret[i] = keyToProto(appID, k)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiValid is a batch version of Key.valid. It returns an error, not a
|
||||||
|
// []bool.
|
||||||
|
func multiValid(key []*Key) error {
|
||||||
|
invalid := false
|
||||||
|
for _, k := range key {
|
||||||
|
if !k.valid() {
|
||||||
|
invalid = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !invalid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := make(appengine.MultiError, len(key))
|
||||||
|
for i, k := range key {
|
||||||
|
if !k.valid() {
|
||||||
|
err[i] = ErrInvalidKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's unfortunate that the two semantically equivalent concepts pb.Reference
|
||||||
|
// and pb.PropertyValue_ReferenceValue aren't the same type. For example, the
|
||||||
|
// two have different protobuf field numbers.
|
||||||
|
|
||||||
|
// referenceValueToKey is the same as protoToKey except the input is a
|
||||||
|
// PropertyValue_ReferenceValue instead of a Reference.
|
||||||
|
func referenceValueToKey(r *pb.PropertyValue_ReferenceValue) (k *Key, err error) {
|
||||||
|
appID := r.GetApp()
|
||||||
|
namespace := r.GetNameSpace()
|
||||||
|
for _, e := range r.Pathelement {
|
||||||
|
k = &Key{
|
||||||
|
kind: e.GetType(),
|
||||||
|
stringID: e.GetName(),
|
||||||
|
intID: e.GetId(),
|
||||||
|
parent: k,
|
||||||
|
appID: appID,
|
||||||
|
namespace: namespace,
|
||||||
|
}
|
||||||
|
if !k.valid() {
|
||||||
|
return nil, ErrInvalidKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyToReferenceValue is the same as keyToProto except the output is a
|
||||||
|
// PropertyValue_ReferenceValue instead of a Reference.
|
||||||
|
func keyToReferenceValue(defaultAppID string, k *Key) *pb.PropertyValue_ReferenceValue {
|
||||||
|
ref := keyToProto(defaultAppID, k)
|
||||||
|
pe := make([]*pb.PropertyValue_ReferenceValue_PathElement, len(ref.Path.Element))
|
||||||
|
for i, e := range ref.Path.Element {
|
||||||
|
pe[i] = &pb.PropertyValue_ReferenceValue_PathElement{
|
||||||
|
Type: e.Type,
|
||||||
|
Id: e.Id,
|
||||||
|
Name: e.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &pb.PropertyValue_ReferenceValue{
|
||||||
|
App: ref.App,
|
||||||
|
NameSpace: ref.NameSpace,
|
||||||
|
Pathelement: pe,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type multiArgType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
multiArgTypeInvalid multiArgType = iota
|
||||||
|
multiArgTypePropertyLoadSaver
|
||||||
|
multiArgTypeStruct
|
||||||
|
multiArgTypeStructPtr
|
||||||
|
multiArgTypeInterface
|
||||||
|
)
|
||||||
|
|
||||||
|
// checkMultiArg checks that v has type []S, []*S, []I, or []P, for some struct
|
||||||
|
// type S, for some interface type I, or some non-interface non-pointer type P
|
||||||
|
// such that P or *P implements PropertyLoadSaver.
|
||||||
|
//
|
||||||
|
// It returns what category the slice's elements are, and the reflect.Type
|
||||||
|
// that represents S, I or P.
|
||||||
|
//
|
||||||
|
// As a special case, PropertyList is an invalid type for v.
|
||||||
|
func checkMultiArg(v reflect.Value) (m multiArgType, elemType reflect.Type) {
|
||||||
|
if v.Kind() != reflect.Slice {
|
||||||
|
return multiArgTypeInvalid, nil
|
||||||
|
}
|
||||||
|
if v.Type() == typeOfPropertyList {
|
||||||
|
return multiArgTypeInvalid, nil
|
||||||
|
}
|
||||||
|
elemType = v.Type().Elem()
|
||||||
|
if reflect.PtrTo(elemType).Implements(typeOfPropertyLoadSaver) {
|
||||||
|
return multiArgTypePropertyLoadSaver, elemType
|
||||||
|
}
|
||||||
|
switch elemType.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
return multiArgTypeStruct, elemType
|
||||||
|
case reflect.Interface:
|
||||||
|
return multiArgTypeInterface, elemType
|
||||||
|
case reflect.Ptr:
|
||||||
|
elemType = elemType.Elem()
|
||||||
|
if elemType.Kind() == reflect.Struct {
|
||||||
|
return multiArgTypeStructPtr, elemType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return multiArgTypeInvalid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get loads the entity stored for k into dst, which must be a struct pointer
|
||||||
|
// or implement PropertyLoadSaver. If there is no such entity for the key, Get
|
||||||
|
// returns ErrNoSuchEntity.
|
||||||
|
//
|
||||||
|
// The values of dst's unmatched struct fields are not modified, and matching
|
||||||
|
// slice-typed fields are not reset before appending to them. In particular, it
|
||||||
|
// is recommended to pass a pointer to a zero valued struct on each Get call.
|
||||||
|
//
|
||||||
|
// ErrFieldMismatch is returned when a field is to be loaded into a different
|
||||||
|
// type than the one it was stored from, or when a field is missing or
|
||||||
|
// unexported in the destination struct. ErrFieldMismatch is only returned if
|
||||||
|
// dst is a struct pointer.
|
||||||
|
func Get(c appengine.Context, key *Key, dst interface{}) error {
|
||||||
|
if dst == nil { // GetMulti catches nil interface; we need to catch nil ptr here
|
||||||
|
return ErrInvalidEntityType
|
||||||
|
}
|
||||||
|
err := GetMulti(c, []*Key{key}, []interface{}{dst})
|
||||||
|
if me, ok := err.(appengine.MultiError); ok {
|
||||||
|
return me[0]
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMulti is a batch version of Get.
|
||||||
|
//
|
||||||
|
// dst must be a []S, []*S, []I or []P, for some struct type S, some interface
|
||||||
|
// type I, or some non-interface non-pointer type P such that P or *P
|
||||||
|
// implements PropertyLoadSaver. If an []I, each element must be a valid dst
|
||||||
|
// for Get: it must be a struct pointer or implement PropertyLoadSaver.
|
||||||
|
//
|
||||||
|
// As a special case, PropertyList is an invalid type for dst, even though a
|
||||||
|
// PropertyList is a slice of structs. It is treated as invalid to avoid being
|
||||||
|
// mistakenly passed when []PropertyList was intended.
|
||||||
|
func GetMulti(c appengine.Context, key []*Key, dst interface{}) error {
|
||||||
|
v := reflect.ValueOf(dst)
|
||||||
|
multiArgType, _ := checkMultiArg(v)
|
||||||
|
if multiArgType == multiArgTypeInvalid {
|
||||||
|
return errors.New("datastore: dst has invalid type")
|
||||||
|
}
|
||||||
|
if len(key) != v.Len() {
|
||||||
|
return errors.New("datastore: key and dst slices have different length")
|
||||||
|
}
|
||||||
|
if len(key) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := multiValid(key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req := &pb.GetRequest{
|
||||||
|
Key: multiKeyToProto(c.FullyQualifiedAppID(), key),
|
||||||
|
}
|
||||||
|
res := &pb.GetResponse{}
|
||||||
|
if err := c.Call("datastore_v3", "Get", req, res, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(key) != len(res.Entity) {
|
||||||
|
return errors.New("datastore: internal error: server returned the wrong number of entities")
|
||||||
|
}
|
||||||
|
multiErr, any := make(appengine.MultiError, len(key)), false
|
||||||
|
for i, e := range res.Entity {
|
||||||
|
if e.Entity == nil {
|
||||||
|
multiErr[i] = ErrNoSuchEntity
|
||||||
|
} else {
|
||||||
|
elem := v.Index(i)
|
||||||
|
if multiArgType == multiArgTypePropertyLoadSaver || multiArgType == multiArgTypeStruct {
|
||||||
|
elem = elem.Addr()
|
||||||
|
}
|
||||||
|
if multiArgType == multiArgTypeStructPtr && elem.IsNil() {
|
||||||
|
elem.Set(reflect.New(elem.Type().Elem()))
|
||||||
|
}
|
||||||
|
multiErr[i] = loadEntity(elem.Interface(), e.Entity)
|
||||||
|
}
|
||||||
|
if multiErr[i] != nil {
|
||||||
|
any = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if any {
|
||||||
|
return multiErr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put saves the entity src into the datastore with key k. src must be a struct
|
||||||
|
// pointer or implement PropertyLoadSaver; if a struct pointer then any
|
||||||
|
// unexported fields of that struct will be skipped. If k is an incomplete key,
|
||||||
|
// the returned key will be a unique key generated by the datastore.
|
||||||
|
func Put(c appengine.Context, key *Key, src interface{}) (*Key, error) {
|
||||||
|
k, err := PutMulti(c, []*Key{key}, []interface{}{src})
|
||||||
|
if err != nil {
|
||||||
|
if me, ok := err.(appengine.MultiError); ok {
|
||||||
|
return nil, me[0]
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return k[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutMulti is a batch version of Put.
|
||||||
|
//
|
||||||
|
// src must satisfy the same conditions as the dst argument to GetMulti.
|
||||||
|
func PutMulti(c appengine.Context, key []*Key, src interface{}) ([]*Key, error) {
|
||||||
|
v := reflect.ValueOf(src)
|
||||||
|
multiArgType, _ := checkMultiArg(v)
|
||||||
|
if multiArgType == multiArgTypeInvalid {
|
||||||
|
return nil, errors.New("datastore: src has invalid type")
|
||||||
|
}
|
||||||
|
if len(key) != v.Len() {
|
||||||
|
return nil, errors.New("datastore: key and src slices have different length")
|
||||||
|
}
|
||||||
|
if len(key) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
appID := c.FullyQualifiedAppID()
|
||||||
|
if err := multiValid(key); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req := &pb.PutRequest{}
|
||||||
|
for i := range key {
|
||||||
|
elem := v.Index(i)
|
||||||
|
if multiArgType == multiArgTypePropertyLoadSaver || multiArgType == multiArgTypeStruct {
|
||||||
|
elem = elem.Addr()
|
||||||
|
}
|
||||||
|
sProto, err := saveEntity(appID, key[i], elem.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Entity = append(req.Entity, sProto)
|
||||||
|
}
|
||||||
|
res := &pb.PutResponse{}
|
||||||
|
if err := c.Call("datastore_v3", "Put", req, res, nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(key) != len(res.Key) {
|
||||||
|
return nil, errors.New("datastore: internal error: server returned the wrong number of keys")
|
||||||
|
}
|
||||||
|
ret := make([]*Key, len(key))
|
||||||
|
for i := range ret {
|
||||||
|
var err error
|
||||||
|
ret[i], err = protoToKey(res.Key[i])
|
||||||
|
if err != nil || ret[i].Incomplete() {
|
||||||
|
return nil, errors.New("datastore: internal error: server returned an invalid key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the entity for the given key.
|
||||||
|
func Delete(c appengine.Context, key *Key) error {
|
||||||
|
err := DeleteMulti(c, []*Key{key})
|
||||||
|
if me, ok := err.(appengine.MultiError); ok {
|
||||||
|
return me[0]
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMulti is a batch version of Delete.
|
||||||
|
func DeleteMulti(c appengine.Context, key []*Key) error {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := multiValid(key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req := &pb.DeleteRequest{
|
||||||
|
Key: multiKeyToProto(c.FullyQualifiedAppID(), key),
|
||||||
|
}
|
||||||
|
res := &pb.DeleteResponse{}
|
||||||
|
return c.Call("datastore_v3", "Delete", req, res, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func namespaceMod(m proto.Message, namespace string) {
|
||||||
|
// pb.Query is the only type that has a name_space field.
|
||||||
|
// All other namespace support in datastore is in the keys.
|
||||||
|
switch m := m.(type) {
|
||||||
|
case *pb.Query:
|
||||||
|
if m.NameSpace == nil {
|
||||||
|
m.NameSpace = &namespace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
internal.NamespaceMods["datastore_v3"] = namespaceMod
|
||||||
|
internal.RegisterErrorCodeMap("datastore_v3", pb.Error_ErrorCode_name)
|
||||||
|
internal.RegisterTimeoutErrorCode("datastore_v3", int32(pb.Error_TIMEOUT))
|
||||||
|
}
|
||||||
1499
Godeps/_workspace/src/google.golang.org/appengine/datastore/datastore_test.go
generated
vendored
Normal file
1499
Godeps/_workspace/src/google.golang.org/appengine/datastore/datastore_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
316
Godeps/_workspace/src/google.golang.org/appengine/datastore/doc.go
generated
vendored
Normal file
316
Godeps/_workspace/src/google.golang.org/appengine/datastore/doc.go
generated
vendored
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package datastore provides a client for App Engine's datastore service.
|
||||||
|
|
||||||
|
|
||||||
|
Basic Operations
|
||||||
|
|
||||||
|
Entities are the unit of storage and are associated with a key. A key
|
||||||
|
consists of an optional parent key, a string application ID, a string kind
|
||||||
|
(also known as an entity type), and either a StringID or an IntID. A
|
||||||
|
StringID is also known as an entity name or key name.
|
||||||
|
|
||||||
|
It is valid to create a key with a zero StringID and a zero IntID; this is
|
||||||
|
called an incomplete key, and does not refer to any saved entity. Putting an
|
||||||
|
entity into the datastore under an incomplete key will cause a unique key
|
||||||
|
to be generated for that entity, with a non-zero IntID.
|
||||||
|
|
||||||
|
An entity's contents are a mapping from case-sensitive field names to values.
|
||||||
|
Valid value types are:
|
||||||
|
- signed integers (int, int8, int16, int32 and int64),
|
||||||
|
- bool,
|
||||||
|
- string,
|
||||||
|
- float32 and float64,
|
||||||
|
- []byte (up to 1 megabyte in length),
|
||||||
|
- any type whose underlying type is one of the above predeclared types,
|
||||||
|
- ByteString,
|
||||||
|
- *Key,
|
||||||
|
- time.Time (stored with microsecond precision),
|
||||||
|
- appengine.BlobKey,
|
||||||
|
- appengine.GeoPoint,
|
||||||
|
- structs whose fields are all valid value types,
|
||||||
|
- slices of any of the above.
|
||||||
|
|
||||||
|
Slices of structs are valid, as are structs that contain slices. However, if
|
||||||
|
one struct contains another, then at most one of those can be repeated. This
|
||||||
|
disqualifies recursively defined struct types: any struct T that (directly or
|
||||||
|
indirectly) contains a []T.
|
||||||
|
|
||||||
|
The Get and Put functions load and save an entity's contents. An entity's
|
||||||
|
contents are typically represented by a struct pointer.
|
||||||
|
|
||||||
|
Example code:
|
||||||
|
|
||||||
|
type Entity struct {
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c := appengine.NewContext(r)
|
||||||
|
|
||||||
|
k := datastore.NewKey(c, "Entity", "stringID", 0, nil)
|
||||||
|
e := new(Entity)
|
||||||
|
if err := datastore.Get(c, k, e); err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
old := e.Value
|
||||||
|
e.Value = r.URL.Path
|
||||||
|
|
||||||
|
if _, err := datastore.Put(c, k, e); err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
fmt.Fprintf(w, "old=%q\nnew=%q\n", old, e.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
GetMulti, PutMulti and DeleteMulti are batch versions of the Get, Put and
|
||||||
|
Delete functions. They take a []*Key instead of a *Key, and may return an
|
||||||
|
appengine.MultiError when encountering partial failure.
|
||||||
|
|
||||||
|
|
||||||
|
Properties
|
||||||
|
|
||||||
|
An entity's contents can be represented by a variety of types. These are
|
||||||
|
typically struct pointers, but can also be any type that implements the
|
||||||
|
PropertyLoadSaver interface. If using a struct pointer, you do not have to
|
||||||
|
explicitly implement the PropertyLoadSaver interface; the datastore will
|
||||||
|
automatically convert via reflection. If a struct pointer does implement that
|
||||||
|
interface then those methods will be used in preference to the default
|
||||||
|
behavior for struct pointers. Struct pointers are more strongly typed and are
|
||||||
|
easier to use; PropertyLoadSavers are more flexible.
|
||||||
|
|
||||||
|
The actual types passed do not have to match between Get and Put calls or even
|
||||||
|
across different App Engine requests. It is valid to put a *PropertyList and
|
||||||
|
get that same entity as a *myStruct, or put a *myStruct0 and get a *myStruct1.
|
||||||
|
Conceptually, any entity is saved as a sequence of properties, and is loaded
|
||||||
|
into the destination value on a property-by-property basis. When loading into
|
||||||
|
a struct pointer, an entity that cannot be completely represented (such as a
|
||||||
|
missing field) will result in an ErrFieldMismatch error but it is up to the
|
||||||
|
caller whether this error is fatal, recoverable or ignorable.
|
||||||
|
|
||||||
|
By default, for struct pointers, all properties are potentially indexed, and
|
||||||
|
the property name is the same as the field name (and hence must start with an
|
||||||
|
upper case letter). Fields may have a `datastore:"name,options"` tag. The tag
|
||||||
|
name is the property name, which must be one or more valid Go identifiers
|
||||||
|
joined by ".", but may start with a lower case letter. An empty tag name means
|
||||||
|
to just use the field name. A "-" tag name means that the datastore will
|
||||||
|
ignore that field. If options is "noindex" then the field will not be indexed.
|
||||||
|
If the options is "" then the comma may be omitted. There are no other
|
||||||
|
recognized options.
|
||||||
|
|
||||||
|
Fields (except for []byte) are indexed by default. Strings longer than 500
|
||||||
|
characters cannot be indexed; fields used to store long strings should be
|
||||||
|
tagged with "noindex". Similarly, ByteStrings longer than 500 bytes cannot be
|
||||||
|
indexed.
|
||||||
|
|
||||||
|
Example code:
|
||||||
|
|
||||||
|
// A and B are renamed to a and b.
|
||||||
|
// A, C and J are not indexed.
|
||||||
|
// D's tag is equivalent to having no tag at all (E).
|
||||||
|
// I is ignored entirely by the datastore.
|
||||||
|
// J has tag information for both the datastore and json packages.
|
||||||
|
type TaggedStruct struct {
|
||||||
|
A int `datastore:"a,noindex"`
|
||||||
|
B int `datastore:"b"`
|
||||||
|
C int `datastore:",noindex"`
|
||||||
|
D int `datastore:""`
|
||||||
|
E int
|
||||||
|
I int `datastore:"-"`
|
||||||
|
J int `datastore:",noindex" json:"j"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Structured Properties
|
||||||
|
|
||||||
|
If the struct pointed to contains other structs, then the nested or embedded
|
||||||
|
structs are flattened. For example, given these definitions:
|
||||||
|
|
||||||
|
type Inner1 struct {
|
||||||
|
W int32
|
||||||
|
X string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Inner2 struct {
|
||||||
|
Y float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Inner3 struct {
|
||||||
|
Z bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Outer struct {
|
||||||
|
A int16
|
||||||
|
I []Inner1
|
||||||
|
J Inner2
|
||||||
|
Inner3
|
||||||
|
}
|
||||||
|
|
||||||
|
then an Outer's properties would be equivalent to those of:
|
||||||
|
|
||||||
|
type OuterEquivalent struct {
|
||||||
|
A int16
|
||||||
|
IDotW []int32 `datastore:"I.W"`
|
||||||
|
IDotX []string `datastore:"I.X"`
|
||||||
|
JDotY float64 `datastore:"J.Y"`
|
||||||
|
Z bool
|
||||||
|
}
|
||||||
|
|
||||||
|
If Outer's embedded Inner3 field was tagged as `datastore:"Foo"` then the
|
||||||
|
equivalent field would instead be: FooDotZ bool `datastore:"Foo.Z"`.
|
||||||
|
|
||||||
|
If an outer struct is tagged "noindex" then all of its implicit flattened
|
||||||
|
fields are effectively "noindex".
|
||||||
|
|
||||||
|
|
||||||
|
The PropertyLoadSaver Interface
|
||||||
|
|
||||||
|
An entity's contents can also be represented by any type that implements the
|
||||||
|
PropertyLoadSaver interface. This type may be a struct pointer, but it does
|
||||||
|
not have to be. The datastore package will call Load when getting the entity's
|
||||||
|
contents, and Save when putting the entity's contents.
|
||||||
|
Possible uses include deriving non-stored fields, verifying fields, or indexing
|
||||||
|
a field only if its value is positive.
|
||||||
|
|
||||||
|
Example code:
|
||||||
|
|
||||||
|
type CustomPropsExample struct {
|
||||||
|
I, J int
|
||||||
|
// Sum is not stored, but should always be equal to I + J.
|
||||||
|
Sum int `datastore:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CustomPropsExample) Load(c <-chan Property) error {
|
||||||
|
// Load I and J as usual.
|
||||||
|
if err := datastore.LoadStruct(x, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Derive the Sum field.
|
||||||
|
x.Sum = x.I + x.J
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CustomPropsExample) Save(c chan<- Property) error {
|
||||||
|
defer close(c)
|
||||||
|
// Validate the Sum field.
|
||||||
|
if x.Sum != x.I + x.J {
|
||||||
|
return errors.New("CustomPropsExample has inconsistent sum")
|
||||||
|
}
|
||||||
|
// Save I and J as usual. The code below is equivalent to calling
|
||||||
|
// "return datastore.SaveStruct(x, c)", but is done manually for
|
||||||
|
// demonstration purposes.
|
||||||
|
c <- datastore.Property{
|
||||||
|
Name: "I",
|
||||||
|
Value: int64(x.I),
|
||||||
|
}
|
||||||
|
c <- datastore.Property{
|
||||||
|
Name: "J",
|
||||||
|
Value: int64(x.J),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
The *PropertyList type implements PropertyLoadSaver, and can therefore hold an
|
||||||
|
arbitrary entity's contents.
|
||||||
|
|
||||||
|
|
||||||
|
Queries
|
||||||
|
|
||||||
|
Queries retrieve entities based on their properties or key's ancestry. Running
|
||||||
|
a query yields an iterator of results: either keys or (key, entity) pairs.
|
||||||
|
Queries are re-usable and it is safe to call Query.Run from concurrent
|
||||||
|
goroutines. Iterators are not safe for concurrent use.
|
||||||
|
|
||||||
|
Queries are immutable, and are either created by calling NewQuery, or derived
|
||||||
|
from an existing query by calling a method like Filter or Order that returns a
|
||||||
|
new query value. A query is typically constructed by calling NewQuery followed
|
||||||
|
by a chain of zero or more such methods. These methods are:
|
||||||
|
- Ancestor and Filter constrain the entities returned by running a query.
|
||||||
|
- Order affects the order in which they are returned.
|
||||||
|
- Project constrains the fields returned.
|
||||||
|
- Distinct de-duplicates projected entities.
|
||||||
|
- KeysOnly makes the iterator return only keys, not (key, entity) pairs.
|
||||||
|
- Start, End, Offset and Limit define which sub-sequence of matching entities
|
||||||
|
to return. Start and End take cursors, Offset and Limit take integers. Start
|
||||||
|
and Offset affect the first result, End and Limit affect the last result.
|
||||||
|
If both Start and Offset are set, then the offset is relative to Start.
|
||||||
|
If both End and Limit are set, then the earliest constraint wins. Limit is
|
||||||
|
relative to Start+Offset, not relative to End. As a special case, a
|
||||||
|
negative limit means unlimited.
|
||||||
|
|
||||||
|
Example code:
|
||||||
|
|
||||||
|
type Widget struct {
|
||||||
|
Description string
|
||||||
|
Price int
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c := appengine.NewContext(r)
|
||||||
|
q := datastore.NewQuery("Widget").
|
||||||
|
Filter("Price <", 1000).
|
||||||
|
Order("-Price")
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
for t := q.Run(c); ; {
|
||||||
|
var x Widget
|
||||||
|
key, err := t.Next(&x)
|
||||||
|
if err == datastore.Done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
serveError(c, w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(b, "Key=%v\nWidget=%#v\n\n", key, x)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
io.Copy(w, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Transactions
|
||||||
|
|
||||||
|
RunInTransaction runs a function in a transaction.
|
||||||
|
|
||||||
|
Example code:
|
||||||
|
|
||||||
|
type Counter struct {
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func inc(c appengine.Context, key *datastore.Key) (int, error) {
|
||||||
|
var x Counter
|
||||||
|
if err := datastore.Get(c, key, &x); err != nil && err != datastore.ErrNoSuchEntity {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
x.Count++
|
||||||
|
if _, err := datastore.Put(c, key, &x); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return x.Count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c := appengine.NewContext(r)
|
||||||
|
var count int
|
||||||
|
err := datastore.RunInTransaction(c, func(c appengine.Context) error {
|
||||||
|
var err1 error
|
||||||
|
count, err1 = inc(c, datastore.NewKey(c, "Counter", "singleton", 0, nil))
|
||||||
|
return err1
|
||||||
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
serveError(c, w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
fmt.Fprintf(w, "Count=%d", count)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package datastore
|
||||||
309
Godeps/_workspace/src/google.golang.org/appengine/datastore/key.go
generated
vendored
Normal file
309
Godeps/_workspace/src/google.golang.org/appengine/datastore/key.go
generated
vendored
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package datastore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
pb "google.golang.org/appengine/internal/datastore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key represents the datastore key for a stored entity, and is immutable.
|
||||||
|
type Key struct {
|
||||||
|
kind string
|
||||||
|
stringID string
|
||||||
|
intID int64
|
||||||
|
parent *Key
|
||||||
|
appID string
|
||||||
|
namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kind returns the key's kind (also known as entity type).
|
||||||
|
func (k *Key) Kind() string {
|
||||||
|
return k.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringID returns the key's string ID (also known as an entity name or key
|
||||||
|
// name), which may be "".
|
||||||
|
func (k *Key) StringID() string {
|
||||||
|
return k.stringID
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntID returns the key's integer ID, which may be 0.
|
||||||
|
func (k *Key) IntID() int64 {
|
||||||
|
return k.intID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent returns the key's parent key, which may be nil.
|
||||||
|
func (k *Key) Parent() *Key {
|
||||||
|
return k.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppID returns the key's application ID.
|
||||||
|
func (k *Key) AppID() string {
|
||||||
|
return k.appID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Namespace returns the key's namespace.
|
||||||
|
func (k *Key) Namespace() string {
|
||||||
|
return k.namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incomplete returns whether the key does not refer to a stored entity.
|
||||||
|
// In particular, whether the key has a zero StringID and a zero IntID.
|
||||||
|
func (k *Key) Incomplete() bool {
|
||||||
|
return k.stringID == "" && k.intID == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid returns whether the key is valid.
|
||||||
|
func (k *Key) valid() bool {
|
||||||
|
if k == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for ; k != nil; k = k.parent {
|
||||||
|
if k.kind == "" || k.appID == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if k.stringID != "" && k.intID != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if k.parent != nil {
|
||||||
|
if k.parent.Incomplete() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if k.parent.appID != k.appID || k.parent.namespace != k.namespace {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns whether two keys are equal.
|
||||||
|
func (k *Key) Equal(o *Key) bool {
|
||||||
|
for k != nil && o != nil {
|
||||||
|
if k.kind != o.kind || k.stringID != o.stringID || k.intID != o.intID || k.appID != o.appID || k.namespace != o.namespace {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
k, o = k.parent, o.parent
|
||||||
|
}
|
||||||
|
return k == o
|
||||||
|
}
|
||||||
|
|
||||||
|
// root returns the furthest ancestor of a key, which may be itself.
|
||||||
|
func (k *Key) root() *Key {
|
||||||
|
for k.parent != nil {
|
||||||
|
k = k.parent
|
||||||
|
}
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal marshals the key's string representation to the buffer.
|
||||||
|
func (k *Key) marshal(b *bytes.Buffer) {
|
||||||
|
if k.parent != nil {
|
||||||
|
k.parent.marshal(b)
|
||||||
|
}
|
||||||
|
b.WriteByte('/')
|
||||||
|
b.WriteString(k.kind)
|
||||||
|
b.WriteByte(',')
|
||||||
|
if k.stringID != "" {
|
||||||
|
b.WriteString(k.stringID)
|
||||||
|
} else {
|
||||||
|
b.WriteString(strconv.FormatInt(k.intID, 10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the key.
|
||||||
|
func (k *Key) String() string {
|
||||||
|
if k == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
b := bytes.NewBuffer(make([]byte, 0, 512))
|
||||||
|
k.marshal(b)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type gobKey struct {
|
||||||
|
Kind string
|
||||||
|
StringID string
|
||||||
|
IntID int64
|
||||||
|
Parent *gobKey
|
||||||
|
AppID string
|
||||||
|
Namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyToGobKey(k *Key) *gobKey {
|
||||||
|
if k == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &gobKey{
|
||||||
|
Kind: k.kind,
|
||||||
|
StringID: k.stringID,
|
||||||
|
IntID: k.intID,
|
||||||
|
Parent: keyToGobKey(k.parent),
|
||||||
|
AppID: k.appID,
|
||||||
|
Namespace: k.namespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gobKeyToKey(gk *gobKey) *Key {
|
||||||
|
if gk == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Key{
|
||||||
|
kind: gk.Kind,
|
||||||
|
stringID: gk.StringID,
|
||||||
|
intID: gk.IntID,
|
||||||
|
parent: gobKeyToKey(gk.Parent),
|
||||||
|
appID: gk.AppID,
|
||||||
|
namespace: gk.Namespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) GobEncode() ([]byte, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := gob.NewEncoder(buf).Encode(keyToGobKey(k)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) GobDecode(buf []byte) error {
|
||||||
|
gk := new(gobKey)
|
||||||
|
if err := gob.NewDecoder(bytes.NewBuffer(buf)).Decode(gk); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*k = *gobKeyToKey(gk)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(`"` + k.Encode() + `"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) UnmarshalJSON(buf []byte) error {
|
||||||
|
if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' {
|
||||||
|
return errors.New("datastore: bad JSON key")
|
||||||
|
}
|
||||||
|
k2, err := DecodeKey(string(buf[1 : len(buf)-1]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*k = *k2
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode returns an opaque representation of the key
|
||||||
|
// suitable for use in HTML and URLs.
|
||||||
|
// This is compatible with the Python and Java runtimes.
|
||||||
|
func (k *Key) Encode() string {
|
||||||
|
ref := keyToProto("", k)
|
||||||
|
|
||||||
|
b, err := proto.Marshal(ref)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trailing padding is stripped.
|
||||||
|
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeKey decodes a key from the opaque representation returned by Encode.
|
||||||
|
func DecodeKey(encoded string) (*Key, error) {
|
||||||
|
// Re-add padding.
|
||||||
|
if m := len(encoded) % 4; m != 0 {
|
||||||
|
encoded += strings.Repeat("=", 4-m)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := base64.URLEncoding.DecodeString(encoded)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ref := new(pb.Reference)
|
||||||
|
if err := proto.Unmarshal(b, ref); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return protoToKey(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIncompleteKey creates a new incomplete key.
|
||||||
|
// kind cannot be empty.
|
||||||
|
func NewIncompleteKey(c appengine.Context, kind string, parent *Key) *Key {
|
||||||
|
return NewKey(c, kind, "", 0, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKey creates a new key.
|
||||||
|
// kind cannot be empty.
|
||||||
|
// Either one or both of stringID and intID must be zero. If both are zero,
|
||||||
|
// the key returned is incomplete.
|
||||||
|
// parent must either be a complete key or nil.
|
||||||
|
func NewKey(c appengine.Context, kind, stringID string, intID int64, parent *Key) *Key {
|
||||||
|
// If there's a parent key, use its namespace.
|
||||||
|
// Otherwise, do a fake RPC to try to get a namespace if c is a namespacedContext (or wraps one).
|
||||||
|
var namespace string
|
||||||
|
if parent != nil {
|
||||||
|
namespace = parent.namespace
|
||||||
|
} else {
|
||||||
|
namespace = internal.VirtAPI(c, "GetNamespace")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Key{
|
||||||
|
kind: kind,
|
||||||
|
stringID: stringID,
|
||||||
|
intID: intID,
|
||||||
|
parent: parent,
|
||||||
|
appID: c.FullyQualifiedAppID(),
|
||||||
|
namespace: namespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllocateIDs returns a range of n integer IDs with the given kind and parent
|
||||||
|
// combination. kind cannot be empty; parent may be nil. The IDs in the range
|
||||||
|
// returned will not be used by the datastore's automatic ID sequence generator
|
||||||
|
// and may be used with NewKey without conflict.
|
||||||
|
//
|
||||||
|
// The range is inclusive at the low end and exclusive at the high end. In
|
||||||
|
// other words, valid intIDs x satisfy low <= x && x < high.
|
||||||
|
//
|
||||||
|
// If no error is returned, low + n == high.
|
||||||
|
func AllocateIDs(c appengine.Context, kind string, parent *Key, n int) (low, high int64, err error) {
|
||||||
|
if kind == "" {
|
||||||
|
return 0, 0, errors.New("datastore: AllocateIDs given an empty kind")
|
||||||
|
}
|
||||||
|
if n < 0 {
|
||||||
|
return 0, 0, fmt.Errorf("datastore: AllocateIDs given a negative count: %d", n)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
req := &pb.AllocateIdsRequest{
|
||||||
|
ModelKey: keyToProto("", NewIncompleteKey(c, kind, parent)),
|
||||||
|
Size: proto.Int64(int64(n)),
|
||||||
|
}
|
||||||
|
res := &pb.AllocateIdsResponse{}
|
||||||
|
if err := c.Call("datastore_v3", "AllocateIds", req, res, nil); err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
// The protobuf is inclusive at both ends. Idiomatic Go (e.g. slices, for loops)
|
||||||
|
// is inclusive at the low end and exclusive at the high end, so we add 1.
|
||||||
|
low = res.GetStart()
|
||||||
|
high = res.GetEnd() + 1
|
||||||
|
if low+int64(n) != high {
|
||||||
|
return 0, 0, fmt.Errorf("datastore: internal error: could not allocate %d IDs", n)
|
||||||
|
}
|
||||||
|
return low, high, nil
|
||||||
|
}
|
||||||
214
Godeps/_workspace/src/google.golang.org/appengine/datastore/key_test.go
generated
vendored
Normal file
214
Godeps/_workspace/src/google.golang.org/appengine/datastore/key_test.go
generated
vendored
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package datastore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKeyEncoding(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
key *Key
|
||||||
|
exp string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "A simple key with an int ID",
|
||||||
|
key: &Key{
|
||||||
|
kind: "Person",
|
||||||
|
intID: 1,
|
||||||
|
appID: "glibrary",
|
||||||
|
},
|
||||||
|
exp: "aghnbGlicmFyeXIMCxIGUGVyc29uGAEM",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "A simple key with a string ID",
|
||||||
|
key: &Key{
|
||||||
|
kind: "Graph",
|
||||||
|
stringID: "graph:7-day-active",
|
||||||
|
appID: "glibrary",
|
||||||
|
},
|
||||||
|
exp: "aghnbGlicmFyeXIdCxIFR3JhcGgiEmdyYXBoOjctZGF5LWFjdGl2ZQw",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "A key with a parent",
|
||||||
|
key: &Key{
|
||||||
|
kind: "WordIndex",
|
||||||
|
intID: 1033,
|
||||||
|
parent: &Key{
|
||||||
|
kind: "WordIndex",
|
||||||
|
intID: 1020032,
|
||||||
|
appID: "glibrary",
|
||||||
|
},
|
||||||
|
appID: "glibrary",
|
||||||
|
},
|
||||||
|
exp: "aghnbGlicmFyeXIhCxIJV29yZEluZGV4GIChPgwLEglXb3JkSW5kZXgYiQgM",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
enc := tc.key.Encode()
|
||||||
|
if enc != tc.exp {
|
||||||
|
t.Errorf("%s: got %q, want %q", tc.desc, enc, tc.exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := DecodeKey(tc.exp)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: failed decoding key: %v", tc.desc, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !key.Equal(tc.key) {
|
||||||
|
t.Errorf("%s: decoded key %v, want %v", tc.desc, key, tc.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyGob(t *testing.T) {
|
||||||
|
k := &Key{
|
||||||
|
kind: "Gopher",
|
||||||
|
intID: 3,
|
||||||
|
parent: &Key{
|
||||||
|
kind: "Mom",
|
||||||
|
stringID: "narwhal",
|
||||||
|
appID: "gopher-con",
|
||||||
|
},
|
||||||
|
appID: "gopher-con",
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := gob.NewEncoder(buf).Encode(k); err != nil {
|
||||||
|
t.Fatalf("gob encode failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
k2 := new(Key)
|
||||||
|
if err := gob.NewDecoder(buf).Decode(k2); err != nil {
|
||||||
|
t.Fatalf("gob decode failed: %v", err)
|
||||||
|
}
|
||||||
|
if !k2.Equal(k) {
|
||||||
|
t.Errorf("gob round trip of %v produced %v", k, k2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNilKeyGob(t *testing.T) {
|
||||||
|
type S struct {
|
||||||
|
Key *Key
|
||||||
|
}
|
||||||
|
s1 := new(S)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := gob.NewEncoder(buf).Encode(s1); err != nil {
|
||||||
|
t.Fatalf("gob encode failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s2 := new(S)
|
||||||
|
if err := gob.NewDecoder(buf).Decode(s2); err != nil {
|
||||||
|
t.Fatalf("gob decode failed: %v", err)
|
||||||
|
}
|
||||||
|
if s2.Key != nil {
|
||||||
|
t.Errorf("gob round trip of nil key produced %v", s2.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyJSON(t *testing.T) {
|
||||||
|
k := &Key{
|
||||||
|
kind: "Gopher",
|
||||||
|
intID: 2,
|
||||||
|
parent: &Key{
|
||||||
|
kind: "Mom",
|
||||||
|
stringID: "narwhal",
|
||||||
|
appID: "gopher-con",
|
||||||
|
},
|
||||||
|
appID: "gopher-con",
|
||||||
|
}
|
||||||
|
exp := `"` + k.Encode() + `"`
|
||||||
|
|
||||||
|
buf, err := json.Marshal(k)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("json.Marshal failed: %v", err)
|
||||||
|
}
|
||||||
|
if s := string(buf); s != exp {
|
||||||
|
t.Errorf("JSON encoding of key %v: got %q, want %q", k, s, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
k2 := new(Key)
|
||||||
|
if err := json.Unmarshal(buf, k2); err != nil {
|
||||||
|
t.Fatalf("json.Unmarshal failed: %v", err)
|
||||||
|
}
|
||||||
|
if !k2.Equal(k) {
|
||||||
|
t.Errorf("JSON round trip of %v produced %v", k, k2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNilKeyJSON(t *testing.T) {
|
||||||
|
type S struct {
|
||||||
|
Key *Key
|
||||||
|
}
|
||||||
|
s1 := new(S)
|
||||||
|
|
||||||
|
buf, err := json.Marshal(s1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("json.Marshal failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s2 := new(S)
|
||||||
|
if err := json.Unmarshal(buf, s2); err != nil {
|
||||||
|
t.Fatalf("json.Unmarshal failed: %v", err)
|
||||||
|
}
|
||||||
|
if s2.Key != nil {
|
||||||
|
t.Errorf("JSON round trip of nil key produced %v", s2.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeKeyer struct {
|
||||||
|
appengine.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fakeKeyer) FullyQualifiedAppID() string { return "s~some-app" }
|
||||||
|
func (fakeKeyer) Call(service, method string, in, out proto.Message, opts *internal.CallOptions) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncompleteKeyWithParent(t *testing.T) {
|
||||||
|
var c appengine.Context = fakeKeyer{}
|
||||||
|
|
||||||
|
// fadduh is a complete key.
|
||||||
|
fadduh := NewKey(c, "Person", "", 1, nil)
|
||||||
|
if fadduh.Incomplete() {
|
||||||
|
t.Fatalf("fadduh is incomplete")
|
||||||
|
}
|
||||||
|
|
||||||
|
// robert is an incomplete key with fadduh as a parent.
|
||||||
|
robert := NewIncompleteKey(c, "Person", fadduh)
|
||||||
|
if !robert.Incomplete() {
|
||||||
|
t.Fatalf("robert is complete")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both should be valid keys.
|
||||||
|
if !fadduh.valid() {
|
||||||
|
t.Errorf("fadduh is invalid: %v", fadduh)
|
||||||
|
}
|
||||||
|
if !robert.valid() {
|
||||||
|
t.Errorf("robert is invalid: %v", robert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNamespace(t *testing.T) {
|
||||||
|
key := &Key{
|
||||||
|
kind: "Person",
|
||||||
|
intID: 1,
|
||||||
|
appID: "s~some-app",
|
||||||
|
namespace: "mynamespace",
|
||||||
|
}
|
||||||
|
if g, w := key.Namespace(), "mynamespace"; g != w {
|
||||||
|
t.Errorf("key.Namespace() = %q, want %q", g, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
334
Godeps/_workspace/src/google.golang.org/appengine/datastore/load.go
generated
vendored
Normal file
334
Godeps/_workspace/src/google.golang.org/appengine/datastore/load.go
generated
vendored
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package datastore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
pb "google.golang.org/appengine/internal/datastore"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
typeOfBlobKey = reflect.TypeOf(appengine.BlobKey(""))
|
||||||
|
typeOfByteSlice = reflect.TypeOf([]byte(nil))
|
||||||
|
typeOfByteString = reflect.TypeOf(ByteString(nil))
|
||||||
|
typeOfGeoPoint = reflect.TypeOf(appengine.GeoPoint{})
|
||||||
|
typeOfTime = reflect.TypeOf(time.Time{})
|
||||||
|
)
|
||||||
|
|
||||||
|
// typeMismatchReason returns a string explaining why the property p could not
|
||||||
|
// be stored in an entity field of type v.Type().
|
||||||
|
func typeMismatchReason(p Property, v reflect.Value) string {
|
||||||
|
entityType := "empty"
|
||||||
|
switch p.Value.(type) {
|
||||||
|
case int64:
|
||||||
|
entityType = "int"
|
||||||
|
case bool:
|
||||||
|
entityType = "bool"
|
||||||
|
case string:
|
||||||
|
entityType = "string"
|
||||||
|
case float64:
|
||||||
|
entityType = "float"
|
||||||
|
case *Key:
|
||||||
|
entityType = "*datastore.Key"
|
||||||
|
case time.Time:
|
||||||
|
entityType = "time.Time"
|
||||||
|
case appengine.BlobKey:
|
||||||
|
entityType = "appengine.BlobKey"
|
||||||
|
case appengine.GeoPoint:
|
||||||
|
entityType = "appengine.GeoPoint"
|
||||||
|
case ByteString:
|
||||||
|
entityType = "datastore.ByteString"
|
||||||
|
case []byte:
|
||||||
|
entityType = "[]byte"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("type mismatch: %s versus %v", entityType, v.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
type propertyLoader struct {
|
||||||
|
// m holds the number of times a substruct field like "Foo.Bar.Baz" has
|
||||||
|
// been seen so far. The map is constructed lazily.
|
||||||
|
m map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p Property, requireSlice bool) string {
|
||||||
|
var v reflect.Value
|
||||||
|
// Traverse a struct's struct-typed fields.
|
||||||
|
for name := p.Name; ; {
|
||||||
|
decoder, ok := codec.byName[name]
|
||||||
|
if !ok {
|
||||||
|
return "no such struct field"
|
||||||
|
}
|
||||||
|
v = structValue.Field(decoder.index)
|
||||||
|
if !v.IsValid() {
|
||||||
|
return "no such struct field"
|
||||||
|
}
|
||||||
|
if !v.CanSet() {
|
||||||
|
return "cannot set struct field"
|
||||||
|
}
|
||||||
|
|
||||||
|
if decoder.substructCodec == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() == reflect.Slice {
|
||||||
|
if l.m == nil {
|
||||||
|
l.m = make(map[string]int)
|
||||||
|
}
|
||||||
|
index := l.m[p.Name]
|
||||||
|
l.m[p.Name] = index + 1
|
||||||
|
for v.Len() <= index {
|
||||||
|
v.Set(reflect.Append(v, reflect.New(v.Type().Elem()).Elem()))
|
||||||
|
}
|
||||||
|
structValue = v.Index(index)
|
||||||
|
requireSlice = false
|
||||||
|
} else {
|
||||||
|
structValue = v
|
||||||
|
}
|
||||||
|
// Strip the "I." from "I.X".
|
||||||
|
name = name[len(codec.byIndex[decoder.index].name):]
|
||||||
|
codec = decoder.substructCodec
|
||||||
|
}
|
||||||
|
|
||||||
|
var slice reflect.Value
|
||||||
|
if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 {
|
||||||
|
slice = v
|
||||||
|
v = reflect.New(v.Type().Elem()).Elem()
|
||||||
|
} else if requireSlice {
|
||||||
|
return "multiple-valued property requires a slice field type"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert indexValues to a Go value with a meaning derived from the
|
||||||
|
// destination type.
|
||||||
|
pValue := p.Value
|
||||||
|
if iv, ok := pValue.(indexValue); ok {
|
||||||
|
meaning := pb.Property_NO_MEANING
|
||||||
|
switch v.Type() {
|
||||||
|
case typeOfBlobKey:
|
||||||
|
meaning = pb.Property_BLOBKEY
|
||||||
|
case typeOfByteSlice:
|
||||||
|
meaning = pb.Property_BLOB
|
||||||
|
case typeOfByteString:
|
||||||
|
meaning = pb.Property_BYTESTRING
|
||||||
|
case typeOfGeoPoint:
|
||||||
|
meaning = pb.Property_GEORSS_POINT
|
||||||
|
case typeOfTime:
|
||||||
|
meaning = pb.Property_GD_WHEN
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
pValue, err = propValue(iv.value, meaning)
|
||||||
|
if err != nil {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
x, ok := pValue.(int64)
|
||||||
|
if !ok && pValue != nil {
|
||||||
|
return typeMismatchReason(p, v)
|
||||||
|
}
|
||||||
|
if v.OverflowInt(x) {
|
||||||
|
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
|
||||||
|
}
|
||||||
|
v.SetInt(x)
|
||||||
|
case reflect.Bool:
|
||||||
|
x, ok := pValue.(bool)
|
||||||
|
if !ok && pValue != nil {
|
||||||
|
return typeMismatchReason(p, v)
|
||||||
|
}
|
||||||
|
v.SetBool(x)
|
||||||
|
case reflect.String:
|
||||||
|
switch x := pValue.(type) {
|
||||||
|
case appengine.BlobKey:
|
||||||
|
v.SetString(string(x))
|
||||||
|
case ByteString:
|
||||||
|
v.SetString(string(x))
|
||||||
|
case string:
|
||||||
|
v.SetString(x)
|
||||||
|
default:
|
||||||
|
if pValue != nil {
|
||||||
|
return typeMismatchReason(p, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
x, ok := pValue.(float64)
|
||||||
|
if !ok && pValue != nil {
|
||||||
|
return typeMismatchReason(p, v)
|
||||||
|
}
|
||||||
|
if v.OverflowFloat(x) {
|
||||||
|
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
|
||||||
|
}
|
||||||
|
v.SetFloat(x)
|
||||||
|
case reflect.Ptr:
|
||||||
|
x, ok := pValue.(*Key)
|
||||||
|
if !ok && pValue != nil {
|
||||||
|
return typeMismatchReason(p, v)
|
||||||
|
}
|
||||||
|
if _, ok := v.Interface().(*Key); !ok {
|
||||||
|
return typeMismatchReason(p, v)
|
||||||
|
}
|
||||||
|
v.Set(reflect.ValueOf(x))
|
||||||
|
case reflect.Struct:
|
||||||
|
switch v.Type() {
|
||||||
|
case typeOfTime:
|
||||||
|
x, ok := pValue.(time.Time)
|
||||||
|
if !ok && pValue != nil {
|
||||||
|
return typeMismatchReason(p, v)
|
||||||
|
}
|
||||||
|
v.Set(reflect.ValueOf(x))
|
||||||
|
case typeOfGeoPoint:
|
||||||
|
x, ok := pValue.(appengine.GeoPoint)
|
||||||
|
if !ok && pValue != nil {
|
||||||
|
return typeMismatchReason(p, v)
|
||||||
|
}
|
||||||
|
v.Set(reflect.ValueOf(x))
|
||||||
|
default:
|
||||||
|
return typeMismatchReason(p, v)
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
x, ok := pValue.([]byte)
|
||||||
|
if !ok {
|
||||||
|
if y, yok := pValue.(ByteString); yok {
|
||||||
|
x, ok = []byte(y), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok && pValue != nil {
|
||||||
|
return typeMismatchReason(p, v)
|
||||||
|
}
|
||||||
|
if v.Type().Elem().Kind() != reflect.Uint8 {
|
||||||
|
return typeMismatchReason(p, v)
|
||||||
|
}
|
||||||
|
v.SetBytes(x)
|
||||||
|
default:
|
||||||
|
return typeMismatchReason(p, v)
|
||||||
|
}
|
||||||
|
if slice.IsValid() {
|
||||||
|
slice.Set(reflect.Append(slice, v))
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadEntity loads an EntityProto into PropertyLoadSaver or struct pointer.
|
||||||
|
func loadEntity(dst interface{}, src *pb.EntityProto) (err error) {
|
||||||
|
props, err := protoToProperties(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e, ok := dst.(PropertyLoadSaver); ok {
|
||||||
|
return e.Load(props)
|
||||||
|
}
|
||||||
|
return LoadStruct(dst, props)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s structPLS) Load(props []Property) error {
|
||||||
|
var fieldName, reason string
|
||||||
|
var l propertyLoader
|
||||||
|
for _, p := range props {
|
||||||
|
if errStr := l.load(s.codec, s.v, p, p.Multiple); errStr != "" {
|
||||||
|
// We don't return early, as we try to load as many properties as possible.
|
||||||
|
// It is valid to load an entity into a struct that cannot fully represent it.
|
||||||
|
// That case returns an error, but the caller is free to ignore it.
|
||||||
|
fieldName, reason = p.Name, errStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reason != "" {
|
||||||
|
return &ErrFieldMismatch{
|
||||||
|
StructType: s.v.Type(),
|
||||||
|
FieldName: fieldName,
|
||||||
|
Reason: reason,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func protoToProperties(src *pb.EntityProto) ([]Property, error) {
|
||||||
|
props, rawProps := src.Property, src.RawProperty
|
||||||
|
out := make([]Property, 0, len(props)+len(rawProps))
|
||||||
|
for {
|
||||||
|
var (
|
||||||
|
x *pb.Property
|
||||||
|
noIndex bool
|
||||||
|
)
|
||||||
|
if len(props) > 0 {
|
||||||
|
x, props = props[0], props[1:]
|
||||||
|
} else if len(rawProps) > 0 {
|
||||||
|
x, rawProps = rawProps[0], rawProps[1:]
|
||||||
|
noIndex = true
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var value interface{}
|
||||||
|
if x.Meaning != nil && *x.Meaning == pb.Property_INDEX_VALUE {
|
||||||
|
value = indexValue{x.Value}
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
value, err = propValue(x.Value, x.GetMeaning())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = append(out, Property{
|
||||||
|
Name: x.GetName(),
|
||||||
|
Value: value,
|
||||||
|
NoIndex: noIndex,
|
||||||
|
Multiple: x.GetMultiple(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// propValue returns a Go value that combines the raw PropertyValue with a
|
||||||
|
// meaning. For example, an Int64Value with GD_WHEN becomes a time.Time.
|
||||||
|
func propValue(v *pb.PropertyValue, m pb.Property_Meaning) (interface{}, error) {
|
||||||
|
switch {
|
||||||
|
case v.Int64Value != nil:
|
||||||
|
if m == pb.Property_GD_WHEN {
|
||||||
|
return fromUnixMicro(*v.Int64Value), nil
|
||||||
|
} else {
|
||||||
|
return *v.Int64Value, nil
|
||||||
|
}
|
||||||
|
case v.BooleanValue != nil:
|
||||||
|
return *v.BooleanValue, nil
|
||||||
|
case v.StringValue != nil:
|
||||||
|
if m == pb.Property_BLOB {
|
||||||
|
return []byte(*v.StringValue), nil
|
||||||
|
} else if m == pb.Property_BLOBKEY {
|
||||||
|
return appengine.BlobKey(*v.StringValue), nil
|
||||||
|
} else if m == pb.Property_BYTESTRING {
|
||||||
|
return ByteString(*v.StringValue), nil
|
||||||
|
} else {
|
||||||
|
return *v.StringValue, nil
|
||||||
|
}
|
||||||
|
case v.DoubleValue != nil:
|
||||||
|
return *v.DoubleValue, nil
|
||||||
|
case v.Referencevalue != nil:
|
||||||
|
key, err := referenceValueToKey(v.Referencevalue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
case v.Pointvalue != nil:
|
||||||
|
// NOTE: Strangely, latitude maps to X, longitude to Y.
|
||||||
|
return appengine.GeoPoint{Lat: v.Pointvalue.GetX(), Lng: v.Pointvalue.GetY()}, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// indexValue is a Property value that is created when entities are loaded from
|
||||||
|
// an index, such as from a projection query.
|
||||||
|
//
|
||||||
|
// Such Property values do not contain all of the metadata required to be
|
||||||
|
// faithfully represented as a Go value, and are instead represented as an
|
||||||
|
// opaque indexValue. Load the properties into a concrete struct type (e.g. by
|
||||||
|
// passing a struct pointer to Iterator.Next) to reconstruct actual Go values
|
||||||
|
// of type int, string, time.Time, etc.
|
||||||
|
type indexValue struct {
|
||||||
|
value *pb.PropertyValue
|
||||||
|
}
|
||||||
294
Godeps/_workspace/src/google.golang.org/appengine/datastore/prop.go
generated
vendored
Normal file
294
Godeps/_workspace/src/google.golang.org/appengine/datastore/prop.go
generated
vendored
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package datastore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Entities with more than this many indexed properties will not be saved.
|
||||||
|
const maxIndexedProperties = 5000
|
||||||
|
|
||||||
|
// []byte fields more than 1 megabyte long will not be loaded or saved.
|
||||||
|
const maxBlobLen = 1 << 20
|
||||||
|
|
||||||
|
// Property is a name/value pair plus some metadata. A datastore entity's
|
||||||
|
// contents are loaded and saved as a sequence of Properties. An entity can
|
||||||
|
// have multiple Properties with the same name, provided that p.Multiple is
|
||||||
|
// true on all of that entity's Properties with that name.
|
||||||
|
type Property struct {
|
||||||
|
// Name is the property name.
|
||||||
|
Name string
|
||||||
|
// Value is the property value. The valid types are:
|
||||||
|
// - int64
|
||||||
|
// - bool
|
||||||
|
// - string
|
||||||
|
// - float64
|
||||||
|
// - ByteString
|
||||||
|
// - *Key
|
||||||
|
// - time.Time
|
||||||
|
// - appengine.BlobKey
|
||||||
|
// - appengine.GeoPoint
|
||||||
|
// - []byte (up to 1 megabyte in length)
|
||||||
|
// This set is smaller than the set of valid struct field types that the
|
||||||
|
// datastore can load and save. A Property Value cannot be a slice (apart
|
||||||
|
// from []byte); use multiple Properties instead. Also, a Value's type
|
||||||
|
// must be explicitly on the list above; it is not sufficient for the
|
||||||
|
// underlying type to be on that list. For example, a Value of "type
|
||||||
|
// myInt64 int64" is invalid. Smaller-width integers and floats are also
|
||||||
|
// invalid. Again, this is more restrictive than the set of valid struct
|
||||||
|
// field types.
|
||||||
|
//
|
||||||
|
// A Value will have an opaque type when loading entities from an index,
|
||||||
|
// such as via a projection query. Load entities into a struct instead
|
||||||
|
// of a PropertyLoadSaver when using a projection query.
|
||||||
|
//
|
||||||
|
// A Value may also be the nil interface value; this is equivalent to
|
||||||
|
// Python's None but not directly representable by a Go struct. Loading
|
||||||
|
// a nil-valued property into a struct will set that field to the zero
|
||||||
|
// value.
|
||||||
|
Value interface{}
|
||||||
|
// NoIndex is whether the datastore cannot index this property.
|
||||||
|
NoIndex bool
|
||||||
|
// Multiple is whether the entity can have multiple properties with
|
||||||
|
// the same name. Even if a particular instance only has one property with
|
||||||
|
// a certain name, Multiple should be true if a struct would best represent
|
||||||
|
// it as a field of type []T instead of type T.
|
||||||
|
Multiple bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByteString is a short byte slice (up to 500 bytes) that can be indexed.
|
||||||
|
type ByteString []byte
|
||||||
|
|
||||||
|
// PropertyLoadSaver can be converted from and to a slice of Properties.
|
||||||
|
type PropertyLoadSaver interface {
|
||||||
|
Load([]Property) error
|
||||||
|
Save() ([]Property, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PropertyList converts a []Property to implement PropertyLoadSaver.
|
||||||
|
type PropertyList []Property
|
||||||
|
|
||||||
|
var (
|
||||||
|
typeOfPropertyLoadSaver = reflect.TypeOf((*PropertyLoadSaver)(nil)).Elem()
|
||||||
|
typeOfPropertyList = reflect.TypeOf(PropertyList(nil))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Load loads all of the provided properties into l.
|
||||||
|
// It does not first reset *l to an empty slice.
|
||||||
|
func (l *PropertyList) Load(p []Property) error {
|
||||||
|
*l = append(*l, p...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save saves all of l's properties as a slice or Properties.
|
||||||
|
func (l *PropertyList) Save() ([]Property, error) {
|
||||||
|
return *l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validPropertyName returns whether name consists of one or more valid Go
|
||||||
|
// identifiers joined by ".".
|
||||||
|
func validPropertyName(name string) bool {
|
||||||
|
if name == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, s := range strings.Split(name, ".") {
|
||||||
|
if s == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
first := true
|
||||||
|
for _, c := range s {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
if c != '_' && !unicode.IsLetter(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if c != '_' && !unicode.IsLetter(c) && !unicode.IsDigit(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// structTag is the parsed `datastore:"name,options"` tag of a struct field.
|
||||||
|
// If a field has no tag, or the tag has an empty name, then the structTag's
|
||||||
|
// name is just the field name. A "-" name means that the datastore ignores
|
||||||
|
// that field.
|
||||||
|
type structTag struct {
|
||||||
|
name string
|
||||||
|
noIndex bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// structCodec describes how to convert a struct to and from a sequence of
|
||||||
|
// properties.
|
||||||
|
type structCodec struct {
|
||||||
|
// byIndex gives the structTag for the i'th field.
|
||||||
|
byIndex []structTag
|
||||||
|
// byName gives the field codec for the structTag with the given name.
|
||||||
|
byName map[string]fieldCodec
|
||||||
|
// hasSlice is whether a struct or any of its nested or embedded structs
|
||||||
|
// has a slice-typed field (other than []byte).
|
||||||
|
hasSlice bool
|
||||||
|
// complete is whether the structCodec is complete. An incomplete
|
||||||
|
// structCodec may be encountered when walking a recursive struct.
|
||||||
|
complete bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldCodec is a struct field's index and, if that struct field's type is
|
||||||
|
// itself a struct, that substruct's structCodec.
|
||||||
|
type fieldCodec struct {
|
||||||
|
index int
|
||||||
|
substructCodec *structCodec
|
||||||
|
}
|
||||||
|
|
||||||
|
// structCodecs collects the structCodecs that have already been calculated.
|
||||||
|
var (
|
||||||
|
structCodecsMutex sync.Mutex
|
||||||
|
structCodecs = make(map[reflect.Type]*structCodec)
|
||||||
|
)
|
||||||
|
|
||||||
|
// getStructCodec returns the structCodec for the given struct type.
|
||||||
|
func getStructCodec(t reflect.Type) (*structCodec, error) {
|
||||||
|
structCodecsMutex.Lock()
|
||||||
|
defer structCodecsMutex.Unlock()
|
||||||
|
return getStructCodecLocked(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStructCodecLocked implements getStructCodec. The structCodecsMutex must
|
||||||
|
// be held when calling this function.
|
||||||
|
func getStructCodecLocked(t reflect.Type) (ret *structCodec, retErr error) {
|
||||||
|
c, ok := structCodecs[t]
|
||||||
|
if ok {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
c = &structCodec{
|
||||||
|
byIndex: make([]structTag, t.NumField()),
|
||||||
|
byName: make(map[string]fieldCodec),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add c to the structCodecs map before we are sure it is good. If t is
|
||||||
|
// a recursive type, it needs to find the incomplete entry for itself in
|
||||||
|
// the map.
|
||||||
|
structCodecs[t] = c
|
||||||
|
defer func() {
|
||||||
|
if retErr != nil {
|
||||||
|
delete(structCodecs, t)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := range c.byIndex {
|
||||||
|
f := t.Field(i)
|
||||||
|
name, opts := f.Tag.Get("datastore"), ""
|
||||||
|
if i := strings.Index(name, ","); i != -1 {
|
||||||
|
name, opts = name[:i], name[i+1:]
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
if !f.Anonymous {
|
||||||
|
name = f.Name
|
||||||
|
}
|
||||||
|
} else if name == "-" {
|
||||||
|
c.byIndex[i] = structTag{name: name}
|
||||||
|
continue
|
||||||
|
} else if !validPropertyName(name) {
|
||||||
|
return nil, fmt.Errorf("datastore: struct tag has invalid property name: %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
substructType, fIsSlice := reflect.Type(nil), false
|
||||||
|
switch f.Type.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
substructType = f.Type
|
||||||
|
case reflect.Slice:
|
||||||
|
if f.Type.Elem().Kind() == reflect.Struct {
|
||||||
|
substructType = f.Type.Elem()
|
||||||
|
}
|
||||||
|
fIsSlice = f.Type != typeOfByteSlice
|
||||||
|
c.hasSlice = c.hasSlice || fIsSlice
|
||||||
|
}
|
||||||
|
|
||||||
|
if substructType != nil && substructType != typeOfTime && substructType != typeOfGeoPoint {
|
||||||
|
if name != "" {
|
||||||
|
name = name + "."
|
||||||
|
}
|
||||||
|
sub, err := getStructCodecLocked(substructType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !sub.complete {
|
||||||
|
return nil, fmt.Errorf("datastore: recursive struct: field %q", f.Name)
|
||||||
|
}
|
||||||
|
if fIsSlice && sub.hasSlice {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"datastore: flattening nested structs leads to a slice of slices: field %q", f.Name)
|
||||||
|
}
|
||||||
|
c.hasSlice = c.hasSlice || sub.hasSlice
|
||||||
|
for relName := range sub.byName {
|
||||||
|
absName := name + relName
|
||||||
|
if _, ok := c.byName[absName]; ok {
|
||||||
|
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", absName)
|
||||||
|
}
|
||||||
|
c.byName[absName] = fieldCodec{index: i, substructCodec: sub}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, ok := c.byName[name]; ok {
|
||||||
|
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", name)
|
||||||
|
}
|
||||||
|
c.byName[name] = fieldCodec{index: i}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.byIndex[i] = structTag{
|
||||||
|
name: name,
|
||||||
|
noIndex: opts == "noindex",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.complete = true
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// structPLS adapts a struct to be a PropertyLoadSaver.
|
||||||
|
type structPLS struct {
|
||||||
|
v reflect.Value
|
||||||
|
codec *structCodec
|
||||||
|
}
|
||||||
|
|
||||||
|
// newStructPLS returns a PropertyLoadSaver for the struct pointer p.
|
||||||
|
func newStructPLS(p interface{}) (PropertyLoadSaver, error) {
|
||||||
|
v := reflect.ValueOf(p)
|
||||||
|
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
|
||||||
|
return nil, ErrInvalidEntityType
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
codec, err := getStructCodec(v.Type())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return structPLS{v, codec}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadStruct loads the properties from p to dst.
|
||||||
|
// dst must be a struct pointer.
|
||||||
|
func LoadStruct(dst interface{}, p []Property) error {
|
||||||
|
x, err := newStructPLS(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return x.Load(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveStruct returns the properties from src as a slice of Properties.
|
||||||
|
// src must be a struct pointer.
|
||||||
|
func SaveStruct(src interface{}) ([]Property, error) {
|
||||||
|
x, err := newStructPLS(src)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x.Save()
|
||||||
|
}
|
||||||
559
Godeps/_workspace/src/google.golang.org/appengine/datastore/prop_test.go
generated
vendored
Normal file
559
Godeps/_workspace/src/google.golang.org/appengine/datastore/prop_test.go
generated
vendored
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package datastore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidPropertyName(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
// Invalid names.
|
||||||
|
{"", false},
|
||||||
|
{"'", false},
|
||||||
|
{".", false},
|
||||||
|
{"..", false},
|
||||||
|
{".foo", false},
|
||||||
|
{"0", false},
|
||||||
|
{"00", false},
|
||||||
|
{"X.X.4.X.X", false},
|
||||||
|
{"\n", false},
|
||||||
|
{"\x00", false},
|
||||||
|
{"abc\xffz", false},
|
||||||
|
{"foo.", false},
|
||||||
|
{"foo..", false},
|
||||||
|
{"foo..bar", false},
|
||||||
|
{"☃", false},
|
||||||
|
{`"`, false},
|
||||||
|
// Valid names.
|
||||||
|
{"AB", true},
|
||||||
|
{"Abc", true},
|
||||||
|
{"X.X.X.X.X", true},
|
||||||
|
{"_", true},
|
||||||
|
{"_0", true},
|
||||||
|
{"a", true},
|
||||||
|
{"a_B", true},
|
||||||
|
{"f00", true},
|
||||||
|
{"f0o", true},
|
||||||
|
{"fo0", true},
|
||||||
|
{"foo", true},
|
||||||
|
{"foo.bar", true},
|
||||||
|
{"foo.bar.baz", true},
|
||||||
|
{"世界", true},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
got := validPropertyName(tc.name)
|
||||||
|
if got != tc.want {
|
||||||
|
t.Errorf("%q: got %v, want %v", tc.name, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructCodec(t *testing.T) {
|
||||||
|
type oStruct struct {
|
||||||
|
O int
|
||||||
|
}
|
||||||
|
type pStruct struct {
|
||||||
|
P int
|
||||||
|
Q int
|
||||||
|
}
|
||||||
|
type rStruct struct {
|
||||||
|
R int
|
||||||
|
S pStruct
|
||||||
|
T oStruct
|
||||||
|
oStruct
|
||||||
|
}
|
||||||
|
type uStruct struct {
|
||||||
|
U int
|
||||||
|
v int
|
||||||
|
}
|
||||||
|
oStructCodec := &structCodec{
|
||||||
|
byIndex: []structTag{
|
||||||
|
{name: "O"},
|
||||||
|
},
|
||||||
|
byName: map[string]fieldCodec{
|
||||||
|
"O": {index: 0},
|
||||||
|
},
|
||||||
|
complete: true,
|
||||||
|
}
|
||||||
|
pStructCodec := &structCodec{
|
||||||
|
byIndex: []structTag{
|
||||||
|
{name: "P"},
|
||||||
|
{name: "Q"},
|
||||||
|
},
|
||||||
|
byName: map[string]fieldCodec{
|
||||||
|
"P": {index: 0},
|
||||||
|
"Q": {index: 1},
|
||||||
|
},
|
||||||
|
complete: true,
|
||||||
|
}
|
||||||
|
rStructCodec := &structCodec{
|
||||||
|
byIndex: []structTag{
|
||||||
|
{name: "R"},
|
||||||
|
{name: "S."},
|
||||||
|
{name: "T."},
|
||||||
|
{name: ""},
|
||||||
|
},
|
||||||
|
byName: map[string]fieldCodec{
|
||||||
|
"R": {index: 0},
|
||||||
|
"S.P": {index: 1, substructCodec: pStructCodec},
|
||||||
|
"S.Q": {index: 1, substructCodec: pStructCodec},
|
||||||
|
"T.O": {index: 2, substructCodec: oStructCodec},
|
||||||
|
"O": {index: 3, substructCodec: oStructCodec},
|
||||||
|
},
|
||||||
|
complete: true,
|
||||||
|
}
|
||||||
|
uStructCodec := &structCodec{
|
||||||
|
byIndex: []structTag{
|
||||||
|
{name: "U"},
|
||||||
|
{name: "v"},
|
||||||
|
},
|
||||||
|
byName: map[string]fieldCodec{
|
||||||
|
"U": {index: 0},
|
||||||
|
"v": {index: 1},
|
||||||
|
},
|
||||||
|
complete: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
structValue interface{}
|
||||||
|
want *structCodec
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"oStruct",
|
||||||
|
oStruct{},
|
||||||
|
oStructCodec,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pStruct",
|
||||||
|
pStruct{},
|
||||||
|
pStructCodec,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rStruct",
|
||||||
|
rStruct{},
|
||||||
|
rStructCodec,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uStruct",
|
||||||
|
uStruct{},
|
||||||
|
uStructCodec,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"non-basic fields",
|
||||||
|
struct {
|
||||||
|
B appengine.BlobKey
|
||||||
|
K *Key
|
||||||
|
T time.Time
|
||||||
|
}{},
|
||||||
|
&structCodec{
|
||||||
|
byIndex: []structTag{
|
||||||
|
{name: "B"},
|
||||||
|
{name: "K"},
|
||||||
|
{name: "T"},
|
||||||
|
},
|
||||||
|
byName: map[string]fieldCodec{
|
||||||
|
"B": {index: 0},
|
||||||
|
"K": {index: 1},
|
||||||
|
"T": {index: 2},
|
||||||
|
},
|
||||||
|
complete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"struct tags with ignored embed",
|
||||||
|
struct {
|
||||||
|
A int `datastore:"a,noindex"`
|
||||||
|
B int `datastore:"b"`
|
||||||
|
C int `datastore:",noindex"`
|
||||||
|
D int `datastore:""`
|
||||||
|
E int
|
||||||
|
I int `datastore:"-"`
|
||||||
|
J int `datastore:",noindex" json:"j"`
|
||||||
|
oStruct `datastore:"-"`
|
||||||
|
}{},
|
||||||
|
&structCodec{
|
||||||
|
byIndex: []structTag{
|
||||||
|
{name: "a", noIndex: true},
|
||||||
|
{name: "b", noIndex: false},
|
||||||
|
{name: "C", noIndex: true},
|
||||||
|
{name: "D", noIndex: false},
|
||||||
|
{name: "E", noIndex: false},
|
||||||
|
{name: "-", noIndex: false},
|
||||||
|
{name: "J", noIndex: true},
|
||||||
|
{name: "-", noIndex: false},
|
||||||
|
},
|
||||||
|
byName: map[string]fieldCodec{
|
||||||
|
"a": {index: 0},
|
||||||
|
"b": {index: 1},
|
||||||
|
"C": {index: 2},
|
||||||
|
"D": {index: 3},
|
||||||
|
"E": {index: 4},
|
||||||
|
"J": {index: 6},
|
||||||
|
},
|
||||||
|
complete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unexported fields",
|
||||||
|
struct {
|
||||||
|
A int
|
||||||
|
b int
|
||||||
|
C int `datastore:"x"`
|
||||||
|
d int `datastore:"Y"`
|
||||||
|
}{},
|
||||||
|
&structCodec{
|
||||||
|
byIndex: []structTag{
|
||||||
|
{name: "A"},
|
||||||
|
{name: "b"},
|
||||||
|
{name: "x"},
|
||||||
|
{name: "Y"},
|
||||||
|
},
|
||||||
|
byName: map[string]fieldCodec{
|
||||||
|
"A": {index: 0},
|
||||||
|
"b": {index: 1},
|
||||||
|
"x": {index: 2},
|
||||||
|
"Y": {index: 3},
|
||||||
|
},
|
||||||
|
complete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nested and embedded structs",
|
||||||
|
struct {
|
||||||
|
A int
|
||||||
|
B int
|
||||||
|
CC oStruct
|
||||||
|
DDD rStruct
|
||||||
|
oStruct
|
||||||
|
}{},
|
||||||
|
&structCodec{
|
||||||
|
byIndex: []structTag{
|
||||||
|
{name: "A"},
|
||||||
|
{name: "B"},
|
||||||
|
{name: "CC."},
|
||||||
|
{name: "DDD."},
|
||||||
|
{name: ""},
|
||||||
|
},
|
||||||
|
byName: map[string]fieldCodec{
|
||||||
|
"A": {index: 0},
|
||||||
|
"B": {index: 1},
|
||||||
|
"CC.O": {index: 2, substructCodec: oStructCodec},
|
||||||
|
"DDD.R": {index: 3, substructCodec: rStructCodec},
|
||||||
|
"DDD.S.P": {index: 3, substructCodec: rStructCodec},
|
||||||
|
"DDD.S.Q": {index: 3, substructCodec: rStructCodec},
|
||||||
|
"DDD.T.O": {index: 3, substructCodec: rStructCodec},
|
||||||
|
"DDD.O": {index: 3, substructCodec: rStructCodec},
|
||||||
|
"O": {index: 4, substructCodec: oStructCodec},
|
||||||
|
},
|
||||||
|
complete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"struct tags with nested and embedded structs",
|
||||||
|
struct {
|
||||||
|
A int `datastore:"-"`
|
||||||
|
B int `datastore:"w"`
|
||||||
|
C oStruct `datastore:"xx"`
|
||||||
|
D rStruct `datastore:"y"`
|
||||||
|
oStruct `datastore:"z"`
|
||||||
|
}{},
|
||||||
|
&structCodec{
|
||||||
|
byIndex: []structTag{
|
||||||
|
{name: "-"},
|
||||||
|
{name: "w"},
|
||||||
|
{name: "xx."},
|
||||||
|
{name: "y."},
|
||||||
|
{name: "z."},
|
||||||
|
},
|
||||||
|
byName: map[string]fieldCodec{
|
||||||
|
"w": {index: 1},
|
||||||
|
"xx.O": {index: 2, substructCodec: oStructCodec},
|
||||||
|
"y.R": {index: 3, substructCodec: rStructCodec},
|
||||||
|
"y.S.P": {index: 3, substructCodec: rStructCodec},
|
||||||
|
"y.S.Q": {index: 3, substructCodec: rStructCodec},
|
||||||
|
"y.T.O": {index: 3, substructCodec: rStructCodec},
|
||||||
|
"y.O": {index: 3, substructCodec: rStructCodec},
|
||||||
|
"z.O": {index: 4, substructCodec: oStructCodec},
|
||||||
|
},
|
||||||
|
complete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unexported nested and embedded structs",
|
||||||
|
struct {
|
||||||
|
a int
|
||||||
|
B int
|
||||||
|
c uStruct
|
||||||
|
D uStruct
|
||||||
|
uStruct
|
||||||
|
}{},
|
||||||
|
&structCodec{
|
||||||
|
byIndex: []structTag{
|
||||||
|
{name: "a"},
|
||||||
|
{name: "B"},
|
||||||
|
{name: "c."},
|
||||||
|
{name: "D."},
|
||||||
|
{name: ""},
|
||||||
|
},
|
||||||
|
byName: map[string]fieldCodec{
|
||||||
|
"a": {index: 0},
|
||||||
|
"B": {index: 1},
|
||||||
|
"c.U": {index: 2, substructCodec: uStructCodec},
|
||||||
|
"c.v": {index: 2, substructCodec: uStructCodec},
|
||||||
|
"D.U": {index: 3, substructCodec: uStructCodec},
|
||||||
|
"D.v": {index: 3, substructCodec: uStructCodec},
|
||||||
|
"U": {index: 4, substructCodec: uStructCodec},
|
||||||
|
"v": {index: 4, substructCodec: uStructCodec},
|
||||||
|
},
|
||||||
|
complete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"noindex nested struct",
|
||||||
|
struct {
|
||||||
|
A oStruct `datastore:",noindex"`
|
||||||
|
}{},
|
||||||
|
&structCodec{
|
||||||
|
byIndex: []structTag{
|
||||||
|
{name: "A.", noIndex: true},
|
||||||
|
},
|
||||||
|
byName: map[string]fieldCodec{
|
||||||
|
"A.O": {index: 0, substructCodec: oStructCodec},
|
||||||
|
},
|
||||||
|
complete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
got, err := getStructCodec(reflect.TypeOf(tc.structValue))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: getStructCodec: %v", tc.desc, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tc.want) {
|
||||||
|
t.Errorf("%s\ngot %v\nwant %v\n", tc.desc, got, tc.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRepeatedPropertyName(t *testing.T) {
|
||||||
|
good := []interface{}{
|
||||||
|
struct {
|
||||||
|
A int `datastore:"-"`
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
A int `datastore:"b"`
|
||||||
|
B int
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
A int
|
||||||
|
B int `datastore:"B"`
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
A int `datastore:"B"`
|
||||||
|
B int `datastore:"-"`
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
A int `datastore:"-"`
|
||||||
|
B int `datastore:"A"`
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
A int `datastore:"B"`
|
||||||
|
B int `datastore:"A"`
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
A int `datastore:"B"`
|
||||||
|
B int `datastore:"C"`
|
||||||
|
C int `datastore:"A"`
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
A int `datastore:"B"`
|
||||||
|
B int `datastore:"C"`
|
||||||
|
C int `datastore:"D"`
|
||||||
|
}{},
|
||||||
|
}
|
||||||
|
bad := []interface{}{
|
||||||
|
struct {
|
||||||
|
A int `datastore:"B"`
|
||||||
|
B int
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
A int
|
||||||
|
B int `datastore:"A"`
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
A int `datastore:"C"`
|
||||||
|
B int `datastore:"C"`
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
A int `datastore:"B"`
|
||||||
|
B int `datastore:"C"`
|
||||||
|
C int `datastore:"B"`
|
||||||
|
}{},
|
||||||
|
}
|
||||||
|
testGetStructCodec(t, good, bad)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlatteningNestedStructs(t *testing.T) {
|
||||||
|
type deepGood struct {
|
||||||
|
A struct {
|
||||||
|
B []struct {
|
||||||
|
C struct {
|
||||||
|
D int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type deepBad struct {
|
||||||
|
A struct {
|
||||||
|
B []struct {
|
||||||
|
C struct {
|
||||||
|
D []int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type iSay struct {
|
||||||
|
Tomato int
|
||||||
|
}
|
||||||
|
type youSay struct {
|
||||||
|
Tomato int
|
||||||
|
}
|
||||||
|
type tweedledee struct {
|
||||||
|
Dee int `datastore:"D"`
|
||||||
|
}
|
||||||
|
type tweedledum struct {
|
||||||
|
Dum int `datastore:"D"`
|
||||||
|
}
|
||||||
|
|
||||||
|
good := []interface{}{
|
||||||
|
struct {
|
||||||
|
X []struct {
|
||||||
|
Y string
|
||||||
|
}
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
X []struct {
|
||||||
|
Y []byte
|
||||||
|
}
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
P []int
|
||||||
|
X struct {
|
||||||
|
Y []int
|
||||||
|
}
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
X struct {
|
||||||
|
Y []int
|
||||||
|
}
|
||||||
|
Q []int
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
P []int
|
||||||
|
X struct {
|
||||||
|
Y []int
|
||||||
|
}
|
||||||
|
Q []int
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
deepGood
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
DG deepGood
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
Foo struct {
|
||||||
|
Z int `datastore:"X"`
|
||||||
|
} `datastore:"A"`
|
||||||
|
Bar struct {
|
||||||
|
Z int `datastore:"Y"`
|
||||||
|
} `datastore:"A"`
|
||||||
|
}{},
|
||||||
|
}
|
||||||
|
bad := []interface{}{
|
||||||
|
struct {
|
||||||
|
X []struct {
|
||||||
|
Y []string
|
||||||
|
}
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
X []struct {
|
||||||
|
Y []int
|
||||||
|
}
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
deepBad
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
DB deepBad
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
iSay
|
||||||
|
youSay
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
tweedledee
|
||||||
|
tweedledum
|
||||||
|
}{},
|
||||||
|
struct {
|
||||||
|
Foo struct {
|
||||||
|
Z int
|
||||||
|
} `datastore:"A"`
|
||||||
|
Bar struct {
|
||||||
|
Z int
|
||||||
|
} `datastore:"A"`
|
||||||
|
}{},
|
||||||
|
}
|
||||||
|
testGetStructCodec(t, good, bad)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGetStructCodec(t *testing.T, good []interface{}, bad []interface{}) {
|
||||||
|
for _, x := range good {
|
||||||
|
if _, err := getStructCodec(reflect.TypeOf(x)); err != nil {
|
||||||
|
t.Errorf("type %T: got non-nil error (%s), want nil", x, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, x := range bad {
|
||||||
|
if _, err := getStructCodec(reflect.TypeOf(x)); err == nil {
|
||||||
|
t.Errorf("type %T: got nil error, want non-nil", x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNilKeyIsStored(t *testing.T) {
|
||||||
|
x := struct {
|
||||||
|
K *Key
|
||||||
|
I int
|
||||||
|
}{}
|
||||||
|
p := PropertyList{}
|
||||||
|
// Save x as properties.
|
||||||
|
p1, _ := SaveStruct(&x)
|
||||||
|
p.Load(p1)
|
||||||
|
// Set x's fields to non-zero.
|
||||||
|
x.K = &Key{}
|
||||||
|
x.I = 2
|
||||||
|
// Load x from properties.
|
||||||
|
p2, _ := p.Save()
|
||||||
|
LoadStruct(&x, p2)
|
||||||
|
// Check that x's fields were set to zero.
|
||||||
|
if x.K != nil {
|
||||||
|
t.Errorf("K field was not zero")
|
||||||
|
}
|
||||||
|
if x.I != 0 {
|
||||||
|
t.Errorf("I field was not zero")
|
||||||
|
}
|
||||||
|
}
|
||||||
712
Godeps/_workspace/src/google.golang.org/appengine/datastore/query.go
generated
vendored
Normal file
712
Godeps/_workspace/src/google.golang.org/appengine/datastore/query.go
generated
vendored
Normal file
@@ -0,0 +1,712 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package datastore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
pb "google.golang.org/appengine/internal/datastore"
|
||||||
|
)
|
||||||
|
|
||||||
|
type operator int
|
||||||
|
|
||||||
|
const (
|
||||||
|
lessThan operator = iota
|
||||||
|
lessEq
|
||||||
|
equal
|
||||||
|
greaterEq
|
||||||
|
greaterThan
|
||||||
|
)
|
||||||
|
|
||||||
|
var operatorToProto = map[operator]*pb.Query_Filter_Operator{
|
||||||
|
lessThan: pb.Query_Filter_LESS_THAN.Enum(),
|
||||||
|
lessEq: pb.Query_Filter_LESS_THAN_OR_EQUAL.Enum(),
|
||||||
|
equal: pb.Query_Filter_EQUAL.Enum(),
|
||||||
|
greaterEq: pb.Query_Filter_GREATER_THAN_OR_EQUAL.Enum(),
|
||||||
|
greaterThan: pb.Query_Filter_GREATER_THAN.Enum(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter is a conditional filter on query results.
|
||||||
|
type filter struct {
|
||||||
|
FieldName string
|
||||||
|
Op operator
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortDirection int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ascending sortDirection = iota
|
||||||
|
descending
|
||||||
|
)
|
||||||
|
|
||||||
|
var sortDirectionToProto = map[sortDirection]*pb.Query_Order_Direction{
|
||||||
|
ascending: pb.Query_Order_ASCENDING.Enum(),
|
||||||
|
descending: pb.Query_Order_DESCENDING.Enum(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// order is a sort order on query results.
|
||||||
|
type order struct {
|
||||||
|
FieldName string
|
||||||
|
Direction sortDirection
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQuery creates a new Query for a specific entity kind.
|
||||||
|
//
|
||||||
|
// An empty kind means to return all entities, including entities created and
|
||||||
|
// managed by other App Engine features, and is called a kindless query.
|
||||||
|
// Kindless queries cannot include filters or sort orders on property values.
|
||||||
|
func NewQuery(kind string) *Query {
|
||||||
|
return &Query{
|
||||||
|
kind: kind,
|
||||||
|
limit: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query represents a datastore query.
|
||||||
|
type Query struct {
|
||||||
|
kind string
|
||||||
|
ancestor *Key
|
||||||
|
filter []filter
|
||||||
|
order []order
|
||||||
|
projection []string
|
||||||
|
|
||||||
|
distinct bool
|
||||||
|
keysOnly bool
|
||||||
|
eventual bool
|
||||||
|
limit int32
|
||||||
|
offset int32
|
||||||
|
start *pb.CompiledCursor
|
||||||
|
end *pb.CompiledCursor
|
||||||
|
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Query) clone() *Query {
|
||||||
|
x := *q
|
||||||
|
// Copy the contents of the slice-typed fields to a new backing store.
|
||||||
|
if len(q.filter) > 0 {
|
||||||
|
x.filter = make([]filter, len(q.filter))
|
||||||
|
copy(x.filter, q.filter)
|
||||||
|
}
|
||||||
|
if len(q.order) > 0 {
|
||||||
|
x.order = make([]order, len(q.order))
|
||||||
|
copy(x.order, q.order)
|
||||||
|
}
|
||||||
|
return &x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ancestor returns a derivative query with an ancestor filter.
|
||||||
|
// The ancestor should not be nil.
|
||||||
|
func (q *Query) Ancestor(ancestor *Key) *Query {
|
||||||
|
q = q.clone()
|
||||||
|
if ancestor == nil {
|
||||||
|
q.err = errors.New("datastore: nil query ancestor")
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
q.ancestor = ancestor
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventualConsistency returns a derivative query that returns eventually
|
||||||
|
// consistent results.
|
||||||
|
// It only has an effect on ancestor queries.
|
||||||
|
func (q *Query) EventualConsistency() *Query {
|
||||||
|
q = q.clone()
|
||||||
|
q.eventual = true
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter returns a derivative query with a field-based filter.
|
||||||
|
// The filterStr argument must be a field name followed by optional space,
|
||||||
|
// followed by an operator, one of ">", "<", ">=", "<=", or "=".
|
||||||
|
// Fields are compared against the provided value using the operator.
|
||||||
|
// Multiple filters are AND'ed together.
|
||||||
|
func (q *Query) Filter(filterStr string, value interface{}) *Query {
|
||||||
|
q = q.clone()
|
||||||
|
filterStr = strings.TrimSpace(filterStr)
|
||||||
|
if len(filterStr) < 1 {
|
||||||
|
q.err = errors.New("datastore: invalid filter: " + filterStr)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
f := filter{
|
||||||
|
FieldName: strings.TrimRight(filterStr, " ><=!"),
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
|
switch op := strings.TrimSpace(filterStr[len(f.FieldName):]); op {
|
||||||
|
case "<=":
|
||||||
|
f.Op = lessEq
|
||||||
|
case ">=":
|
||||||
|
f.Op = greaterEq
|
||||||
|
case "<":
|
||||||
|
f.Op = lessThan
|
||||||
|
case ">":
|
||||||
|
f.Op = greaterThan
|
||||||
|
case "=":
|
||||||
|
f.Op = equal
|
||||||
|
default:
|
||||||
|
q.err = fmt.Errorf("datastore: invalid operator %q in filter %q", op, filterStr)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
q.filter = append(q.filter, f)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order returns a derivative query with a field-based sort order. Orders are
|
||||||
|
// applied in the order they are added. The default order is ascending; to sort
|
||||||
|
// in descending order prefix the fieldName with a minus sign (-).
|
||||||
|
func (q *Query) Order(fieldName string) *Query {
|
||||||
|
q = q.clone()
|
||||||
|
fieldName = strings.TrimSpace(fieldName)
|
||||||
|
o := order{
|
||||||
|
Direction: ascending,
|
||||||
|
FieldName: fieldName,
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(fieldName, "-") {
|
||||||
|
o.Direction = descending
|
||||||
|
o.FieldName = strings.TrimSpace(fieldName[1:])
|
||||||
|
} else if strings.HasPrefix(fieldName, "+") {
|
||||||
|
q.err = fmt.Errorf("datastore: invalid order: %q", fieldName)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
if len(o.FieldName) == 0 {
|
||||||
|
q.err = errors.New("datastore: empty order")
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
q.order = append(q.order, o)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project returns a derivative query that yields only the given fields. It
|
||||||
|
// cannot be used with KeysOnly.
|
||||||
|
func (q *Query) Project(fieldNames ...string) *Query {
|
||||||
|
q = q.clone()
|
||||||
|
q.projection = append([]string(nil), fieldNames...)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distinct returns a derivative query that yields de-duplicated entities with
|
||||||
|
// respect to the set of projected fields. It is only used for projection
|
||||||
|
// queries.
|
||||||
|
func (q *Query) Distinct() *Query {
|
||||||
|
q = q.clone()
|
||||||
|
q.distinct = true
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeysOnly returns a derivative query that yields only keys, not keys and
|
||||||
|
// entities. It cannot be used with projection queries.
|
||||||
|
func (q *Query) KeysOnly() *Query {
|
||||||
|
q = q.clone()
|
||||||
|
q.keysOnly = true
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit returns a derivative query that has a limit on the number of results
|
||||||
|
// returned. A negative value means unlimited.
|
||||||
|
func (q *Query) Limit(limit int) *Query {
|
||||||
|
q = q.clone()
|
||||||
|
if limit < math.MinInt32 || limit > math.MaxInt32 {
|
||||||
|
q.err = errors.New("datastore: query limit overflow")
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
q.limit = int32(limit)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset returns a derivative query that has an offset of how many keys to
|
||||||
|
// skip over before returning results. A negative value is invalid.
|
||||||
|
func (q *Query) Offset(offset int) *Query {
|
||||||
|
q = q.clone()
|
||||||
|
if offset < 0 {
|
||||||
|
q.err = errors.New("datastore: negative query offset")
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
if offset > math.MaxInt32 {
|
||||||
|
q.err = errors.New("datastore: query offset overflow")
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
q.offset = int32(offset)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start returns a derivative query with the given start point.
|
||||||
|
func (q *Query) Start(c Cursor) *Query {
|
||||||
|
q = q.clone()
|
||||||
|
if c.cc == nil {
|
||||||
|
q.err = errors.New("datastore: invalid cursor")
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
q.start = c.cc
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// End returns a derivative query with the given end point.
|
||||||
|
func (q *Query) End(c Cursor) *Query {
|
||||||
|
q = q.clone()
|
||||||
|
if c.cc == nil {
|
||||||
|
q.err = errors.New("datastore: invalid cursor")
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
q.end = c.cc
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// toProto converts the query to a protocol buffer.
|
||||||
|
func (q *Query) toProto(dst *pb.Query, appID string) error {
|
||||||
|
if len(q.projection) != 0 && q.keysOnly {
|
||||||
|
return errors.New("datastore: query cannot both project and be keys-only")
|
||||||
|
}
|
||||||
|
dst.Reset()
|
||||||
|
dst.App = proto.String(appID)
|
||||||
|
if q.kind != "" {
|
||||||
|
dst.Kind = proto.String(q.kind)
|
||||||
|
}
|
||||||
|
if q.ancestor != nil {
|
||||||
|
dst.Ancestor = keyToProto(appID, q.ancestor)
|
||||||
|
if q.eventual {
|
||||||
|
dst.Strong = proto.Bool(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if q.projection != nil {
|
||||||
|
dst.PropertyName = q.projection
|
||||||
|
if q.distinct {
|
||||||
|
dst.GroupByPropertyName = q.projection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if q.keysOnly {
|
||||||
|
dst.KeysOnly = proto.Bool(true)
|
||||||
|
dst.RequirePerfectPlan = proto.Bool(true)
|
||||||
|
}
|
||||||
|
for _, qf := range q.filter {
|
||||||
|
if qf.FieldName == "" {
|
||||||
|
return errors.New("datastore: empty query filter field name")
|
||||||
|
}
|
||||||
|
p, errStr := valueToProto(appID, qf.FieldName, reflect.ValueOf(qf.Value), false)
|
||||||
|
if errStr != "" {
|
||||||
|
return errors.New("datastore: bad query filter value type: " + errStr)
|
||||||
|
}
|
||||||
|
xf := &pb.Query_Filter{
|
||||||
|
Op: operatorToProto[qf.Op],
|
||||||
|
Property: []*pb.Property{p},
|
||||||
|
}
|
||||||
|
if xf.Op == nil {
|
||||||
|
return errors.New("datastore: unknown query filter operator")
|
||||||
|
}
|
||||||
|
dst.Filter = append(dst.Filter, xf)
|
||||||
|
}
|
||||||
|
for _, qo := range q.order {
|
||||||
|
if qo.FieldName == "" {
|
||||||
|
return errors.New("datastore: empty query order field name")
|
||||||
|
}
|
||||||
|
xo := &pb.Query_Order{
|
||||||
|
Property: proto.String(qo.FieldName),
|
||||||
|
Direction: sortDirectionToProto[qo.Direction],
|
||||||
|
}
|
||||||
|
if xo.Direction == nil {
|
||||||
|
return errors.New("datastore: unknown query order direction")
|
||||||
|
}
|
||||||
|
dst.Order = append(dst.Order, xo)
|
||||||
|
}
|
||||||
|
if q.limit >= 0 {
|
||||||
|
dst.Limit = proto.Int32(q.limit)
|
||||||
|
}
|
||||||
|
if q.offset != 0 {
|
||||||
|
dst.Offset = proto.Int32(q.offset)
|
||||||
|
}
|
||||||
|
dst.CompiledCursor = q.start
|
||||||
|
dst.EndCompiledCursor = q.end
|
||||||
|
dst.Compile = proto.Bool(true)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the number of results for the query.
|
||||||
|
func (q *Query) Count(c appengine.Context) (int, error) {
|
||||||
|
// Check that the query is well-formed.
|
||||||
|
if q.err != nil {
|
||||||
|
return 0, q.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a copy of the query, with keysOnly true (if we're not a projection,
|
||||||
|
// since the two are incompatible), and an adjusted offset. We also set the
|
||||||
|
// limit to zero, as we don't want any actual entity data, just the number
|
||||||
|
// of skipped results.
|
||||||
|
newQ := q.clone()
|
||||||
|
newQ.keysOnly = len(newQ.projection) == 0
|
||||||
|
newQ.limit = 0
|
||||||
|
if q.limit < 0 {
|
||||||
|
// If the original query was unlimited, set the new query's offset to maximum.
|
||||||
|
newQ.offset = math.MaxInt32
|
||||||
|
} else {
|
||||||
|
newQ.offset = q.offset + q.limit
|
||||||
|
if newQ.offset < 0 {
|
||||||
|
// Do the best we can, in the presence of overflow.
|
||||||
|
newQ.offset = math.MaxInt32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req := &pb.Query{}
|
||||||
|
if err := newQ.toProto(req, c.FullyQualifiedAppID()); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
res := &pb.QueryResult{}
|
||||||
|
if err := c.Call("datastore_v3", "RunQuery", req, res, nil); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// n is the count we will return. For example, suppose that our original
|
||||||
|
// query had an offset of 4 and a limit of 2008: the count will be 2008,
|
||||||
|
// provided that there are at least 2012 matching entities. However, the
|
||||||
|
// RPCs will only skip 1000 results at a time. The RPC sequence is:
|
||||||
|
// call RunQuery with (offset, limit) = (2012, 0) // 2012 == newQ.offset
|
||||||
|
// response has (skippedResults, moreResults) = (1000, true)
|
||||||
|
// n += 1000 // n == 1000
|
||||||
|
// call Next with (offset, limit) = (1012, 0) // 1012 == newQ.offset - n
|
||||||
|
// response has (skippedResults, moreResults) = (1000, true)
|
||||||
|
// n += 1000 // n == 2000
|
||||||
|
// call Next with (offset, limit) = (12, 0) // 12 == newQ.offset - n
|
||||||
|
// response has (skippedResults, moreResults) = (12, false)
|
||||||
|
// n += 12 // n == 2012
|
||||||
|
// // exit the loop
|
||||||
|
// n -= 4 // n == 2008
|
||||||
|
var n int32
|
||||||
|
for {
|
||||||
|
// The QueryResult should have no actual entity data, just skipped results.
|
||||||
|
if len(res.Result) != 0 {
|
||||||
|
return 0, errors.New("datastore: internal error: Count request returned too much data")
|
||||||
|
}
|
||||||
|
n += res.GetSkippedResults()
|
||||||
|
if !res.GetMoreResults() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err := callNext(c, res, newQ.offset-n, 0); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n -= q.offset
|
||||||
|
if n < 0 {
|
||||||
|
// If the offset was greater than the number of matching entities,
|
||||||
|
// return 0 instead of negative.
|
||||||
|
n = 0
|
||||||
|
}
|
||||||
|
return int(n), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// callNext issues a datastore_v3/Next RPC to advance a cursor, such as that
|
||||||
|
// returned by a query with more results.
|
||||||
|
func callNext(c appengine.Context, res *pb.QueryResult, offset, limit int32) error {
|
||||||
|
if res.Cursor == nil {
|
||||||
|
return errors.New("datastore: internal error: server did not return a cursor")
|
||||||
|
}
|
||||||
|
req := &pb.NextRequest{
|
||||||
|
Cursor: res.Cursor,
|
||||||
|
}
|
||||||
|
if limit >= 0 {
|
||||||
|
req.Count = proto.Int32(limit)
|
||||||
|
}
|
||||||
|
if offset != 0 {
|
||||||
|
req.Offset = proto.Int32(offset)
|
||||||
|
}
|
||||||
|
if res.CompiledCursor != nil {
|
||||||
|
req.Compile = proto.Bool(true)
|
||||||
|
}
|
||||||
|
res.Reset()
|
||||||
|
return c.Call("datastore_v3", "Next", req, res, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll runs the query in the given context and returns all keys that match
|
||||||
|
// that query, as well as appending the values to dst.
|
||||||
|
//
|
||||||
|
// dst must have type *[]S or *[]*S or *[]P, for some struct type S or some non-
|
||||||
|
// interface, non-pointer type P such that P or *P implements PropertyLoadSaver.
|
||||||
|
//
|
||||||
|
// As a special case, *PropertyList is an invalid type for dst, even though a
|
||||||
|
// PropertyList is a slice of structs. It is treated as invalid to avoid being
|
||||||
|
// mistakenly passed when *[]PropertyList was intended.
|
||||||
|
//
|
||||||
|
// The keys returned by GetAll will be in a 1-1 correspondence with the entities
|
||||||
|
// added to dst.
|
||||||
|
//
|
||||||
|
// If q is a ``keys-only'' query, GetAll ignores dst and only returns the keys.
|
||||||
|
func (q *Query) GetAll(c appengine.Context, dst interface{}) ([]*Key, error) {
|
||||||
|
var (
|
||||||
|
dv reflect.Value
|
||||||
|
mat multiArgType
|
||||||
|
elemType reflect.Type
|
||||||
|
errFieldMismatch error
|
||||||
|
)
|
||||||
|
if !q.keysOnly {
|
||||||
|
dv = reflect.ValueOf(dst)
|
||||||
|
if dv.Kind() != reflect.Ptr || dv.IsNil() {
|
||||||
|
return nil, ErrInvalidEntityType
|
||||||
|
}
|
||||||
|
dv = dv.Elem()
|
||||||
|
mat, elemType = checkMultiArg(dv)
|
||||||
|
if mat == multiArgTypeInvalid || mat == multiArgTypeInterface {
|
||||||
|
return nil, ErrInvalidEntityType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys []*Key
|
||||||
|
for t := q.Run(c); ; {
|
||||||
|
k, e, err := t.next()
|
||||||
|
if err == Done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return keys, err
|
||||||
|
}
|
||||||
|
if !q.keysOnly {
|
||||||
|
ev := reflect.New(elemType)
|
||||||
|
if elemType.Kind() == reflect.Map {
|
||||||
|
// This is a special case. The zero values of a map type are
|
||||||
|
// not immediately useful; they have to be make'd.
|
||||||
|
//
|
||||||
|
// Funcs and channels are similar, in that a zero value is not useful,
|
||||||
|
// but even a freshly make'd channel isn't useful: there's no fixed
|
||||||
|
// channel buffer size that is always going to be large enough, and
|
||||||
|
// there's no goroutine to drain the other end. Theoretically, these
|
||||||
|
// types could be supported, for example by sniffing for a constructor
|
||||||
|
// method or requiring prior registration, but for now it's not a
|
||||||
|
// frequent enough concern to be worth it. Programmers can work around
|
||||||
|
// it by explicitly using Iterator.Next instead of the Query.GetAll
|
||||||
|
// convenience method.
|
||||||
|
x := reflect.MakeMap(elemType)
|
||||||
|
ev.Elem().Set(x)
|
||||||
|
}
|
||||||
|
if err = loadEntity(ev.Interface(), e); err != nil {
|
||||||
|
if _, ok := err.(*ErrFieldMismatch); ok {
|
||||||
|
// We continue loading entities even in the face of field mismatch errors.
|
||||||
|
// If we encounter any other error, that other error is returned. Otherwise,
|
||||||
|
// an ErrFieldMismatch is returned.
|
||||||
|
errFieldMismatch = err
|
||||||
|
} else {
|
||||||
|
return keys, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mat != multiArgTypeStructPtr {
|
||||||
|
ev = ev.Elem()
|
||||||
|
}
|
||||||
|
dv.Set(reflect.Append(dv, ev))
|
||||||
|
}
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
return keys, errFieldMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the query in the given context.
|
||||||
|
func (q *Query) Run(c appengine.Context) *Iterator {
|
||||||
|
if q.err != nil {
|
||||||
|
return &Iterator{err: q.err}
|
||||||
|
}
|
||||||
|
t := &Iterator{
|
||||||
|
c: c,
|
||||||
|
limit: q.limit,
|
||||||
|
q: q,
|
||||||
|
prevCC: q.start,
|
||||||
|
}
|
||||||
|
var req pb.Query
|
||||||
|
if err := q.toProto(&req, c.FullyQualifiedAppID()); err != nil {
|
||||||
|
t.err = err
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
if err := c.Call("datastore_v3", "RunQuery", &req, &t.res, nil); err != nil {
|
||||||
|
t.err = err
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
offset := q.offset - t.res.GetSkippedResults()
|
||||||
|
for offset > 0 && t.res.GetMoreResults() {
|
||||||
|
t.prevCC = t.res.CompiledCursor
|
||||||
|
if err := callNext(t.c, &t.res, offset, t.limit); err != nil {
|
||||||
|
t.err = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
skip := t.res.GetSkippedResults()
|
||||||
|
if skip < 0 {
|
||||||
|
t.err = errors.New("datastore: internal error: negative number of skipped_results")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset -= skip
|
||||||
|
}
|
||||||
|
if offset < 0 {
|
||||||
|
t.err = errors.New("datastore: internal error: query offset was overshot")
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterator is the result of running a query.
|
||||||
|
type Iterator struct {
|
||||||
|
c appengine.Context
|
||||||
|
err error
|
||||||
|
// res is the result of the most recent RunQuery or Next API call.
|
||||||
|
res pb.QueryResult
|
||||||
|
// i is how many elements of res.Result we have iterated over.
|
||||||
|
i int
|
||||||
|
// limit is the limit on the number of results this iterator should return.
|
||||||
|
// A negative value means unlimited.
|
||||||
|
limit int32
|
||||||
|
// q is the original query which yielded this iterator.
|
||||||
|
q *Query
|
||||||
|
// prevCC is the compiled cursor that marks the end of the previous batch
|
||||||
|
// of results.
|
||||||
|
prevCC *pb.CompiledCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done is returned when a query iteration has completed.
|
||||||
|
var Done = errors.New("datastore: query has no more results")
|
||||||
|
|
||||||
|
// Next returns the key of the next result. When there are no more results,
|
||||||
|
// Done is returned as the error.
|
||||||
|
//
|
||||||
|
// If the query is not keys only and dst is non-nil, it also loads the entity
|
||||||
|
// stored for that key into the struct pointer or PropertyLoadSaver dst, with
|
||||||
|
// the same semantics and possible errors as for the Get function.
|
||||||
|
func (t *Iterator) Next(dst interface{}) (*Key, error) {
|
||||||
|
k, e, err := t.next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if dst != nil && !t.q.keysOnly {
|
||||||
|
err = loadEntity(dst, e)
|
||||||
|
}
|
||||||
|
return k, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Iterator) next() (*Key, *pb.EntityProto, error) {
|
||||||
|
if t.err != nil {
|
||||||
|
return nil, nil, t.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue datastore_v3/Next RPCs as necessary.
|
||||||
|
for t.i == len(t.res.Result) {
|
||||||
|
if !t.res.GetMoreResults() {
|
||||||
|
t.err = Done
|
||||||
|
return nil, nil, t.err
|
||||||
|
}
|
||||||
|
t.prevCC = t.res.CompiledCursor
|
||||||
|
if err := callNext(t.c, &t.res, 0, t.limit); err != nil {
|
||||||
|
t.err = err
|
||||||
|
return nil, nil, t.err
|
||||||
|
}
|
||||||
|
if t.res.GetSkippedResults() != 0 {
|
||||||
|
t.err = errors.New("datastore: internal error: iterator has skipped results")
|
||||||
|
return nil, nil, t.err
|
||||||
|
}
|
||||||
|
t.i = 0
|
||||||
|
if t.limit >= 0 {
|
||||||
|
t.limit -= int32(len(t.res.Result))
|
||||||
|
if t.limit < 0 {
|
||||||
|
t.err = errors.New("datastore: internal error: query returned more results than the limit")
|
||||||
|
return nil, nil, t.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the key from the t.i'th element of t.res.Result.
|
||||||
|
e := t.res.Result[t.i]
|
||||||
|
t.i++
|
||||||
|
if e.Key == nil {
|
||||||
|
return nil, nil, errors.New("datastore: internal error: server did not return a key")
|
||||||
|
}
|
||||||
|
k, err := protoToKey(e.Key)
|
||||||
|
if err != nil || k.Incomplete() {
|
||||||
|
return nil, nil, errors.New("datastore: internal error: server returned an invalid key")
|
||||||
|
}
|
||||||
|
return k, e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor returns a cursor for the iterator's current location.
|
||||||
|
func (t *Iterator) Cursor() (Cursor, error) {
|
||||||
|
if t.err != nil && t.err != Done {
|
||||||
|
return Cursor{}, t.err
|
||||||
|
}
|
||||||
|
// If we are at either end of the current batch of results,
|
||||||
|
// return the compiled cursor at that end.
|
||||||
|
skipped := t.res.GetSkippedResults()
|
||||||
|
if t.i == 0 && skipped == 0 {
|
||||||
|
if t.prevCC == nil {
|
||||||
|
// A nil pointer (of type *pb.CompiledCursor) means no constraint:
|
||||||
|
// passing it as the end cursor of a new query means unlimited results
|
||||||
|
// (glossing over the integer limit parameter for now).
|
||||||
|
// A non-nil pointer to an empty pb.CompiledCursor means the start:
|
||||||
|
// passing it as the end cursor of a new query means 0 results.
|
||||||
|
// If prevCC was nil, then the original query had no start cursor, but
|
||||||
|
// Iterator.Cursor should return "the start" instead of unlimited.
|
||||||
|
return Cursor{&zeroCC}, nil
|
||||||
|
}
|
||||||
|
return Cursor{t.prevCC}, nil
|
||||||
|
}
|
||||||
|
if t.i == len(t.res.Result) {
|
||||||
|
return Cursor{t.res.CompiledCursor}, nil
|
||||||
|
}
|
||||||
|
// Otherwise, re-run the query offset to this iterator's position, starting from
|
||||||
|
// the most recent compiled cursor. This is done on a best-effort basis, as it
|
||||||
|
// is racy; if a concurrent process has added or removed entities, then the
|
||||||
|
// cursor returned may be inconsistent.
|
||||||
|
q := t.q.clone()
|
||||||
|
q.start = t.prevCC
|
||||||
|
q.offset = skipped + int32(t.i)
|
||||||
|
q.limit = 0
|
||||||
|
q.keysOnly = len(q.projection) == 0
|
||||||
|
t1 := q.Run(t.c)
|
||||||
|
_, _, err := t1.next()
|
||||||
|
if err != Done {
|
||||||
|
if err == nil {
|
||||||
|
err = fmt.Errorf("datastore: internal error: zero-limit query did not have zero results")
|
||||||
|
}
|
||||||
|
return Cursor{}, err
|
||||||
|
}
|
||||||
|
return Cursor{t1.res.CompiledCursor}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var zeroCC pb.CompiledCursor
|
||||||
|
|
||||||
|
// Cursor is an iterator's position. It can be converted to and from an opaque
|
||||||
|
// string. A cursor can be used from different HTTP requests, but only with a
|
||||||
|
// query with the same kind, ancestor, filter and order constraints.
|
||||||
|
type Cursor struct {
|
||||||
|
cc *pb.CompiledCursor
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a base-64 string representation of a cursor.
|
||||||
|
func (c Cursor) String() string {
|
||||||
|
if c.cc == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
b, err := proto.Marshal(c.cc)
|
||||||
|
if err != nil {
|
||||||
|
// The only way to construct a Cursor with a non-nil cc field is to
|
||||||
|
// unmarshal from the byte representation. We panic if the unmarshal
|
||||||
|
// succeeds but the marshaling of the unchanged protobuf value fails.
|
||||||
|
panic(fmt.Sprintf("datastore: internal error: malformed cursor: %v", err))
|
||||||
|
}
|
||||||
|
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes a cursor from its base-64 string representation.
|
||||||
|
func DecodeCursor(s string) (Cursor, error) {
|
||||||
|
if s == "" {
|
||||||
|
return Cursor{&zeroCC}, nil
|
||||||
|
}
|
||||||
|
if n := len(s) % 4; n != 0 {
|
||||||
|
s += strings.Repeat("=", 4-n)
|
||||||
|
}
|
||||||
|
b, err := base64.URLEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return Cursor{}, err
|
||||||
|
}
|
||||||
|
cc := &pb.CompiledCursor{}
|
||||||
|
if err := proto.Unmarshal(b, cc); err != nil {
|
||||||
|
return Cursor{}, err
|
||||||
|
}
|
||||||
|
return Cursor{cc}, nil
|
||||||
|
}
|
||||||
580
Godeps/_workspace/src/google.golang.org/appengine/datastore/query_test.go
generated
vendored
Normal file
580
Godeps/_workspace/src/google.golang.org/appengine/datastore/query_test.go
generated
vendored
Normal file
@@ -0,0 +1,580 @@
|
|||||||
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package datastore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
"google.golang.org/appengine/internal/aetesting"
|
||||||
|
pb "google.golang.org/appengine/internal/datastore"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
path1 = &pb.Path{
|
||||||
|
Element: []*pb.Path_Element{
|
||||||
|
{
|
||||||
|
Type: proto.String("Gopher"),
|
||||||
|
Id: proto.Int64(6),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
path2 = &pb.Path{
|
||||||
|
Element: []*pb.Path_Element{
|
||||||
|
{
|
||||||
|
Type: proto.String("Gopher"),
|
||||||
|
Id: proto.Int64(6),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: proto.String("Gopher"),
|
||||||
|
Id: proto.Int64(8),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func fakeRunQuery(in *pb.Query, out *pb.QueryResult) error {
|
||||||
|
expectedIn := &pb.Query{
|
||||||
|
App: proto.String("dev~fake-app"),
|
||||||
|
Kind: proto.String("Gopher"),
|
||||||
|
Compile: proto.Bool(true),
|
||||||
|
}
|
||||||
|
if !proto.Equal(in, expectedIn) {
|
||||||
|
return fmt.Errorf("unsupported argument: got %v want %v", in, expectedIn)
|
||||||
|
}
|
||||||
|
*out = pb.QueryResult{
|
||||||
|
Result: []*pb.EntityProto{
|
||||||
|
{
|
||||||
|
Key: &pb.Reference{
|
||||||
|
App: proto.String("s~test-app"),
|
||||||
|
Path: path1,
|
||||||
|
},
|
||||||
|
EntityGroup: path1,
|
||||||
|
Property: []*pb.Property{
|
||||||
|
{
|
||||||
|
Meaning: pb.Property_TEXT.Enum(),
|
||||||
|
Name: proto.String("Name"),
|
||||||
|
Value: &pb.PropertyValue{
|
||||||
|
StringValue: proto.String("George"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: proto.String("Height"),
|
||||||
|
Value: &pb.PropertyValue{
|
||||||
|
Int64Value: proto.Int64(32),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: &pb.Reference{
|
||||||
|
App: proto.String("s~test-app"),
|
||||||
|
Path: path2,
|
||||||
|
},
|
||||||
|
EntityGroup: path1, // ancestor is George
|
||||||
|
Property: []*pb.Property{
|
||||||
|
{
|
||||||
|
Meaning: pb.Property_TEXT.Enum(),
|
||||||
|
Name: proto.String("Name"),
|
||||||
|
Value: &pb.PropertyValue{
|
||||||
|
StringValue: proto.String("Rufus"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// No height for Rufus.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MoreResults: proto.Bool(false),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructThatImplementsPLS struct{}
|
||||||
|
|
||||||
|
func (StructThatImplementsPLS) Load(p []Property) error { return nil }
|
||||||
|
func (StructThatImplementsPLS) Save() ([]Property, error) { return nil, nil }
|
||||||
|
|
||||||
|
var _ PropertyLoadSaver = StructThatImplementsPLS{}
|
||||||
|
|
||||||
|
type StructPtrThatImplementsPLS struct{}
|
||||||
|
|
||||||
|
func (*StructPtrThatImplementsPLS) Load(p []Property) error { return nil }
|
||||||
|
func (*StructPtrThatImplementsPLS) Save() ([]Property, error) { return nil, nil }
|
||||||
|
|
||||||
|
var _ PropertyLoadSaver = &StructPtrThatImplementsPLS{}
|
||||||
|
|
||||||
|
type PropertyMap map[string]Property
|
||||||
|
|
||||||
|
func (m PropertyMap) Load(props []Property) error {
|
||||||
|
for _, p := range props {
|
||||||
|
if p.Multiple {
|
||||||
|
return errors.New("PropertyMap does not support multiple properties")
|
||||||
|
}
|
||||||
|
m[p.Name] = p
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m PropertyMap) Save() ([]Property, error) {
|
||||||
|
props := make([]Property, 0, len(m))
|
||||||
|
for _, p := range m {
|
||||||
|
if p.Multiple {
|
||||||
|
return nil, errors.New("PropertyMap does not support multiple properties")
|
||||||
|
}
|
||||||
|
props = append(props, p)
|
||||||
|
}
|
||||||
|
return props, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PropertyLoadSaver = PropertyMap{}
|
||||||
|
|
||||||
|
type Gopher struct {
|
||||||
|
Name string
|
||||||
|
Height int
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeOfEmptyInterface is the type of interface{}, but we can't use
|
||||||
|
// reflect.TypeOf((interface{})(nil)) directly because TypeOf takes an
|
||||||
|
// interface{}.
|
||||||
|
var typeOfEmptyInterface = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||||
|
|
||||||
|
func TestCheckMultiArg(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
v interface{}
|
||||||
|
mat multiArgType
|
||||||
|
elemType reflect.Type
|
||||||
|
}{
|
||||||
|
// Invalid cases.
|
||||||
|
{nil, multiArgTypeInvalid, nil},
|
||||||
|
{Gopher{}, multiArgTypeInvalid, nil},
|
||||||
|
{&Gopher{}, multiArgTypeInvalid, nil},
|
||||||
|
{PropertyList{}, multiArgTypeInvalid, nil}, // This is a special case.
|
||||||
|
{PropertyMap{}, multiArgTypeInvalid, nil},
|
||||||
|
{[]*PropertyList(nil), multiArgTypeInvalid, nil},
|
||||||
|
{[]*PropertyMap(nil), multiArgTypeInvalid, nil},
|
||||||
|
{[]**Gopher(nil), multiArgTypeInvalid, nil},
|
||||||
|
{[]*interface{}(nil), multiArgTypeInvalid, nil},
|
||||||
|
// Valid cases.
|
||||||
|
{
|
||||||
|
[]PropertyList(nil),
|
||||||
|
multiArgTypePropertyLoadSaver,
|
||||||
|
reflect.TypeOf(PropertyList{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]PropertyMap(nil),
|
||||||
|
multiArgTypePropertyLoadSaver,
|
||||||
|
reflect.TypeOf(PropertyMap{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]StructThatImplementsPLS(nil),
|
||||||
|
multiArgTypePropertyLoadSaver,
|
||||||
|
reflect.TypeOf(StructThatImplementsPLS{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]StructPtrThatImplementsPLS(nil),
|
||||||
|
multiArgTypePropertyLoadSaver,
|
||||||
|
reflect.TypeOf(StructPtrThatImplementsPLS{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]Gopher(nil),
|
||||||
|
multiArgTypeStruct,
|
||||||
|
reflect.TypeOf(Gopher{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]*Gopher(nil),
|
||||||
|
multiArgTypeStructPtr,
|
||||||
|
reflect.TypeOf(Gopher{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}(nil),
|
||||||
|
multiArgTypeInterface,
|
||||||
|
typeOfEmptyInterface,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
mat, elemType := checkMultiArg(reflect.ValueOf(tc.v))
|
||||||
|
if mat != tc.mat || elemType != tc.elemType {
|
||||||
|
t.Errorf("checkMultiArg(%T): got %v, %v want %v, %v",
|
||||||
|
tc.v, mat, elemType, tc.mat, tc.elemType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleQuery(t *testing.T) {
|
||||||
|
struct1 := Gopher{Name: "George", Height: 32}
|
||||||
|
struct2 := Gopher{Name: "Rufus"}
|
||||||
|
pList1 := PropertyList{
|
||||||
|
{
|
||||||
|
Name: "Name",
|
||||||
|
Value: "George",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Height",
|
||||||
|
Value: int64(32),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pList2 := PropertyList{
|
||||||
|
{
|
||||||
|
Name: "Name",
|
||||||
|
Value: "Rufus",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pMap1 := PropertyMap{
|
||||||
|
"Name": Property{
|
||||||
|
Name: "Name",
|
||||||
|
Value: "George",
|
||||||
|
},
|
||||||
|
"Height": Property{
|
||||||
|
Name: "Height",
|
||||||
|
Value: int64(32),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pMap2 := PropertyMap{
|
||||||
|
"Name": Property{
|
||||||
|
Name: "Name",
|
||||||
|
Value: "Rufus",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
dst interface{}
|
||||||
|
want interface{}
|
||||||
|
}{
|
||||||
|
// The destination must have type *[]P, *[]S or *[]*S, for some non-interface
|
||||||
|
// type P such that *P implements PropertyLoadSaver, or for some struct type S.
|
||||||
|
{new([]Gopher), &[]Gopher{struct1, struct2}},
|
||||||
|
{new([]*Gopher), &[]*Gopher{&struct1, &struct2}},
|
||||||
|
{new([]PropertyList), &[]PropertyList{pList1, pList2}},
|
||||||
|
{new([]PropertyMap), &[]PropertyMap{pMap1, pMap2}},
|
||||||
|
|
||||||
|
// Any other destination type is invalid.
|
||||||
|
{0, nil},
|
||||||
|
{Gopher{}, nil},
|
||||||
|
{PropertyList{}, nil},
|
||||||
|
{PropertyMap{}, nil},
|
||||||
|
{[]int{}, nil},
|
||||||
|
{[]Gopher{}, nil},
|
||||||
|
{[]PropertyList{}, nil},
|
||||||
|
{new(int), nil},
|
||||||
|
{new(Gopher), nil},
|
||||||
|
{new(PropertyList), nil}, // This is a special case.
|
||||||
|
{new(PropertyMap), nil},
|
||||||
|
{new([]int), nil},
|
||||||
|
{new([]map[int]int), nil},
|
||||||
|
{new([]map[string]Property), nil},
|
||||||
|
{new([]map[string]interface{}), nil},
|
||||||
|
{new([]*int), nil},
|
||||||
|
{new([]*map[int]int), nil},
|
||||||
|
{new([]*map[string]Property), nil},
|
||||||
|
{new([]*map[string]interface{}), nil},
|
||||||
|
{new([]**Gopher), nil},
|
||||||
|
{new([]*PropertyList), nil},
|
||||||
|
{new([]*PropertyMap), nil},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
nCall := 0
|
||||||
|
c := aetesting.FakeSingleContext(t, "datastore_v3", "RunQuery", func(in *pb.Query, out *pb.QueryResult) error {
|
||||||
|
nCall++
|
||||||
|
return fakeRunQuery(in, out)
|
||||||
|
})
|
||||||
|
|
||||||
|
var (
|
||||||
|
expectedErr error
|
||||||
|
expectedNCall int
|
||||||
|
)
|
||||||
|
if tc.want == nil {
|
||||||
|
expectedErr = ErrInvalidEntityType
|
||||||
|
} else {
|
||||||
|
expectedNCall = 1
|
||||||
|
}
|
||||||
|
keys, err := NewQuery("Gopher").GetAll(c, tc.dst)
|
||||||
|
if err != expectedErr {
|
||||||
|
t.Errorf("dst type %T: got error %v, want %v", tc.dst, err, expectedErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nCall != expectedNCall {
|
||||||
|
t.Errorf("dst type %T: Context.Call was called an incorrect number of times: got %d want %d", tc.dst, nCall, expectedNCall)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key1 := NewKey(c, "Gopher", "", 6, nil)
|
||||||
|
expectedKeys := []*Key{
|
||||||
|
key1,
|
||||||
|
NewKey(c, "Gopher", "", 8, key1),
|
||||||
|
}
|
||||||
|
if l1, l2 := len(keys), len(expectedKeys); l1 != l2 {
|
||||||
|
t.Errorf("dst type %T: got %d keys, want %d keys", tc.dst, l1, l2)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
if key.AppID() != "s~test-app" {
|
||||||
|
t.Errorf(`dst type %T: Key #%d's AppID = %q, want "s~test-app"`, tc.dst, i, key.AppID())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !keysEqual(key, expectedKeys[i]) {
|
||||||
|
t.Errorf("dst type %T: got key #%d %v, want %v", tc.dst, i, key, expectedKeys[i])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.dst, tc.want) {
|
||||||
|
t.Errorf("dst type %T: Entities got %+v, want %+v", tc.dst, tc.dst, tc.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keysEqual is like (*Key).Equal, but ignores the App ID.
|
||||||
|
func keysEqual(a, b *Key) bool {
|
||||||
|
for a != nil && b != nil {
|
||||||
|
if a.Kind() != b.Kind() || a.StringID() != b.StringID() || a.IntID() != b.IntID() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
a, b = a.Parent(), b.Parent()
|
||||||
|
}
|
||||||
|
return a == b
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueriesAreImmutable(t *testing.T) {
|
||||||
|
// Test that deriving q2 from q1 does not modify q1.
|
||||||
|
q0 := NewQuery("foo")
|
||||||
|
q1 := NewQuery("foo")
|
||||||
|
q2 := q1.Offset(2)
|
||||||
|
if !reflect.DeepEqual(q0, q1) {
|
||||||
|
t.Errorf("q0 and q1 were not equal")
|
||||||
|
}
|
||||||
|
if reflect.DeepEqual(q1, q2) {
|
||||||
|
t.Errorf("q1 and q2 were equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that deriving from q4 twice does not conflict, even though
|
||||||
|
// q4 has a long list of order clauses. This tests that the arrays
|
||||||
|
// backed by a query's slice of orders are not shared.
|
||||||
|
f := func() *Query {
|
||||||
|
q := NewQuery("bar")
|
||||||
|
// 47 is an ugly number that is unlikely to be near a re-allocation
|
||||||
|
// point in repeated append calls. For example, it's not near a power
|
||||||
|
// of 2 or a multiple of 10.
|
||||||
|
for i := 0; i < 47; i++ {
|
||||||
|
q = q.Order(fmt.Sprintf("x%d", i))
|
||||||
|
}
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
q3 := f().Order("y")
|
||||||
|
q4 := f()
|
||||||
|
q5 := q4.Order("y")
|
||||||
|
q6 := q4.Order("z")
|
||||||
|
if !reflect.DeepEqual(q3, q5) {
|
||||||
|
t.Errorf("q3 and q5 were not equal")
|
||||||
|
}
|
||||||
|
if reflect.DeepEqual(q5, q6) {
|
||||||
|
t.Errorf("q5 and q6 were equal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterParser(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
filterStr string
|
||||||
|
wantOK bool
|
||||||
|
wantFieldName string
|
||||||
|
wantOp operator
|
||||||
|
}{
|
||||||
|
// Supported ops.
|
||||||
|
{"x<", true, "x", lessThan},
|
||||||
|
{"x <", true, "x", lessThan},
|
||||||
|
{"x <", true, "x", lessThan},
|
||||||
|
{" x < ", true, "x", lessThan},
|
||||||
|
{"x <=", true, "x", lessEq},
|
||||||
|
{"x =", true, "x", equal},
|
||||||
|
{"x >=", true, "x", greaterEq},
|
||||||
|
{"x >", true, "x", greaterThan},
|
||||||
|
{"in >", true, "in", greaterThan},
|
||||||
|
{"in>", true, "in", greaterThan},
|
||||||
|
// Valid but (currently) unsupported ops.
|
||||||
|
{"x!=", false, "", 0},
|
||||||
|
{"x !=", false, "", 0},
|
||||||
|
{" x != ", false, "", 0},
|
||||||
|
{"x IN", false, "", 0},
|
||||||
|
{"x in", false, "", 0},
|
||||||
|
// Invalid ops.
|
||||||
|
{"x EQ", false, "", 0},
|
||||||
|
{"x lt", false, "", 0},
|
||||||
|
{"x <>", false, "", 0},
|
||||||
|
{"x >>", false, "", 0},
|
||||||
|
{"x ==", false, "", 0},
|
||||||
|
{"x =<", false, "", 0},
|
||||||
|
{"x =>", false, "", 0},
|
||||||
|
{"x !", false, "", 0},
|
||||||
|
{"x ", false, "", 0},
|
||||||
|
{"x", false, "", 0},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
q := NewQuery("foo").Filter(tc.filterStr, 42)
|
||||||
|
if ok := q.err == nil; ok != tc.wantOK {
|
||||||
|
t.Errorf("%q: ok=%t, want %t", tc.filterStr, ok, tc.wantOK)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !tc.wantOK {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(q.filter) != 1 {
|
||||||
|
t.Errorf("%q: len=%d, want %d", tc.filterStr, len(q.filter), 1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
got, want := q.filter[0], filter{tc.wantFieldName, tc.wantOp, 42}
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("%q: got %v, want %v", tc.filterStr, got, want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryToProto(t *testing.T) {
|
||||||
|
// The context is required to make Keys for the test cases.
|
||||||
|
var got *pb.Query
|
||||||
|
NoErr := errors.New("No error")
|
||||||
|
c := aetesting.FakeSingleContext(t, "datastore_v3", "RunQuery", func(in *pb.Query, out *pb.QueryResult) error {
|
||||||
|
got = in
|
||||||
|
return NoErr // return a non-nil error so Run doesn't keep going.
|
||||||
|
})
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
query *Query
|
||||||
|
want *pb.Query
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "empty",
|
||||||
|
query: NewQuery(""),
|
||||||
|
want: &pb.Query{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "standard query",
|
||||||
|
query: NewQuery("kind").Order("-I").Filter("I >", 17).Filter("U =", "Dave").Limit(7).Offset(42),
|
||||||
|
want: &pb.Query{
|
||||||
|
Kind: proto.String("kind"),
|
||||||
|
Filter: []*pb.Query_Filter{
|
||||||
|
{
|
||||||
|
Op: pb.Query_Filter_GREATER_THAN.Enum(),
|
||||||
|
Property: []*pb.Property{
|
||||||
|
{
|
||||||
|
Name: proto.String("I"),
|
||||||
|
Value: &pb.PropertyValue{Int64Value: proto.Int64(17)},
|
||||||
|
Multiple: proto.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Op: pb.Query_Filter_EQUAL.Enum(),
|
||||||
|
Property: []*pb.Property{
|
||||||
|
{
|
||||||
|
Name: proto.String("U"),
|
||||||
|
Value: &pb.PropertyValue{StringValue: proto.String("Dave")},
|
||||||
|
Multiple: proto.Bool(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Order: []*pb.Query_Order{
|
||||||
|
{
|
||||||
|
Property: proto.String("I"),
|
||||||
|
Direction: pb.Query_Order_DESCENDING.Enum(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Limit: proto.Int32(7),
|
||||||
|
Offset: proto.Int32(42),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "ancestor",
|
||||||
|
query: NewQuery("").Ancestor(NewKey(c, "kind", "Mummy", 0, nil)),
|
||||||
|
want: &pb.Query{
|
||||||
|
Ancestor: &pb.Reference{
|
||||||
|
App: proto.String("dev~fake-app"),
|
||||||
|
Path: &pb.Path{
|
||||||
|
Element: []*pb.Path_Element{{Type: proto.String("kind"), Name: proto.String("Mummy")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "projection",
|
||||||
|
query: NewQuery("").Project("A", "B"),
|
||||||
|
want: &pb.Query{
|
||||||
|
PropertyName: []string{"A", "B"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "projection with distinct",
|
||||||
|
query: NewQuery("").Project("A", "B").Distinct(),
|
||||||
|
want: &pb.Query{
|
||||||
|
PropertyName: []string{"A", "B"},
|
||||||
|
GroupByPropertyName: []string{"A", "B"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "keys only",
|
||||||
|
query: NewQuery("").KeysOnly(),
|
||||||
|
want: &pb.Query{
|
||||||
|
KeysOnly: proto.Bool(true),
|
||||||
|
RequirePerfectPlan: proto.Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty filter",
|
||||||
|
query: NewQuery("kind").Filter("=", 17),
|
||||||
|
err: "empty query filter field nam",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "bad filter type",
|
||||||
|
query: NewQuery("kind").Filter("M =", map[string]bool{}),
|
||||||
|
err: "bad query filter value type",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "bad filter operator",
|
||||||
|
query: NewQuery("kind").Filter("I <<=", 17),
|
||||||
|
err: `invalid operator "<<=" in filter "I <<="`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty order",
|
||||||
|
query: NewQuery("kind").Order(""),
|
||||||
|
err: "empty order",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "bad order direction",
|
||||||
|
query: NewQuery("kind").Order("+I"),
|
||||||
|
err: `invalid order: "+I`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range testCases {
|
||||||
|
got = nil
|
||||||
|
if _, err := tt.query.Run(c).Next(nil); err != NoErr {
|
||||||
|
if tt.err == "" || !strings.Contains(err.Error(), tt.err) {
|
||||||
|
t.Errorf("%s: error %v, want %q", tt.desc, err, tt.err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.err != "" {
|
||||||
|
t.Errorf("%s: no error, want %q", tt.desc, tt.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Fields that are common to all protos.
|
||||||
|
tt.want.App = proto.String("dev~fake-app")
|
||||||
|
tt.want.Compile = proto.Bool(true)
|
||||||
|
if !proto.Equal(got, tt.want) {
|
||||||
|
t.Errorf("%s:\ngot %v\nwant %v", tt.desc, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
300
Godeps/_workspace/src/google.golang.org/appengine/datastore/save.go
generated
vendored
Normal file
300
Godeps/_workspace/src/google.golang.org/appengine/datastore/save.go
generated
vendored
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package datastore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
pb "google.golang.org/appengine/internal/datastore"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toUnixMicro(t time.Time) int64 {
|
||||||
|
// We cannot use t.UnixNano() / 1e3 because we want to handle times more than
|
||||||
|
// 2^63 nanoseconds (which is about 292 years) away from 1970, and those cannot
|
||||||
|
// be represented in the numerator of a single int64 divide.
|
||||||
|
return t.Unix()*1e6 + int64(t.Nanosecond()/1e3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromUnixMicro(t int64) time.Time {
|
||||||
|
return time.Unix(t/1e6, (t%1e6)*1e3)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
minTime = time.Unix(int64(math.MinInt64)/1e6, (int64(math.MinInt64)%1e6)*1e3)
|
||||||
|
maxTime = time.Unix(int64(math.MaxInt64)/1e6, (int64(math.MaxInt64)%1e6)*1e3)
|
||||||
|
)
|
||||||
|
|
||||||
|
// valueToProto converts a named value to a newly allocated Property.
|
||||||
|
// The returned error string is empty on success.
|
||||||
|
func valueToProto(defaultAppID, name string, v reflect.Value, multiple bool) (p *pb.Property, errStr string) {
|
||||||
|
var (
|
||||||
|
pv pb.PropertyValue
|
||||||
|
unsupported bool
|
||||||
|
)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// No-op.
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
pv.Int64Value = proto.Int64(v.Int())
|
||||||
|
case reflect.Bool:
|
||||||
|
pv.BooleanValue = proto.Bool(v.Bool())
|
||||||
|
case reflect.String:
|
||||||
|
pv.StringValue = proto.String(v.String())
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
pv.DoubleValue = proto.Float64(v.Float())
|
||||||
|
case reflect.Ptr:
|
||||||
|
if k, ok := v.Interface().(*Key); ok {
|
||||||
|
if k != nil {
|
||||||
|
pv.Referencevalue = keyToReferenceValue(defaultAppID, k)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unsupported = true
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
switch t := v.Interface().(type) {
|
||||||
|
case time.Time:
|
||||||
|
if t.Before(minTime) || t.After(maxTime) {
|
||||||
|
return nil, "time value out of range"
|
||||||
|
}
|
||||||
|
pv.Int64Value = proto.Int64(toUnixMicro(t))
|
||||||
|
case appengine.GeoPoint:
|
||||||
|
if !t.Valid() {
|
||||||
|
return nil, "invalid GeoPoint value"
|
||||||
|
}
|
||||||
|
// NOTE: Strangely, latitude maps to X, longitude to Y.
|
||||||
|
pv.Pointvalue = &pb.PropertyValue_PointValue{X: &t.Lat, Y: &t.Lng}
|
||||||
|
default:
|
||||||
|
unsupported = true
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
if b, ok := v.Interface().([]byte); ok {
|
||||||
|
pv.StringValue = proto.String(string(b))
|
||||||
|
} else {
|
||||||
|
// nvToProto should already catch slice values.
|
||||||
|
// If we get here, we have a slice of slice values.
|
||||||
|
unsupported = true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
unsupported = true
|
||||||
|
}
|
||||||
|
if unsupported {
|
||||||
|
return nil, "unsupported datastore value type: " + v.Type().String()
|
||||||
|
}
|
||||||
|
p = &pb.Property{
|
||||||
|
Name: proto.String(name),
|
||||||
|
Value: &pv,
|
||||||
|
Multiple: proto.Bool(multiple),
|
||||||
|
}
|
||||||
|
if v.IsValid() {
|
||||||
|
switch v.Interface().(type) {
|
||||||
|
case []byte:
|
||||||
|
p.Meaning = pb.Property_BLOB.Enum()
|
||||||
|
case ByteString:
|
||||||
|
p.Meaning = pb.Property_BYTESTRING.Enum()
|
||||||
|
case appengine.BlobKey:
|
||||||
|
p.Meaning = pb.Property_BLOBKEY.Enum()
|
||||||
|
case time.Time:
|
||||||
|
p.Meaning = pb.Property_GD_WHEN.Enum()
|
||||||
|
case appengine.GeoPoint:
|
||||||
|
p.Meaning = pb.Property_GEORSS_POINT.Enum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveEntity saves an EntityProto into a PropertyLoadSaver or struct pointer.
|
||||||
|
func saveEntity(defaultAppID string, key *Key, src interface{}) (*pb.EntityProto, error) {
|
||||||
|
var err error
|
||||||
|
var props []Property
|
||||||
|
if e, ok := src.(PropertyLoadSaver); ok {
|
||||||
|
props, err = e.Save()
|
||||||
|
} else {
|
||||||
|
props, err = SaveStruct(src)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return propertiesToProto(defaultAppID, key, props)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveStructProperty(props *[]Property, name string, noIndex, multiple bool, v reflect.Value) error {
|
||||||
|
p := Property{
|
||||||
|
Name: name,
|
||||||
|
NoIndex: noIndex,
|
||||||
|
Multiple: multiple,
|
||||||
|
}
|
||||||
|
switch x := v.Interface().(type) {
|
||||||
|
case *Key:
|
||||||
|
p.Value = x
|
||||||
|
case time.Time:
|
||||||
|
p.Value = x
|
||||||
|
case appengine.BlobKey:
|
||||||
|
p.Value = x
|
||||||
|
case appengine.GeoPoint:
|
||||||
|
p.Value = x
|
||||||
|
case ByteString:
|
||||||
|
p.Value = x
|
||||||
|
default:
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
p.Value = v.Int()
|
||||||
|
case reflect.Bool:
|
||||||
|
p.Value = v.Bool()
|
||||||
|
case reflect.String:
|
||||||
|
p.Value = v.String()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
p.Value = v.Float()
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.Type().Elem().Kind() == reflect.Uint8 {
|
||||||
|
p.NoIndex = true
|
||||||
|
p.Value = v.Bytes()
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
if !v.CanAddr() {
|
||||||
|
return fmt.Errorf("datastore: unsupported struct field: value is unaddressable")
|
||||||
|
}
|
||||||
|
sub, err := newStructPLS(v.Addr().Interface())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("datastore: unsupported struct field: %v", err)
|
||||||
|
}
|
||||||
|
return sub.(structPLS).save(props, name, noIndex, multiple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Value == nil {
|
||||||
|
return fmt.Errorf("datastore: unsupported struct field type: %v", v.Type())
|
||||||
|
}
|
||||||
|
*props = append(*props, p)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s structPLS) Save() ([]Property, error) {
|
||||||
|
var props []Property
|
||||||
|
if err := s.save(&props, "", false, false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return props, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s structPLS) save(props *[]Property, prefix string, noIndex, multiple bool) error {
|
||||||
|
for i, t := range s.codec.byIndex {
|
||||||
|
if t.name == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := t.name
|
||||||
|
if prefix != "" {
|
||||||
|
name = prefix + name
|
||||||
|
}
|
||||||
|
v := s.v.Field(i)
|
||||||
|
if !v.IsValid() || !v.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
noIndex1 := noIndex || t.noIndex
|
||||||
|
// For slice fields that aren't []byte, save each element.
|
||||||
|
if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 {
|
||||||
|
for j := 0; j < v.Len(); j++ {
|
||||||
|
if err := saveStructProperty(props, name, noIndex1, true, v.Index(j)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Otherwise, save the field itself.
|
||||||
|
if err := saveStructProperty(props, name, noIndex1, multiple, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func propertiesToProto(defaultAppID string, key *Key, props []Property) (*pb.EntityProto, error) {
|
||||||
|
e := &pb.EntityProto{
|
||||||
|
Key: keyToProto(defaultAppID, key),
|
||||||
|
}
|
||||||
|
if key.parent == nil {
|
||||||
|
e.EntityGroup = &pb.Path{}
|
||||||
|
} else {
|
||||||
|
e.EntityGroup = keyToProto(defaultAppID, key.root()).Path
|
||||||
|
}
|
||||||
|
prevMultiple := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, p := range props {
|
||||||
|
if pm, ok := prevMultiple[p.Name]; ok {
|
||||||
|
if !pm || !p.Multiple {
|
||||||
|
return nil, fmt.Errorf("datastore: multiple Properties with Name %q, but Multiple is false", p.Name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prevMultiple[p.Name] = p.Multiple
|
||||||
|
}
|
||||||
|
|
||||||
|
x := &pb.Property{
|
||||||
|
Name: proto.String(p.Name),
|
||||||
|
Value: new(pb.PropertyValue),
|
||||||
|
Multiple: proto.Bool(p.Multiple),
|
||||||
|
}
|
||||||
|
switch v := p.Value.(type) {
|
||||||
|
case int64:
|
||||||
|
x.Value.Int64Value = proto.Int64(v)
|
||||||
|
case bool:
|
||||||
|
x.Value.BooleanValue = proto.Bool(v)
|
||||||
|
case string:
|
||||||
|
x.Value.StringValue = proto.String(v)
|
||||||
|
if p.NoIndex {
|
||||||
|
x.Meaning = pb.Property_TEXT.Enum()
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
x.Value.DoubleValue = proto.Float64(v)
|
||||||
|
case *Key:
|
||||||
|
if v != nil {
|
||||||
|
x.Value.Referencevalue = keyToReferenceValue(defaultAppID, v)
|
||||||
|
}
|
||||||
|
case time.Time:
|
||||||
|
if v.Before(minTime) || v.After(maxTime) {
|
||||||
|
return nil, fmt.Errorf("datastore: time value out of range")
|
||||||
|
}
|
||||||
|
x.Value.Int64Value = proto.Int64(toUnixMicro(v))
|
||||||
|
x.Meaning = pb.Property_GD_WHEN.Enum()
|
||||||
|
case appengine.BlobKey:
|
||||||
|
x.Value.StringValue = proto.String(string(v))
|
||||||
|
x.Meaning = pb.Property_BLOBKEY.Enum()
|
||||||
|
case appengine.GeoPoint:
|
||||||
|
if !v.Valid() {
|
||||||
|
return nil, fmt.Errorf("datastore: invalid GeoPoint value")
|
||||||
|
}
|
||||||
|
// NOTE: Strangely, latitude maps to X, longitude to Y.
|
||||||
|
x.Value.Pointvalue = &pb.PropertyValue_PointValue{X: &v.Lat, Y: &v.Lng}
|
||||||
|
x.Meaning = pb.Property_GEORSS_POINT.Enum()
|
||||||
|
case []byte:
|
||||||
|
x.Value.StringValue = proto.String(string(v))
|
||||||
|
x.Meaning = pb.Property_BLOB.Enum()
|
||||||
|
if !p.NoIndex {
|
||||||
|
return nil, fmt.Errorf("datastore: cannot index a []byte valued Property with Name %q", p.Name)
|
||||||
|
}
|
||||||
|
case ByteString:
|
||||||
|
x.Value.StringValue = proto.String(string(v))
|
||||||
|
x.Meaning = pb.Property_BYTESTRING.Enum()
|
||||||
|
default:
|
||||||
|
if p.Value != nil {
|
||||||
|
return nil, fmt.Errorf("datastore: invalid Value type for a Property with Name %q", p.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.NoIndex {
|
||||||
|
e.RawProperty = append(e.RawProperty, x)
|
||||||
|
} else {
|
||||||
|
e.Property = append(e.Property, x)
|
||||||
|
if len(e.Property) > maxIndexedProperties {
|
||||||
|
return nil, errors.New("datastore: too many indexed properties")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
65
Godeps/_workspace/src/google.golang.org/appengine/datastore/time_test.go
generated
vendored
Normal file
65
Godeps/_workspace/src/google.golang.org/appengine/datastore/time_test.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package datastore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnixMicro(t *testing.T) {
|
||||||
|
// Test that all these time.Time values survive a round trip to unix micros.
|
||||||
|
testCases := []time.Time{
|
||||||
|
{},
|
||||||
|
time.Date(2, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(23, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(234, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(1600, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(1700, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(1800, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Unix(-1e6, -1000),
|
||||||
|
time.Unix(-1e6, 0),
|
||||||
|
time.Unix(-1e6, +1000),
|
||||||
|
time.Unix(-60, -1000),
|
||||||
|
time.Unix(-60, 0),
|
||||||
|
time.Unix(-60, +1000),
|
||||||
|
time.Unix(-1, -1000),
|
||||||
|
time.Unix(-1, 0),
|
||||||
|
time.Unix(-1, +1000),
|
||||||
|
time.Unix(0, -3000),
|
||||||
|
time.Unix(0, -2000),
|
||||||
|
time.Unix(0, -1000),
|
||||||
|
time.Unix(0, 0),
|
||||||
|
time.Unix(0, +1000),
|
||||||
|
time.Unix(0, +2000),
|
||||||
|
time.Unix(+60, -1000),
|
||||||
|
time.Unix(+60, 0),
|
||||||
|
time.Unix(+60, +1000),
|
||||||
|
time.Unix(+1e6, -1000),
|
||||||
|
time.Unix(+1e6, 0),
|
||||||
|
time.Unix(+1e6, +1000),
|
||||||
|
time.Date(1999, 12, 31, 23, 59, 59, 999000, time.UTC),
|
||||||
|
time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2006, 1, 2, 15, 4, 5, 678000, time.UTC),
|
||||||
|
time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(3456, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
got := fromUnixMicro(toUnixMicro(tc))
|
||||||
|
if !got.Equal(tc) {
|
||||||
|
t.Errorf("got %q, want %q", got, tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that a time.Time that isn't an integral number of microseconds
|
||||||
|
// is not perfectly reconstructed after a round trip.
|
||||||
|
t0 := time.Unix(0, 123)
|
||||||
|
t1 := fromUnixMicro(toUnixMicro(t0))
|
||||||
|
if t1.Nanosecond()%1000 != 0 || t0.Nanosecond()%1000 == 0 {
|
||||||
|
t.Errorf("quantization to µs: got %q with %d ns, started with %d ns", t1, t1.Nanosecond(), t0.Nanosecond())
|
||||||
|
}
|
||||||
|
}
|
||||||
138
Godeps/_workspace/src/google.golang.org/appengine/datastore/transaction.go
generated
vendored
Normal file
138
Godeps/_workspace/src/google.golang.org/appengine/datastore/transaction.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package datastore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
basepb "google.golang.org/appengine/internal/base"
|
||||||
|
pb "google.golang.org/appengine/internal/datastore"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
internal.RegisterTransactionSetter(func(x *pb.Query, t *pb.Transaction) {
|
||||||
|
x.Transaction = t
|
||||||
|
})
|
||||||
|
internal.RegisterTransactionSetter(func(x *pb.GetRequest, t *pb.Transaction) {
|
||||||
|
x.Transaction = t
|
||||||
|
})
|
||||||
|
internal.RegisterTransactionSetter(func(x *pb.PutRequest, t *pb.Transaction) {
|
||||||
|
x.Transaction = t
|
||||||
|
})
|
||||||
|
internal.RegisterTransactionSetter(func(x *pb.DeleteRequest, t *pb.Transaction) {
|
||||||
|
x.Transaction = t
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrConcurrentTransaction is returned when a transaction is rolled back due
|
||||||
|
// to a conflict with a concurrent transaction.
|
||||||
|
var ErrConcurrentTransaction = errors.New("datastore: concurrent transaction")
|
||||||
|
|
||||||
|
type transaction struct {
|
||||||
|
appengine.Context
|
||||||
|
transaction pb.Transaction
|
||||||
|
finished bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transaction) Call(service, method string, in, out proto.Message, opts *internal.CallOptions) error {
|
||||||
|
if t.finished {
|
||||||
|
return errors.New("datastore: transaction context has expired")
|
||||||
|
}
|
||||||
|
internal.ApplyTransaction(in, &t.transaction)
|
||||||
|
return t.Context.Call(service, method, in, out, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runOnce(c appengine.Context, f func(appengine.Context) error, opts *TransactionOptions) error {
|
||||||
|
// Begin the transaction.
|
||||||
|
t := &transaction{Context: c}
|
||||||
|
req := &pb.BeginTransactionRequest{
|
||||||
|
App: proto.String(c.FullyQualifiedAppID()),
|
||||||
|
}
|
||||||
|
if opts != nil && opts.XG {
|
||||||
|
req.AllowMultipleEg = proto.Bool(true)
|
||||||
|
}
|
||||||
|
if err := t.Context.Call("datastore_v3", "BeginTransaction", req, &t.transaction, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call f, rolling back the transaction if f returns a non-nil error, or panics.
|
||||||
|
// The panic is not recovered.
|
||||||
|
defer func() {
|
||||||
|
if t.finished {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.finished = true
|
||||||
|
// Ignore the error return value, since we are already returning a non-nil
|
||||||
|
// error (or we're panicking).
|
||||||
|
c.Call("datastore_v3", "Rollback", &t.transaction, &basepb.VoidProto{}, nil)
|
||||||
|
}()
|
||||||
|
if err := f(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.finished = true
|
||||||
|
|
||||||
|
// Commit the transaction.
|
||||||
|
res := &pb.CommitResponse{}
|
||||||
|
err := c.Call("datastore_v3", "Commit", &t.transaction, res, nil)
|
||||||
|
if ae, ok := err.(*internal.APIError); ok {
|
||||||
|
if appengine.IsDevAppServer() {
|
||||||
|
// The Python Dev AppServer raises an ApplicationError with error code 2 (which is
|
||||||
|
// Error.CONCURRENT_TRANSACTION) and message "Concurrency exception.".
|
||||||
|
if ae.Code == int32(pb.Error_BAD_REQUEST) && ae.Detail == "ApplicationError: 2 Concurrency exception." {
|
||||||
|
return ErrConcurrentTransaction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ae.Code == int32(pb.Error_CONCURRENT_TRANSACTION) {
|
||||||
|
return ErrConcurrentTransaction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunInTransaction runs f in a transaction. It calls f with a transaction
|
||||||
|
// context tc that f should use for all App Engine operations.
|
||||||
|
//
|
||||||
|
// If f returns nil, RunInTransaction attempts to commit the transaction,
|
||||||
|
// returning nil if it succeeds. If the commit fails due to a conflicting
|
||||||
|
// transaction, RunInTransaction retries f, each time with a new transaction
|
||||||
|
// context. It gives up and returns ErrConcurrentTransaction after three
|
||||||
|
// failed attempts.
|
||||||
|
//
|
||||||
|
// If f returns non-nil, then any datastore changes will not be applied and
|
||||||
|
// RunInTransaction returns that same error. The function f is not retried.
|
||||||
|
//
|
||||||
|
// Note that when f returns, the transaction is not yet committed. Calling code
|
||||||
|
// must be careful not to assume that any of f's changes have been committed
|
||||||
|
// until RunInTransaction returns nil.
|
||||||
|
//
|
||||||
|
// Nested transactions are not supported; c may not be a transaction context.
|
||||||
|
func RunInTransaction(c appengine.Context, f func(tc appengine.Context) error, opts *TransactionOptions) error {
|
||||||
|
if _, ok := c.(*transaction); ok {
|
||||||
|
return errors.New("datastore: nested transactions are not supported")
|
||||||
|
}
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
if err := runOnce(c, f, opts); err != ErrConcurrentTransaction {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ErrConcurrentTransaction
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionOptions are the options for running a transaction.
|
||||||
|
type TransactionOptions struct {
|
||||||
|
// XG is whether the transaction can cross multiple entity groups. In
|
||||||
|
// comparison, a single group transaction is one where all datastore keys
|
||||||
|
// used have the same root key. Note that cross group transactions do not
|
||||||
|
// have the same behavior as single group transactions. In particular, it
|
||||||
|
// is much more likely to see partially applied transactions in different
|
||||||
|
// entity groups, in global queries.
|
||||||
|
// It is valid to set XG to true even if the transaction is within a
|
||||||
|
// single entity group.
|
||||||
|
XG bool
|
||||||
|
}
|
||||||
275
Godeps/_workspace/src/google.golang.org/appengine/delay/delay.go
generated
vendored
Normal file
275
Godeps/_workspace/src/google.golang.org/appengine/delay/delay.go
generated
vendored
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package delay provides a way to execute code outside the scope of a
|
||||||
|
user request by using the taskqueue API.
|
||||||
|
|
||||||
|
To declare a function that may be executed later, call Func
|
||||||
|
in a top-level assignment context, passing it an arbitrary string key
|
||||||
|
and a function whose first argument is of type appengine.Context.
|
||||||
|
var laterFunc = delay.Func("key", myFunc)
|
||||||
|
It is also possible to use a function literal.
|
||||||
|
var laterFunc = delay.Func("key", func(c appengine.Context, x string) {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
|
||||||
|
To call a function, invoke its Call method.
|
||||||
|
laterFunc.Call(c, "something")
|
||||||
|
A function may be called any number of times. If the function has any
|
||||||
|
return arguments, and the last one is of type error, the function may
|
||||||
|
return a non-nil error to signal that the function should be retried.
|
||||||
|
|
||||||
|
The arguments to functions may be of any type that is encodable by the gob
|
||||||
|
package. If an argument is of interface type, it is the client's responsibility
|
||||||
|
to register with the gob package whatever concrete type may be passed for that
|
||||||
|
argument; see http://golang.org/pkg/gob/#Register for details.
|
||||||
|
|
||||||
|
Any errors during initialization or execution of a function will be
|
||||||
|
logged to the application logs. Error logs that occur during initialization will
|
||||||
|
be associated with the request that invoked the Call method.
|
||||||
|
|
||||||
|
The state of a function invocation that has not yet successfully
|
||||||
|
executed is preserved by combining the file name in which it is declared
|
||||||
|
with the string key that was passed to the Func function. Updating an app
|
||||||
|
with pending function invocations is safe as long as the relevant
|
||||||
|
functions have the (filename, key) combination preserved.
|
||||||
|
|
||||||
|
The delay package uses the Task Queue API to create tasks that call the
|
||||||
|
reserved application path "/_ah/queue/go/delay".
|
||||||
|
This path must not be marked as "login: required" in app.yaml;
|
||||||
|
it must be marked as "login: admin" or have no access restriction.
|
||||||
|
*/
|
||||||
|
package delay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
"google.golang.org/appengine/taskqueue"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Function represents a function that may have a delayed invocation.
|
||||||
|
type Function struct {
|
||||||
|
fv reflect.Value // Kind() == reflect.Func
|
||||||
|
key string
|
||||||
|
err error // any error during initialization
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The HTTP path for invocations.
|
||||||
|
path = "/_ah/queue/go/delay"
|
||||||
|
// Use the default queue.
|
||||||
|
queue = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// registry of all delayed functions
|
||||||
|
funcs = make(map[string]*Function)
|
||||||
|
|
||||||
|
// precomputed types
|
||||||
|
contextType = reflect.TypeOf((*appengine.Context)(nil)).Elem()
|
||||||
|
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
|
|
||||||
|
// errors
|
||||||
|
errFirstArg = errors.New("first argument must be appengine.Context")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Func declares a new Function. The second argument must be a function with a
|
||||||
|
// first argument of type appengine.Context.
|
||||||
|
// This function must be called at program initialization time. That means it
|
||||||
|
// must be called in a global variable declaration or from an init function.
|
||||||
|
// This restriction is necessary because the instance that delays a function
|
||||||
|
// call may not be the one that executes it. Only the code executed at program
|
||||||
|
// initialization time is guaranteed to have been run by an instance before it
|
||||||
|
// receives a request.
|
||||||
|
func Func(key string, i interface{}) *Function {
|
||||||
|
f := &Function{fv: reflect.ValueOf(i)}
|
||||||
|
|
||||||
|
// Derive unique, somewhat stable key for this func.
|
||||||
|
_, file, _, _ := runtime.Caller(1)
|
||||||
|
f.key = file + ":" + key
|
||||||
|
|
||||||
|
t := f.fv.Type()
|
||||||
|
if t.Kind() != reflect.Func {
|
||||||
|
f.err = errors.New("not a function")
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
if t.NumIn() == 0 || t.In(0) != contextType {
|
||||||
|
f.err = errFirstArg
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the function's arguments with the gob package.
|
||||||
|
// This is required because they are marshaled inside a []interface{}.
|
||||||
|
// gob.Register only expects to be called during initialization;
|
||||||
|
// that's fine because this function expects the same.
|
||||||
|
for i := 0; i < t.NumIn(); i++ {
|
||||||
|
// Only concrete types may be registered. If the argument has
|
||||||
|
// interface type, the client is resposible for registering the
|
||||||
|
// concrete types it will hold.
|
||||||
|
if t.In(i).Kind() == reflect.Interface {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gob.Register(reflect.Zero(t.In(i)).Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
funcs[f.key] = f
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
type invocation struct {
|
||||||
|
Key string
|
||||||
|
Args []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call invokes a delayed function.
|
||||||
|
// f.Call(c, ...)
|
||||||
|
// is equivalent to
|
||||||
|
// t, _ := f.Task(...)
|
||||||
|
// taskqueue.Add(c, t, "")
|
||||||
|
func (f *Function) Call(c appengine.Context, args ...interface{}) {
|
||||||
|
t, err := f.Task(args...)
|
||||||
|
if err != nil {
|
||||||
|
c.Errorf("%v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := taskqueueAdder(c, t, queue); err != nil {
|
||||||
|
c.Errorf("delay: taskqueue.Add failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Task creates a Task that will invoke the function.
|
||||||
|
// Its parameters may be tweaked before adding it to a queue.
|
||||||
|
// Users should not modify the Path or Payload fields of the returned Task.
|
||||||
|
func (f *Function) Task(args ...interface{}) (*taskqueue.Task, error) {
|
||||||
|
if f.err != nil {
|
||||||
|
return nil, fmt.Errorf("delay: func is invalid: %v", f.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nArgs := len(args) + 1 // +1 for the appengine.Context
|
||||||
|
ft := f.fv.Type()
|
||||||
|
minArgs := ft.NumIn()
|
||||||
|
if ft.IsVariadic() {
|
||||||
|
minArgs--
|
||||||
|
}
|
||||||
|
if nArgs < minArgs {
|
||||||
|
return nil, fmt.Errorf("delay: too few arguments to func: %d < %d", nArgs, minArgs)
|
||||||
|
}
|
||||||
|
if !ft.IsVariadic() && nArgs > minArgs {
|
||||||
|
return nil, fmt.Errorf("delay: too many arguments to func: %d > %d", nArgs, minArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check arg types.
|
||||||
|
for i := 1; i < nArgs; i++ {
|
||||||
|
at := reflect.TypeOf(args[i-1])
|
||||||
|
var dt reflect.Type
|
||||||
|
if i < minArgs {
|
||||||
|
// not a variadic arg
|
||||||
|
dt = ft.In(i)
|
||||||
|
} else {
|
||||||
|
// a variadic arg
|
||||||
|
dt = ft.In(minArgs).Elem()
|
||||||
|
}
|
||||||
|
// nil arguments won't have a type, so they need special handling.
|
||||||
|
if at == nil {
|
||||||
|
// nil interface
|
||||||
|
switch dt.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||||
|
continue // may be nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("delay: argument %d has wrong type: %v is not nilable", i, dt)
|
||||||
|
}
|
||||||
|
switch at.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||||
|
av := reflect.ValueOf(args[i-1])
|
||||||
|
if av.IsNil() {
|
||||||
|
// nil value in interface; not supported by gob, so we replace it
|
||||||
|
// with a nil interface value
|
||||||
|
args[i-1] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !at.AssignableTo(dt) {
|
||||||
|
return nil, fmt.Errorf("delay: argument %d has wrong type: %v is not assignable to %v", i, at, dt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inv := invocation{
|
||||||
|
Key: f.key,
|
||||||
|
Args: args,
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := gob.NewEncoder(buf).Encode(inv); err != nil {
|
||||||
|
return nil, fmt.Errorf("delay: gob encoding failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &taskqueue.Task{
|
||||||
|
Path: path,
|
||||||
|
Payload: buf.Bytes(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskqueueAdder = taskqueue.Add // for testing
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
http.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
runFunc(appengine.NewContext(req), w, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func runFunc(c appengine.Context, w http.ResponseWriter, req *http.Request) {
|
||||||
|
defer req.Body.Close()
|
||||||
|
|
||||||
|
var inv invocation
|
||||||
|
if err := gob.NewDecoder(req.Body).Decode(&inv); err != nil {
|
||||||
|
c.Errorf("delay: failed decoding task payload: %v", err)
|
||||||
|
c.Warningf("delay: dropping task")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f := funcs[inv.Key]
|
||||||
|
if f == nil {
|
||||||
|
c.Errorf("delay: no func with key %q found", inv.Key)
|
||||||
|
c.Warningf("delay: dropping task")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ft := f.fv.Type()
|
||||||
|
in := []reflect.Value{reflect.ValueOf(c)}
|
||||||
|
for _, arg := range inv.Args {
|
||||||
|
var v reflect.Value
|
||||||
|
if arg != nil {
|
||||||
|
v = reflect.ValueOf(arg)
|
||||||
|
} else {
|
||||||
|
// Task was passed a nil argument, so we must construct
|
||||||
|
// the zero value for the argument here.
|
||||||
|
n := len(in) // we're constructing the nth argument
|
||||||
|
var at reflect.Type
|
||||||
|
if !ft.IsVariadic() || n < ft.NumIn()-1 {
|
||||||
|
at = ft.In(n)
|
||||||
|
} else {
|
||||||
|
at = ft.In(ft.NumIn() - 1).Elem()
|
||||||
|
}
|
||||||
|
v = reflect.Zero(at)
|
||||||
|
}
|
||||||
|
in = append(in, v)
|
||||||
|
}
|
||||||
|
out := f.fv.Call(in)
|
||||||
|
|
||||||
|
if n := ft.NumOut(); n > 0 && ft.Out(n-1) == errorType {
|
||||||
|
if errv := out[n-1]; !errv.IsNil() {
|
||||||
|
c.Errorf("delay: func failed (will retry): %v", errv.Interface())
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
307
Godeps/_workspace/src/google.golang.org/appengine/delay/delay_test.go
generated
vendored
Normal file
307
Godeps/_workspace/src/google.golang.org/appengine/delay/delay_test.go
generated
vendored
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package delay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
"google.golang.org/appengine/taskqueue"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomType struct {
|
||||||
|
N int
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomInterface interface {
|
||||||
|
N() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomImpl int
|
||||||
|
|
||||||
|
func (c CustomImpl) N() int { return int(c) }
|
||||||
|
|
||||||
|
// CustomImpl needs to be registered with gob.
|
||||||
|
func init() {
|
||||||
|
gob.Register(CustomImpl(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
invalidFunc = Func("invalid", func() {})
|
||||||
|
|
||||||
|
regFuncRuns = 0
|
||||||
|
regFuncMsg = ""
|
||||||
|
regFunc = Func("reg", func(c appengine.Context, arg string) {
|
||||||
|
regFuncRuns++
|
||||||
|
regFuncMsg = arg
|
||||||
|
})
|
||||||
|
|
||||||
|
custFuncTally = 0
|
||||||
|
custFunc = Func("cust", func(c appengine.Context, ct *CustomType, ci CustomInterface) {
|
||||||
|
a, b := 2, 3
|
||||||
|
if ct != nil {
|
||||||
|
a = ct.N
|
||||||
|
}
|
||||||
|
if ci != nil {
|
||||||
|
b = ci.N()
|
||||||
|
}
|
||||||
|
custFuncTally += a + b
|
||||||
|
})
|
||||||
|
|
||||||
|
anotherCustFunc = Func("cust2", func(c appengine.Context, n int, ct *CustomType, ci CustomInterface) {
|
||||||
|
})
|
||||||
|
|
||||||
|
varFuncMsg = ""
|
||||||
|
varFunc = Func("variadic", func(c appengine.Context, format string, args ...int) {
|
||||||
|
// convert []int to []interface{} for fmt.Sprintf.
|
||||||
|
as := make([]interface{}, len(args))
|
||||||
|
for i, a := range args {
|
||||||
|
as[i] = a
|
||||||
|
}
|
||||||
|
varFuncMsg = fmt.Sprintf(format, as...)
|
||||||
|
})
|
||||||
|
|
||||||
|
errFuncRuns = 0
|
||||||
|
errFuncErr = errors.New("error!")
|
||||||
|
errFunc = Func("err", func(c appengine.Context) error {
|
||||||
|
errFuncRuns++
|
||||||
|
if errFuncRuns == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errFuncErr
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeContext struct {
|
||||||
|
appengine.Context
|
||||||
|
logging [][]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeContext) log(level, format string, args ...interface{}) {
|
||||||
|
f.logging = append(f.logging, append([]interface{}{level, format}, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeContext) Infof(format string, args ...interface{}) { f.log("INFO", format, args...) }
|
||||||
|
func (f *fakeContext) Errorf(format string, args ...interface{}) { f.log("ERROR", format, args...) }
|
||||||
|
|
||||||
|
func TestInvalidFunction(t *testing.T) {
|
||||||
|
c := &fakeContext{}
|
||||||
|
|
||||||
|
invalidFunc.Call(c)
|
||||||
|
|
||||||
|
wantLogging := [][]interface{}{
|
||||||
|
{"ERROR", "%v", fmt.Errorf("delay: func is invalid: %s", errFirstArg)},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(c.logging, wantLogging) {
|
||||||
|
t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariadicFunctionArguments(t *testing.T) {
|
||||||
|
// Check the argument type validation for variadic functions.
|
||||||
|
|
||||||
|
c := &fakeContext{}
|
||||||
|
|
||||||
|
calls := 0
|
||||||
|
taskqueueAdder = func(c appengine.Context, t *taskqueue.Task, _ string) (*taskqueue.Task, error) {
|
||||||
|
calls++
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
varFunc.Call(c, "hi")
|
||||||
|
varFunc.Call(c, "%d", 12)
|
||||||
|
varFunc.Call(c, "%d %d %d", 3, 1, 4)
|
||||||
|
if calls != 3 {
|
||||||
|
t.Errorf("Got %d calls to taskqueueAdder, want 3", calls)
|
||||||
|
}
|
||||||
|
|
||||||
|
varFunc.Call(c, "%d %s", 12, "a string is bad")
|
||||||
|
wantLogging := [][]interface{}{
|
||||||
|
{"ERROR", "%v", errors.New("delay: argument 3 has wrong type: string is not assignable to int")},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(c.logging, wantLogging) {
|
||||||
|
t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadArguments(t *testing.T) {
|
||||||
|
// Try running regFunc with different sets of inappropriate arguments.
|
||||||
|
|
||||||
|
c := &fakeContext{}
|
||||||
|
|
||||||
|
regFunc.Call(c)
|
||||||
|
regFunc.Call(c, "lala", 53)
|
||||||
|
regFunc.Call(c, 53)
|
||||||
|
|
||||||
|
wantLogging := [][]interface{}{
|
||||||
|
{"ERROR", "%v", errors.New("delay: too few arguments to func: 1 < 2")},
|
||||||
|
{"ERROR", "%v", errors.New("delay: too many arguments to func: 3 > 2")},
|
||||||
|
{"ERROR", "%v", errors.New("delay: argument 1 has wrong type: int is not assignable to string")},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(c.logging, wantLogging) {
|
||||||
|
t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunningFunction(t *testing.T) {
|
||||||
|
c := &fakeContext{}
|
||||||
|
|
||||||
|
// Fake out the adding of a task.
|
||||||
|
var task *taskqueue.Task
|
||||||
|
taskqueueAdder = func(_ appengine.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
|
||||||
|
if queue != "" {
|
||||||
|
t.Errorf(`Got queue %q, expected ""`, queue)
|
||||||
|
}
|
||||||
|
task = tk
|
||||||
|
return tk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
regFuncRuns, regFuncMsg = 0, "" // reset state
|
||||||
|
const msg = "Why, hello!"
|
||||||
|
regFunc.Call(c, msg)
|
||||||
|
|
||||||
|
// Simulate the Task Queue service.
|
||||||
|
req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed making http.Request: %v", err)
|
||||||
|
}
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
runFunc(c, rw, req)
|
||||||
|
|
||||||
|
if regFuncRuns != 1 {
|
||||||
|
t.Errorf("regFuncRuns: got %d, want 1", regFuncRuns)
|
||||||
|
}
|
||||||
|
if regFuncMsg != msg {
|
||||||
|
t.Errorf("regFuncMsg: got %q, want %q", regFuncMsg, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomType(t *testing.T) {
|
||||||
|
c := &fakeContext{}
|
||||||
|
|
||||||
|
// Fake out the adding of a task.
|
||||||
|
var task *taskqueue.Task
|
||||||
|
taskqueueAdder = func(_ appengine.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
|
||||||
|
if queue != "" {
|
||||||
|
t.Errorf(`Got queue %q, expected ""`, queue)
|
||||||
|
}
|
||||||
|
task = tk
|
||||||
|
return tk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
custFuncTally = 0 // reset state
|
||||||
|
custFunc.Call(c, &CustomType{N: 11}, CustomImpl(13))
|
||||||
|
|
||||||
|
// Simulate the Task Queue service.
|
||||||
|
req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed making http.Request: %v", err)
|
||||||
|
}
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
runFunc(c, rw, req)
|
||||||
|
|
||||||
|
if custFuncTally != 24 {
|
||||||
|
t.Errorf("custFuncTally = %d, want 24", custFuncTally)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try the same, but with nil values; one is a nil pointer (and thus a non-nil interface value),
|
||||||
|
// and the other is a nil interface value.
|
||||||
|
custFuncTally = 0 // reset state
|
||||||
|
custFunc.Call(c, (*CustomType)(nil), nil)
|
||||||
|
|
||||||
|
// Simulate the Task Queue service.
|
||||||
|
req, err = http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed making http.Request: %v", err)
|
||||||
|
}
|
||||||
|
rw = httptest.NewRecorder()
|
||||||
|
runFunc(c, rw, req)
|
||||||
|
|
||||||
|
if custFuncTally != 5 {
|
||||||
|
t.Errorf("custFuncTally = %d, want 5", custFuncTally)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunningVariadic(t *testing.T) {
|
||||||
|
c := &fakeContext{}
|
||||||
|
|
||||||
|
// Fake out the adding of a task.
|
||||||
|
var task *taskqueue.Task
|
||||||
|
taskqueueAdder = func(_ appengine.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
|
||||||
|
if queue != "" {
|
||||||
|
t.Errorf(`Got queue %q, expected ""`, queue)
|
||||||
|
}
|
||||||
|
task = tk
|
||||||
|
return tk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
varFuncMsg = "" // reset state
|
||||||
|
varFunc.Call(c, "Amiga %d has %d KB RAM", 500, 512)
|
||||||
|
|
||||||
|
// Simulate the Task Queue service.
|
||||||
|
req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed making http.Request: %v", err)
|
||||||
|
}
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
runFunc(c, rw, req)
|
||||||
|
|
||||||
|
const expected = "Amiga 500 has 512 KB RAM"
|
||||||
|
if varFuncMsg != expected {
|
||||||
|
t.Errorf("varFuncMsg = %q, want %q", varFuncMsg, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorFunction(t *testing.T) {
|
||||||
|
c := &fakeContext{}
|
||||||
|
|
||||||
|
// Fake out the adding of a task.
|
||||||
|
var task *taskqueue.Task
|
||||||
|
taskqueueAdder = func(_ appengine.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
|
||||||
|
if queue != "" {
|
||||||
|
t.Errorf(`Got queue %q, expected ""`, queue)
|
||||||
|
}
|
||||||
|
task = tk
|
||||||
|
return tk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errFunc.Call(c)
|
||||||
|
|
||||||
|
// Simulate the Task Queue service.
|
||||||
|
// The first call should succeed; the second call should fail.
|
||||||
|
{
|
||||||
|
req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed making http.Request: %v", err)
|
||||||
|
}
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
runFunc(c, rw, req)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed making http.Request: %v", err)
|
||||||
|
}
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
runFunc(c, rw, req)
|
||||||
|
if rw.Code != http.StatusInternalServerError {
|
||||||
|
t.Errorf("Got status code %d, want %d", rw.Code, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantLogging := [][]interface{}{
|
||||||
|
{"ERROR", "delay: func failed (will retry): %v", errFuncErr},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(c.logging, wantLogging) {
|
||||||
|
t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Godeps/_workspace/src/google.golang.org/appengine/demos/guestbook/app.yaml
generated
vendored
Normal file
19
Godeps/_workspace/src/google.golang.org/appengine/demos/guestbook/app.yaml
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Demo application for Managed VMs.
|
||||||
|
application: vm-guestbook
|
||||||
|
version: 1
|
||||||
|
runtime: go
|
||||||
|
vm: true
|
||||||
|
api_version: go1
|
||||||
|
|
||||||
|
manual_scaling:
|
||||||
|
instances: 1
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
# Favicon. Without this, the browser hits this once per page view.
|
||||||
|
- url: /favicon.ico
|
||||||
|
static_files: favicon.ico
|
||||||
|
upload: favicon.ico
|
||||||
|
|
||||||
|
# Main app. All the real work is here.
|
||||||
|
- url: /.*
|
||||||
|
script: _go_app
|
||||||
BIN
Godeps/_workspace/src/google.golang.org/appengine/demos/guestbook/favicon.ico
generated
vendored
Normal file
BIN
Godeps/_workspace/src/google.golang.org/appengine/demos/guestbook/favicon.ico
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
102
Godeps/_workspace/src/google.golang.org/appengine/demos/guestbook/guestbook.go
generated
vendored
Normal file
102
Godeps/_workspace/src/google.golang.org/appengine/demos/guestbook/guestbook.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package guestbook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
"google.golang.org/appengine/datastore"
|
||||||
|
"google.golang.org/appengine/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
var initTime time.Time
|
||||||
|
|
||||||
|
type Greeting struct {
|
||||||
|
Author string
|
||||||
|
Content string
|
||||||
|
Date time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
http.HandleFunc("/", handleMainPage)
|
||||||
|
http.HandleFunc("/sign", handleSign)
|
||||||
|
}
|
||||||
|
|
||||||
|
// guestbookKey returns the key used for all guestbook entries.
|
||||||
|
func guestbookKey(c appengine.Context) *datastore.Key {
|
||||||
|
// The string "default_guestbook" here could be varied to have multiple guestbooks.
|
||||||
|
return datastore.NewKey(c, "Guestbook", "default_guestbook", 0, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tpl = template.Must(template.ParseGlob("templates/*.html"))
|
||||||
|
|
||||||
|
func handleMainPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "GET" {
|
||||||
|
http.Error(w, "GET requests only", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.Path != "/" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c := appengine.NewContext(r)
|
||||||
|
tic := time.Now()
|
||||||
|
q := datastore.NewQuery("Greeting").Ancestor(guestbookKey(c)).Order("-Date").Limit(10)
|
||||||
|
var gg []*Greeting
|
||||||
|
if _, err := q.GetAll(c, &gg); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
c.Errorf("GetAll: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Infof("Datastore lookup took %s", time.Since(tic).String())
|
||||||
|
c.Infof("Rendering %d greetings", len(gg))
|
||||||
|
|
||||||
|
var email, logout, login string
|
||||||
|
if u := user.Current(c); u != nil {
|
||||||
|
logout, _ = user.LogoutURL(c, "/")
|
||||||
|
email = u.Email
|
||||||
|
} else {
|
||||||
|
login, _ = user.LoginURL(c, "/")
|
||||||
|
}
|
||||||
|
data := struct {
|
||||||
|
Greetings []*Greeting
|
||||||
|
Login, Logout, Email string
|
||||||
|
}{
|
||||||
|
Greetings: gg,
|
||||||
|
Login: login,
|
||||||
|
Logout: logout,
|
||||||
|
Email: email,
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
if err := tpl.ExecuteTemplate(w, "guestbook.html", data); err != nil {
|
||||||
|
c.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSign(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "POST" {
|
||||||
|
http.Error(w, "POST requests only", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c := appengine.NewContext(r)
|
||||||
|
g := &Greeting{
|
||||||
|
Content: r.FormValue("content"),
|
||||||
|
Date: time.Now(),
|
||||||
|
}
|
||||||
|
if u := user.Current(c); u != nil {
|
||||||
|
g.Author = u.String()
|
||||||
|
}
|
||||||
|
key := datastore.NewIncompleteKey(c, "Greeting", guestbookKey(c))
|
||||||
|
if _, err := datastore.Put(c, key, g); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Redirect with 303 which causes the subsequent request to use GET.
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
}
|
||||||
7
Godeps/_workspace/src/google.golang.org/appengine/demos/guestbook/index.yaml
generated
vendored
Normal file
7
Godeps/_workspace/src/google.golang.org/appengine/demos/guestbook/index.yaml
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
indexes:
|
||||||
|
|
||||||
|
- kind: Greeting
|
||||||
|
ancestor: yes
|
||||||
|
properties:
|
||||||
|
- name: Date
|
||||||
|
direction: desc
|
||||||
26
Godeps/_workspace/src/google.golang.org/appengine/demos/guestbook/templates/guestbook.html
generated
vendored
Normal file
26
Godeps/_workspace/src/google.golang.org/appengine/demos/guestbook/templates/guestbook.html
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Guestbook Demo</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
{{with .Email}}You are currently logged in as {{.}}.{{end}}
|
||||||
|
{{with .Login}}<a href="{{.}}">Sign in</a>{{end}}
|
||||||
|
{{with .Logout}}<a href="{{.}}">Sign out</a>{{end}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{{range .Greetings }}
|
||||||
|
<p>
|
||||||
|
{{with .Author}}<b>{{.}}</b>{{else}}An anonymous person{{end}}
|
||||||
|
on <em>{{.Date.Format "3:04pm, Mon 2 Jan"}}</em>
|
||||||
|
wrote <blockquote>{{.Content}}</blockquote>
|
||||||
|
</p>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<form action="/sign" method="post">
|
||||||
|
<div><textarea name="content" rows="3" cols="60"></textarea></div>
|
||||||
|
<div><input type="submit" value="Sign Guestbook"></div>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
15
Godeps/_workspace/src/google.golang.org/appengine/demos/helloworld/app.yaml
generated
vendored
Normal file
15
Godeps/_workspace/src/google.golang.org/appengine/demos/helloworld/app.yaml
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
application: helloworld
|
||||||
|
version: 1
|
||||||
|
runtime: go
|
||||||
|
api_version: go1
|
||||||
|
vm: true
|
||||||
|
|
||||||
|
manual_scaling:
|
||||||
|
instances: 1
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- url: /favicon.ico
|
||||||
|
static_files: favicon.ico
|
||||||
|
upload: favicon.ico
|
||||||
|
- url: /.*
|
||||||
|
script: _go_app
|
||||||
BIN
Godeps/_workspace/src/google.golang.org/appengine/demos/helloworld/favicon.ico
generated
vendored
Normal file
BIN
Godeps/_workspace/src/google.golang.org/appengine/demos/helloworld/favicon.ico
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
45
Godeps/_workspace/src/google.golang.org/appengine/demos/helloworld/helloworld.go
generated
vendored
Normal file
45
Godeps/_workspace/src/google.golang.org/appengine/demos/helloworld/helloworld.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package helloworld
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
)
|
||||||
|
|
||||||
|
var initTime = time.Now()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
http.HandleFunc("/", handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c := appengine.NewContext(r)
|
||||||
|
c.Infof("Serving the front page.")
|
||||||
|
|
||||||
|
tmpl.Execute(w, time.Since(initTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmpl = template.Must(template.New("front").Parse(`
|
||||||
|
<html><body>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Hello, World! 세상아 안녕!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This instance has been running for <em>{{.}}</em>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</body></html>
|
||||||
|
`))
|
||||||
46
Godeps/_workspace/src/google.golang.org/appengine/errors.go
generated
vendored
Normal file
46
Godeps/_workspace/src/google.golang.org/appengine/errors.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2011 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// This file provides error functions for common API failure modes.
|
||||||
|
|
||||||
|
package appengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/appengine/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsOverQuota reports whether err represents an API call failure
|
||||||
|
// due to insufficient available quota.
|
||||||
|
func IsOverQuota(err error) bool {
|
||||||
|
callErr, ok := err.(*internal.CallError)
|
||||||
|
return ok && callErr.Code == 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiError is returned by batch operations when there are errors with
|
||||||
|
// particular elements. Errors will be in a one-to-one correspondence with
|
||||||
|
// the input elements; successful elements will have a nil entry.
|
||||||
|
type MultiError []error
|
||||||
|
|
||||||
|
func (m MultiError) Error() string {
|
||||||
|
s, n := "", 0
|
||||||
|
for _, e := range m {
|
||||||
|
if e != nil {
|
||||||
|
if n == 0 {
|
||||||
|
s = e.Error()
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch n {
|
||||||
|
case 0:
|
||||||
|
return "(0 errors)"
|
||||||
|
case 1:
|
||||||
|
return s
|
||||||
|
case 2:
|
||||||
|
return s + " (and 1 other error)"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
|
||||||
|
}
|
||||||
26
Godeps/_workspace/src/google.golang.org/appengine/file/file.go
generated
vendored
Normal file
26
Godeps/_workspace/src/google.golang.org/appengine/file/file.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package file provides helper functions for using Google Cloud Storage.
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
aipb "google.golang.org/appengine/internal/app_identity"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultBucketName returns the name of this application's
|
||||||
|
// default Google Cloud Storage bucket.
|
||||||
|
func DefaultBucketName(c appengine.Context) (string, error) {
|
||||||
|
req := &aipb.GetDefaultGcsBucketNameRequest{}
|
||||||
|
res := &aipb.GetDefaultGcsBucketNameResponse{}
|
||||||
|
|
||||||
|
err := c.Call("app_identity_service", "GetDefaultGcsBucketName", req, res, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("file: no default bucket name returned in RPC response: %v", res)
|
||||||
|
}
|
||||||
|
return res.GetDefaultGcsBucketName(), nil
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user