1package graphql
2
3import (
4 "fmt"
5 "reflect"
6 "sort"
7
8 "github.com/graphql-go/graphql/language/ast"
9 "github.com/graphql-go/graphql/language/printer"
10)
11
12const (
13 TypeKindScalar = "SCALAR"
14 TypeKindObject = "OBJECT"
15 TypeKindInterface = "INTERFACE"
16 TypeKindUnion = "UNION"
17 TypeKindEnum = "ENUM"
18 TypeKindInputObject = "INPUT_OBJECT"
19 TypeKindList = "LIST"
20 TypeKindNonNull = "NON_NULL"
21)
22
23// SchemaType is type definition for __Schema
24var SchemaType *Object
25
26// DirectiveType is type definition for __Directive
27var DirectiveType *Object
28
29// TypeType is type definition for __Type
30var TypeType *Object
31
32// FieldType is type definition for __Field
33var FieldType *Object
34
35// InputValueType is type definition for __InputValue
36var InputValueType *Object
37
38// EnumValueType is type definition for __EnumValue
39var EnumValueType *Object
40
41// TypeKindEnumType is type definition for __TypeKind
42var TypeKindEnumType *Enum
43
44// DirectiveLocationEnumType is type definition for __DirectiveLocation
45var DirectiveLocationEnumType *Enum
46
47// Meta-field definitions.
48
49// SchemaMetaFieldDef Meta field definition for Schema
50var SchemaMetaFieldDef *FieldDefinition
51
52// TypeMetaFieldDef Meta field definition for types
53var TypeMetaFieldDef *FieldDefinition
54
55// TypeNameMetaFieldDef Meta field definition for type names
56var TypeNameMetaFieldDef *FieldDefinition
57
58func init() {
59
60 TypeKindEnumType = NewEnum(EnumConfig{
61 Name: "__TypeKind",
62 Description: "An enum describing what kind of type a given `__Type` is",
63 Values: EnumValueConfigMap{
64 "SCALAR": &EnumValueConfig{
65 Value: TypeKindScalar,
66 Description: "Indicates this type is a scalar.",
67 },
68 "OBJECT": &EnumValueConfig{
69 Value: TypeKindObject,
70 Description: "Indicates this type is an object. " +
71 "`fields` and `interfaces` are valid fields.",
72 },
73 "INTERFACE": &EnumValueConfig{
74 Value: TypeKindInterface,
75 Description: "Indicates this type is an interface. " +
76 "`fields` and `possibleTypes` are valid fields.",
77 },
78 "UNION": &EnumValueConfig{
79 Value: TypeKindUnion,
80 Description: "Indicates this type is a union. " +
81 "`possibleTypes` is a valid field.",
82 },
83 "ENUM": &EnumValueConfig{
84 Value: TypeKindEnum,
85 Description: "Indicates this type is an enum. " +
86 "`enumValues` is a valid field.",
87 },
88 "INPUT_OBJECT": &EnumValueConfig{
89 Value: TypeKindInputObject,
90 Description: "Indicates this type is an input object. " +
91 "`inputFields` is a valid field.",
92 },
93 "LIST": &EnumValueConfig{
94 Value: TypeKindList,
95 Description: "Indicates this type is a list. " +
96 "`ofType` is a valid field.",
97 },
98 "NON_NULL": &EnumValueConfig{
99 Value: TypeKindNonNull,
100 Description: "Indicates this type is a non-null. " +
101 "`ofType` is a valid field.",
102 },
103 },
104 })
105
106 DirectiveLocationEnumType = NewEnum(EnumConfig{
107 Name: "__DirectiveLocation",
108 Description: "A Directive can be adjacent to many parts of the GraphQL language, a " +
109 "__DirectiveLocation describes one such possible adjacencies.",
110 Values: EnumValueConfigMap{
111 "QUERY": &EnumValueConfig{
112 Value: DirectiveLocationQuery,
113 Description: "Location adjacent to a query operation.",
114 },
115 "MUTATION": &EnumValueConfig{
116 Value: DirectiveLocationMutation,
117 Description: "Location adjacent to a mutation operation.",
118 },
119 "SUBSCRIPTION": &EnumValueConfig{
120 Value: DirectiveLocationSubscription,
121 Description: "Location adjacent to a subscription operation.",
122 },
123 "FIELD": &EnumValueConfig{
124 Value: DirectiveLocationField,
125 Description: "Location adjacent to a field.",
126 },
127 "FRAGMENT_DEFINITION": &EnumValueConfig{
128 Value: DirectiveLocationFragmentDefinition,
129 Description: "Location adjacent to a fragment definition.",
130 },
131 "FRAGMENT_SPREAD": &EnumValueConfig{
132 Value: DirectiveLocationFragmentSpread,
133 Description: "Location adjacent to a fragment spread.",
134 },
135 "INLINE_FRAGMENT": &EnumValueConfig{
136 Value: DirectiveLocationInlineFragment,
137 Description: "Location adjacent to an inline fragment.",
138 },
139 "SCHEMA": &EnumValueConfig{
140 Value: DirectiveLocationSchema,
141 Description: "Location adjacent to a schema definition.",
142 },
143 "SCALAR": &EnumValueConfig{
144 Value: DirectiveLocationScalar,
145 Description: "Location adjacent to a scalar definition.",
146 },
147 "OBJECT": &EnumValueConfig{
148 Value: DirectiveLocationObject,
149 Description: "Location adjacent to a object definition.",
150 },
151 "FIELD_DEFINITION": &EnumValueConfig{
152 Value: DirectiveLocationFieldDefinition,
153 Description: "Location adjacent to a field definition.",
154 },
155 "ARGUMENT_DEFINITION": &EnumValueConfig{
156 Value: DirectiveLocationArgumentDefinition,
157 Description: "Location adjacent to an argument definition.",
158 },
159 "INTERFACE": &EnumValueConfig{
160 Value: DirectiveLocationInterface,
161 Description: "Location adjacent to an interface definition.",
162 },
163 "UNION": &EnumValueConfig{
164 Value: DirectiveLocationUnion,
165 Description: "Location adjacent to a union definition.",
166 },
167 "ENUM": &EnumValueConfig{
168 Value: DirectiveLocationEnum,
169 Description: "Location adjacent to an enum definition.",
170 },
171 "ENUM_VALUE": &EnumValueConfig{
172 Value: DirectiveLocationEnumValue,
173 Description: "Location adjacent to an enum value definition.",
174 },
175 "INPUT_OBJECT": &EnumValueConfig{
176 Value: DirectiveLocationInputObject,
177 Description: "Location adjacent to an input object type definition.",
178 },
179 "INPUT_FIELD_DEFINITION": &EnumValueConfig{
180 Value: DirectiveLocationInputFieldDefinition,
181 Description: "Location adjacent to an input object field definition.",
182 },
183 },
184 })
185
186 // Note: some fields (for e.g "fields", "interfaces") are defined later due to cyclic reference
187 TypeType = NewObject(ObjectConfig{
188 Name: "__Type",
189 Description: "The fundamental unit of any GraphQL Schema is the type. There are " +
190 "many kinds of types in GraphQL as represented by the `__TypeKind` enum." +
191 "\n\nDepending on the kind of a type, certain fields describe " +
192 "information about that type. Scalar types provide no information " +
193 "beyond a name and description, while Enum types provide their values. " +
194 "Object and Interface types provide the fields they describe. Abstract " +
195 "types, Union and Interface, provide the Object types possible " +
196 "at runtime. List and NonNull types compose other types.",
197
198 Fields: Fields{
199 "kind": &Field{
200 Type: NewNonNull(TypeKindEnumType),
201 Resolve: func(p ResolveParams) (interface{}, error) {
202 switch p.Source.(type) {
203 case *Scalar:
204 return TypeKindScalar, nil
205 case *Object:
206 return TypeKindObject, nil
207 case *Interface:
208 return TypeKindInterface, nil
209 case *Union:
210 return TypeKindUnion, nil
211 case *Enum:
212 return TypeKindEnum, nil
213 case *InputObject:
214 return TypeKindInputObject, nil
215 case *List:
216 return TypeKindList, nil
217 case *NonNull:
218 return TypeKindNonNull, nil
219 }
220 return nil, fmt.Errorf("Unknown kind of type: %v", p.Source)
221 },
222 },
223 "name": &Field{
224 Type: String,
225 },
226 "description": &Field{
227 Type: String,
228 },
229 "fields": &Field{},
230 "interfaces": &Field{},
231 "possibleTypes": &Field{},
232 "enumValues": &Field{},
233 "inputFields": &Field{},
234 "ofType": &Field{},
235 },
236 })
237
238 InputValueType = NewObject(ObjectConfig{
239 Name: "__InputValue",
240 Description: "Arguments provided to Fields or Directives and the input fields of an " +
241 "InputObject are represented as Input Values which describe their type " +
242 "and optionally a default value.",
243 Fields: Fields{
244 "name": &Field{
245 Type: NewNonNull(String),
246 },
247 "description": &Field{
248 Type: String,
249 },
250 "type": &Field{
251 Type: NewNonNull(TypeType),
252 },
253 "defaultValue": &Field{
254 Type: String,
255 Description: "A GraphQL-formatted string representing the default value for this " +
256 "input value.",
257 Resolve: func(p ResolveParams) (interface{}, error) {
258 if inputVal, ok := p.Source.(*Argument); ok {
259 if inputVal.DefaultValue == nil {
260 return nil, nil
261 }
262 if isNullish(inputVal.DefaultValue) {
263 return nil, nil
264 }
265 astVal := astFromValue(inputVal.DefaultValue, inputVal)
266 return printer.Print(astVal), nil
267 }
268 if inputVal, ok := p.Source.(*InputObjectField); ok {
269 if inputVal.DefaultValue == nil {
270 return nil, nil
271 }
272 astVal := astFromValue(inputVal.DefaultValue, inputVal)
273 return printer.Print(astVal), nil
274 }
275 return nil, nil
276 },
277 },
278 },
279 })
280
281 FieldType = NewObject(ObjectConfig{
282 Name: "__Field",
283 Description: "Object and Interface types are described by a list of Fields, each of " +
284 "which has a name, potentially a list of arguments, and a return type.",
285 Fields: Fields{
286 "name": &Field{
287 Type: NewNonNull(String),
288 },
289 "description": &Field{
290 Type: String,
291 },
292 "args": &Field{
293 Type: NewNonNull(NewList(NewNonNull(InputValueType))),
294 Resolve: func(p ResolveParams) (interface{}, error) {
295 if field, ok := p.Source.(*FieldDefinition); ok {
296 return field.Args, nil
297 }
298 return []interface{}{}, nil
299 },
300 },
301 "type": &Field{
302 Type: NewNonNull(TypeType),
303 },
304 "isDeprecated": &Field{
305 Type: NewNonNull(Boolean),
306 Resolve: func(p ResolveParams) (interface{}, error) {
307 if field, ok := p.Source.(*FieldDefinition); ok {
308 return (field.DeprecationReason != ""), nil
309 }
310 return false, nil
311 },
312 },
313 "deprecationReason": &Field{
314 Type: String,
315 },
316 },
317 })
318
319 DirectiveType = NewObject(ObjectConfig{
320 Name: "__Directive",
321 Description: "A Directive provides a way to describe alternate runtime execution and " +
322 "type validation behavior in a GraphQL document. " +
323 "\n\nIn some cases, you need to provide options to alter GraphQL's " +
324 "execution behavior in ways field arguments will not suffice, such as " +
325 "conditionally including or skipping a field. Directives provide this by " +
326 "describing additional information to the executor.",
327 Fields: Fields{
328 "name": &Field{
329 Type: NewNonNull(String),
330 },
331 "description": &Field{
332 Type: String,
333 },
334 "locations": &Field{
335 Type: NewNonNull(NewList(
336 NewNonNull(DirectiveLocationEnumType),
337 )),
338 },
339 "args": &Field{
340 Type: NewNonNull(NewList(
341 NewNonNull(InputValueType),
342 )),
343 },
344 // NOTE: the following three fields are deprecated and are no longer part
345 // of the GraphQL specification.
346 "onOperation": &Field{
347 DeprecationReason: "Use `locations`.",
348 Type: NewNonNull(Boolean),
349 Resolve: func(p ResolveParams) (interface{}, error) {
350 if dir, ok := p.Source.(*Directive); ok {
351 res := false
352 for _, loc := range dir.Locations {
353 if loc == DirectiveLocationQuery ||
354 loc == DirectiveLocationMutation ||
355 loc == DirectiveLocationSubscription {
356 res = true
357 break
358 }
359 }
360 return res, nil
361 }
362 return false, nil
363 },
364 },
365 "onFragment": &Field{
366 DeprecationReason: "Use `locations`.",
367 Type: NewNonNull(Boolean),
368 Resolve: func(p ResolveParams) (interface{}, error) {
369 if dir, ok := p.Source.(*Directive); ok {
370 res := false
371 for _, loc := range dir.Locations {
372 if loc == DirectiveLocationFragmentSpread ||
373 loc == DirectiveLocationInlineFragment ||
374 loc == DirectiveLocationFragmentDefinition {
375 res = true
376 break
377 }
378 }
379 return res, nil
380 }
381 return false, nil
382 },
383 },
384 "onField": &Field{
385 DeprecationReason: "Use `locations`.",
386 Type: NewNonNull(Boolean),
387 Resolve: func(p ResolveParams) (interface{}, error) {
388 if dir, ok := p.Source.(*Directive); ok {
389 res := false
390 for _, loc := range dir.Locations {
391 if loc == DirectiveLocationField {
392 res = true
393 break
394 }
395 }
396 return res, nil
397 }
398 return false, nil
399 },
400 },
401 },
402 })
403
404 SchemaType = NewObject(ObjectConfig{
405 Name: "__Schema",
406 Description: `A GraphQL Schema defines the capabilities of a GraphQL server. ` +
407 `It exposes all available types and directives on the server, as well as ` +
408 `the entry points for query, mutation, and subscription operations.`,
409 Fields: Fields{
410 "types": &Field{
411 Description: "A list of all types supported by this server.",
412 Type: NewNonNull(NewList(
413 NewNonNull(TypeType),
414 )),
415 Resolve: func(p ResolveParams) (interface{}, error) {
416 if schema, ok := p.Source.(Schema); ok {
417 results := []Type{}
418 for _, ttype := range schema.TypeMap() {
419 results = append(results, ttype)
420 }
421 return results, nil
422 }
423 return []Type{}, nil
424 },
425 },
426 "queryType": &Field{
427 Description: "The type that query operations will be rooted at.",
428 Type: NewNonNull(TypeType),
429 Resolve: func(p ResolveParams) (interface{}, error) {
430 if schema, ok := p.Source.(Schema); ok {
431 return schema.QueryType(), nil
432 }
433 return nil, nil
434 },
435 },
436 "mutationType": &Field{
437 Description: `If this server supports mutation, the type that ` +
438 `mutation operations will be rooted at.`,
439 Type: TypeType,
440 Resolve: func(p ResolveParams) (interface{}, error) {
441 if schema, ok := p.Source.(Schema); ok {
442 if schema.MutationType() != nil {
443 return schema.MutationType(), nil
444 }
445 }
446 return nil, nil
447 },
448 },
449 "subscriptionType": &Field{
450 Description: `If this server supports subscription, the type that ` +
451 `subscription operations will be rooted at.`,
452 Type: TypeType,
453 Resolve: func(p ResolveParams) (interface{}, error) {
454 if schema, ok := p.Source.(Schema); ok {
455 if schema.SubscriptionType() != nil {
456 return schema.SubscriptionType(), nil
457 }
458 }
459 return nil, nil
460 },
461 },
462 "directives": &Field{
463 Description: `A list of all directives supported by this server.`,
464 Type: NewNonNull(NewList(
465 NewNonNull(DirectiveType),
466 )),
467 Resolve: func(p ResolveParams) (interface{}, error) {
468 if schema, ok := p.Source.(Schema); ok {
469 return schema.Directives(), nil
470 }
471 return nil, nil
472 },
473 },
474 },
475 })
476
477 EnumValueType = NewObject(ObjectConfig{
478 Name: "__EnumValue",
479 Description: "One possible value for a given Enum. Enum values are unique values, not " +
480 "a placeholder for a string or numeric value. However an Enum value is " +
481 "returned in a JSON response as a string.",
482 Fields: Fields{
483 "name": &Field{
484 Type: NewNonNull(String),
485 },
486 "description": &Field{
487 Type: String,
488 },
489 "isDeprecated": &Field{
490 Type: NewNonNull(Boolean),
491 Resolve: func(p ResolveParams) (interface{}, error) {
492 if field, ok := p.Source.(*EnumValueDefinition); ok {
493 return (field.DeprecationReason != ""), nil
494 }
495 return false, nil
496 },
497 },
498 "deprecationReason": &Field{
499 Type: String,
500 },
501 },
502 })
503
504 // Again, adding field configs to __Type that have cyclic reference here
505 // because golang don't like them too much during init/compile-time
506 TypeType.AddFieldConfig("fields", &Field{
507 Type: NewList(NewNonNull(FieldType)),
508 Args: FieldConfigArgument{
509 "includeDeprecated": &ArgumentConfig{
510 Type: Boolean,
511 DefaultValue: false,
512 },
513 },
514 Resolve: func(p ResolveParams) (interface{}, error) {
515 includeDeprecated, _ := p.Args["includeDeprecated"].(bool)
516 switch ttype := p.Source.(type) {
517 case *Object:
518 if ttype == nil {
519 return nil, nil
520 }
521 fields := []*FieldDefinition{}
522 var fieldNames sort.StringSlice
523 for name, field := range ttype.Fields() {
524 if !includeDeprecated && field.DeprecationReason != "" {
525 continue
526 }
527 fieldNames = append(fieldNames, name)
528 }
529 sort.Sort(fieldNames)
530 for _, name := range fieldNames {
531 fields = append(fields, ttype.Fields()[name])
532 }
533 return fields, nil
534 case *Interface:
535 if ttype == nil {
536 return nil, nil
537 }
538 fields := []*FieldDefinition{}
539 for _, field := range ttype.Fields() {
540 if !includeDeprecated && field.DeprecationReason != "" {
541 continue
542 }
543 fields = append(fields, field)
544 }
545 return fields, nil
546 }
547 return nil, nil
548 },
549 })
550 TypeType.AddFieldConfig("interfaces", &Field{
551 Type: NewList(NewNonNull(TypeType)),
552 Resolve: func(p ResolveParams) (interface{}, error) {
553 switch ttype := p.Source.(type) {
554 case *Object:
555 return ttype.Interfaces(), nil
556 }
557 return nil, nil
558 },
559 })
560 TypeType.AddFieldConfig("possibleTypes", &Field{
561 Type: NewList(NewNonNull(TypeType)),
562 Resolve: func(p ResolveParams) (interface{}, error) {
563 switch ttype := p.Source.(type) {
564 case *Interface:
565 return p.Info.Schema.PossibleTypes(ttype), nil
566 case *Union:
567 return p.Info.Schema.PossibleTypes(ttype), nil
568 }
569 return nil, nil
570 },
571 })
572 TypeType.AddFieldConfig("enumValues", &Field{
573 Type: NewList(NewNonNull(EnumValueType)),
574 Args: FieldConfigArgument{
575 "includeDeprecated": &ArgumentConfig{
576 Type: Boolean,
577 DefaultValue: false,
578 },
579 },
580 Resolve: func(p ResolveParams) (interface{}, error) {
581 includeDeprecated, _ := p.Args["includeDeprecated"].(bool)
582 switch ttype := p.Source.(type) {
583 case *Enum:
584 if includeDeprecated {
585 return ttype.Values(), nil
586 }
587 values := []*EnumValueDefinition{}
588 for _, value := range ttype.Values() {
589 if value.DeprecationReason != "" {
590 continue
591 }
592 values = append(values, value)
593 }
594 return values, nil
595 }
596 return nil, nil
597 },
598 })
599 TypeType.AddFieldConfig("inputFields", &Field{
600 Type: NewList(NewNonNull(InputValueType)),
601 Resolve: func(p ResolveParams) (interface{}, error) {
602 switch ttype := p.Source.(type) {
603 case *InputObject:
604 fields := []*InputObjectField{}
605 for _, field := range ttype.Fields() {
606 fields = append(fields, field)
607 }
608 return fields, nil
609 }
610 return nil, nil
611 },
612 })
613 TypeType.AddFieldConfig("ofType", &Field{
614 Type: TypeType,
615 })
616
617 // Note that these are FieldDefinition and not FieldConfig,
618 // so the format for args is different.
619 SchemaMetaFieldDef = &FieldDefinition{
620 Name: "__schema",
621 Type: NewNonNull(SchemaType),
622 Description: "Access the current type schema of this server.",
623 Args: []*Argument{},
624 Resolve: func(p ResolveParams) (interface{}, error) {
625 return p.Info.Schema, nil
626 },
627 }
628 TypeMetaFieldDef = &FieldDefinition{
629 Name: "__type",
630 Type: TypeType,
631 Description: "Request the type information of a single type.",
632 Args: []*Argument{
633 {
634 PrivateName: "name",
635 Type: NewNonNull(String),
636 },
637 },
638 Resolve: func(p ResolveParams) (interface{}, error) {
639 name, ok := p.Args["name"].(string)
640 if !ok {
641 return nil, nil
642 }
643 return p.Info.Schema.Type(name), nil
644 },
645 }
646
647 TypeNameMetaFieldDef = &FieldDefinition{
648 Name: "__typename",
649 Type: NewNonNull(String),
650 Description: "The name of the current Object type at runtime.",
651 Args: []*Argument{},
652 Resolve: func(p ResolveParams) (interface{}, error) {
653 return p.Info.ParentType.Name(), nil
654 },
655 }
656
657}
658
659// Produces a GraphQL Value AST given a Golang value.
660//
661// Optionally, a GraphQL type may be provided, which will be used to
662// disambiguate between value primitives.
663//
664// | JSON Value | GraphQL Value |
665// | ------------- | -------------------- |
666// | Object | Input Object |
667// | Array | List |
668// | Boolean | Boolean |
669// | String | String / Enum Value |
670// | Number | Int / Float |
671
672func astFromValue(value interface{}, ttype Type) ast.Value {
673
674 if ttype, ok := ttype.(*NonNull); ok {
675 // Note: we're not checking that the result is non-null.
676 // This function is not responsible for validating the input value.
677 val := astFromValue(value, ttype.OfType)
678 return val
679 }
680 if isNullish(value) {
681 return nil
682 }
683 valueVal := reflect.ValueOf(value)
684 if !valueVal.IsValid() {
685 return nil
686 }
687 if valueVal.Type().Kind() == reflect.Ptr {
688 valueVal = valueVal.Elem()
689 }
690 if !valueVal.IsValid() {
691 return nil
692 }
693
694 // Convert Golang slice to GraphQL list. If the Type is a list, but
695 // the value is not an array, convert the value using the list's item type.
696 if ttype, ok := ttype.(*List); ok {
697 if valueVal.Type().Kind() == reflect.Slice {
698 itemType := ttype.OfType
699 values := []ast.Value{}
700 for i := 0; i < valueVal.Len(); i++ {
701 item := valueVal.Index(i).Interface()
702 itemAST := astFromValue(item, itemType)
703 if itemAST != nil {
704 values = append(values, itemAST)
705 }
706 }
707 return ast.NewListValue(&ast.ListValue{
708 Values: values,
709 })
710 }
711 // Because GraphQL will accept single values as a "list of one" when
712 // expecting a list, if there's a non-array value and an expected list type,
713 // create an AST using the list's item type.
714 val := astFromValue(value, ttype.OfType)
715 return val
716 }
717
718 if valueVal.Type().Kind() == reflect.Map {
719 // TODO: implement astFromValue from Map to Value
720 }
721
722 if value, ok := value.(bool); ok {
723 return ast.NewBooleanValue(&ast.BooleanValue{
724 Value: value,
725 })
726 }
727 if value, ok := value.(int); ok {
728 if ttype == Float {
729 return ast.NewIntValue(&ast.IntValue{
730 Value: fmt.Sprintf("%v.0", value),
731 })
732 }
733 return ast.NewIntValue(&ast.IntValue{
734 Value: fmt.Sprintf("%v", value),
735 })
736 }
737 if value, ok := value.(float32); ok {
738 return ast.NewFloatValue(&ast.FloatValue{
739 Value: fmt.Sprintf("%v", value),
740 })
741 }
742 if value, ok := value.(float64); ok {
743 return ast.NewFloatValue(&ast.FloatValue{
744 Value: fmt.Sprintf("%v", value),
745 })
746 }
747
748 if value, ok := value.(string); ok {
749 if _, ok := ttype.(*Enum); ok {
750 return ast.NewEnumValue(&ast.EnumValue{
751 Value: fmt.Sprintf("%v", value),
752 })
753 }
754 return ast.NewStringValue(&ast.StringValue{
755 Value: fmt.Sprintf("%v", value),
756 })
757 }
758
759 // fallback, treat as string
760 return ast.NewStringValue(&ast.StringValue{
761 Value: fmt.Sprintf("%v", value),
762 })
763}