Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolver Api with dynamic argument parsing #36

Open
fungl164 opened this issue Jan 6, 2019 · 2 comments
Open

Resolver Api with dynamic argument parsing #36

fungl164 opened this issue Jan 6, 2019 · 2 comments

Comments

@fungl164
Copy link

fungl164 commented Jan 6, 2019

Was wondering if there is any appetite for enhancing the resolver api by enabling dynamic argument parsing for faster/safer coding.

Below is a rough (but working) example of how it could work...aided by some minimal external packages I quickly pulled together...

Any comments? Takers?

package main

import (
	"errors"
	"net/http"

	handler "github.com/krypton97/HandleGraphQL"
	router "github.com/luisjakon/playlyfe-router"
	graphiql "github.com/luisjakon/graphiql"
	graphql "github.com/playlyfe/go-graphql"
)

var (
	Salutations = map[string]string{
		"ro": "Salut",
		"en": "Hello",
		"fr": "Oui",
		"it": "Ciao",
		"de": "Hallo",
	}

	Students = map[string]map[string]interface{}{
		"1": {"__typename": "Student", "name": "alex", "age": 18, "id": "1"},
		"2": {"__typename": "Student", "name": "bob", "age": 19, "id": "2"},
		"3": {"__typename": "Student", "name": "john", "age": 20, "id": "3"},
	}
)

func main() {

	schema := `
type Student {
	name: String
	age: Int
}

type Query {
	hello: String
	salutation(lang: String!): String
	student(id: String!): Student
}	
`
	router := router.NewRouter()

	router.Register("Query/hello", QueryResolver.Hello)
	router.Register("Query/salutation", QueryResolver.Salutation)
	router.Register("Query/student", QueryResolver.StudentById)
	router.Register("Student/age", StudentResolver.Age)

	executor, err := graphql.NewExecutor(schema, "Query", "", router)
	if err != nil {
		panic(err)
	}

	api := handler.New(&handler.Config{
		Executor: executor,
		Context:  "",
		Pretty:   true,
	})

	http.Handle("/graphql", graphiql.Handler(api))
	http.ListenAndServe("localhost:3000", nil)
}

resolvers.go

// Resolvers
var (
	QueryResolver   = queryRes{}
	StudentResolver = studentRes{}
)

// Query Resolver
type queryRes struct{}

func (r queryRes) Hello(params *graphql.ResolveParams) (interface{}, error) {
	return "world", nil
}

func (r queryRes) Salutation(params *graphql.ResolveParams, args struct { Lang string }) (interface{}, error) {
	s := Salutations[args.Lang]
	if s == "" {
		return nil, errors.New("Unknown Language: " + args.Lang)
	}
	return s, nil
}

func (r queryRes) StudentById(params *graphql.ResolveParams, args struct { Id string }) (interface{}, error) {
	return Students[args.Id], nil
}

// Student Resolver
type studentRes struct{}

func (r studentRes) Age(params *graphql.ResolveParams) (interface{}, error) {
	if v, ok := params.Source.(int); ok {
		return v, nil
	}
	source := params.Source.(map[string]interface{})
	id := source["id"].(string)
	return Students[id]["age"].(int), nil
}
@atrniv
Copy link
Member

atrniv commented Jan 20, 2019

This is a very interesting approach, but I'm not sure if it's the best way to do this and if we should include it directly within the go-graphql library.

I personally use a slightly different approach to handle this using a simple Map struct with functions for parsing each key into a specific type of value.

type Map map[string]interface{}

// String returns the value at 'key' as a string
func (m Map) String(key string, defaultValue ...string) string {
	if len(defaultValue) == 0 {
		return m[key].(string)
	}
	value, ok := m[key].(string)
	if !ok {
		value = defaultValue[0]
	}
	return value
}

// Uint32 returns the value at 'key' as a uint32
func (m Map) Uint32(key string, defaultValue ...uint32) uint32 {
	if len(defaultValue) == 0 {
		return m[key].(uint32)
	}
	value, ok := m[key].(uint32)
	if !ok {
		value = defaultValue[0]
	}
	return value
}

resolvers["Example/resolver"] = func(params *graphql.ResolveParams) (interface{}, error) {
		tx := helpers.LoadTransaction(params)
		args := model.Map(params.Args)
                println(args.String("key1")) // Fetch the value of argument 'key1' as a string, will crash on nil assertion if the argument is not passed
                println(args.Uint32("key2", uint32(42))) // Fetch value of argument 'key2' as an uint32. If it does not exist use the value 42
                return nil, nil
}

The biggest issue I've had with my approach is that it will crash while trying to access a nil value if you don't pass an input argument and haven't mentioned a default value.

The main reason I don't want to include it in the library is that it needs to include a way to parse all the basic types you could read out from the input.

One disadvantage with your approach would be that you can't specify default values easily without verbose if checks for each field.

@fungl164
Copy link
Author

@atrniv Thnxs for the response, perhaps for now it is better to leave as an external package.

Am sure the basic resolver argument-parsing mechanism I'm using in the github.com/luisjakon/playlyfe-router package can be improved and/or generalized a bit more to include hooks for additional validations using external packages and/or other user-defined functions as needed .

I'm happy with any thoughts and/or contributions anyone provides. : )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants