// Copyright 2014 The go-github AUTHORS. All rights reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// This tool tests for the JSON mappings in the go-github data types. It will
|
|
// identify fields that are returned by the live GitHub API, but that are not
|
|
// currently mapped into a struct field of the relevant go-github type. This
|
|
// helps to ensure that all relevant data returned by the API is being made
|
|
// accessible, particularly new fields that are periodically (and sometimes
|
|
// quietly) added to the API over time.
|
|
//
|
|
// These tests simply aid in identifying which fields aren't being mapped; it
|
|
// is not necessarily true that every one of them should always be mapped.
|
|
// Some fields may be undocumented for a reason, either because they aren't
|
|
// actually used yet or should not be relied upon.
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"code.google.com/p/goauth2/oauth"
|
|
|
|
"github.com/google/go-github/github"
|
|
)
|
|
|
|
var (
|
|
client *github.Client
|
|
|
|
// auth indicates whether tests are being run with an OAuth token.
|
|
// Tests can use this flag to skip certain tests when run without auth.
|
|
auth bool
|
|
|
|
skipURLs = flag.Bool("skip_urls", false, "skip url fields")
|
|
)
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
token := os.Getenv("GITHUB_AUTH_TOKEN")
|
|
if token == "" {
|
|
println("!!! No OAuth token. Some tests won't run. !!!\n")
|
|
client = github.NewClient(nil)
|
|
} else {
|
|
t := &oauth.Transport{
|
|
Token: &oauth.Token{AccessToken: token},
|
|
}
|
|
client = github.NewClient(t.Client())
|
|
auth = true
|
|
}
|
|
|
|
testType("rate_limit", github.Rate{})
|
|
testType("users/octocat", github.User{})
|
|
testType("orgs/google", github.Organization{})
|
|
testType("repos/google/go-github", github.Repository{})
|
|
}
|
|
|
|
func checkAuth(name string) bool {
|
|
if !auth {
|
|
fmt.Printf("No auth - skipping portions of %v\n", name)
|
|
}
|
|
return auth
|
|
}
|
|
|
|
// testType fetches the JSON resource at urlStr and compares its keys to the
|
|
// struct fields of typ.
|
|
//
|
|
// TODO: handle resources that are more easily fetched as an array of objects,
|
|
// rather than a single object (e.g. a user's public keys). In this case, we
|
|
// should just take the first object in the array, and use that. In that case,
|
|
// should typ also be specified as a slice?
|
|
func testType(urlStr string, typ interface{}) error {
|
|
req, err := client.NewRequest("GET", urlStr, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// I'm thinking we might want to unmarshall the response both as a
|
|
// map[string]interface{} as well as typ, though I'm not 100% sure.
|
|
// That's why we unmarshal to json.RawMessage first here.
|
|
raw := new(json.RawMessage)
|
|
_, err = client.Do(req, raw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var m map[string]interface{}
|
|
err = json.Unmarshal(*raw, &m)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fields := jsonFields(typ)
|
|
|
|
for k, v := range m {
|
|
if *skipURLs && strings.HasSuffix(k, "_url") {
|
|
continue
|
|
}
|
|
if _, ok := fields[k]; !ok {
|
|
fmt.Printf("%v missing field for key: %v (example value: %v)\n", reflect.TypeOf(typ), k, v)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseTag splits a struct field's url tag into its name and comma-separated
|
|
// options.
|
|
func parseTag(tag string) (string, []string) {
|
|
s := strings.Split(tag, ",")
|
|
return s[0], s[1:]
|
|
}
|
|
|
|
// jsonFields returns a map of JSON fields that have an explicit mapping to a
|
|
// field in v. The fields will be in the returned map's keys; the map values
|
|
// for those keys is currently undefined.
|
|
func jsonFields(v interface{}) map[string]interface{} {
|
|
fields := make(map[string]interface{})
|
|
|
|
typ := reflect.TypeOf(v)
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
sf := typ.Field(i)
|
|
if sf.PkgPath != "" { // unexported
|
|
continue
|
|
}
|
|
|
|
tag := sf.Tag.Get("json")
|
|
if tag == "-" {
|
|
continue
|
|
}
|
|
|
|
name, opts := parseTag(tag)
|
|
fields[name] = opts
|
|
}
|
|
return fields
|
|
}
|