Created
July 13, 2015 08:59
-
-
Save jawr/b4c585b69b8e83255513 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// post a description of the object we want using graphql, this | |
// is then looped over and then each node that is registered checks | |
// and returns a json copy of the requested fields | |
package main | |
import ( | |
"errors" | |
"log" | |
"reflect" | |
"sevki.org/graphql/parse" | |
"sevki.org/graphql/query" | |
"sevki.org/lib/prettyprint" | |
"strings" | |
) | |
func main() { | |
defer func() { | |
r := recover() | |
if r != nil { | |
log.Printf("Error: %s", r) | |
return | |
} | |
}() | |
packages = make(map[string]Package, 0) | |
packages["user"] = GetUser | |
packages["profile_picture"] = GetUser | |
query := ` | |
{ | |
user(id: 3500401) { | |
id, | |
name, | |
isViewerFriend, | |
profile_picture(size: 50) { | |
uri, | |
width, | |
height | |
} | |
} | |
} | |
` | |
parsed, err := Parse([]byte(query)) | |
if err != nil { | |
panic(err) | |
} else { | |
log.Printf(query) | |
log.Printf(prettyprint.AsJSON(parsed)) | |
} | |
} | |
// each package registers with a Package function which takes a graphql | |
// ast and returns the populated json | |
type Package func([]string, Params) (ParsedList, error) | |
// params is a farcade for query.Params | |
type Param query.Param | |
type Params map[string]Param | |
type Parsed map[string]interface{} | |
type ParsedList []Parsed | |
// registered packages | |
var packages map[string]Package | |
// Parse takes a graphql and attempts to build the requested object using | |
// registered packages | |
func Parse(query []byte) (parsed ParsedList, err error) { | |
ast, err := parse.NewQuery(query) | |
if err != nil { | |
return | |
} | |
// error handeling for recursive calls | |
defer func() { | |
r := recover() | |
if r != nil { | |
err = r.(error) | |
} | |
return | |
}() | |
parsed = parseNode(ast) | |
return | |
} | |
// parseNode loops over the node tree and generates objects as required | |
func parseNode(node *query.Node) (parsed ParsedList) { | |
name := string(node.Name) | |
fn, ok := packages[name] | |
if !ok { | |
err = errors.New("No package registered with the name: " + name) | |
panic(err) | |
} | |
var fields []string | |
subNodes := make(map[string]*query.Node, 0) | |
// generate list of fields and non field edges/sub nodes | |
for _, f := range node.Edges { | |
if len(f.Edges) == 0 { | |
fields = append(fields, string(f.Name)) | |
} else { | |
subNodes[string(f.Name)] = &f | |
} | |
} | |
// translate in to our params | |
params := make(Params, len(node.Params)) | |
for k, v := range node.Params { | |
params[k] = v.(Param) | |
} | |
// handle fields for this edge | |
fieldsParsedList, err := fn(fields, params) | |
if err != nil { | |
panic(err) | |
} | |
// initiate our parsed list to be the same length as our results | |
parsed = make(ParsedList, len(fieldsParsedList)) | |
// for each result add appropriate fields and also recurse on sub | |
// nodes | |
for i := 0; i < len(fieldsParsedList); i++ { | |
parsed[i] = make(Parsed, len(fieldsParsedList)) | |
// populate our fields per item received | |
for k, v := range fieldsParsedList[i] { | |
parsed[i][k] = v | |
} | |
// handle none field edges | |
for k, v := range subNodes { | |
parsed[i][k], err = parseNode(v) | |
if err != nil { | |
panic(err) | |
} | |
} | |
} | |
return | |
} | |
// test struct | |
type User struct { | |
ID int `json:"id"` | |
Name string `json:"name"` | |
IsViewerFriend bool `json:"isViewerFriend"` | |
URI string `json:"uri"` | |
Width int `json:"width"` | |
Height int `json:"height"` | |
} | |
// function to demonstrate package GetFn this might need to be changed to | |
// handle just lists | |
// we can register multiple per package, i.e. if we wanted a function called | |
// latest_users we could have a seperate function (then functionalise the | |
// output section for reuse) | |
func GetUser(fields []string, params Params) (parsed ParsedList, err error) { | |
// this is where we would use params to build our specific list | |
users := []User{} | |
users = append(users, | |
User{ | |
ID: 1, | |
Name: "Foo Bar", | |
IsViewerFriend: true, | |
URI: "http://....", | |
Width: 50, | |
Height: 60, | |
}, | |
User{ | |
ID: 2, | |
Name: "Jess", | |
IsViewerFriend: false, | |
URI: "https://.....", | |
Width: 120, | |
Height: 120, | |
}, | |
) | |
// set parsed list length to number of results found | |
parsed = make(ParsedList, len(users)) | |
// output in to parsed list | |
for idx, user := range users { | |
st := reflect.TypeOf(user) | |
vt := reflect.ValueOf(user) | |
// initiate our map | |
parsed[idx] = make(Parsed, len(fields)) | |
// add every item | |
for i := 0; i < st.NumField(); i++ { | |
field := st.Field(i) | |
fieldTag := strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") | |
for _, tag := range fields { | |
if fieldTag == tag { | |
parsed[idx][tag] = vt.Field(i).Interface() | |
} | |
} | |
} | |
} | |
return | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment