package graphql import ( "fmt" "math" "strconv" "time" "github.com/graphql-go/graphql/language/ast" ) // As per the GraphQL Spec, Integers are only treated as valid when a valid // 32-bit signed integer, providing the broadest support across platforms. // // n.b. JavaScript's integers are safe between -(2^53 - 1) and 2^53 - 1 because // they are internally represented as IEEE 754 doubles. func coerceInt(value interface{}) interface{} { switch value := value.(type) { case bool: if value == true { return 1 } return 0 case int: if value < int(math.MinInt32) || value > int(math.MaxInt32) { return nil } return value case *int: return coerceInt(*value) case int8: return int(value) case *int8: return int(*value) case int16: return int(value) case *int16: return int(*value) case int32: return int(value) case *int32: return int(*value) case int64: if value < int64(math.MinInt32) || value > int64(math.MaxInt32) { return nil } return int(value) case *int64: return coerceInt(*value) case uint: if value > math.MaxInt32 { return nil } return int(value) case *uint: return coerceInt(*value) case uint8: return int(value) case *uint8: return int(*value) case uint16: return int(value) case *uint16: return int(*value) case uint32: if value > uint32(math.MaxInt32) { return nil } return int(value) case *uint32: return coerceInt(*value) case uint64: if value > uint64(math.MaxInt32) { return nil } return int(value) case *uint64: return coerceInt(*value) case float32: if value < float32(math.MinInt32) || value > float32(math.MaxInt32) { return nil } return int(value) case *float32: return coerceInt(*value) case float64: if value < float64(math.MinInt32) || value > float64(math.MaxInt32) { return nil } return int(value) case *float64: return coerceInt(*value) case string: val, err := strconv.ParseFloat(value, 0) if err != nil { return nil } return coerceInt(val) case *string: return coerceInt(*value) } // If the value cannot be transformed into an int, return nil instead of '0' // to denote 'no integer found' return nil } // Int is the GraphQL Integer type definition. var Int = NewScalar(ScalarConfig{ Name: "Int", Description: "The `Int` scalar type represents non-fractional signed whole numeric " + "values. Int can represent values between -(2^31) and 2^31 - 1. ", Serialize: coerceInt, ParseValue: coerceInt, ParseLiteral: func(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.IntValue: if intValue, err := strconv.Atoi(valueAST.Value); err == nil { return intValue } } return nil }, }) func coerceFloat(value interface{}) interface{} { switch value := value.(type) { case bool: if value == true { return 1.0 } return 0.0 case *bool: return coerceFloat(*value) case int: return float64(value) case *int32: return coerceFloat(*value) case float32: return value case *float32: return coerceFloat(*value) case float64: return value case *float64: return coerceFloat(*value) case string: val, err := strconv.ParseFloat(value, 0) if err != nil { return nil } return val case *string: return coerceFloat(*value) } return 0.0 } // Float is the GraphQL float type definition. var Float = NewScalar(ScalarConfig{ Name: "Float", Description: "The `Float` scalar type represents signed double-precision fractional " + "values as specified by " + "[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). ", Serialize: coerceFloat, ParseValue: coerceFloat, ParseLiteral: func(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.FloatValue: if floatValue, err := strconv.ParseFloat(valueAST.Value, 32); err == nil { return floatValue } case *ast.IntValue: if floatValue, err := strconv.ParseFloat(valueAST.Value, 32); err == nil { return floatValue } } return nil }, }) func coerceString(value interface{}) interface{} { if v, ok := value.(*string); ok { return *v } return fmt.Sprintf("%v", value) } // String is the GraphQL string type definition var String = NewScalar(ScalarConfig{ Name: "String", Description: "The `String` scalar type represents textual data, represented as UTF-8 " + "character sequences. The String type is most often used by GraphQL to " + "represent free-form human-readable text.", Serialize: coerceString, ParseValue: coerceString, ParseLiteral: func(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.StringValue: return valueAST.Value } return nil }, }) func coerceBool(value interface{}) interface{} { switch value := value.(type) { case bool: return value case *bool: return *value case string: switch value { case "", "false": return false } return true case *string: return coerceBool(*value) case float64: if value != 0 { return true } return false case *float64: return coerceBool(*value) case float32: if value != 0 { return true } return false case *float32: return coerceBool(*value) case int: if value != 0 { return true } return false case *int: return coerceBool(*value) } return false } // Boolean is the GraphQL boolean type definition var Boolean = NewScalar(ScalarConfig{ Name: "Boolean", Description: "The `Boolean` scalar type represents `true` or `false`.", Serialize: coerceBool, ParseValue: coerceBool, ParseLiteral: func(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.BooleanValue: return valueAST.Value } return nil }, }) // ID is the GraphQL id type definition var ID = NewScalar(ScalarConfig{ Name: "ID", Description: "The `ID` scalar type represents a unique identifier, often used to " + "refetch an object or as key for a cache. The ID type appears in a JSON " + "response as a String; however, it is not intended to be human-readable. " + "When expected as an input type, any string (such as `\"4\"`) or integer " + "(such as `4`) input value will be accepted as an ID.", Serialize: coerceString, ParseValue: coerceString, ParseLiteral: func(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.IntValue: return valueAST.Value case *ast.StringValue: return valueAST.Value } return nil }, }) func serializeDateTime(value interface{}) interface{} { switch value := value.(type) { case time.Time: buff, err := value.MarshalText() if err != nil { return nil } return string(buff) case *time.Time: return serializeDateTime(*value) default: return nil } } func unserializeDateTime(value interface{}) interface{} { switch value := value.(type) { case []byte: t := time.Time{} err := t.UnmarshalText(value) if err != nil { return nil } return t case string: return unserializeDateTime([]byte(value)) case *string: return unserializeDateTime([]byte(*value)) default: return nil } } var DateTime = NewScalar(ScalarConfig{ Name: "DateTime", Description: "The `DateTime` scalar type represents a DateTime." + " The DateTime is serialized as an RFC 3339 quoted string", Serialize: serializeDateTime, ParseValue: unserializeDateTime, ParseLiteral: func(valueAST ast.Value) interface{} { switch valueAST := valueAST.(type) { case *ast.StringValue: return valueAST.Value } return nil }, })