From 16505ffe94ef09102e2bd1ecdca72541c2d85ae6 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Thu, 25 Jun 2015 12:21:47 -0400 Subject: updating make --- .../src/github.com/huandu/facebook/CHANGELOG.md | 47 - .../src/github.com/huandu/facebook/CONTRIBUTING.md | 7 - .../src/github.com/huandu/facebook/LICENSE | 19 - .../src/github.com/huandu/facebook/README.md | 347 ----- .../src/github.com/huandu/facebook/api.go | 180 --- .../src/github.com/huandu/facebook/app.go | 255 ---- .../src/github.com/huandu/facebook/batch_result.go | 52 - .../src/github.com/huandu/facebook/const.go | 74 - .../github.com/huandu/facebook/facebook_test.go | 1469 -------------------- .../src/github.com/huandu/facebook/misc.go | 131 -- .../github.com/huandu/facebook/paging_result.go | 146 -- .../src/github.com/huandu/facebook/params.go | 227 --- .../src/github.com/huandu/facebook/result.go | 1097 --------------- .../src/github.com/huandu/facebook/session.go | 667 --------- .../src/github.com/huandu/facebook/type.go | 127 -- 15 files changed, 4845 deletions(-) delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/CHANGELOG.md delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/CONTRIBUTING.md delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/LICENSE delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/README.md delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/api.go delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/app.go delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/batch_result.go delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/const.go delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/facebook_test.go delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/misc.go delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/paging_result.go delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/params.go delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/result.go delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/session.go delete mode 100644 Godeps/_workspace/src/github.com/huandu/facebook/type.go (limited to 'Godeps/_workspace/src/github.com/huandu') diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/CHANGELOG.md b/Godeps/_workspace/src/github.com/huandu/facebook/CHANGELOG.md deleted file mode 100644 index d1c14a215..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/CHANGELOG.md +++ /dev/null @@ -1,47 +0,0 @@ -# Change Log # - -## v1.5.2 ## - -* `[FIX]` [#32](https://github.com/huandu/facebook/pull/32) BatchApi/Batch returns facebook error when access token is not valid. - -## v1.5.1 ## - -* `[FIX]` [#31](https://github.com/huandu/facebook/pull/31) When `/oauth/access_token` returns a query string instead of json, this package can correctly handle it. - -## v1.5.0 ## - -* `[NEW]` [#28](https://github.com/huandu/facebook/pull/28) Support debug mode introduced by facebook graph API v2.3. -* `[FIX]` Removed all test cases depending on facebook graph API v1.0. - -## v1.4.1 ## - -* `[NEW]` [#27](https://github.com/huandu/facebook/pull/27) Timestamp value in Graph API response can be decoded as a `time.Time` value now. Thanks, [@Lazyshot](https://github.com/Lazyshot). - -## v1.4.0 ## - -* `[FIX]` [#23](https://github.com/huandu/facebook/issues/24) Algorithm change: Camel case string to underscore string supports abbreviation - -Fix for [#23](https://github.com/huandu/facebook/issues/24) could be a breaking change. Camel case string `HTTPServer` will be converted to `http_server` instead of `h_t_t_p_server`. See issue description for detail. - -## v1.3.0 ## - -* `[NEW]` [#22](https://github.com/huandu/facebook/issues/22) Add a new helper struct `BatchResult` to hold batch request responses. - -## v1.2.0 ## - -* `[NEW]` [#20](https://github.com/huandu/facebook/issues/20) Add Decode functionality for paging results. Thanks, [@cbroglie](https://github.com/cbroglie). -* `[FIX]` [#21](https://github.com/huandu/facebook/issues/21) `Session#Inspect` cannot return error if access token is invalid. - -Fix for [#21](https://github.com/huandu/facebook/issues/21) will result a possible breaking change in `Session#Inspect`. It was return whole result returned by facebook inspect api. Now it only return its "data" sub-tree. As facebook puts everything including error message in "data" sub-tree, I believe it's reasonable to make this change. - -## v1.1.0 ## - -* `[FIX]` [#19](https://github.com/huandu/facebook/issues/19) Any valid int64 number larger than 2^53 or smaller than -2^53 can be correctly decoded without precision lost. - -Fix for [#19](https://github.com/huandu/facebook/issues/19) will result a possible breaking change in `Result#Get` and `Result#GetField`. If a JSON field is a number, these two functions will return json.Number instead of float64. - -The fix also introduces a side effect in `Result#Decode` and `Result#DecodeField`. A number field (`int*` and `float*`) can be decoded to a string. It was not allowed in previous version. - -## v1.0.0 ## - -Initial tag. Library is stable enough for all features mentioned in README.md. diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/CONTRIBUTING.md b/Godeps/_workspace/src/github.com/huandu/facebook/CONTRIBUTING.md deleted file mode 100644 index c001d2511..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/CONTRIBUTING.md +++ /dev/null @@ -1,7 +0,0 @@ -Thanks for contributing this project! - -Please don't forget to use `gofmt` to make your code look good. - -Here is the command I use. Please always use the same parameters. - - go fmt diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/LICENSE b/Godeps/_workspace/src/github.com/huandu/facebook/LICENSE deleted file mode 100644 index 9569215e9..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2012 - 2015 Huan Du - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/README.md b/Godeps/_workspace/src/github.com/huandu/facebook/README.md deleted file mode 100644 index a21de8d7c..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/README.md +++ /dev/null @@ -1,347 +0,0 @@ -# A Facebook Graph API SDK In Golang # - -[![Build Status](https://travis-ci.org/huandu/facebook.png?branch=master)](https://travis-ci.org/huandu/facebook) - -This is a Go package fully supports Facebook Graph API with file upload, batch request, FQL and multi-FQL. It can be used in Google App Engine. - -API documents can be found on [godoc](http://godoc.org/github.com/huandu/facebook). - -Feel free to create an issue or send me a pull request if you have any "how-to" question or bug or suggestion when using this package. I'll try my best to reply it. - -## Get It ## - -Use `go get -u github.com/huandu/facebook` to get or update it. - -## Usage ## - -### Quick start ### - -Here is a sample to read my Facebook username by uid. - -```go -package main - -import ( - "fmt" - fb "github.com/huandu/facebook" -) - -func main() { - res, _ := fb.Get("/538744468", fb.Params{ - "fields": "username", - "access_token": "a-valid-access-token", - }) - fmt.Println("here is my facebook username:", res["username"]) -} -``` - -Type of `res` is `fb.Result` (a.k.a. `map[string]interface{}`). -This type has several useful methods to decode `res` to any Go type safely. - -```go -// Decode "username" to a Go string. -var username string -res.DecodeField("username", &username) -fmt.Println("alternative way to get username:", username) - -// It's also possible to decode the whole result into a predefined struct. -type User struct { - Username string -} - -var user User -res.Decode(&user) -fmt.Println("print username in struct:", user.Username) -``` - -### Read a graph `user` object with a valid access token ### - -```go -res, err := fb.Get("/me/feed", fb.Params{ - "access_token": "a-valid-access-token", -}) - -if err != nil { - // err can be an facebook API error. - // if so, the Error struct contains error details. - if e, ok := err.(*Error); ok { - fmt.Logf("facebook error. [message:%v] [type:%v] [code:%v] [subcode:%v]", - e.Message, e.Type, e.Code, e.ErrorSubcode) - return - } - - return -} - -// read my last feed. -fmt.Println("my latest feed story is:", res.Get("data.0.story")) -``` - -### Read a graph `search` for page and decode slice of maps - -```go -res, _ := fb.Get("/search", fb.Params{ - "access_token": "a-valid-access-token", - "type": "page", - "q": "nightlife,singapore", - }) - -var items []fb.Result - -err := res.DecodeField("data", &items) - -if err != nil { - fmt.Logf("An error has happened %v", err) - return -} - -for _, item := range items { - fmt.Println(item["id"]) -} -``` - -### Use `App` and `Session` ### - -It's recommended to use `App` and `Session` in a production app. They provide more controls over all API calls. They can also make code clear and concise. - -```go -// create a global App var to hold app id and secret. -var globalApp = fb.New("your-app-id", "your-app-secret") - -// facebook asks for a valid redirect uri when parsing signed request. -// it's a new enforced policy starting in late 2013. -globalApp.RedirectUri = "http://your.site/canvas/url/" - -// here comes a client with a facebook signed request string in query string. -// creates a new session with signed request. -session, _ := globalApp.SessionFromSignedRequest(signedRequest) - -// if there is another way to get decoded access token, -// creates a session directly with the token. -session := globalApp.Session(token) - -// validate access token. err is nil if token is valid. -err := session.Validate() - -// use session to send api request with access token. -res, _ := session.Get("/me/feed", nil) -``` - -### Use `paging` field in response. ### - -Some Graph API responses use a special JSON structure to provide paging information. Use `Result.Paging()` to walk through all data in such results. - -```go -res, _ := session.Get("/me/home", nil) - -// create a paging structure. -paging, _ := res.Paging(session) - -// get current results. -results := paging.Data() - -// get next page. -noMore, err := paging.Next() -results = paging.Data() -``` - -### Read graph api response and decode result into a struct ### - -As facebook Graph API always uses lower case words as keys in API response. -This package can convert go's camel-case-style struct field name to facebook's underscore-style API key name. - -For instance, to decode following JSON response... - -```json -{ - "foo_bar": "player" -} -``` - -One can use following struct. - -```go -type Data struct { - FooBar string // "FooBar" maps to "foo_bar" in JSON automatically in this case. -} -``` - -Decoding behavior can be changed per field through field tag -- just like what `encoding/json` does. - -Following is a sample shows all possible field tags. - -```go -// define a facebook feed object. -type FacebookFeed struct { - Id string `facebook:",required"` // this field must exist in response. - // mind the "," before "required". - Story string - FeedFrom *FacebookFeedFrom `facebook:"from"` // use customized field name "from" - CreatedTime string `facebook:"created_time,required"` // both customized field name and "required" flag. -} - -type FacebookFeedFrom struct { - Name, Id string -} - -// create a feed object direct from graph api result. -var feed FacebookFeed -res, _ := session.Get("/me/feed", nil) -res.DecodeField("data.0", &feed) // read latest feed -``` - -### Send a batch request ### - -```go -params1 := Params{ - "method": fb.GET, - "relative_url": "me", -} -params2 := Params{ - "method": fb.GET, - "relative_url": uint64(100002828925788), -} -results, err := fb.BatchApi(your_access_token, params1, params2) - -if err != nil { - // check error... - return -} - -// batchResult1 and batchResult2 are response for params1 and params2. -batchResult1, _ := results[0].Batch() -batchResult2, _ := results[1].Batch() - -// Use parsed result. -var id string -res := batchResult1.Result -res.DecodeField("id", &id) - -// Use response header. -contentType := batchResult1.Header.Get("Content-Type") -``` - -### Send FQL query ### - -```go -results, _ := fb.FQL("SELECT username FROM page WHERE page_id = 20531316728") -fmt.Println(results[0]["username"]) // print "facebook" - -// most FQL query requires access token. create session to hold access token. -session := &fb.Session{} -session.SetAccessToken("A-VALID-ACCESS-TOKEN") -results, _ := session.FQL("SELECT username FROM page WHERE page_id = 20531316728") -fmt.Println(results[0]["username"]) // print "facebook" -``` - -### Make multi-FQL ### - -```go -res, _ := fb.MultiFQL(Params{ - "query1": "SELECT username FROM page WHERE page_id = 20531316728", - "query2": "SELECT uid FROM user WHERE uid = 538744468", -}) -var query1, query2 []Result - -// get response for query1 and query2. -res.DecodeField("query1", &query1) -res.DecodeField("query2", &query2) - -// most FQL query requires access token. create session to hold access token. -session := &fb.Session{} -session.SetAccessToken("A-VALID-ACCESS-TOKEN") -res, _ := session.MultiFQL(Params{ - "query1": "...", - "query2": "...", -}) - -// same as the sample without access token... -``` - -### Use it in Google App Engine ### - -Google App Engine provide `appengine/urlfetch` package as standard http client package. Default client in `net/http` doesn't work. One must explicitly set http client in `Session` to make it work. - -```go -import ( - "appengine" - "appengine/urlfetch" -) - -// suppose it's the appengine context initialized somewhere. -var context appengine.Context - -// default Session object uses http.DefaultClient which is not allowed to use -// in appengine. one has to create a Session and assign it a special client. -seesion := globalApp.Session("a-access-token") -session.HttpClient = urlfetch.Client(context) - -// now, session uses appengine http client now. -res, err := session.Get("/me", nil) -``` - -### Select Graph API version ### - -See [Platform Versioning](https://developers.facebook.com/docs/apps/versions) to understand facebook versioning strategy. - -```go -// this package uses default version which is controlled by facebook app setting. -// change following global variable to specific a global default version. -fb.Version = "v2.0" - -// starting with graph api v2.0, it's not allowed to get user information without access token. -fb.Api("huan.du", GET, nil) - -// it's possible to specify version per session. -session := &fb.Session{} -session.Version = "v2.0" // overwrite global default. -``` - -### Enable `appsecret_proof` ### - -Facebook can verify Graph API Calls with `appsecret_proof`. It's a feature to make Graph API call more secure. See [Securing Graph API Requests](https://developers.facebook.com/docs/graph-api/securing-requests) to know more about it. - -```go -globalApp := fb.New("your-app-id", "your-app-secret") - -// enable "appsecret_proof" for all sessions created by this app. -globalApp.EnableAppsecretProof = true - -// all calls in this session are secured. -session := globalApp.Session("a-valid-access-token") -session.Get("/me", nil) - -// it's also possible to enable/disable this feature per session. -session.EnableAppsecretProof(false) -``` - -### Debugging API Requests ### - -Facebook introduces a way to debug graph API calls. See [Debugging API Requests](https://developers.facebook.com/docs/graph-api/using-graph-api/v2.3#debugging) for details. - -This package provides both package level and per session debug flag. Set `Debug` to a `DEBUG_*` constant to change debug mode globally; or use `Session#SetDebug` to change debug mode for one session. - -When debug mode is turned on, use `Result#DebugInfo` to get `DebugInfo` struct from result. - -```go -fb.Debug = fb.DEBUG_ALL - -res, _ := fb.Get("/me", fb.Params{"access_token": "xxx"}) -debugInfo := res.DebugInfo() - -fmt.Println("http headers:", debugInfo.Header) -fmt.Println("facebook api version:", debugInfo.FacebookApiVersion) -``` - -## Change Log ## - -See [CHANGELOG.md](CHANGELOG.md). - -## Out of Scope ## - -1. No OAuth integration. This package only provides APIs to parse/verify access token and code generated in OAuth 2.0 authentication process. -2. No old RESTful API support. Such APIs are deprecated for years. Forget about them. - -## License ## - -This package is licensed under MIT license. See LICENSE for details. diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/api.go b/Godeps/_workspace/src/github.com/huandu/facebook/api.go deleted file mode 100644 index 57945d26e..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/api.go +++ /dev/null @@ -1,180 +0,0 @@ -// A facebook graph api client in go. -// https://github.com/huandu/facebook/ -// -// Copyright 2012 - 2015, Huan Du -// Licensed under the MIT license -// https://github.com/huandu/facebook/blob/master/LICENSE - -// This is a Go library fully supports Facebook Graph API (both 1.0 and 2.x) with -// file upload, batch request, FQL and multi-FQL. It can be used in Google App Engine. -// -// Library design is highly influenced by facebook official PHP/JS SDK. -// If you have experience with PHP/JS SDK, you may feel quite familiar with it. -// -// Go to project home page to see samples. Link: https://github.com/huandu/facebook -// -// This library doesn't implement any deprecated old RESTful API. And it won't. -package facebook - -import ( - "net/http" -) - -var ( - // Default facebook api version. - // It can be any valid version string (e.g. "v2.3") or empty. - // - // See https://developers.facebook.com/docs/apps/versions for details. - Version string - - // Set app level debug mode. - // After setting DebugMode, all newly created session will use the mode - // to communicate with graph API. - // - // See https://developers.facebook.com/docs/graph-api/using-graph-api/v2.3#debugging - Debug DebugMode -) - -// Makes a facebook graph api call with default session. -// -// Method can be GET, POST, DELETE or PUT. -// -// Params represents query strings in this call. -// Keys and values in params will be encoded for URL automatically. So there is -// no need to encode keys or values in params manually. Params can be nil. -// -// If you want to get -// https://graph.facebook.com/huandu?fields=name,username -// Api should be called as following -// Api("/huandu", GET, Params{"fields": "name,username"}) -// or in a simplified way -// Get("/huandu", Params{"fields": "name,username"}) -// -// Api is a wrapper of Session.Api(). It's designed for graph api that doesn't require -// app id, app secret and access token. It can be called in multiple goroutines. -// -// If app id, app secret or access token is required in graph api, caller should -// create a new facebook session through App instance instead. -func Api(path string, method Method, params Params) (Result, error) { - return defaultSession.Api(path, method, params) -} - -// Get is a short hand of Api(path, GET, params). -func Get(path string, params Params) (Result, error) { - return Api(path, GET, params) -} - -// Post is a short hand of Api(path, POST, params). -func Post(path string, params Params) (Result, error) { - return Api(path, POST, params) -} - -// Delete is a short hand of Api(path, DELETE, params). -func Delete(path string, params Params) (Result, error) { - return Api(path, DELETE, params) -} - -// Put is a short hand of Api(path, PUT, params). -func Put(path string, params Params) (Result, error) { - return Api(path, PUT, params) -} - -// Makes a batch facebook graph api call with default session. -// -// BatchApi supports most kinds of batch calls defines in facebook batch api document, -// except uploading binary data. Use Batch to do so. -// -// Note: API response is stored in "body" field of a Result. -// results, _ := BatchApi(accessToken, Params{...}, Params{...}) -// -// // Use first batch api response. -// var res1 *BatchResult -// var err error -// res1, err = results[0].Batch() -// -// if err != nil { -// // this is not a valid batch api response. -// } -// -// // Use BatchResult#Result to get response body content as Result. -// res := res1.Result -// -// Facebook document: https://developers.facebook.com/docs/graph-api/making-multiple-requests -func BatchApi(accessToken string, params ...Params) ([]Result, error) { - return Batch(Params{"access_token": accessToken}, params...) -} - -// Makes a batch facebook graph api call with default session. -// Batch is designed for more advanced usage including uploading binary files. -// -// An uploading files sample -// // equivalent to following curl command (borrowed from facebook docs) -// // curl \ -// // -F 'access_token=…' \ -// // -F 'batch=[{"method":"POST","relative_url":"me/photos","body":"message=My cat photo","attached_files":"file1"},{"method":"POST","relative_url":"me/photos","body":"message=My dog photo","attached_files":"file2"},]' \ -// // -F 'file1=@cat.gif' \ -// // -F 'file2=@dog.jpg' \ -// // https://graph.facebook.com -// Batch(Params{ -// "access_token": "the-access-token", -// "file1": File("cat.gif"), -// "file2": File("dog.jpg"), -// }, Params{ -// "method": "POST", -// "relative_url": "me/photos", -// "body": "message=My cat photo", -// "attached_files": "file1", -// }, Params{ -// "method": "POST", -// "relative_url": "me/photos", -// "body": "message=My dog photo", -// "attached_files": "file2", -// }) -// -// Facebook document: https://developers.facebook.com/docs/graph-api/making-multiple-requests -func Batch(batchParams Params, params ...Params) ([]Result, error) { - return defaultSession.Batch(batchParams, params...) -} - -// Makes a FQL query with default session. -// Returns a slice of Result. If there is no query result, the result is nil. -// -// FQL can only make query without "access_token". For query requiring "access_token", create -// Session and call its FQL method. -// -// Facebook document: https://developers.facebook.com/docs/technical-guides/fql#query -func FQL(query string) ([]Result, error) { - return defaultSession.FQL(query) -} - -// Makes a multi FQL query with default session. -// Returns a parsed Result. The key is the multi query key, and the value is the query result. -// -// MultiFQL can only make query without "access_token". For query requiring "access_token", create -// Session and call its MultiFQL method. -// -// See Session.MultiFQL document for samples. -// -// Facebook document: https://developers.facebook.com/docs/technical-guides/fql#multi -func MultiFQL(queries Params) (Result, error) { - return defaultSession.MultiFQL(queries) -} - -// Makes an arbitrary HTTP request with default session. -// It expects server responses a facebook Graph API response. -// request, _ := http.NewRequest("https://graph.facebook.com/538744468", "GET", nil) -// res, err := Request(request) -// fmt.Println(res["gender"]) // get "male" -func Request(request *http.Request) (Result, error) { - return defaultSession.Request(request) -} - -// DefaultHttpClient returns the http client for default session. -func DefaultHttpClient() HttpClient { - return defaultSession.HttpClient -} - -// SetHttpClient updates the http client of default session. -func SetHttpClient(client HttpClient) { - defaultSession.HttpClient = client -} diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/app.go b/Godeps/_workspace/src/github.com/huandu/facebook/app.go deleted file mode 100644 index d8787aa87..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/app.go +++ /dev/null @@ -1,255 +0,0 @@ -// A facebook graph api client in go. -// https://github.com/huandu/facebook/ -// -// Copyright 2012 - 2015, Huan Du -// Licensed under the MIT license -// https://github.com/huandu/facebook/blob/master/LICENSE - -package facebook - -import ( - "bytes" - "crypto/hmac" - "crypto/sha256" - "encoding/json" - "fmt" - "strings" -) - -// Creates a new App and sets app id and secret. -func New(appId, appSecret string) *App { - return &App{ - AppId: appId, - AppSecret: appSecret, - } -} - -// Gets application access token, useful for gathering public information about users and applications. -func (app *App) AppAccessToken() string { - return app.AppId + "|" + app.AppSecret -} - -// Parses signed request. -func (app *App) ParseSignedRequest(signedRequest string) (res Result, err error) { - strs := strings.SplitN(signedRequest, ".", 2) - - if len(strs) != 2 { - err = fmt.Errorf("invalid signed request format.") - return - } - - sig, e1 := decodeBase64URLEncodingString(strs[0]) - - if e1 != nil { - err = fmt.Errorf("cannot decode signed request sig. error is %v.", e1) - return - } - - payload, e2 := decodeBase64URLEncodingString(strs[1]) - - if e2 != nil { - err = fmt.Errorf("cannot decode signed request payload. error is %v.", e2) - return - } - - err = json.Unmarshal(payload, &res) - - if err != nil { - err = fmt.Errorf("signed request payload is not a valid json string. error is %v.", err) - return - } - - var hashMethod string - err = res.DecodeField("algorithm", &hashMethod) - - if err != nil { - err = fmt.Errorf("signed request payload doesn't contains a valid 'algorithm' field.") - return - } - - hashMethod = strings.ToUpper(hashMethod) - - if hashMethod != "HMAC-SHA256" { - err = fmt.Errorf("signed request payload uses an unknown HMAC method. expect 'HMAC-SHA256'. actual '%v'.", hashMethod) - return - } - - hash := hmac.New(sha256.New, []byte(app.AppSecret)) - hash.Write([]byte(strs[1])) // note: here uses the payload base64 string, not decoded bytes - expectedSig := hash.Sum(nil) - - if bytes.Compare(sig, expectedSig) != 0 { - err = fmt.Errorf("bad signed request signiture.") - return - } - - return -} - -// ParseCode redeems code for a valid access token. -// It's a shorthand call to ParseCodeInfo(code, ""). -// -// In facebook PHP SDK, there is a CSRF state to avoid attack. -// That state is not checked in this library. -// Caller is responsible to store and check state if possible. -func (app *App) ParseCode(code string) (token string, err error) { - token, _, _, err = app.ParseCodeInfo(code, "") - return -} - -// ParseCodeInfo redeems code for access token and returns extra information. -// The machineId is optional. -// -// See https://developers.facebook.com/docs/facebook-login/access-tokens#extending -func (app *App) ParseCodeInfo(code, machineId string) (token string, expires int, newMachineId string, err error) { - if code == "" { - err = fmt.Errorf("code is empty") - return - } - - var res Result - res, err = defaultSession.sendOauthRequest("/oauth/access_token", Params{ - "client_id": app.AppId, - "redirect_uri": app.RedirectUri, - "code": code, - }) - - if err != nil { - err = fmt.Errorf("cannot parse facebook response. error is %v.", err) - return - } - - err = res.DecodeField("access_token", &token) - - if err != nil { - return - } - - err = res.DecodeField("expires_in", &expires) - - if err != nil { - return - } - - if _, ok := res["machine_id"]; ok { - err = res.DecodeField("machine_id", &newMachineId) - } - - return -} - -// Exchange a short lived access token to a long lived access token. -// Return new access token and its expires time. -func (app *App) ExchangeToken(accessToken string) (token string, expires int, err error) { - if accessToken == "" { - err = fmt.Errorf("short lived accessToken is empty") - return - } - - var res Result - res, err = defaultSession.sendOauthRequest("/oauth/access_token", Params{ - "grant_type": "fb_exchange_token", - "client_id": app.AppId, - "client_secret": app.AppSecret, - "fb_exchange_token": accessToken, - }) - - if err != nil { - err = fmt.Errorf("cannot parse facebook response. error is %v.", err) - return - } - - err = res.DecodeField("access_token", &token) - - if err != nil { - return - } - - err = res.DecodeField("expires_in", &expires) - return -} - -// Get code from a long lived access token. -// Return the code retrieved from facebook. -func (app *App) GetCode(accessToken string) (code string, err error) { - if accessToken == "" { - err = fmt.Errorf("long lived accessToken is empty") - return - } - - var res Result - res, err = defaultSession.sendOauthRequest("/oauth/client_code", Params{ - "client_id": app.AppId, - "client_secret": app.AppSecret, - "redirect_uri": app.RedirectUri, - "access_token": accessToken, - }) - - if err != nil { - err = fmt.Errorf("cannot get code from facebook. error is %v.", err) - return - } - - err = res.DecodeField("code", &code) - return -} - -// Creates a session based on current App setting. -func (app *App) Session(accessToken string) *Session { - return &Session{ - accessToken: accessToken, - app: app, - enableAppsecretProof: app.EnableAppsecretProof, - } -} - -// Creates a session from a signed request. -// If signed request contains a code, it will automatically use this code -// to exchange a valid access token. -func (app *App) SessionFromSignedRequest(signedRequest string) (session *Session, err error) { - var res Result - - res, err = app.ParseSignedRequest(signedRequest) - - if err != nil { - return - } - - var id, token string - - res.DecodeField("user_id", &id) // it's ok without user id. - err = res.DecodeField("oauth_token", &token) - - if err == nil { - session = &Session{ - accessToken: token, - app: app, - id: id, - enableAppsecretProof: app.EnableAppsecretProof, - } - return - } - - // cannot get "oauth_token"? try to get "code". - err = res.DecodeField("code", &token) - - if err != nil { - // no code? no way to continue. - err = fmt.Errorf("cannot find 'oauth_token' and 'code'. no way to continue.") - return - } - - token, err = app.ParseCode(token) - - if err != nil { - return - } - - session = &Session{ - accessToken: token, - app: app, - id: id, - enableAppsecretProof: app.EnableAppsecretProof, - } - return -} diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/batch_result.go b/Godeps/_workspace/src/github.com/huandu/facebook/batch_result.go deleted file mode 100644 index 43a38358e..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/batch_result.go +++ /dev/null @@ -1,52 +0,0 @@ -// A facebook graph api client in go. -// https://github.com/huandu/facebook/ -// -// Copyright 2012 - 2015, Huan Du -// Licensed under the MIT license -// https://github.com/huandu/facebook/blob/master/LICENSE - -package facebook - -import ( - "encoding/json" - "net/http" -) - -type batchResultHeader struct { - Name string `facebook=",required"` - Value string `facebook=",required"` -} - -type batchResultData struct { - Code int `facebook=",required"` - Headers []batchResultHeader `facebook=",required"` - Body string `facebook=",required"` -} - -func newBatchResult(res Result) (*BatchResult, error) { - var data batchResultData - err := res.Decode(&data) - - if err != nil { - return nil, err - } - - result := &BatchResult{ - StatusCode: data.Code, - Header: http.Header{}, - Body: data.Body, - } - - err = json.Unmarshal([]byte(result.Body), &result.Result) - - if err != nil { - return nil, err - } - - // add headers to result. - for _, header := range data.Headers { - result.Header.Add(header.Name, header.Value) - } - - return result, nil -} diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/const.go b/Godeps/_workspace/src/github.com/huandu/facebook/const.go deleted file mode 100644 index aa8be0de2..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/const.go +++ /dev/null @@ -1,74 +0,0 @@ -// A facebook graph api client in go. -// https://github.com/huandu/facebook/ -// -// Copyright 2012 - 2015, Huan Du -// Licensed under the MIT license -// https://github.com/huandu/facebook/blob/master/LICENSE - -package facebook - -import ( - "encoding/json" - "reflect" - "regexp" - "time" -) - -// Facebook graph api methods. -const ( - GET Method = "GET" - POST Method = "POST" - DELETE Method = "DELETE" - PUT Method = "PUT" -) - -const ( - ERROR_CODE_UNKNOWN = -1 // unknown facebook graph api error code. - - _MIME_FORM_URLENCODED = "application/x-www-form-urlencoded" -) - -// Graph API debug mode values. -const ( - DEBUG_OFF DebugMode = "" // turn off debug. - - DEBUG_ALL DebugMode = "all" - DEBUG_INFO DebugMode = "info" - DEBUG_WARNING DebugMode = "warning" -) - -const ( - debugInfoKey = "__debug__" - debugProtoKey = "__proto__" - debugHeaderKey = "__header__" - - facebookApiVersionHeader = "facebook-api-version" - facebookDebugHeader = "x-fb-debug" - facebookRevHeader = "x-fb-rev" -) - -var ( - // Maps aliases to Facebook domains. - // Copied from Facebook PHP SDK. - domainMap = map[string]string{ - "api": "https://api.facebook.com/", - "api_video": "https://api-video.facebook.com/", - "api_read": "https://api-read.facebook.com/", - "graph": "https://graph.facebook.com/", - "graph_video": "https://graph-video.facebook.com/", - "www": "https://www.facebook.com/", - } - - // checks whether it's a video post. - regexpIsVideoPost = regexp.MustCompile(`/^(\/)(.+)(\/)(videos)$/`) - - // default facebook session. - defaultSession = &Session{} - - typeOfPointerToBinaryData = reflect.TypeOf(&binaryData{}) - typeOfPointerToBinaryFile = reflect.TypeOf(&binaryFile{}) - typeOfJSONNumber = reflect.TypeOf(json.Number("")) - typeOfTime = reflect.TypeOf(time.Time{}) - - facebookSuccessJsonBytes = []byte("true") -) diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/facebook_test.go b/Godeps/_workspace/src/github.com/huandu/facebook/facebook_test.go deleted file mode 100644 index 154881f38..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/facebook_test.go +++ /dev/null @@ -1,1469 +0,0 @@ -// A facebook graph api client in go. -// https://github.com/huandu/facebook/ -// -// Copyright 2012 - 2015, Huan Du -// Licensed under the MIT license -// https://github.com/huandu/facebook/blob/master/LICENSE - -package facebook - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "testing" - "time" -) - -const ( - FB_TEST_APP_ID = "169186383097898" - FB_TEST_APP_SECRET = "b2e4262c306caa3c7f5215d2d099b319" - - // remeber to change it to a valid token to run test - //FB_TEST_VALID_ACCESS_TOKEN = "CAACZA38ZAD8CoBAFCaVgLBNdz0RrH45yUBUA95exI1FY5i4mZBY5iULfM3YEpS53nP6eSF4cf3nmoiePHvMkdSZApkxu1heAupW7OE8tmiySRZAYkZBZBvhveCZCgPaJlFovlI0ZAhWdWTLxxmJaZCKDG0B8n9VGEvcN3zoS1AHjokSz4aNos39xthp7XtAz9X3NRvp1qU4UTOlxK8IJOC1ApAMmvcEE0kWvgZD" - FB_TEST_VALID_ACCESS_TOKEN = "" - - // remember to change it to a valid signed request to run test - //FB_TEST_VALID_SIGNED_REQUEST = "ZAxP-ILRQBOwKKxCBMNlGmVraiowV7WFNg761OYBNGc.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImV4cGlyZXMiOjEzNDM0OTg0MDAsImlzc3VlZF9hdCI6MTM0MzQ5MzI2NSwib2F1dGhfdG9rZW4iOiJBQUFDWkEzOFpBRDhDb0JBRFpCcmZ5TFpDanBNUVczdThVTWZmRldSWkNpZGw5Tkx4a1BsY2tTcXZaQnpzTW9OWkF2bVk2RUd2NG1hUUFaQ0t2VlpBWkJ5VXA5a0FCU2x6THFJejlvZTdOdHBzdzhyQVpEWkQiLCJ1c2VyIjp7ImNvdW50cnkiOiJ1cyIsImxvY2FsZSI6ImVuX1VTIiwiYWdlIjp7Im1pbiI6MjF9fSwidXNlcl9pZCI6IjUzODc0NDQ2OCJ9" - FB_TEST_VALID_SIGNED_REQUEST = "" - - // test binary file base64 value - FB_TEST_BINARY_JPG_FILE = "/9j/4AAQSkZJRgABAQEASABIAAD/4gv4SUNDX1BST0ZJTEUAAQEAAAvoAAAAAAIAAABtbnRy" + - "UkdCIFhZWiAH2QADABsAFQAkAB9hY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAA" + - "9tYAAQAAAADTLQAAAAAp+D3er/JVrnhC+uTKgzkNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAABBkZXNjAAABRAAAAHliWFlaAAABwAAAABRiVFJDAAAB1AAACAxkbWRkAAAJ4AAA" + - "AIhnWFlaAAAKaAAAABRnVFJDAAAB1AAACAxsdW1pAAAKfAAAABRtZWFzAAAKkAAAACRia3B0" + - "AAAKtAAAABRyWFlaAAAKyAAAABRyVFJDAAAB1AAACAx0ZWNoAAAK3AAAAAx2dWVkAAAK6AAA" + - "AId3dHB0AAALcAAAABRjcHJ0AAALhAAAADdjaGFkAAALvAAAACxkZXNjAAAAAAAAAB9zUkdC" + - "IElFQzYxOTY2LTItMSBibGFjayBzY2FsZWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "WFlaIAAAAAAAACSgAAAPhAAAts9jdXJ2AAAAAAAABAAAAAAFAAoADwAUABkAHgAjACgALQAy" + - "ADcAOwBAAEUASgBPAFQAWQBeAGMAaABtAHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3" + - "ALwAwQDGAMsA0ADVANsA4ADlAOsA8AD2APsBAQEHAQ0BEwEZAR8BJQErATIBOAE+AUUBTAFS" + - "AVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGpAbEBuQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQIm" + - "Ai8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4" + - "A0MDTwNaA2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSM" + - "BJoEqAS2BMQE0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYn" + - "BjcGSAZZBmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgL" + - "CB8IMghGCFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9" + - "ClQKagqBCpgKrgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzA" + - "DNkM8w0NDSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+W" + - "D7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLD" + - "EuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJ" + - "FmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3Gd0aBBoq" + - "GlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1HR4dRx1wHZkdwx3sHhYeQB5q" + - "HpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1IaEhziH7IiciVSKCIq8i3SMK" + - "IzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocmtyboJxgnSSd6J6sn3CgN" + - "KD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSudK9EsBSw5LG4soizXLQwtQS12" + - "Last4S4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNG" + - "M38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/" + - "Obw5+To2OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAj" + - "QGRApkDnQSlBakGsQe5CMEJyQrVC90M6Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1" + - "R3tHwEgFSEtIkUjXSR1JY0mpSfBKN0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63" + - "TwBPSU+TT91QJ1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFap" + - "VvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8P" + - "X2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fp" + - "aD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6" + - "cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnnekZ6pXsE" + - "e2N7wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VH" + - "hauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaPnpAG" + - "kG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8mmia1ZtC" + - "m6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMelOKWpphqmi6b9" + - "p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4" + - "s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7urW7LrunvCG8m70VvY++Cr6Evv+/er/1" + - "wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/IPci8yTrJuco4yrfLNsu2zDXMtc01" + - "zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls2fHadtr7" + - "24DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG" + - "6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ" + - "+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23//2Rlc2MAAAAAAAAALklFQyA2MTk2Ni0yLTEg" + - "RGVmYXVsdCBSR0IgQ29sb3VyIFNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AABYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAAAAAAAAAAAABQAAAAAAAAbWVhcwAAAAAAAAAB" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACWFlaIAAAAAAAAAMWAAADMwAAAqRYWVogAAAAAAAA" + - "b6IAADj1AAADkHNpZyAAAAAAQ1JUIGRlc2MAAAAAAAAALVJlZmVyZW5jZSBWaWV3aW5nIENv" + - "bmRpdGlvbiBpbiBJRUMgNjE5NjYtMi0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYWVog" + - "AAAAAAAA9tYAAQAAAADTLXRleHQAAAAAQ29weXJpZ2h0IEludGVybmF0aW9uYWwgQ29sb3Ig" + - "Q29uc29ydGl1bSwgMjAwOQAAc2YzMgAAAAAAAQxEAAAF3///8yYAAAeUAAD9j///+6H///2i" + - "AAAD2wAAwHX/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQa" + - "FRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4e" + - "Hh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAxADIDASIAAhEB" + - "AxEB/8QAHQAAAQQDAQEAAAAAAAAAAAAAAAUGBwgBAwQJAv/EADYQAAEDAwIEAgcGBwAAAAAA" + - "AAECAwQABREGIQcSEzFBUQgUIjJhgZEVQnFyobEWIzeFkrLx/8QAGQEBAAMBAQAAAAAAAAAA" + - "AAAABAECAwUG/8QAKREAAgEDAgQFBQAAAAAAAAAAAAECAxEhBBITMUGBBRQzscEiMlFhcf/a" + - "AAwDAQACEQMRAD8A23GcGQVdFS2BgPLSfdHiaZnEjWdtslhaehy0rcceCm2G0+1sd1DPbsae" + - "EvTlylyWnnG5MVbYw44hsHrIIIKVDwG/6VWTXaHJ2qJwiuuyWmXVNoUrJPKk4Hxoiozg1vTX" + - "YSqkJp7Gmd184namuAS03MSy2kJ91tKlE+ZJFK2iOMGu9OT/AFpq5IlNqQErZksJW2tIOcbA" + - "EfiDTHi2h1SA6GnNiAsFJwnPY58jQ7Floe6K0FByBvt3pEYJ/bgzluSyXh4N8WbLxEjLjttG" + - "33lhHO/DWrmCk9ittX3k589xnfzqRDXnroO+TtE8QbVdFKciuw5iA8CO7ROHEkeIKSa9CkLb" + - "dQl1lYW0sBSFA5CkncH6UiN+oeSszHyorNFSVOt1hooV/KQdj90VRdFmeZ4x6gtcpohaZLx5" + - "AAAoFfMPwGCk58Kvear3xq0tDsvFWzau6eIl05oM7yC1JPTV8M45f8aPX6N/z5XsJ0rW+wl6" + - "fYhyz9lyrVDCgA0oNykO4z2CwB7JPfFcz+kXXLq0hNjYmLIKOvIc5W2UeCUoAPN8zTtkQ7PZ" + - "bJ1oCGmQVJUrlABAGNzj4Ab/AIVmPqQLkSHYBDkVCeo4txPK2CfAKPjQZVat9sVj8noI0YW+" + - "p5RCPpC6RRbplrnwkIDzmGHEp2ClAeyf3H0q3mj0BrSVnaBJCILKdz5IAqAdfSbc65b7tqRa" + - "W7e1cI63EkcwS3zjm7fAmpI0nxo0LqPWTWk7C7NfdWFIjyBG5WF8iSSE5PMAAnYkAGmaW6ja" + - "T5YOP4go8S8VzySTRXzmilnNuKWaS9T2S36gtTtuuLCXWXB2I7HuD9QD8qUqwTUSgpKz5Exk" + - "4u6K9a0tU+yvvwFOuMpcOGHSkLHnjfYn/tN6FEU6EMTOmpCXAtTjrhUV/AA7AUn+m9qWYNV2" + - "SwxnXGmokcyiWyQS6okA5HkAfqaj7SOp4lyt5/iCZLPQbPUSl3AOPEgbkGiwpykttzqUta4L" + - "lkdfEWbF1A1PZVJS1aYLC+rI+6XMYAT54P67VF3D25XDTd4b1FBe9XkRN2XAMnON9j3GNsfG" + - "tl8v0nUjyYMVr1K0ML5m2UjHNjsVeZ8h4V1x4DK2Exjnp8u/L479hVnTUFh4DTq8WX7LFwPS" + - "V04qCwqXpy7iQWkl0NcpQF435Sd8ZziioOQEpQlKUAJAwBjsKKr5iRXgIvpWFdqKKaEKVemf" + - "/Vj+3M/7KqEo3vK/LRRR6XJ9/dm8+nb4HFC7R/yinDA9wfL9qKK01Hpopp/UOs0UUUAWf//Z" -) - -var ( - testGlobalApp = New(FB_TEST_APP_ID, FB_TEST_APP_SECRET) -) - -type AllTypes struct { - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - Float32 float32 - Float64 float64 - String string - ArrayOfInt []int - MapOfString map[string]string - NestedStruct *NestedStruct -} - -type NestedStruct struct { - Int int - String string - ArrayOfString []string -} - -type ParamsStruct struct { - Foo string - Bar *ParamsNestedStruct -} - -type ParamsNestedStruct struct { - AAA int - BBB string - CCC bool -} - -type FieldTagStruct struct { - Field1 string `facebook:"field2"` - Required string `facebook:",required"` - Foo string `facebook:"bar,required"` - CanAbsent string -} - -type MessageTag struct { - Id string - Name string - Type string -} - -type MessageTags map[string][]*MessageTag - -type NullStruct struct { - Null *int -} - -func TestApiGetUserInfoV2(t *testing.T) { - Version = "v2.2" - defer func() { - Version = "" - }() - - // It's not allowed to get user info by name. So I get "me" with access token instead. - if FB_TEST_VALID_ACCESS_TOKEN != "" { - me, err := Api("me", GET, Params{ - "access_token": FB_TEST_VALID_ACCESS_TOKEN, - }) - - if err != nil { - t.Fatalf("cannot get my info. [e:%v]", err) - } - - if e := me.Err(); e != nil { - t.Fatalf("facebook returns error. [e:%v]", e) - } - - t.Logf("my info. %v", me) - } -} - -func TestBatchApiGetInfo(t *testing.T) { - if FB_TEST_VALID_ACCESS_TOKEN == "" { - t.Skipf("cannot call batch api without access token. skip this test.") - } - - verifyBatchResult := func(t *testing.T, index int, res Result) { - batch, err := res.Batch() - - if err != nil { - t.Fatalf("cannot parse batch api results[%v]. [e:%v] [result:%v]", index, err, res) - } - - if batch.StatusCode != 200 { - t.Fatalf("facebook returns unexpected http status code in results[%v]. [code:%v] [result:%v]", index, batch.StatusCode, res) - } - - contentType := batch.Header.Get("Content-Type") - - if contentType == "" { - t.Fatalf("facebook returns unexpected http header in results[%v]. [header:%v]", index, batch.Header) - } - - if batch.Body == "" { - t.Fatalf("facebook returns unexpected http body in results[%v]. [body:%v]", index, batch.Body) - } - - var id string - err = batch.Result.DecodeField("id", &id) - - if err != nil { - t.Fatalf("cannot get 'id' field in results[%v]. [result:%v]", index, res) - } - - if id == "" { - t.Fatalf("facebook should return account id in results[%v].", index) - } - } - - test := func(t *testing.T) { - params1 := Params{ - "method": GET, - "relative_url": "me", - } - params2 := Params{ - "method": GET, - "relative_url": uint64(100002828925788), // id of my another facebook account - } - - results, err := BatchApi(FB_TEST_VALID_ACCESS_TOKEN, params1, params2) - - if err != nil { - t.Fatalf("cannot get batch result. [e:%v]", err) - } - - if len(results) != 2 { - t.Fatalf("batch api should return results in an array with 2 entries. [len:%v]", len(results)) - } - - if Version == "" { - t.Log("use default facebook version.") - } else { - t.Logf("global facebook version: %v", Version) - } - - for index, result := range results { - verifyBatchResult(t, index, result) - } - } - - // Use default Version. - Version = "" - test(t) - - // User "v2.2". - Version = "v2.2" - defer func() { - Version = "" - }() - test(t) - - // when providing an invalid access token, BatchApi should return a facebook error. - _, err := BatchApi("an_invalid_access_token", Params{ - "method": GET, - "relative_url": "me", - }) - - if err == nil { - t.Fatalf("expect an error when providing an invalid access token to BatchApi.") - } - - if _, ok := err.(*Error); !ok { - t.Fatalf("batch result error must be an *Error. [e:%v]", err) - } -} - -func TestApiParseSignedRequest(t *testing.T) { - if FB_TEST_VALID_SIGNED_REQUEST == "" { - t.Logf("skip this case as we don't have a valid signed request.") - return - } - - app := New(FB_TEST_APP_ID, FB_TEST_APP_SECRET) - res, err := app.ParseSignedRequest(FB_TEST_VALID_SIGNED_REQUEST) - - if err != nil { - t.Fatalf("cannot parse signed request. [e:%v]", err) - } - - t.Logf("signed request is '%v'.", res) -} - -func TestSession(t *testing.T) { - if FB_TEST_VALID_ACCESS_TOKEN == "" { - t.Skipf("skip this case as we don't have a valid access token.") - } - - session := &Session{} - session.SetAccessToken(FB_TEST_VALID_ACCESS_TOKEN) - - test := func(t *testing.T, session *Session) { - id, err := session.User() - - if err != nil { - t.Fatalf("cannot get current user id. [e:%v]", err) - } - - t.Logf("current user id is %v", id) - - result, e := session.Api("/me", GET, Params{ - "fields": "id,email,website", - }) - - if e != nil { - t.Fatalf("cannot get my extended info. [e:%v]", e) - } - - if Version == "" { - t.Log("use default facebook version.") - } else { - t.Logf("global facebook version: %v", Version) - } - - if session.Version == "" { - t.Log("use default session facebook version.") - } else { - t.Logf("session facebook version: %v", session.Version) - } - - t.Logf("my extended info is: %v", result) - } - - // Default version. - test(t, session) - - // Global version overwrite default session version. - func() { - Version = "v2.2" - defer func() { - Version = "" - }() - - test(t, session) - }() - - // Session version overwrite default version. - func() { - Version = "vx.y" // an invalid version. - session.Version = "v2.2" - defer func() { - Version = "" - }() - - test(t, session) - }() - - // Session with appsecret proof enabled. - if FB_TEST_VALID_ACCESS_TOKEN != "" { - app := New(FB_TEST_APP_ID, FB_TEST_APP_SECRET) - app.EnableAppsecretProof = true - session := app.Session(FB_TEST_VALID_ACCESS_TOKEN) - - _, e := session.Api("/me", GET, Params{ - "fields": "id", - }) - - if e != nil { - t.Fatalf("cannot get my info with proof. [e:%v]", e) - } - } -} - -func TestUploadingBinary(t *testing.T) { - if FB_TEST_VALID_ACCESS_TOKEN == "" { - t.Skipf("skip this case as we don't have a valid access token.") - } - - buf := bytes.NewBufferString(FB_TEST_BINARY_JPG_FILE) - reader := base64.NewDecoder(base64.StdEncoding, buf) - - session := &Session{} - session.SetAccessToken(FB_TEST_VALID_ACCESS_TOKEN) - - result, e := session.Api("/me/photos", POST, Params{ - "message": "Test photo from https://github.com/huandu/facebook", - "source": Data("my_profile.jpg", reader), - }) - - if e != nil { - t.Fatalf("cannot create photo on my timeline. [e:%v]", e) - } - - var id string - e = result.DecodeField("id", &id) - - if e != nil { - t.Fatalf("facebook should return photo id on success. [e:%v]", e) - } - - t.Logf("newly created photo id is %v", id) -} - -func TestUploadBinaryWithBatch(t *testing.T) { - if FB_TEST_VALID_ACCESS_TOKEN == "" { - t.Skipf("skip this case as we don't have a valid access token.") - } - - buf1 := bytes.NewBufferString(FB_TEST_BINARY_JPG_FILE) - reader1 := base64.NewDecoder(base64.StdEncoding, buf1) - buf2 := bytes.NewBufferString(FB_TEST_BINARY_JPG_FILE) - reader2 := base64.NewDecoder(base64.StdEncoding, buf2) - - session := &Session{} - session.SetAccessToken(FB_TEST_VALID_ACCESS_TOKEN) - - // sample comes from facebook batch api sample. - // https://developers.facebook.com/docs/reference/api/batch/ - // - // curl - // -F 'access_token=…' \ - // -F 'batch=[{"method":"POST","relative_url":"me/photos","body":"message=My cat photo","attached_files":"file1"},{"method":"POST","relative_url":"me/photos","body":"message=My dog photo","attached_files":"file2"},]' \ - // -F 'file1=@cat.gif' \ - // -F 'file2=@dog.jpg' \ - // https://graph.facebook.com - result, e := session.Batch(Params{ - "file1": Data("cat.jpg", reader1), - "file2": Data("dog.jpg", reader2), - }, Params{ - "method": POST, - "relative_url": "me/photos", - "body": "message=My cat photo", - "attached_files": "file1", - }, Params{ - "method": POST, - "relative_url": "me/photos", - "body": "message=My dog photo", - "attached_files": "file2", - }) - - if e != nil { - t.Fatalf("cannot create photo on my timeline. [e:%v]", e) - } - - t.Logf("batch call result. [result:%v]", result) -} - -func TestSimpleFQL(t *testing.T) { - defer func() { - Version = "" - }() - - test := func(t *testing.T, session *Session) { - me, err := session.FQL("SELECT name FROM user WHERE uid = 538744468") - - if err != nil { - t.Fatalf("cannot get my info. [e:%v]", err) - } - - if len(me) != 1 { - t.Fatalf("expect to get only 1 result. [len:%v]", len(me)) - } - - t.Logf("my name. %v", me[0]["name"]) - } - - // v2.2 api doesn't allow me to query user without access token. - if FB_TEST_VALID_ACCESS_TOKEN == "" { - return - } - - Version = "v2.2" - session := &Session{} - session.SetAccessToken(FB_TEST_VALID_ACCESS_TOKEN) - test(t, session) -} - -func TestMultiFQL(t *testing.T) { - defer func() { - Version = "" - }() - - test := func(t *testing.T, session *Session) { - res, err := session.MultiFQL(Params{ - "query1": "SELECT username FROM page WHERE page_id = 20531316728", - "query2": "SELECT uid FROM user WHERE uid = 538744468", - }) - - if err != nil { - t.Fatalf("cannot get my info. [e:%v]", err) - } - - if err = res.Err(); err != nil { - t.Fatalf("fail to parse facebook api error. [e:%v]", err) - } - - var query1, query2 []Result - - err = res.DecodeField("query1", &query1) - - if err != nil { - t.Fatalf("cannot get result of query1. [e:%v]", err) - } - - if len(query1) != 1 { - t.Fatalf("expect to get only 1 result in query1. [len:%v]", len(query1)) - } - - err = res.DecodeField("query2", &query2) - - if err != nil { - t.Fatalf("cannot get result of query2. [e:%v]", err) - } - - if len(query2) != 1 { - t.Fatalf("expect to get only 1 result in query2. [len:%v]", len(query2)) - } - - var username string - var uid string - - err = query1[0].DecodeField("username", &username) - - if err != nil { - t.Fatalf("cannot decode username from query1. [e:%v]", err) - } - - if username != "facebook" { - t.Fatalf("username is expected to be 'facebook'. [username:%v]", username) - } - - err = query2[0].DecodeField("uid", &uid) - - if err != nil { - t.Fatalf("cannot decode username from query2. [e:%v] [query2:%v]", err, query2) - } - - if uid != "538744468" { - t.Fatalf("username is expected to be 'facebook'. [username:%v]", username) - } - } - - // v2.2 api doesn't allow me to query user without access token. - if FB_TEST_VALID_ACCESS_TOKEN == "" { - return - } - - Version = "v2.2" - session := &Session{} - session.SetAccessToken(FB_TEST_VALID_ACCESS_TOKEN) - test(t, session) -} - -func TestGraphDebuggingAPI(t *testing.T) { - if FB_TEST_VALID_ACCESS_TOKEN == "" { - t.Skipf("cannot call batch api without access token. skip this test.") - } - - test := func(t *testing.T, session *Session) { - session.SetAccessToken(FB_TEST_VALID_ACCESS_TOKEN) - defer session.SetAccessToken("") - - // test app must not grant "read_friendlists" permission. - // otherwise there is no way to get a warning from facebook. - res, _ := session.Get("/me/friendlists", nil) - - if res == nil { - t.Fatalf("res must not be nil.") - } - - debugInfo := res.DebugInfo() - - if debugInfo == nil { - t.Fatalf("debug info must exist.") - } - - t.Logf("facebook response is: %v", res) - t.Logf("debug info is: %v", *debugInfo) - - if debugInfo.Messages == nil && len(debugInfo.Messages) > 0 { - t.Fatalf("facebook must warn me for the permission issue.") - } - - msg := debugInfo.Messages[0] - - if msg.Type == "" || msg.Message == "" { - t.Fatalf("facebook must say something. [msg:%v]", msg) - } - - if debugInfo.FacebookApiVersion == "" { - t.Fatalf("facebook must tell me api version.") - } - - if debugInfo.FacebookDebug == "" { - t.Fatalf("facebook must tell me X-FB-Debug.") - } - - if debugInfo.FacebookRev == "" { - t.Fatalf("facebook must tell me x-fb-rev.") - } - } - - defer func() { - Debug = DEBUG_OFF - Version = "" - }() - - Version = "v2.2" - Debug = DEBUG_ALL - test(t, defaultSession) - session := &Session{} - session.SetDebug(DEBUG_ALL) - test(t, session) - - // test changing debug mode. - old := session.SetDebug(DEBUG_OFF) - - if old != DEBUG_ALL { - t.Fatalf("debug mode must be DEBUG_ALL. [debug:%v]", old) - } - - if session.Debug() != DEBUG_ALL { - t.Fatalf("debug mode must be DEBUG_ALL [debug:%v]", session.Debug()) - } - - Debug = DEBUG_OFF - - if session.Debug() != DEBUG_OFF { - t.Fatalf("debug mode must be DEBUG_OFF. [debug:%v]", session.Debug()) - } -} - -func TestResultDecode(t *testing.T) { - strNormal := `{ - "int": 1234, - "int8": 23, - "int16": 12345, - "int32": -127372843, - "int64": 192438483489298, - "uint": 1283829, - "uint8": 233, - "uint16": 62121, - "uint32": 3083747392, - "uint64": 2034857382993849, - "float32": 9382.38429, - "float64": 3984.293848292, - "map_of_string": {"a": "1", "b": "2"}, - "array_of_int": [12, 34, 56], - "string": "abcd", - "notused": 1234, - "nested_struct": { - "string": "hello", - "int": 123, - "array_of_string": ["a", "b", "c"] - } - }` - strOverflow := `{ - "int": 1234, - "int8": 23, - "int16": 12345, - "int32": -127372843, - "int64": 192438483489298, - "uint": 1283829, - "uint8": 233, - "uint16": 62121, - "uint32": 383083747392, - "uint64": 2034857382993849, - "float32": 9382.38429, - "float64": 3984.293848292, - "string": "abcd", - "map_of_string": {"a": "1", "b": "2"}, - "array_of_int": [12, 34, 56], - "string": "abcd", - "notused": 1234, - "nested_struct": { - "string": "hello", - "int": 123, - "array_of_string": ["a", "b", "c"] - } - }` - strMissAField := `{ - "int": 1234, - "int8": 23, - "int16": 12345, - "int32": -127372843, - - "missed": "int64", - - "uint": 1283829, - "uint8": 233, - "uint16": 62121, - "uint32": 383083747392, - "uint64": 2034857382993849, - "float32": 9382.38429, - "float64": 3984.293848292, - "string": "abcd", - "map_of_string": {"a": "1", "b": "2"}, - "array_of_int": [12, 34, 56], - "string": "abcd", - "notused": 1234, - "nested_struct": { - "string": "hello", - "int": 123, - "array_of_string": ["a", "b", "c"] - } - }` - var result Result - var err error - var normal, withError AllTypes - var anInt int - - err = json.Unmarshal([]byte(strNormal), &result) - - if err != nil { - t.Fatalf("cannot unmarshal json string. [e:%v]", err) - } - - err = result.Decode(&normal) - - if err != nil { - t.Fatalf("cannot decode normal struct. [e:%v]", err) - } - - err = json.Unmarshal([]byte(strOverflow), &result) - - if err != nil { - t.Fatalf("cannot unmarshal json string. [e:%v]", err) - } - - err = result.Decode(&withError) - - if err == nil { - t.Fatalf("struct should be overflow") - } - - t.Logf("overflow struct. e:%v", err) - - err = json.Unmarshal([]byte(strMissAField), &result) - - if err != nil { - t.Fatalf("cannot unmarshal json string. [e:%v]", err) - } - - err = result.Decode(&withError) - - if err == nil { - t.Fatalf("a field in struct should absent in json map.") - } - - t.Logf("miss-a-field struct. e:%v", err) - - err = result.DecodeField("array_of_int.2", &anInt) - - if err != nil { - t.Fatalf("cannot decode array item. [e:%v]", err) - } - - if anInt != 56 { - t.Fatalf("invalid array value. expected 56, actual %v", anInt) - } - - err = result.DecodeField("nested_struct.int", &anInt) - - if err != nil { - t.Fatalf("cannot decode nested struct item. [e:%v]", err) - } - - if anInt != 123 { - t.Fatalf("invalid array value. expected 123, actual %v", anInt) - } -} - -func TestParamsEncode(t *testing.T) { - var params Params - buf := &bytes.Buffer{} - - if mime, err := params.Encode(buf); err != nil || mime != _MIME_FORM_URLENCODED || buf.Len() != 0 { - t.Fatalf("empty params must encode to an empty string. actual is [e:%v] [str:%v] [mime:%v]", err, buf.String(), mime) - } - - buf.Reset() - params = Params{} - params["need_escape"] = "&=+" - expectedEncoding := "need_escape=%26%3D%2B" - - if mime, err := params.Encode(buf); err != nil || mime != _MIME_FORM_URLENCODED || buf.String() != expectedEncoding { - t.Fatalf("wrong params encode result. expected is '%v'. actual is '%v'. [e:%v] [mime:%v]", expectedEncoding, buf.String(), err, mime) - } - - buf.Reset() - data := ParamsStruct{ - Foo: "hello, world!", - Bar: &ParamsNestedStruct{ - AAA: 1234, - BBB: "bbb", - CCC: true, - }, - } - params = MakeParams(data) - /* there is no easy way to compare two encoded maps. so i just write expect map here, not test it. - expectedParams := Params{ - "foo": "hello, world!", - "bar": map[string]interface{}{ - "aaa": 1234, - "bbb": "bbb", - "ccc": true, - }, - } - */ - - if params == nil { - t.Fatalf("make params error.") - } - - mime, err := params.Encode(buf) - t.Logf("complex encode result is '%v'. [e:%v] [mime:%v]", buf.String(), err, mime) -} - -func TestStructFieldTag(t *testing.T) { - strNormalField := `{ - "field2": "hey", - "required": "my", - "bar": "dear" - }` - strMissingField2Field := `{ - "field1": "hey", - "required": "my", - "bar": "dear" - }` - strMissingRequiredField := `{ - "field1": "hey", - "bar": "dear", - "can_absent": "babe" - }` - strMissingBarField := `{ - "field1": "hey", - "required": "my" - }` - - var result Result - var value FieldTagStruct - var err error - - err = json.Unmarshal([]byte(strNormalField), &result) - - if err != nil { - t.Fatalf("cannot unmarshal json string. [e:%v]", err) - } - - err = result.Decode(&value) - - if err != nil { - t.Fatalf("cannot decode struct. [e:%v]", err) - } - - result = Result{} - value = FieldTagStruct{} - err = json.Unmarshal([]byte(strMissingField2Field), &result) - - if err != nil { - t.Fatalf("cannot unmarshal json string. [e:%v]", err) - } - - err = result.Decode(&value) - - if err != nil { - t.Fatalf("cannot decode struct. [e:%v]", err) - } - - if value.Field1 != "" { - t.Fatalf("value field1 should be kept unchanged. [field1:%v]", value.Field1) - } - - result = Result{} - value = FieldTagStruct{} - err = json.Unmarshal([]byte(strMissingRequiredField), &result) - - if err != nil { - t.Fatalf("cannot unmarshal json string. [e:%v]", err) - } - - err = result.Decode(&value) - - if err == nil { - t.Fatalf("should fail to decode struct.") - } - - t.Logf("expected decode error. [e:%v]", err) - - result = Result{} - value = FieldTagStruct{} - err = json.Unmarshal([]byte(strMissingBarField), &result) - - if err != nil { - t.Fatalf("cannot unmarshal json string. [e:%v]", err) - } - - err = result.Decode(&value) - - if err == nil { - t.Fatalf("should fail to decode struct.") - } - - t.Logf("expected decode error. [e:%v]", err) -} - -type myTime time.Time - -func TestDecodeField(t *testing.T) { - jsonStr := `{ - "int": 1234, - "array": ["abcd", "efgh"], - "map": { - "key1": 5678, - "nested_map": { - "key2": "ijkl", - "key3": [{ - "key4": "mnop" - }, { - "key5": 9012 - }] - } - }, - "message_tags": { - "2": [ - { - "id": "4838901", - "name": "Foo Bar", - "type": "page" - }, - { - "id": "293450302", - "name": "Player Rocks", - "type": "page" - } - ] - }, - "nullStruct": { - "null": null - }, - "timestamp": "2015-01-03T11:15:01+0000", - "custom_timestamp": "2014-03-04T11:15:01+0000" - }` - - var result Result - var err error - var anInt int - var aString string - var aSlice []string - var subResults []Result - var aNull NullStruct = NullStruct{ - Null: &anInt, - } - var aTimestamp time.Time - var aCustomTimestamp myTime - - err = json.Unmarshal([]byte(jsonStr), &result) - - if err != nil { - t.Fatalf("invalid json string. [e:%v]", err) - } - - err = result.DecodeField("int", &anInt) - - if err != nil { - t.Fatalf("cannot decode int field. [e:%v]", err) - } - - if anInt != 1234 { - t.Fatalf("expected int value is 1234. [int:%v]", anInt) - } - - err = result.DecodeField("array.0", &aString) - - if err != nil { - t.Fatalf("cannot decode array.0 field. [e:%v]", err) - } - - if aString != "abcd" { - t.Fatalf("expected array.0 value is 'abcd'. [string:%v]", aString) - } - - err = result.DecodeField("array.1", &aString) - - if err != nil { - t.Fatalf("cannot decode array.1 field. [e:%v]", err) - } - - if aString != "efgh" { - t.Fatalf("expected array.1 value is 'abcd'. [string:%v]", aString) - } - - err = result.DecodeField("array.2", &aString) - - if err == nil { - t.Fatalf("array.2 doesn't exist. expect an error.") - } - - err = result.DecodeField("map.key1", &anInt) - - if err != nil { - t.Fatalf("cannot decode map.key1 field. [e:%v]", err) - } - - if anInt != 5678 { - t.Fatalf("expected map.key1 value is 5678. [int:%v]", anInt) - } - - err = result.DecodeField("map.nested_map.key2", &aString) - - if err != nil { - t.Fatalf("cannot decode map.nested_map.key2 field. [e:%v]", err) - } - - if aString != "ijkl" { - t.Fatalf("expected map.nested_map.key2 value is 'ijkl'. [string:%v]", aString) - } - - err = result.DecodeField("array", &aSlice) - - if err != nil { - t.Fatalf("cannot decode array field. [e:%v]", err) - } - - if len(aSlice) != 2 || aSlice[0] != "abcd" || aSlice[1] != "efgh" { - t.Fatalf("expected array value is ['abcd', 'efgh']. [slice:%v]", aSlice) - } - - err = result.DecodeField("map.nested_map.key3", &subResults) - - if err != nil { - t.Fatalf("cannot decode map.nested_map.key3 field. [e:%v]", err) - } - - if len(subResults) != 2 { - t.Fatalf("expected sub results len is 2. [len:%v] [results:%v]", subResults) - } - - err = subResults[0].DecodeField("key4", &aString) - - if err != nil { - t.Fatalf("cannot decode key4 field in sub result. [e:%v]", err) - } - - if aString != "mnop" { - t.Fatalf("expected map.nested_map.key2 value is 'mnop'. [string:%v]", aString) - } - - err = subResults[1].DecodeField("key5", &anInt) - - if err != nil { - t.Fatalf("cannot decode key5 field. [e:%v]", err) - } - - if anInt != 9012 { - t.Fatalf("expected key5 value is 9012. [int:%v]", anInt) - } - - err = result.DecodeField("message_tags.2.0.id", &aString) - - if err != nil { - t.Fatalf("cannot decode message_tags.2.0.id field. [e:%v]", err) - } - - if aString != "4838901" { - t.Fatalf("expected message_tags.2.0.id value is '4838901'. [string:%v]", aString) - } - - var messageTags MessageTags - err = result.DecodeField("message_tags", &messageTags) - - if err != nil { - t.Fatalf("cannot decode message_tags field. [e:%v]", err) - } - - if len(messageTags) != 1 { - t.Fatalf("expect messageTags have only 1 element. [len:%v]", len(messageTags)) - } - - aString = messageTags["2"][1].Id - - if aString != "293450302" { - t.Fatalf("expect messageTags.2.1.id value is '293450302'. [value:%v]", aString) - } - - err = result.DecodeField("nullStruct", &aNull) - - if err != nil { - t.Fatalf("cannot decode nullStruct field. [e:%v]", err) - } - - if aNull.Null != nil { - t.Fatalf("expect aNull.Null is reset to nil.") - } - - err = result.DecodeField("timestamp", &aTimestamp) - - if err != nil { - t.Fatalf("cannot decode timestamp field. [e:%v]", err) - } - - if !aTimestamp.Equal(time.Date(2015, time.January, 3, 11, 15, 1, 0, time.FixedZone("no-offset", 0))) { - t.Fatalf("expect aTimestamp date to be 2015-01-03 11:15:01 +0000 [value:%v]", aTimestamp.String()) - } - - err = result.DecodeField("custom_timestamp", &aCustomTimestamp) - - if err != nil { - t.Fatalf("cannot decode custom_timestamp field. [e:%v]", err) - } - - if !time.Time(aCustomTimestamp).Equal(time.Date(2014, time.March, 4, 11, 15, 1, 0, time.FixedZone("no-offset", 0))) { - t.Fatalf("expect aCustomTimestamp date to be 2014-03-04 11:15:01 +0000 [value:%v]", time.Time(aCustomTimestamp).String()) - } -} - -func TestGraphError(t *testing.T) { - res, err := Get("/me", Params{ - "access_token": "fake", - }) - - if err == nil { - t.Fatalf("facebook should return error for bad access token. [res:%v]", res) - } - - fbErr, ok := err.(*Error) - - if !ok { - t.Fatalf("error must be a *Error. [e:%v]", err) - } - - t.Logf("facebook error. [e:%v] [message:%v] [type:%v] [code:%v] [subcode:%v]", err, fbErr.Message, fbErr.Type, fbErr.Code, fbErr.ErrorSubcode) -} - -type FacebookFriend struct { - Id string `facebook:",required"` - Name string `facebook:",required"` -} - -type FacebookFriends struct { - Friends []FacebookFriend `facebook:"data,required"` -} - -func TestPagingResultDecode(t *testing.T) { - res := Result{ - "data": []interface{}{ - map[string]interface{}{ - "name": "friend 1", - "id": "1", - }, - map[string]interface{}{ - "name": "friend 2", - "id": "2", - }, - }, - "paging": map[string]interface{}{ - "next": "https://graph.facebook.com/...", - }, - } - paging, err := newPagingResult(nil, res) - if err != nil { - t.Fatalf("cannot create paging result. [e:%v]", err) - } - var friends FacebookFriends - if err := paging.Decode(&friends); err != nil { - t.Fatalf("cannot decode paging result. [e:%v]", err) - } - if len(friends.Friends) != 2 { - t.Fatalf("expect to have 2 friends. [len:%v]", len(friends.Friends)) - } - if friends.Friends[0].Name != "friend 1" { - t.Fatalf("expect name to be 'friend 1'. [name:%v]", friends.Friends[0].Name) - } - if friends.Friends[0].Id != "1" { - t.Fatalf("expect id to be '1'. [id:%v]", friends.Friends[0].Id) - } - if friends.Friends[1].Name != "friend 2" { - t.Fatalf("expect name to be 'friend 2'. [name:%v]", friends.Friends[1].Name) - } - if friends.Friends[1].Id != "2" { - t.Fatalf("expect id to be '2'. [id:%v]", friends.Friends[1].Id) - } -} - -func TestPagingResult(t *testing.T) { - if FB_TEST_VALID_ACCESS_TOKEN == "" { - t.Skipf("skip this case as we don't have a valid access token.") - } - - session := &Session{} - session.SetAccessToken(FB_TEST_VALID_ACCESS_TOKEN) - res, err := session.Get("/me/home", Params{ - "limit": 2, - }) - - if err != nil { - t.Fatalf("cannot get my home post. [e:%v]", err) - } - - paging, err := res.Paging(session) - - if err != nil { - t.Fatalf("cannot get paging information. [e:%v]", err) - } - - data := paging.Data() - - if len(data) != 2 { - t.Fatalf("expect to have only 2 post. [len:%v]", len(data)) - } - - t.Logf("result: %v", res) - t.Logf("previous: %v", paging.previous) - - noMore, err := paging.Previous() - - if err != nil { - t.Fatalf("cannot get paging information. [e:%v]", err) - } - - if !noMore { - t.Fatalf("should have no more post. %v", *paging.paging.Paging) - } - - noMore, err = paging.Next() - - if err != nil { - t.Fatalf("cannot get paging information. [e:%v]", err) - } - - data = paging.Data() - - if len(data) != 2 { - t.Fatalf("expect to have only 2 post. [len:%v]", len(data)) - } - - noMore, err = paging.Next() - - if err != nil { - t.Fatalf("cannot get paging information. [e:%v]", err) - } - - if len(paging.Data()) != 2 { - t.Fatalf("expect to have only 2 post. [len:%v]", len(paging.Data())) - } -} - -func TestDecodeLargeInteger(t *testing.T) { - bigIntegers := []int64{ - 1<<53 - 2, - 1<<53 - 1, - 1 << 53, - 1<<53 + 1, - 1<<53 + 2, - - 1<<54 - 2, - 1<<54 - 1, - 1 << 54, - 1<<54 + 1, - 1<<54 + 2, - - 1<<60 - 2, - 1<<60 - 1, - 1 << 60, - 1<<60 + 1, - 1<<60 + 2, - - 1<<63 - 2, - 1<<63 - 1, - - -(1<<53 - 2), - -(1<<53 - 1), - -(1 << 53), - -(1<<53 + 1), - -(1<<53 + 2), - - -(1<<54 - 2), - -(1<<54 - 1), - -(1 << 54), - -(1<<54 + 1), - -(1<<54 + 2), - - -(1<<60 - 2), - -(1<<60 - 1), - -(1 << 60), - -(1<<60 + 1), - -(1<<60 + 2), - - -(1<<53 - 2), - -(1<<63 - 1), - -(1 << 63), - } - jsonStr := `{ - "integers": [%v] - }` - - buf := &bytes.Buffer{} - - for _, v := range bigIntegers { - buf.WriteString(fmt.Sprintf("%v", v)) - buf.WriteRune(',') - } - - buf.WriteRune('0') - json := fmt.Sprintf(jsonStr, buf.String()) - - res, err := MakeResult([]byte(json)) - - if err != nil { - t.Fatalf("cannot make result on test json string. [e:%v]", err) - } - - var actualIntegers []int64 - err = res.DecodeField("integers", &actualIntegers) - - if err != nil { - t.Fatalf("cannot decode integers from json. [e:%v]", err) - } - - if len(actualIntegers) != len(bigIntegers)+1 { - t.Fatalf("count of decoded integers is not correct. [expected:%v] [actual:%v]", len(bigIntegers)+1, len(actualIntegers)) - } - - for k, _ := range bigIntegers { - if bigIntegers[k] != actualIntegers[k] { - t.Logf("expected integers: %v", bigIntegers) - t.Logf("actual integers: %v", actualIntegers) - t.Fatalf("a decoded integer is not expected. [expected:%v] [actual:%v]", bigIntegers[k], actualIntegers[k]) - } - } -} - -func TestInspectValidToken(t *testing.T) { - if FB_TEST_VALID_ACCESS_TOKEN == "" { - t.Skipf("skip this case as we don't have a valid access token.") - } - - session := testGlobalApp.Session(FB_TEST_VALID_ACCESS_TOKEN) - result, err := session.Inspect() - - if err != nil { - t.Fatalf("cannot inspect a valid access token. [e:%v]", err) - } - - var isValid bool - err = result.DecodeField("is_valid", &isValid) - - if err != nil { - t.Fatalf("cannot get 'is_valid' in inspect result. [e:%v]", err) - } - - if !isValid { - t.Fatalf("inspect result shows access token is invalid. why? [result:%v]", result) - } -} - -func TestInspectInvalidToken(t *testing.T) { - invalidToken := "CAACZA38ZAD8CoBAe2bDC6EdThnni3b56scyshKINjZARoC9ZAuEUTgYUkYnKdimqfA2ZAXcd2wLd7Rr8jLmMXTY9vqAhQGqObZBIUz1WwbqVoCsB3AAvLtwoWNhsxM76mK0eiJSLXHZCdPVpyhmtojvzXA7f69Bm6b5WZBBXia8iOpPZAUHTGp1UQLFMt47c7RqJTrYIl3VfAR0deN82GMFL2" - session := testGlobalApp.Session(invalidToken) - result, err := session.Inspect() - - if err == nil { - t.Fatalf("facebook should indicate it's an invalid token. why not? [result:%v]", result) - } - - if _, ok := err.(*Error); !ok { - t.Fatalf("inspect error should be a standard facebook error. why not? [e:%v]", err) - } - - isValid := true - err = result.DecodeField("is_valid", &isValid) - - if err != nil { - t.Fatalf("cannot get 'is_valid' in inspect result. [e:%v]", err) - } - - if isValid { - t.Fatalf("inspect result shows access token is valid. why? [result:%v]", result) - } -} - -func TestCamelCaseToUnderScore(t *testing.T) { - cases := map[string]string{ - "TestCase": "test_case", - "HTTPServer": "http_server", - "NoHTTPS": "no_https", - "Wi_thF": "wi_th_f", - "_AnotherTES_TCaseP": "_another_tes_t_case_p", - "ALL": "all", - "UserID": "user_id", - } - - for k, v := range cases { - str := camelCaseToUnderScore(k) - - if str != v { - t.Fatalf("wrong underscore string. [expect:%v] [actual:%v]", v, str) - } - } -} - -func TestMakeSliceResult(t *testing.T) { - jsonStr := `{ - "error": { - "message": "Invalid OAuth access token.", - "type": "OAuthException", - "code": 190 - } - }` - var res []Result - err := makeResult([]byte(jsonStr), &res) - - if err == nil { - t.Fatalf("makeResult must fail") - } - - fbErr, ok := err.(*Error) - - if !ok { - t.Fatalf("error must be a facebook error. [e:%v]", err) - } - - if fbErr.Code != 190 { - t.Fatalf("invalid facebook error. [e:%v]", fbErr.Error()) - } -} - -func TestMakeSliceResultWithNilElements(t *testing.T) { - jsonStr := `[ - null, - { - "foo": "bar" - }, - null - ]` - var res []Result - err := makeResult([]byte(jsonStr), &res) - - if err != nil { - t.Fatalf("fail to decode results. [e:%v]", err) - } - - if len(res) != 3 { - t.Fatalf("expect 3 elements in res. [res:%v]", res) - } - - if res[0] != nil || res[1] == nil || res[2] != nil { - t.Fatalf("decoded res is not expected. [res:%v]", res) - } - - if res[1]["foo"].(string) != "bar" { - t.Fatalf("decode res is not expected. [res:%v]", res) - } -} diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/misc.go b/Godeps/_workspace/src/github.com/huandu/facebook/misc.go deleted file mode 100644 index cdf7a9577..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/misc.go +++ /dev/null @@ -1,131 +0,0 @@ -// A facebook graph api client in go. -// https://github.com/huandu/facebook/ -// -// Copyright 2012 - 2015, Huan Du -// Licensed under the MIT license -// https://github.com/huandu/facebook/blob/master/LICENSE - -package facebook - -import ( - "bytes" - "io" - "unicode" - "unicode/utf8" -) - -func camelCaseToUnderScore(str string) string { - if len(str) == 0 { - return "" - } - - buf := &bytes.Buffer{} - var prev, r0, r1 rune - var size int - - r0 = '_' - - for len(str) > 0 { - prev = r0 - r0, size = utf8.DecodeRuneInString(str) - str = str[size:] - - switch { - case r0 == utf8.RuneError: - buf.WriteByte(byte(str[0])) - - case unicode.IsUpper(r0): - if prev != '_' { - buf.WriteRune('_') - } - - buf.WriteRune(unicode.ToLower(r0)) - - if len(str) == 0 { - break - } - - r0, size = utf8.DecodeRuneInString(str) - str = str[size:] - - if !unicode.IsUpper(r0) { - buf.WriteRune(r0) - break - } - - // find next non-upper-case character and insert `_` properly. - // it's designed to convert `HTTPServer` to `http_server`. - // if there are more than 2 adjacent upper case characters in a word, - // treat them as an abbreviation plus a normal word. - for len(str) > 0 { - r1 = r0 - r0, size = utf8.DecodeRuneInString(str) - str = str[size:] - - if r0 == utf8.RuneError { - buf.WriteRune(unicode.ToLower(r1)) - buf.WriteByte(byte(str[0])) - break - } - - if !unicode.IsUpper(r0) { - if r0 == '_' || r0 == ' ' || r0 == '-' { - r0 = '_' - - buf.WriteRune(unicode.ToLower(r1)) - } else { - buf.WriteRune('_') - buf.WriteRune(unicode.ToLower(r1)) - buf.WriteRune(r0) - } - - break - } - - buf.WriteRune(unicode.ToLower(r1)) - } - - if len(str) == 0 || r0 == '_' { - buf.WriteRune(unicode.ToLower(r0)) - break - } - - default: - if r0 == ' ' || r0 == '-' { - r0 = '_' - } - - buf.WriteRune(r0) - } - } - - return buf.String() -} - -// Returns error string. -func (e *Error) Error() string { - return e.Message -} - -// Creates a new binary data holder. -func Data(filename string, source io.Reader) *binaryData { - return &binaryData{ - Filename: filename, - Source: source, - } -} - -// Creates a binary file holder. -func File(filename, path string) *binaryFile { - return &binaryFile{ - Filename: filename, - } -} - -// Creates a binary file holder and specific a different path for reading. -func FileAlias(filename, path string) *binaryFile { - return &binaryFile{ - Filename: filename, - Path: path, - } -} diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/paging_result.go b/Godeps/_workspace/src/github.com/huandu/facebook/paging_result.go deleted file mode 100644 index f1eb9b7f1..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/paging_result.go +++ /dev/null @@ -1,146 +0,0 @@ -// A facebook graph api client in go. -// https://github.com/huandu/facebook/ -// -// Copyright 2012 - 2015, Huan Du -// Licensed under the MIT license -// https://github.com/huandu/facebook/blob/master/LICENSE - -package facebook - -import ( - "bytes" - "fmt" - "net/http" -) - -type pagingData struct { - Data []Result `facebook:",required"` - Paging *pagingNavigator -} - -type pagingNavigator struct { - Previous string - Next string -} - -func newPagingResult(session *Session, res Result) (*PagingResult, error) { - // quick check whether Result is a paging response. - if _, ok := res["data"]; !ok { - return nil, fmt.Errorf("current Result is not a paging response.") - } - - pr := &PagingResult{ - session: session, - } - paging := &pr.paging - err := res.Decode(paging) - - if err != nil { - return nil, err - } - - if paging.Paging != nil { - pr.previous = paging.Paging.Previous - pr.next = paging.Paging.Next - } - - return pr, nil -} - -// Get current data. -func (pr *PagingResult) Data() []Result { - return pr.paging.Data -} - -// Decodes the current full result to a struct. See Result#Decode. -func (pr *PagingResult) Decode(v interface{}) (err error) { - res := Result{ - "data": pr.Data(), - } - return res.Decode(v) -} - -// Read previous page. -func (pr *PagingResult) Previous() (noMore bool, err error) { - if !pr.HasPrevious() { - noMore = true - return - } - - return pr.navigate(&pr.previous) -} - -// Read next page. -func (pr *PagingResult) Next() (noMore bool, err error) { - if !pr.HasNext() { - noMore = true - return - } - - return pr.navigate(&pr.next) -} - -// Check whether there is previous page. -func (pr *PagingResult) HasPrevious() bool { - return pr.previous != "" -} - -// Check whether there is next page. -func (pr *PagingResult) HasNext() bool { - return pr.next != "" -} - -func (pr *PagingResult) navigate(url *string) (noMore bool, err error) { - var pagingUrl string - - // add session information in paging url. - params := Params{} - pr.session.prepareParams(params) - - if len(params) == 0 { - pagingUrl = *url - } else { - buf := &bytes.Buffer{} - buf.WriteString(*url) - buf.WriteRune('&') - params.Encode(buf) - - pagingUrl = buf.String() - } - - var request *http.Request - var res Result - - request, err = http.NewRequest("GET", pagingUrl, nil) - - if err != nil { - return - } - - res, err = pr.session.Request(request) - - if err != nil { - return - } - - if pr.paging.Paging != nil { - pr.paging.Paging.Next = "" - pr.paging.Paging.Previous = "" - } - paging := &pr.paging - err = res.Decode(paging) - - if err != nil { - return - } - - if paging.Paging == nil || len(paging.Data) == 0 { - *url = "" - noMore = true - } else { - pr.previous = paging.Paging.Previous - pr.next = paging.Paging.Next - } - - return -} diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/params.go b/Godeps/_workspace/src/github.com/huandu/facebook/params.go deleted file mode 100644 index bcce18a3a..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/params.go +++ /dev/null @@ -1,227 +0,0 @@ -// A facebook graph api client in go. -// https://github.com/huandu/facebook/ -// -// Copyright 2012 - 2015, Huan Du -// Licensed under the MIT license -// https://github.com/huandu/facebook/blob/master/LICENSE - -package facebook - -import ( - "encoding/json" - "io" - "mime/multipart" - "net/url" - "os" - "reflect" - "runtime" -) - -// Makes a new Params instance by given data. -// Data must be a struct or a map with string keys. -// MakeParams will change all struct field name to lower case name with underscore. -// e.g. "FooBar" will be changed to "foo_bar". -// -// Returns nil if data cannot be used to make a Params instance. -func MakeParams(data interface{}) (params Params) { - if p, ok := data.(Params); ok { - return p - } - - defer func() { - if r := recover(); r != nil { - if _, ok := r.(runtime.Error); ok { - panic(r) - } - - params = nil - } - }() - - params = makeParams(reflect.ValueOf(data)) - return -} - -func makeParams(value reflect.Value) (params Params) { - for value.Kind() == reflect.Ptr || value.Kind() == reflect.Interface { - value = value.Elem() - } - - // only map with string keys can be converted to Params - if value.Kind() == reflect.Map && value.Type().Key().Kind() == reflect.String { - params = Params{} - - for _, key := range value.MapKeys() { - params[key.String()] = value.MapIndex(key).Interface() - } - - return - } - - if value.Kind() != reflect.Struct { - return - } - - params = Params{} - num := value.NumField() - - for i := 0; i < num; i++ { - name := camelCaseToUnderScore(value.Type().Field(i).Name) - field := value.Field(i) - - for field.Kind() == reflect.Ptr { - field = field.Elem() - } - - switch field.Kind() { - case reflect.Chan, reflect.Func, reflect.UnsafePointer, reflect.Invalid: - // these types won't be marshalled in json. - params = nil - return - - default: - params[name] = field.Interface() - } - } - - return -} - -// Encodes params to query string. -// If map value is not a string, Encode uses json.Marshal() to convert value to string. -// -// Encode will panic if Params contains values that cannot be marshalled to json string. -func (params Params) Encode(writer io.Writer) (mime string, err error) { - if params == nil || len(params) == 0 { - mime = _MIME_FORM_URLENCODED - return - } - - // check whether params contains any binary data. - hasBinary := false - - for _, v := range params { - typ := reflect.TypeOf(v) - - if typ == typeOfPointerToBinaryData || typ == typeOfPointerToBinaryFile { - hasBinary = true - break - } - } - - if hasBinary { - return params.encodeMultipartForm(writer) - } - - return params.encodeFormUrlEncoded(writer) -} - -func (params Params) encodeFormUrlEncoded(writer io.Writer) (mime string, err error) { - var jsonStr []byte - written := false - - for k, v := range params { - if written { - io.WriteString(writer, "&") - } - - io.WriteString(writer, url.QueryEscape(k)) - io.WriteString(writer, "=") - - if reflect.TypeOf(v).Kind() == reflect.String { - io.WriteString(writer, url.QueryEscape(reflect.ValueOf(v).String())) - } else { - jsonStr, err = json.Marshal(v) - - if err != nil { - return - } - - io.WriteString(writer, url.QueryEscape(string(jsonStr))) - } - - written = true - } - - mime = _MIME_FORM_URLENCODED - return -} - -func (params Params) encodeMultipartForm(writer io.Writer) (mime string, err error) { - w := multipart.NewWriter(writer) - defer func() { - w.Close() - mime = w.FormDataContentType() - }() - - for k, v := range params { - switch value := v.(type) { - case *binaryData: - var dst io.Writer - dst, err = w.CreateFormFile(k, value.Filename) - - if err != nil { - return - } - - _, err = io.Copy(dst, value.Source) - - if err != nil { - return - } - - case *binaryFile: - var dst io.Writer - var file *os.File - var path string - - dst, err = w.CreateFormFile(k, value.Filename) - - if err != nil { - return - } - - if value.Path == "" { - path = value.Filename - } else { - path = value.Path - } - - file, err = os.Open(path) - - if err != nil { - return - } - - _, err = io.Copy(dst, file) - - if err != nil { - return - } - - default: - var dst io.Writer - var jsonStr []byte - - dst, err = w.CreateFormField(k) - - if reflect.TypeOf(v).Kind() == reflect.String { - io.WriteString(dst, reflect.ValueOf(v).String()) - } else { - jsonStr, err = json.Marshal(v) - - if err != nil { - return - } - - _, err = dst.Write(jsonStr) - - if err != nil { - return - } - } - } - } - - return -} diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/result.go b/Godeps/_workspace/src/github.com/huandu/facebook/result.go deleted file mode 100644 index fd760be4e..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/result.go +++ /dev/null @@ -1,1097 +0,0 @@ -// A facebook graph api client in go. -// https://github.com/huandu/facebook/ -// -// Copyright 2012 - 2015, Huan Du -// Licensed under the MIT license -// https://github.com/huandu/facebook/blob/master/LICENSE - -package facebook - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "reflect" - "runtime" - "strconv" - "strings" - "time" -) - -// MakeResult makes a Result from facebook Graph API response. -func MakeResult(jsonBytes []byte) (Result, error) { - res := Result{} - err := makeResult(jsonBytes, &res) - - if err != nil { - return nil, err - } - - // facebook may return an error - return res, res.Err() -} - -func makeResult(jsonBytes []byte, res interface{}) error { - if bytes.Equal(jsonBytes, facebookSuccessJsonBytes) { - return nil - } - - jsonReader := bytes.NewReader(jsonBytes) - dec := json.NewDecoder(jsonReader) - - // issue #19 - // app_scoped user_id in a post-Facebook graph 2.0 would exceeds 2^53. - // use Number instead of float64 to avoid precision lost. - dec.UseNumber() - - err := dec.Decode(res) - - if err != nil { - typ := reflect.TypeOf(res) - - if typ != nil { - // if res is a slice, jsonBytes may be a facebook error. - // try to decode it as Error. - kind := typ.Kind() - - if kind == reflect.Ptr { - typ = typ.Elem() - kind = typ.Kind() - } - - if kind == reflect.Array || kind == reflect.Slice { - var errRes Result - err = makeResult(jsonBytes, &errRes) - - if err != nil { - return err - } - - err = errRes.Err() - - if err == nil { - err = fmt.Errorf("cannot format facebook response. expect an array but get an object.") - } - - return err - } - } - - return fmt.Errorf("cannot format facebook response. %v", err) - } - - return nil -} - -// Get gets a field from Result. -// -// Field can be a dot separated string. -// If field name is "a.b.c", it will try to return value of res["a"]["b"]["c"]. -// -// To access array items, use index value in field. -// For instance, field "a.0.c" means to read res["a"][0]["c"]. -// -// It doesn't work with Result which has a key contains dot. Use GetField in this case. -// -// Returns nil if field doesn't exist. -func (res Result) Get(field string) interface{} { - if field == "" { - return res - } - - f := strings.Split(field, ".") - return res.get(f) -} - -// GetField gets a field from Result. -// -// Arguments are treated as keys to access value in Result. -// If arguments are "a","b","c", it will try to return value of res["a"]["b"]["c"]. -// -// To access array items, use index value as a string. -// For instance, args of "a", "0", "c" means to read res["a"][0]["c"]. -// -// Returns nil if field doesn't exist. -func (res Result) GetField(fields ...string) interface{} { - if len(fields) == 0 { - return res - } - - return res.get(fields) -} - -func (res Result) get(fields []string) interface{} { - v, ok := res[fields[0]] - - if !ok || v == nil { - return nil - } - - if len(fields) == 1 { - return v - } - - value := getValueField(reflect.ValueOf(v), fields[1:]) - - if !value.IsValid() { - return nil - } - - return value.Interface() -} - -func getValueField(value reflect.Value, fields []string) reflect.Value { - valueType := value.Type() - kind := valueType.Kind() - field := fields[0] - - switch kind { - case reflect.Array, reflect.Slice: - // field must be a number. - n, err := strconv.ParseUint(field, 10, 0) - - if err != nil { - return reflect.Value{} - } - - if n >= uint64(value.Len()) { - return reflect.Value{} - } - - // work around a reflect package pitfall. - value = reflect.ValueOf(value.Index(int(n)).Interface()) - - case reflect.Map: - v := value.MapIndex(reflect.ValueOf(field)) - - if !v.IsValid() { - return v - } - - // get real value type. - value = reflect.ValueOf(v.Interface()) - - default: - return reflect.Value{} - } - - if len(fields) == 1 { - return value - } - - return getValueField(value, fields[1:]) -} - -// Decode decodes full result to a struct. -// It only decodes fields defined in the struct. -// -// As all facebook response fields are lower case strings, -// Decode will convert all camel-case field names to lower case string. -// e.g. field name "FooBar" will be converted to "foo_bar". -// The side effect is that if a struct has 2 fields with only capital -// differences, decoder will map these fields to a same result value. -// -// If a field is missing in the result, Decode keeps it unchanged by default. -// -// Decode can read struct field tag value to change default behavior. -// -// Examples: -// -// type Foo struct { -// // "id" must exist in response. note the leading comma. -// Id string `facebook:",required"` -// -// // use "name" as field name in response. -// TheName string `facebook:"name"` -// } -// -// To change default behavior, set a struct tag `facebook:",required"` to fields -// should not be missing. -// -// Returns error if v is not a struct or any required v field name absents in res. -func (res Result) Decode(v interface{}) (err error) { - defer func() { - if r := recover(); r != nil { - if _, ok := r.(runtime.Error); ok { - panic(r) - } - - err = r.(error) - } - }() - - err = res.decode(reflect.ValueOf(v), "") - return -} - -// DecodeField decodes a field of result to any type, including struct. -// Field name format is defined in Result.Get(). -// -// More details about decoding struct see Result.Decode(). -func (res Result) DecodeField(field string, v interface{}) error { - f := res.Get(field) - - if f == nil { - return fmt.Errorf("field '%v' doesn't exist in result.", field) - } - - return decodeField(reflect.ValueOf(f), reflect.ValueOf(v), field) -} - -// Err returns an error if Result is a Graph API error. -// -// The returned error can be converted to Error by type assertion. -// err := res.Err() -// if err != nil { -// if e, ok := err.(*Error); ok { -// // read more details in e.Message, e.Code and e.Type -// } -// } -// -// For more information about Graph API Errors, see -// https://developers.facebook.com/docs/reference/api/errors/ -func (res Result) Err() error { - var err Error - e := res.DecodeField("error", &err) - - // no "error" in result. result is not an error. - if e != nil { - return nil - } - - // code may be missing in error. - // assign a non-zero value to it. - if err.Code == 0 { - err.Code = ERROR_CODE_UNKNOWN - } - - return &err -} - -// Paging creates a PagingResult for this Result and -// returns error if the Result cannot be used for paging. -// -// Facebook uses following JSON structure to response paging information. -// If "data" doesn't present in Result, Paging will return error. -// { -// "data": [...], -// "paging": { -// "previous": "https://graph.facebook.com/...", -// "next": "https://graph.facebook.com/..." -// } -// } -func (res Result) Paging(session *Session) (*PagingResult, error) { - return newPagingResult(session, res) -} - -// Batch creates a BatchResult for this result and -// returns error if the Result is not a batch api response. -// -// See BatchApi document for a sample usage. -func (res Result) Batch() (*BatchResult, error) { - return newBatchResult(res) -} - -// DebugInfo creates a DebugInfo for this result if this result -// has "__debug__" key. -func (res Result) DebugInfo() *DebugInfo { - var info Result - err := res.DecodeField(debugInfoKey, &info) - - if err != nil { - return nil - } - - debugInfo := &DebugInfo{} - info.DecodeField("messages", &debugInfo.Messages) - - if proto, ok := info[debugProtoKey]; ok { - if v, ok := proto.(string); ok { - debugInfo.Proto = v - } - } - - if header, ok := info[debugHeaderKey]; ok { - if v, ok := header.(http.Header); ok { - debugInfo.Header = v - - debugInfo.FacebookApiVersion = v.Get(facebookApiVersionHeader) - debugInfo.FacebookDebug = v.Get(facebookDebugHeader) - debugInfo.FacebookRev = v.Get(facebookRevHeader) - } - } - - return debugInfo -} - -func (res Result) decode(v reflect.Value, fullName string) error { - for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { - v = v.Elem() - } - - if v.Kind() != reflect.Struct { - return fmt.Errorf("output value must be a struct.") - } - - if !v.CanSet() { - return fmt.Errorf("output value cannot be set.") - } - - if fullName != "" { - fullName += "." - } - - var field reflect.Value - var name, fbTag string - var val interface{} - var ok, required bool - var err error - - vType := v.Type() - num := vType.NumField() - - for i := 0; i < num; i++ { - name = "" - required = false - field = v.Field(i) - fbTag = vType.Field(i).Tag.Get("facebook") - - // parse struct field tag - if fbTag != "" { - index := strings.IndexRune(fbTag, ',') - - if index == -1 { - name = fbTag - } else { - name = fbTag[:index] - - if fbTag[index:] == ",required" { - required = true - } - } - } - - if name == "" { - name = camelCaseToUnderScore(v.Type().Field(i).Name) - } - - val, ok = res[name] - - if !ok { - // check whether the field is required. if so, report error. - if required { - return fmt.Errorf("cannot find field '%v%v' in result.", fullName, name) - } - - continue - } - - if err = decodeField(reflect.ValueOf(val), field, fmt.Sprintf("%v%v", fullName, name)); err != nil { - return err - } - } - - return nil -} - -func decodeField(val reflect.Value, field reflect.Value, fullName string) error { - if field.Kind() == reflect.Ptr { - // reset Ptr field if val is nil. - if !val.IsValid() { - if !field.IsNil() && field.CanSet() { - field.Set(reflect.Zero(field.Type())) - } - - return nil - } - - if field.IsNil() { - field.Set(reflect.New(field.Type().Elem())) - } - - field = field.Elem() - } - - if !field.CanSet() { - return fmt.Errorf("field '%v' cannot be decoded. make sure the output value is able to be set.", fullName) - } - - if !val.IsValid() { - return fmt.Errorf("field '%v' is not a pointer. cannot assign nil to it.", fullName) - } - - kind := field.Kind() - valType := val.Type() - - switch kind { - case reflect.Bool: - if valType.Kind() == reflect.Bool { - field.SetBool(val.Bool()) - } else { - return fmt.Errorf("field '%v' is not a bool in result.", fullName) - } - - case reflect.Int8: - switch valType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n := val.Int() - - if n < -128 || n > 127 { - return fmt.Errorf("field '%v' value exceeds the range of int8.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n := val.Uint() - - if n > 127 { - return fmt.Errorf("field '%v' value exceeds the range of int8.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.Float32, reflect.Float64: - n := val.Float() - - if n < -128 || n > 127 { - return fmt.Errorf("field '%v' value exceeds the range of int8.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.String: - // only json.Number is allowed to be used as number. - if val.Type() != typeOfJSONNumber { - return fmt.Errorf("field '%v' value is string, not a number.", fullName) - } - - n, err := strconv.ParseInt(val.String(), 10, 8) - - if err != nil { - return fmt.Errorf("field '%v' value is not a valid int8.", fullName) - } - - field.SetInt(n) - - default: - return fmt.Errorf("field '%v' is not an integer in result.", fullName) - } - - case reflect.Int16: - switch valType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n := val.Int() - - if n < -32768 || n > 32767 { - return fmt.Errorf("field '%v' value exceeds the range of int16.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n := val.Uint() - - if n > 32767 { - return fmt.Errorf("field '%v' value exceeds the range of int16.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.Float32, reflect.Float64: - n := val.Float() - - if n < -32768 || n > 32767 { - return fmt.Errorf("field '%v' value exceeds the range of int16.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.String: - // only json.Number is allowed to be used as number. - if val.Type() != typeOfJSONNumber { - return fmt.Errorf("field '%v' value is string, not a number.", fullName) - } - - n, err := strconv.ParseInt(val.String(), 10, 16) - - if err != nil { - return fmt.Errorf("field '%v' value is not a valid int16.", fullName) - } - - field.SetInt(n) - - default: - return fmt.Errorf("field '%v' is not an integer in result.", fullName) - } - - case reflect.Int32: - switch valType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n := val.Int() - - if n < -2147483648 || n > 2147483647 { - return fmt.Errorf("field '%v' value exceeds the range of int32.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n := val.Uint() - - if n > 2147483647 { - return fmt.Errorf("field '%v' value exceeds the range of int32.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.Float32, reflect.Float64: - n := val.Float() - - if n < -2147483648 || n > 2147483647 { - return fmt.Errorf("field '%v' value exceeds the range of int32.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.String: - // only json.Number is allowed to be used as number. - if val.Type() != typeOfJSONNumber { - return fmt.Errorf("field '%v' value is string, not a number.", fullName) - } - - n, err := strconv.ParseInt(val.String(), 10, 32) - - if err != nil { - return fmt.Errorf("field '%v' value is not a valid int32.", fullName) - } - - field.SetInt(n) - - default: - return fmt.Errorf("field '%v' is not an integer in result.", fullName) - } - - case reflect.Int64: - switch valType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n := val.Int() - field.SetInt(n) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n := val.Uint() - - if n > 9223372036854775807 { - return fmt.Errorf("field '%v' value exceeds the range of int64.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.Float32, reflect.Float64: - n := val.Float() - - if n < -9223372036854775808 || n > 9223372036854775807 { - return fmt.Errorf("field '%v' value exceeds the range of int64.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.String: - // only json.Number is allowed to be used as number. - if val.Type() != typeOfJSONNumber { - return fmt.Errorf("field '%v' value is string, not a number.", fullName) - } - - n, err := strconv.ParseInt(val.String(), 10, 64) - - if err != nil { - return fmt.Errorf("field '%v' value is not a valid int64.", fullName) - } - - field.SetInt(n) - - default: - return fmt.Errorf("field '%v' is not an integer in result.", fullName) - } - - case reflect.Int: - bits := field.Type().Bits() - - var min, max int64 - - if bits == 32 { - min = -2147483648 - max = 2147483647 - } else if bits == 64 { - min = -9223372036854775808 - max = 9223372036854775807 - } - - switch valType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n := val.Int() - - if n < min || n > max { - return fmt.Errorf("field '%v' value exceeds the range of int.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n := val.Uint() - - if n > uint64(max) { - return fmt.Errorf("field '%v' value exceeds the range of int.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.Float32, reflect.Float64: - n := val.Float() - - if n < float64(min) || n > float64(max) { - return fmt.Errorf("field '%v' value exceeds the range of int.", fullName) - } - - field.SetInt(int64(n)) - - case reflect.String: - // only json.Number is allowed to be used as number. - if val.Type() != typeOfJSONNumber { - return fmt.Errorf("field '%v' value is string, not a number.", fullName) - } - - n, err := strconv.ParseInt(val.String(), 10, bits) - - if err != nil { - return fmt.Errorf("field '%v' value is not a valid int%v.", fullName, bits) - } - - field.SetInt(n) - - default: - return fmt.Errorf("field '%v' is not an integer in result.", fullName) - } - - case reflect.Uint8: - switch valType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n := val.Int() - - if n < 0 || n > 0xFF { - return fmt.Errorf("field '%v' value exceeds the range of uint8.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n := val.Uint() - - if n > 0xFF { - return fmt.Errorf("field '%v' value exceeds the range of uint8.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.Float32, reflect.Float64: - n := val.Float() - - if n < 0 || n > 0xFF { - return fmt.Errorf("field '%v' value exceeds the range of uint8.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.String: - // only json.Number is allowed to be used as number. - if val.Type() != typeOfJSONNumber { - return fmt.Errorf("field '%v' value is string, not a number.", fullName) - } - - n, err := strconv.ParseUint(val.String(), 10, 8) - - if err != nil { - return fmt.Errorf("field '%v' value is not a valid uint8.", fullName) - } - - field.SetUint(n) - - default: - return fmt.Errorf("field '%v' is not an integer in result.", fullName) - } - - case reflect.Uint16: - switch valType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n := val.Int() - - if n < 0 || n > 0xFFFF { - return fmt.Errorf("field '%v' value exceeds the range of uint16.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n := val.Uint() - - if n > 0xFFFF { - return fmt.Errorf("field '%v' value exceeds the range of uint16.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.Float32, reflect.Float64: - n := val.Float() - - if n < 0 || n > 0xFFFF { - return fmt.Errorf("field '%v' value exceeds the range of uint16.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.String: - // only json.Number is allowed to be used as number. - if val.Type() != typeOfJSONNumber { - return fmt.Errorf("field '%v' value is string, not a number.", fullName) - } - - n, err := strconv.ParseUint(val.String(), 10, 16) - - if err != nil { - return fmt.Errorf("field '%v' value is not a valid uint16.", fullName) - } - - field.SetUint(n) - - default: - return fmt.Errorf("field '%v' is not an integer in result.", fullName) - } - - case reflect.Uint32: - switch valType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n := val.Int() - - if n < 0 || n > 0xFFFFFFFF { - return fmt.Errorf("field '%v' value exceeds the range of uint32.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n := val.Uint() - - if n > 0xFFFFFFFF { - return fmt.Errorf("field '%v' value exceeds the range of uint32.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.Float32, reflect.Float64: - n := val.Float() - - if n < 0 || n > 0xFFFFFFFF { - return fmt.Errorf("field '%v' value exceeds the range of uint32.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.String: - // only json.Number is allowed to be used as number. - if val.Type() != typeOfJSONNumber { - return fmt.Errorf("field '%v' value is string, not a number.", fullName) - } - - n, err := strconv.ParseUint(val.String(), 10, 32) - - if err != nil { - return fmt.Errorf("field '%v' value is not a valid uint32.", fullName) - } - - field.SetUint(n) - - default: - return fmt.Errorf("field '%v' is not an integer in result.", fullName) - } - - case reflect.Uint64: - switch valType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n := val.Int() - - if n < 0 { - return fmt.Errorf("field '%v' value exceeds the range of uint64.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n := val.Uint() - field.SetUint(n) - - case reflect.Float32, reflect.Float64: - n := val.Float() - - if n < 0 || n > 0xFFFFFFFFFFFFFFFF { - return fmt.Errorf("field '%v' value exceeds the range of uint64.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.String: - // only json.Number is allowed to be used as number. - if val.Type() != typeOfJSONNumber { - return fmt.Errorf("field '%v' value is string, not a number.", fullName) - } - - n, err := strconv.ParseUint(val.String(), 10, 64) - - if err != nil { - return fmt.Errorf("field '%v' value is not a valid uint64.", fullName) - } - - field.SetUint(n) - - default: - return fmt.Errorf("field '%v' is not an integer in result.", fullName) - } - - case reflect.Uint: - bits := field.Type().Bits() - - var max uint64 - - if bits == 32 { - max = 0xFFFFFFFF - } else if bits == 64 { - max = 0xFFFFFFFFFFFFFFFF - } - - switch valType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n := val.Int() - - if n < 0 || uint64(n) > max { - return fmt.Errorf("field '%v' value exceeds the range of uint.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n := val.Uint() - - if n > max { - return fmt.Errorf("field '%v' value exceeds the range of uint.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.Float32, reflect.Float64: - n := val.Float() - - if n < 0 || n > float64(max) { - return fmt.Errorf("field '%v' value exceeds the range of uint.", fullName) - } - - field.SetUint(uint64(n)) - - case reflect.String: - // only json.Number is allowed to be used as number. - if val.Type() != typeOfJSONNumber { - return fmt.Errorf("field '%v' value is string, not a number.", fullName) - } - - n, err := strconv.ParseUint(val.String(), 10, bits) - - if err != nil { - return fmt.Errorf("field '%v' value is not a valid uint%v.", fullName, bits) - } - - field.SetUint(n) - - default: - return fmt.Errorf("field '%v' is not an integer in result.", fullName) - } - - case reflect.Float32, reflect.Float64: - switch valType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n := val.Int() - field.SetFloat(float64(n)) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - n := val.Uint() - field.SetFloat(float64(n)) - - case reflect.Float32, reflect.Float64: - n := val.Float() - field.SetFloat(n) - - case reflect.String: - // only json.Number is allowed to be used as number. - if val.Type() != typeOfJSONNumber { - return fmt.Errorf("field '%v' value is string, not a number.", fullName) - } - - n, err := strconv.ParseFloat(val.String(), 64) - - if err != nil { - return fmt.Errorf("field '%v' is not a valid float64.", fullName) - } - - field.SetFloat(n) - - default: - return fmt.Errorf("field '%v' is not a float in result.", fullName) - } - - case reflect.String: - if valType.Kind() != reflect.String { - return fmt.Errorf("field '%v' is not a string in result.", fullName) - } - - field.SetString(val.String()) - - case reflect.Struct: - if field.Type().ConvertibleTo(typeOfTime) { - if valType.Kind() != reflect.String { - return fmt.Errorf("field '%v' is not a string in result.", fullName) - } - - t, err := time.Parse("2006-01-02T15:04:05-0700", val.String()) - - if err != nil { - return fmt.Errorf("field '%v' was unable to parse the time string '%s'.", fullName, val.String()) - } - - matchedType := reflect.ValueOf(t).Convert(field.Type()) - field.Set(matchedType) - return nil - } - - if valType.Kind() != reflect.Map || valType.Key().Kind() != reflect.String { - return fmt.Errorf("field '%v' is not a json object in result.", fullName) - } - - // safe convert val to Result. type assertion doesn't work in this case. - var r Result - reflect.ValueOf(&r).Elem().Set(val) - - if err := r.decode(field, fullName); err != nil { - return err - } - - case reflect.Map: - if valType.Kind() != reflect.Map || valType.Key().Kind() != reflect.String { - return fmt.Errorf("field '%v' is not a json object in result.", fullName) - } - - // map key must be string - if field.Type().Key().Kind() != reflect.String { - return fmt.Errorf("field '%v' in struct is a map with non-string key type. it's not allowed.", fullName) - } - - var needAddr bool - valueType := field.Type().Elem() - - // shortcut for map[string]interface{}. - if valueType.Kind() == reflect.Interface { - field.Set(val) - break - } - - if field.IsNil() { - field.Set(reflect.MakeMap(field.Type())) - } - - if valueType.Kind() == reflect.Ptr { - valueType = valueType.Elem() - needAddr = true - } - - for _, key := range val.MapKeys() { - // val.MapIndex(key) returns a Value with wrong type. - // use following trick to get correct Value. - value := reflect.ValueOf(val.MapIndex(key).Interface()) - newValue := reflect.New(valueType) - - if err := decodeField(value, newValue, fmt.Sprintf("%v.%v", fullName, key)); err != nil { - return err - } - - if needAddr { - field.SetMapIndex(key, newValue) - } else { - field.SetMapIndex(key, newValue.Elem()) - } - } - - case reflect.Slice, reflect.Array: - if valType.Kind() != reflect.Slice && valType.Kind() != reflect.Array { - return fmt.Errorf("field '%v' is not a json array in result.", fullName) - } - - valLen := val.Len() - - if kind == reflect.Array { - if field.Len() < valLen { - return fmt.Errorf("cannot copy all field '%v' values to struct. expected len is %v. actual len is %v.", - fullName, field.Len(), valLen) - } - } - - var slc reflect.Value - var needAddr bool - - valueType := field.Type().Elem() - - // shortcut for array of interface - if valueType.Kind() == reflect.Interface { - if kind == reflect.Array { - for i := 0; i < valLen; i++ { - field.Index(i).Set(val.Index(i)) - } - } else { // kind is slice - field.Set(val) - } - - break - } - - if kind == reflect.Array { - slc = field.Slice(0, valLen) - } else { - // kind is slice - slc = reflect.MakeSlice(field.Type(), valLen, valLen) - field.Set(slc) - } - - if valueType.Kind() == reflect.Ptr { - needAddr = true - valueType = valueType.Elem() - } - - for i := 0; i < valLen; i++ { - // val.Index(i) returns a Value with wrong type. - // use following trick to get correct Value. - valIndexValue := reflect.ValueOf(val.Index(i).Interface()) - newValue := reflect.New(valueType) - - if err := decodeField(valIndexValue, newValue, fmt.Sprintf("%v.%v", fullName, i)); err != nil { - return err - } - - if needAddr { - slc.Index(i).Set(newValue) - } else { - slc.Index(i).Set(newValue.Elem()) - } - } - - default: - return fmt.Errorf("field '%v' in struct uses unsupported type '%v'.", fullName, kind) - } - - return nil -} diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/session.go b/Godeps/_workspace/src/github.com/huandu/facebook/session.go deleted file mode 100644 index 95b4ad8d2..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/session.go +++ /dev/null @@ -1,667 +0,0 @@ -// A facebook graph api client in go. -// https://github.com/huandu/facebook/ -// -// Copyright 2012 - 2015, Huan Du -// Licensed under the MIT license -// https://github.com/huandu/facebook/blob/master/LICENSE - -package facebook - -import ( - "bytes" - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "encoding/hex" - "fmt" - "io" - "net/http" - "net/url" - "strings" -) - -// Makes a facebook graph api call. -// -// If session access token is set, "access_token" in params will be set to the token value. -// -// Returns facebook graph api call result. -// If facebook returns error in response, returns error details in res and set err. -func (session *Session) Api(path string, method Method, params Params) (Result, error) { - return session.graph(path, method, params) -} - -// Get is a short hand of Api(path, GET, params). -func (session *Session) Get(path string, params Params) (Result, error) { - return session.Api(path, GET, params) -} - -// Post is a short hand of Api(path, POST, params). -func (session *Session) Post(path string, params Params) (Result, error) { - return session.Api(path, POST, params) -} - -// Delete is a short hand of Api(path, DELETE, params). -func (session *Session) Delete(path string, params Params) (Result, error) { - return session.Api(path, DELETE, params) -} - -// Put is a short hand of Api(path, PUT, params). -func (session *Session) Put(path string, params Params) (Result, error) { - return session.Api(path, PUT, params) -} - -// Makes a batch call. Each params represent a single facebook graph api call. -// -// BatchApi supports most kinds of batch calls defines in facebook batch api document, -// except uploading binary data. Use Batch to upload binary data. -// -// If session access token is set, the token will be used in batch api call. -// -// Returns an array of batch call result on success. -// -// Facebook document: https://developers.facebook.com/docs/graph-api/making-multiple-requests -func (session *Session) BatchApi(params ...Params) ([]Result, error) { - return session.Batch(nil, params...) -} - -// Makes a batch facebook graph api call. -// Batch is designed for more advanced usage including uploading binary files. -// -// If session access token is set, "access_token" in batchParams will be set to the token value. -// -// Facebook document: https://developers.facebook.com/docs/graph-api/making-multiple-requests -func (session *Session) Batch(batchParams Params, params ...Params) ([]Result, error) { - return session.graphBatch(batchParams, params...) -} - -// Makes a FQL query. -// Returns a slice of Result. If there is no query result, the result is nil. -// -// Facebook document: https://developers.facebook.com/docs/technical-guides/fql#query -func (session *Session) FQL(query string) ([]Result, error) { - res, err := session.graphFQL(Params{ - "q": query, - }) - - if err != nil { - return nil, err - } - - // query result is stored in "data" field. - var data []Result - err = res.DecodeField("data", &data) - - if err != nil { - return nil, err - } - - return data, nil -} - -// Makes a multi FQL query. -// Returns a parsed Result. The key is the multi query key, and the value is the query result. -// -// Here is a multi-query sample. -// -// res, _ := session.MultiFQL(Params{ -// "query1": "SELECT name FROM user WHERE uid = me()", -// "query2": "SELECT uid1, uid2 FROM friend WHERE uid1 = me()", -// }) -// -// // Get query results from response. -// var query1, query2 []Result -// res.DecodeField("query1", &query1) -// res.DecodeField("query2", &query2) -// -// Facebook document: https://developers.facebook.com/docs/technical-guides/fql#multi -func (session *Session) MultiFQL(queries Params) (Result, error) { - res, err := session.graphFQL(Params{ - "q": queries, - }) - - if err != nil { - return res, err - } - - // query result is stored in "data" field. - var data []Result - err = res.DecodeField("data", &data) - - if err != nil { - return nil, err - } - - if data == nil { - return nil, fmt.Errorf("multi-fql result is not found.") - } - - // Multi-fql data structure is: - // { - // "data": [ - // { - // "name": "query1", - // "fql_result_set": [ - // {...}, {...}, ... - // ] - // }, - // { - // "name": "query2", - // "fql_result_set": [ - // {...}, {...}, ... - // ] - // }, - // ... - // ] - // } - // - // Parse the structure to following go map. - // { - // "query1": [ - // // Come from field "fql_result_set". - // {...}, {...}, ... - // ], - // "query2": [ - // {...}, {...}, ... - // ], - // ... - // } - var name string - var apiResponse interface{} - var ok bool - result := Result{} - - for k, v := range data { - err = v.DecodeField("name", &name) - - if err != nil { - return nil, fmt.Errorf("missing required field 'name' in multi-query data.%v. %v", k, err) - } - - apiResponse, ok = v["fql_result_set"] - - if !ok { - return nil, fmt.Errorf("missing required field 'fql_result_set' in multi-query data.%v.", k) - } - - result[name] = apiResponse - } - - return result, nil -} - -// Makes an arbitrary HTTP request. -// It expects server responses a facebook Graph API response. -// request, _ := http.NewRequest("https://graph.facebook.com/538744468", "GET", nil) -// res, err := session.Request(request) -// fmt.Println(res["gender"]) // get "male" -func (session *Session) Request(request *http.Request) (res Result, err error) { - var response *http.Response - var data []byte - - response, data, err = session.sendRequest(request) - - if err != nil { - return - } - - res, err = MakeResult(data) - session.addDebugInfo(res, response) - - if res != nil { - err = res.Err() - } - - return -} - -// Gets current user id from access token. -// -// Returns error if access token is not set or invalid. -// -// It's a standard way to validate a facebook access token. -func (session *Session) User() (id string, err error) { - if session.id != "" { - id = session.id - return - } - - if session.accessToken == "" { - err = fmt.Errorf("access token is not set.") - return - } - - var result Result - result, err = session.Api("/me", GET, Params{"fields": "id"}) - - if err != nil { - return - } - - err = result.DecodeField("id", &id) - - if err != nil { - return - } - - return -} - -// Validates Session access token. -// Returns nil if access token is valid. -func (session *Session) Validate() (err error) { - if session.accessToken == "" { - err = fmt.Errorf("access token is not set.") - return - } - - var result Result - result, err = session.Api("/me", GET, Params{"fields": "id"}) - - if err != nil { - return - } - - if f := result.Get("id"); f == nil { - err = fmt.Errorf("invalid access token.") - return - } - - return -} - -// Inspect Session access token. -// Returns JSON array containing data about the inspected token. -// See https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.2#checktoken -func (session *Session) Inspect() (result Result, err error) { - if session.accessToken == "" { - err = fmt.Errorf("access token is not set.") - return - } - - if session.app == nil { - err = fmt.Errorf("cannot inspect access token without binding an app.") - return - } - - appAccessToken := session.app.AppAccessToken() - - if appAccessToken == "" { - err = fmt.Errorf("app access token is not set.") - return - } - - result, err = session.Api("/debug_token", GET, Params{ - "input_token": session.accessToken, - "access_token": appAccessToken, - }) - - if err != nil { - return - } - - // facebook stores everything, including error, inside result["data"]. - // make sure that result["data"] exists and doesn't contain error. - if _, ok := result["data"]; !ok { - err = fmt.Errorf("facebook inspect api returns unexpected result.") - return - } - - var data Result - result.DecodeField("data", &data) - result = data - err = result.Err() - return -} - -// Gets current access token. -func (session *Session) AccessToken() string { - return session.accessToken -} - -// Sets a new access token. -func (session *Session) SetAccessToken(token string) { - if token != session.accessToken { - session.id = "" - session.accessToken = token - session.appsecretProof = "" - } -} - -// Check appsecret proof is enabled or not. -func (session *Session) AppsecretProof() string { - if !session.enableAppsecretProof { - return "" - } - - if session.accessToken == "" || session.app == nil { - return "" - } - - if session.appsecretProof == "" { - hash := hmac.New(sha256.New, []byte(session.app.AppSecret)) - hash.Write([]byte(session.accessToken)) - session.appsecretProof = hex.EncodeToString(hash.Sum(nil)) - } - - return session.appsecretProof -} - -// Enable or disable appsecret proof status. -// Returns error if there is no App associasted with this Session. -func (session *Session) EnableAppsecretProof(enabled bool) error { - if session.app == nil { - return fmt.Errorf("cannot change appsecret proof status without an associated App.") - } - - if session.enableAppsecretProof != enabled { - session.enableAppsecretProof = enabled - - // reset pre-calculated proof here to give caller a way to do so in some rare case, - // e.g. associated app's secret is changed. - session.appsecretProof = "" - } - - return nil -} - -// Gets associated App. -func (session *Session) App() *App { - return session.app -} - -// Debug returns current debug mode. -func (session *Session) Debug() DebugMode { - if session.debug != DEBUG_OFF { - return session.debug - } - - return Debug -} - -// SetDebug updates per session debug mode and returns old mode. -// If per session debug mode is DEBUG_OFF, session will use global -// Debug mode. -func (session *Session) SetDebug(debug DebugMode) DebugMode { - old := session.debug - session.debug = debug - return old -} - -func (session *Session) graph(path string, method Method, params Params) (res Result, err error) { - var graphUrl string - - if params == nil { - params = Params{} - } - - // always format as json. - params["format"] = "json" - - // overwrite method as we always use post - params["method"] = method - - // get graph api url. - if session.isVideoPost(path, method) { - graphUrl = session.getUrl("graph_video", path, nil) - } else { - graphUrl = session.getUrl("graph", path, nil) - } - - var response *http.Response - response, err = session.sendPostRequest(graphUrl, params, &res) - session.addDebugInfo(res, response) - - if res != nil { - err = res.Err() - } - - return -} - -func (session *Session) graphBatch(batchParams Params, params ...Params) ([]Result, error) { - if batchParams == nil { - batchParams = Params{} - } - - batchParams["batch"] = params - - var res []Result - graphUrl := session.getUrl("graph", "", nil) - _, err := session.sendPostRequest(graphUrl, batchParams, &res) - return res, err -} - -func (session *Session) graphFQL(params Params) (res Result, err error) { - if params == nil { - params = Params{} - } - - session.prepareParams(params) - - // encode url. - buf := &bytes.Buffer{} - buf.WriteString(domainMap["graph"]) - buf.WriteString("fql?") - _, err = params.Encode(buf) - - if err != nil { - return nil, fmt.Errorf("cannot encode params. %v", err) - } - - // it seems facebook disallow POST to /fql. always use GET for FQL. - var response *http.Response - response, err = session.sendGetRequest(buf.String(), &res) - session.addDebugInfo(res, response) - - if res != nil { - err = res.Err() - } - - return -} - -func (session *Session) prepareParams(params Params) { - if _, ok := params["access_token"]; !ok && session.accessToken != "" { - params["access_token"] = session.accessToken - } - - if session.enableAppsecretProof && session.accessToken != "" && session.app != nil { - params["appsecret_proof"] = session.AppsecretProof() - } - - debug := session.Debug() - - if debug != DEBUG_OFF { - params["debug"] = debug - } -} - -func (session *Session) sendGetRequest(uri string, res interface{}) (*http.Response, error) { - request, err := http.NewRequest("GET", uri, nil) - - if err != nil { - return nil, err - } - - response, data, err := session.sendRequest(request) - - if err != nil { - return response, err - } - - err = makeResult(data, res) - return response, err -} - -func (session *Session) sendPostRequest(uri string, params Params, res interface{}) (*http.Response, error) { - session.prepareParams(params) - - buf := &bytes.Buffer{} - mime, err := params.Encode(buf) - - if err != nil { - return nil, fmt.Errorf("cannot encode POST params. %v", err) - } - - var request *http.Request - - request, err = http.NewRequest("POST", uri, buf) - - if err != nil { - return nil, err - } - - request.Header.Set("Content-Type", mime) - response, data, err := session.sendRequest(request) - - if err != nil { - return response, err - } - - err = makeResult(data, res) - return response, err -} - -func (session *Session) sendOauthRequest(uri string, params Params) (Result, error) { - urlStr := session.getUrl("graph", uri, nil) - buf := &bytes.Buffer{} - mime, err := params.Encode(buf) - - if err != nil { - return nil, fmt.Errorf("cannot encode POST params. %v", err) - } - - var request *http.Request - - request, err = http.NewRequest("POST", urlStr, buf) - - if err != nil { - return nil, err - } - - request.Header.Set("Content-Type", mime) - _, data, err := session.sendRequest(request) - - if err != nil { - return nil, err - } - - if len(data) == 0 { - return nil, fmt.Errorf("empty response from facebook") - } - - // facebook may return a query string. - if 'a' <= data[0] && data[0] <= 'z' { - query, err := url.ParseQuery(string(data)) - - if err != nil { - return nil, err - } - - // convert a query to Result. - res := Result{} - - for k := range query { - res[k] = query.Get(k) - } - - return res, nil - } - - res, err := MakeResult(data) - return res, err -} - -func (session *Session) sendRequest(request *http.Request) (response *http.Response, data []byte, err error) { - if session.HttpClient == nil { - response, err = http.DefaultClient.Do(request) - } else { - response, err = session.HttpClient.Do(request) - } - - if err != nil { - err = fmt.Errorf("cannot reach facebook server. %v", err) - return - } - - buf := &bytes.Buffer{} - _, err = io.Copy(buf, response.Body) - response.Body.Close() - - if err != nil { - err = fmt.Errorf("cannot read facebook response. %v", err) - } - - data = buf.Bytes() - return -} - -func (session *Session) isVideoPost(path string, method Method) bool { - return method == POST && regexpIsVideoPost.MatchString(path) -} - -func (session *Session) getUrl(name, path string, params Params) string { - offset := 0 - - if path != "" && path[0] == '/' { - offset = 1 - } - - buf := &bytes.Buffer{} - buf.WriteString(domainMap[name]) - - // facebook versioning. - if session.Version == "" { - if Version != "" { - buf.WriteString(Version) - buf.WriteRune('/') - } - } else { - buf.WriteString(session.Version) - buf.WriteRune('/') - } - - buf.WriteString(string(path[offset:])) - - if params != nil { - buf.WriteRune('?') - params.Encode(buf) - } - - return buf.String() -} - -func (session *Session) addDebugInfo(res Result, response *http.Response) Result { - if session.Debug() == DEBUG_OFF || res == nil || response == nil { - return res - } - - debugInfo := make(map[string]interface{}) - - // save debug information in result directly. - res.DecodeField("__debug__", &debugInfo) - debugInfo[debugProtoKey] = response.Proto - debugInfo[debugHeaderKey] = response.Header - - res["__debug__"] = debugInfo - return res -} - -func decodeBase64URLEncodingString(data string) ([]byte, error) { - buf := bytes.NewBufferString(data) - - // go's URLEncoding implementation requires base64 padding. - if m := len(data) % 4; m != 0 { - buf.WriteString(strings.Repeat("=", 4-m)) - } - - reader := base64.NewDecoder(base64.URLEncoding, buf) - output := &bytes.Buffer{} - _, err := io.Copy(output, reader) - - if err != nil { - return nil, err - } - - return output.Bytes(), nil -} diff --git a/Godeps/_workspace/src/github.com/huandu/facebook/type.go b/Godeps/_workspace/src/github.com/huandu/facebook/type.go deleted file mode 100644 index d09865415..000000000 --- a/Godeps/_workspace/src/github.com/huandu/facebook/type.go +++ /dev/null @@ -1,127 +0,0 @@ -// A facebook graph api client in go. -// https://github.com/huandu/facebook/ -// -// Copyright 2012 - 2015, Huan Du -// Licensed under the MIT license -// https://github.com/huandu/facebook/blob/master/LICENSE - -package facebook - -import ( - "io" - "net/http" -) - -// Holds facebook application information. -type App struct { - // Facebook app id - AppId string - - // Facebook app secret - AppSecret string - - // Facebook app redirect URI in the app's configuration. - RedirectUri string - - // Enable appsecret proof in every API call to facebook. - // Facebook document: https://developers.facebook.com/docs/graph-api/securing-requests - EnableAppsecretProof bool -} - -// An interface to send http request. -// This interface is designed to be compatible with type `*http.Client`. -type HttpClient interface { - Do(req *http.Request) (resp *http.Response, err error) - Get(url string) (resp *http.Response, err error) - Post(url string, bodyType string, body io.Reader) (resp *http.Response, err error) -} - -// Holds a facebook session with an access token. -// Session should be created by App.Session or App.SessionFromSignedRequest. -type Session struct { - HttpClient HttpClient - Version string // facebook versioning. - - accessToken string // facebook access token. can be empty. - app *App - id string - - enableAppsecretProof bool // add "appsecret_proof" parameter in every facebook API call. - appsecretProof string // pre-calculated "appsecret_proof" value. - - debug DebugMode // using facebook debugging api in every request. -} - -// API HTTP method. -// Can be GET, POST or DELETE. -type Method string - -// Graph API debug mode. -// See https://developers.facebook.com/docs/graph-api/using-graph-api/v2.3#graphapidebugmode -type DebugMode string - -// API params. -// -// For general uses, just use Params as a ordinary map. -// -// For advanced uses, use MakeParams to create Params from any struct. -type Params map[string]interface{} - -// Facebook API call result. -type Result map[string]interface{} - -// Represents facebook API call result with paging information. -type PagingResult struct { - session *Session - paging pagingData - previous string - next string -} - -// Represents facebook batch API call result. -// See https://developers.facebook.com/docs/graph-api/making-multiple-requests/#multiple_methods. -type BatchResult struct { - StatusCode int // HTTP status code. - Header http.Header // HTTP response headers. - Body string // Raw HTTP response body string. - Result Result // Facebook api result parsed from body. -} - -// Facebook API error. -type Error struct { - Message string - Type string - Code int - ErrorSubcode int // subcode for authentication related errors. -} - -// Binary data. -type binaryData struct { - Filename string // filename used in multipart form writer. - Source io.Reader // file data source. -} - -// Binary file. -type binaryFile struct { - Filename string // filename used in multipart form writer. - Path string // path to file. must be readable. -} - -// DebugInfo is the debug information returned by facebook when debug mode is enabled. -type DebugInfo struct { - Messages []DebugMessage // debug messages. it can be nil if there is no message. - Header http.Header // all HTTP headers for this response. - Proto string // HTTP protocol name for this response. - - // Facebook debug HTTP headers. - FacebookApiVersion string // the actual graph API version provided by facebook-api-version HTTP header. - FacebookDebug string // the X-FB-Debug HTTP header. - FacebookRev string // the x-fb-rev HTTP header. -} - -// DebugMessage is one debug message in "__debug__" of graph API response. -type DebugMessage struct { - Type string - Message string - Link string -} -- cgit v1.2.3-1-g7c22