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
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))
}
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.