1package graphql
2
3import (
4 "fmt"
5 "math"
6 "strconv"
7 "time"
8
9 "github.com/graphql-go/graphql/language/ast"
10)
11
12// As per the GraphQL Spec, Integers are only treated as valid when a valid
13// 32-bit signed integer, providing the broadest support across platforms.
14//
15// n.b. JavaScript's integers are safe between -(2^53 - 1) and 2^53 - 1 because
16// they are internally represented as IEEE 754 doubles.
17func coerceInt(value interface{}) interface{} {
18 switch value := value.(type) {
19 case bool:
20 if value == true {
21 return 1
22 }
23 return 0
24 case int:
25 if value < int(math.MinInt32) || value > int(math.MaxInt32) {
26 return nil
27 }
28 return value
29 case *int:
30 return coerceInt(*value)
31 case int8:
32 return int(value)
33 case *int8:
34 return int(*value)
35 case int16:
36 return int(value)
37 case *int16:
38 return int(*value)
39 case int32:
40 return int(value)
41 case *int32:
42 return int(*value)
43 case int64:
44 if value < int64(math.MinInt32) || value > int64(math.MaxInt32) {
45 return nil
46 }
47 return int(value)
48 case *int64:
49 return coerceInt(*value)
50 case uint:
51 if value > math.MaxInt32 {
52 return nil
53 }
54 return int(value)
55 case *uint:
56 return coerceInt(*value)
57 case uint8:
58 return int(value)
59 case *uint8:
60 return int(*value)
61 case uint16:
62 return int(value)
63 case *uint16:
64 return int(*value)
65 case uint32:
66 if value > uint32(math.MaxInt32) {
67 return nil
68 }
69 return int(value)
70 case *uint32:
71 return coerceInt(*value)
72 case uint64:
73 if value > uint64(math.MaxInt32) {
74 return nil
75 }
76 return int(value)
77 case *uint64:
78 return coerceInt(*value)
79 case float32:
80 if value < float32(math.MinInt32) || value > float32(math.MaxInt32) {
81 return nil
82 }
83 return int(value)
84 case *float32:
85 return coerceInt(*value)
86 case float64:
87 if value < float64(math.MinInt32) || value > float64(math.MaxInt32) {
88 return nil
89 }
90 return int(value)
91 case *float64:
92 return coerceInt(*value)
93 case string:
94 val, err := strconv.ParseFloat(value, 0)
95 if err != nil {
96 return nil
97 }
98 return coerceInt(val)
99 case *string:
100 return coerceInt(*value)
101 }
102
103 // If the value cannot be transformed into an int, return nil instead of '0'
104 // to denote 'no integer found'
105 return nil
106}
107
108// Int is the GraphQL Integer type definition.
109var Int = NewScalar(ScalarConfig{
110 Name: "Int",
111 Description: "The `Int` scalar type represents non-fractional signed whole numeric " +
112 "values. Int can represent values between -(2^31) and 2^31 - 1. ",
113 Serialize: coerceInt,
114 ParseValue: coerceInt,
115 ParseLiteral: func(valueAST ast.Value) interface{} {
116 switch valueAST := valueAST.(type) {
117 case *ast.IntValue:
118 if intValue, err := strconv.Atoi(valueAST.Value); err == nil {
119 return intValue
120 }
121 }
122 return nil
123 },
124})
125
126func coerceFloat(value interface{}) interface{} {
127 switch value := value.(type) {
128 case bool:
129 if value == true {
130 return 1.0
131 }
132 return 0.0
133 case *bool:
134 return coerceFloat(*value)
135 case int:
136 return float64(value)
137 case *int32:
138 return coerceFloat(*value)
139 case float32:
140 return value
141 case *float32:
142 return coerceFloat(*value)
143 case float64:
144 return value
145 case *float64:
146 return coerceFloat(*value)
147 case string:
148 val, err := strconv.ParseFloat(value, 0)
149 if err != nil {
150 return nil
151 }
152 return val
153 case *string:
154 return coerceFloat(*value)
155 }
156 return 0.0
157}
158
159// Float is the GraphQL float type definition.
160var Float = NewScalar(ScalarConfig{
161 Name: "Float",
162 Description: "The `Float` scalar type represents signed double-precision fractional " +
163 "values as specified by " +
164 "[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). ",
165 Serialize: coerceFloat,
166 ParseValue: coerceFloat,
167 ParseLiteral: func(valueAST ast.Value) interface{} {
168 switch valueAST := valueAST.(type) {
169 case *ast.FloatValue:
170 if floatValue, err := strconv.ParseFloat(valueAST.Value, 32); err == nil {
171 return floatValue
172 }
173 case *ast.IntValue:
174 if floatValue, err := strconv.ParseFloat(valueAST.Value, 32); err == nil {
175 return floatValue
176 }
177 }
178 return nil
179 },
180})
181
182func coerceString(value interface{}) interface{} {
183 if v, ok := value.(*string); ok {
184 return *v
185 }
186 return fmt.Sprintf("%v", value)
187}
188
189// String is the GraphQL string type definition
190var String = NewScalar(ScalarConfig{
191 Name: "String",
192 Description: "The `String` scalar type represents textual data, represented as UTF-8 " +
193 "character sequences. The String type is most often used by GraphQL to " +
194 "represent free-form human-readable text.",
195 Serialize: coerceString,
196 ParseValue: coerceString,
197 ParseLiteral: func(valueAST ast.Value) interface{} {
198 switch valueAST := valueAST.(type) {
199 case *ast.StringValue:
200 return valueAST.Value
201 }
202 return nil
203 },
204})
205
206func coerceBool(value interface{}) interface{} {
207 switch value := value.(type) {
208 case bool:
209 return value
210 case *bool:
211 return *value
212 case string:
213 switch value {
214 case "", "false":
215 return false
216 }
217 return true
218 case *string:
219 return coerceBool(*value)
220 case float64:
221 if value != 0 {
222 return true
223 }
224 return false
225 case *float64:
226 return coerceBool(*value)
227 case float32:
228 if value != 0 {
229 return true
230 }
231 return false
232 case *float32:
233 return coerceBool(*value)
234 case int:
235 if value != 0 {
236 return true
237 }
238 return false
239 case *int:
240 return coerceBool(*value)
241 }
242 return false
243}
244
245// Boolean is the GraphQL boolean type definition
246var Boolean = NewScalar(ScalarConfig{
247 Name: "Boolean",
248 Description: "The `Boolean` scalar type represents `true` or `false`.",
249 Serialize: coerceBool,
250 ParseValue: coerceBool,
251 ParseLiteral: func(valueAST ast.Value) interface{} {
252 switch valueAST := valueAST.(type) {
253 case *ast.BooleanValue:
254 return valueAST.Value
255 }
256 return nil
257 },
258})
259
260// ID is the GraphQL id type definition
261var ID = NewScalar(ScalarConfig{
262 Name: "ID",
263 Description: "The `ID` scalar type represents a unique identifier, often used to " +
264 "refetch an object or as key for a cache. The ID type appears in a JSON " +
265 "response as a String; however, it is not intended to be human-readable. " +
266 "When expected as an input type, any string (such as `\"4\"`) or integer " +
267 "(such as `4`) input value will be accepted as an ID.",
268 Serialize: coerceString,
269 ParseValue: coerceString,
270 ParseLiteral: func(valueAST ast.Value) interface{} {
271 switch valueAST := valueAST.(type) {
272 case *ast.IntValue:
273 return valueAST.Value
274 case *ast.StringValue:
275 return valueAST.Value
276 }
277 return nil
278 },
279})
280
281func serializeDateTime(value interface{}) interface{} {
282 switch value := value.(type) {
283 case time.Time:
284 buff, err := value.MarshalText()
285 if err != nil {
286 return nil
287 }
288
289 return string(buff)
290 case *time.Time:
291 return serializeDateTime(*value)
292 default:
293 return nil
294 }
295}
296
297func unserializeDateTime(value interface{}) interface{} {
298 switch value := value.(type) {
299 case []byte:
300 t := time.Time{}
301 err := t.UnmarshalText(value)
302 if err != nil {
303 return nil
304 }
305
306 return t
307 case string:
308 return unserializeDateTime([]byte(value))
309 case *string:
310 return unserializeDateTime([]byte(*value))
311 default:
312 return nil
313 }
314}
315
316var DateTime = NewScalar(ScalarConfig{
317 Name: "DateTime",
318 Description: "The `DateTime` scalar type represents a DateTime." +
319 " The DateTime is serialized as an RFC 3339 quoted string",
320 Serialize: serializeDateTime,
321 ParseValue: unserializeDateTime,
322 ParseLiteral: func(valueAST ast.Value) interface{} {
323 switch valueAST := valueAST.(type) {
324 case *ast.StringValue:
325 return valueAST.Value
326 }
327 return nil
328 },
329})