Skip to content

Instantly share code, notes, and snippets.

@jawr
Created July 13, 2015 08:59
Show Gist options
  • Save jawr/b4c585b69b8e83255513 to your computer and use it in GitHub Desktop.
Save jawr/b4c585b69b8e83255513 to your computer and use it in GitHub Desktop.
// 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