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

When using custom scalar types, it's crucial to provide error feedback to users when issues arise with their submitted data. However, triggering exceptions within the ParseValue and ParseLiteral methods can lead to program crashes when using the graphql.Do method. This prevents the necessary error messages from being delivered to users. #679

Open
vyks520 opened this issue Sep 12, 2023 · 0 comments

Comments

@vyks520
Copy link

vyks520 commented Sep 12, 2023

When using custom scalar types, it's crucial to provide error feedback to users when issues arise with their submitted data. However, triggering exceptions within the ParseValue and ParseLiteral methods can lead to program crashes when using the graphql.Do method. This prevents the necessary error messages from being delivered to users.

The proposed improvement is to handle exceptions by checking the error object's type when they occur. If the error is created using gqlerrors.Error{}, it signifies that the error object's Message field contains information intended for user feedback, allowing us to capture and return that Message to users. In cases where the error is not created using gqlerrors.Error{}, it can be rethrown as an exception. This approach enhances error handling and ensures that valuable information is communicated to users while avoiding the potential exposure of sensitive information by preventing all exceptions from being exposed to the frontend.

package main

import (
	"encoding/json"
	"fmt"
	"github.com/graphql-go/graphql"
	"github.com/graphql-go/graphql/gqlerrors"
	"github.com/graphql-go/graphql/language/ast"
	"github.com/graphql-go/graphql/language/kinds"
	"log"
	"strconv"
)

func PtrSliceToSlice[T any](s []*T) []T {
	if s == nil {
		return nil
	}
	ret := make([]T, 0, len(s))
	for _, v := range s {
		ret = append(ret, *v)
	}
	return ret
}

func parseObject(valueAST interface{}) interface{} {
	var value = make(map[string]interface{})
	var fieldList []ast.ObjectField
	switch obj := valueAST.(type) {
	case []*ast.ObjectField:
		fieldList = PtrSliceToSlice(obj)
	case []ast.ObjectField:
		fieldList = obj
	default:
		err := gqlerrors.NewError(
			fmt.Sprintf("JSON cannot represent value: %v", valueAST),
			nil, "", nil, nil, nil)
		panic(err)
	}
	for _, field := range fieldList {
		value[field.Name.Value] = parseLiteral(field.Value)
	}
	return value
}

func parseList(valueAST interface{}) interface{} {
	var valueList []ast.Value
	switch vs := valueAST.(type) {
	case []ast.Value:
		valueList = vs
	case []*ast.Value:
		valueList = PtrSliceToSlice(vs)
	default:
		err := gqlerrors.NewError(
			fmt.Sprintf("JSON cannot represent value: %v", valueAST),
			nil, "", nil, nil, nil)
		panic(err)
	}
	value := make([]interface{}, len(valueList))
	for i, item := range valueList {
		value[i] = parseLiteral(item)
	}
	return value
}

func parseLiteral(valueAST ast.Value) interface{} {
	switch valueAST.GetKind() {
	case kinds.StringValue, kinds.BooleanValue:
		return valueAST.GetValue()
	case kinds.IntValue:
		intValue := (valueAST.GetValue()).(string)
		v, _ := strconv.ParseFloat(intValue, 64)
		return v
	case kinds.FloatValue:
		floatValue := (valueAST.GetValue()).(string)
		v, _ := strconv.ParseFloat(floatValue, 64)
		return v
	case kinds.ObjectValue:
		return parseObject(valueAST.GetValue())
	case kinds.ListValue:
		return parseList(valueAST.GetValue())
	case kinds.NonNull:
		return nil
	case kinds.Variable:
		var name ast.Name
		switch v := valueAST.GetValue().(type) {
		case *ast.Name:
			name = *v
		case ast.Name:
			name = v
		}

		err := gqlerrors.NewError(
			fmt.Sprintf("JSON does not support the use of variable: $%s", name.Value),
			nil, "", nil, nil, nil)
		panic(err)
	}
	err := gqlerrors.NewError(
		fmt.Sprintf("JSON cannot represent value: %v", valueAST),
		nil, "", nil, nil, nil)
	panic(err)
}

var JSONScalarType = graphql.NewScalar(graphql.ScalarConfig{
	Name:        "JSON",
	Description: "The `JSON` scalar",
	Serialize: func(value interface{}) interface{} {
		err := gqlerrors.NewError(
			"This method is successfully caught by graphql.Do, and any errors that occur will be added to Result.Errors.",
			nil, "", nil, nil, nil)
		panic(err)
		return value
	},
	ParseValue: func(value interface{}) interface{} {
		/*err := gqlerrors.NewError(
			"graphql.Do does not catch exceptions, and the program crashes directly when an error occurs.",
			nil, "", nil, nil, nil)
		panic(err)*/
		return value
	},
	// ParseLiteral does not catch exceptions, and the program crashes directly when an error occurs.
	ParseLiteral: parseLiteral,
})

func main() {
	schema, err := graphql.NewSchema(graphql.SchemaConfig{
		Query: graphql.NewObject(graphql.ObjectConfig{
			Name: "Query",
			Fields: graphql.Fields{
				"getData": &graphql.Field{
					Type: JSONScalarType,
					Args: graphql.FieldConfigArgument{
						"input": &graphql.ArgumentConfig{
							Type: JSONScalarType,
						},
					},
					Resolve: func(p graphql.ResolveParams) (interface{}, error) {
						return map[string]interface{}{
							"test": "testValue",
						}, nil
					},
				},
			},
		}),
	})
	if err != nil {
		log.Fatal(err)
	}
	query := `
		query ($input: JSON) {
			getData(input: {test: $input})
		}
	`
	result := graphql.Do(graphql.Params{
		Schema:        schema,
		RequestString: query,
		VariableValues: map[string]interface{}{
			"input": 89897886,
		},
	})
	b, err := json.Marshal(result)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(b))
}
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

1 participant