package graphql import ( "context" "fmt" "reflect" "regexp" "github.com/graphql-go/graphql/language/ast" ) // Type interface for all of the possible kinds of GraphQL types type Type interface { Name() string Description() string String() string Error() error } var _ Type = (*Scalar)(nil) var _ Type = (*Object)(nil) var _ Type = (*Interface)(nil) var _ Type = (*Union)(nil) var _ Type = (*Enum)(nil) var _ Type = (*InputObject)(nil) var _ Type = (*List)(nil) var _ Type = (*NonNull)(nil) var _ Type = (*Argument)(nil) // Input interface for types that may be used as input types for arguments and directives. type Input interface { Name() string Description() string String() string Error() error } var _ Input = (*Scalar)(nil) var _ Input = (*Enum)(nil) var _ Input = (*InputObject)(nil) var _ Input = (*List)(nil) var _ Input = (*NonNull)(nil) // IsInputType determines if given type is a GraphQLInputType func IsInputType(ttype Type) bool { named := GetNamed(ttype) if _, ok := named.(*Scalar); ok { return true } if _, ok := named.(*Enum); ok { return true } if _, ok := named.(*InputObject); ok { return true } return false } // IsOutputType determines if given type is a GraphQLOutputType func IsOutputType(ttype Type) bool { name := GetNamed(ttype) if _, ok := name.(*Scalar); ok { return true } if _, ok := name.(*Object); ok { return true } if _, ok := name.(*Interface); ok { return true } if _, ok := name.(*Union); ok { return true } if _, ok := name.(*Enum); ok { return true } return false } // Leaf interface for types that may be leaf values type Leaf interface { Name() string Description() string String() string Error() error Serialize(value interface{}) interface{} } var _ Leaf = (*Scalar)(nil) var _ Leaf = (*Enum)(nil) // IsLeafType determines if given type is a leaf value func IsLeafType(ttype Type) bool { named := GetNamed(ttype) if _, ok := named.(*Scalar); ok { return true } if _, ok := named.(*Enum); ok { return true } return false } // Output interface for types that may be used as output types as the result of fields. type Output interface { Name() string Description() string String() string Error() error } var _ Output = (*Scalar)(nil) var _ Output = (*Object)(nil) var _ Output = (*Interface)(nil) var _ Output = (*Union)(nil) var _ Output = (*Enum)(nil) var _ Output = (*List)(nil) var _ Output = (*NonNull)(nil) // Composite interface for types that may describe the parent context of a selection set. type Composite interface { Name() string Description() string String() string Error() error } var _ Composite = (*Object)(nil) var _ Composite = (*Interface)(nil) var _ Composite = (*Union)(nil) // IsCompositeType determines if given type is a GraphQLComposite type func IsCompositeType(ttype interface{}) bool { if _, ok := ttype.(*Object); ok { return true } if _, ok := ttype.(*Interface); ok { return true } if _, ok := ttype.(*Union); ok { return true } return false } // Abstract interface for types that may describe the parent context of a selection set. type Abstract interface { Name() string } var _ Abstract = (*Interface)(nil) var _ Abstract = (*Union)(nil) func IsAbstractType(ttype interface{}) bool { if _, ok := ttype.(*Interface); ok { return true } if _, ok := ttype.(*Union); ok { return true } return false } // Nullable interface for types that can accept null as a value. type Nullable interface { } var _ Nullable = (*Scalar)(nil) var _ Nullable = (*Object)(nil) var _ Nullable = (*Interface)(nil) var _ Nullable = (*Union)(nil) var _ Nullable = (*Enum)(nil) var _ Nullable = (*InputObject)(nil) var _ Nullable = (*List)(nil) // GetNullable returns the Nullable type of the given GraphQL type func GetNullable(ttype Type) Nullable { if ttype, ok := ttype.(*NonNull); ok { return ttype.OfType } return ttype } // Named interface for types that do not include modifiers like List or NonNull. type Named interface { String() string } var _ Named = (*Scalar)(nil) var _ Named = (*Object)(nil) var _ Named = (*Interface)(nil) var _ Named = (*Union)(nil) var _ Named = (*Enum)(nil) var _ Named = (*InputObject)(nil) // GetNamed returns the Named type of the given GraphQL type func GetNamed(ttype Type) Named { unmodifiedType := ttype for { if ttype, ok := unmodifiedType.(*List); ok { unmodifiedType = ttype.OfType continue } if ttype, ok := unmodifiedType.(*NonNull); ok { unmodifiedType = ttype.OfType continue } break } return unmodifiedType } // Scalar Type Definition // // The leaf values of any request and input values to arguments are // Scalars (or Enums) and are defined with a name and a series of functions // used to parse input from ast or variables and to ensure validity. // // Example: // // var OddType = new Scalar({ // name: 'Odd', // serialize(value) { // return value % 2 === 1 ? value : null; // } // }); // type Scalar struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` scalarConfig ScalarConfig err error } // SerializeFn is a function type for serializing a GraphQLScalar type value type SerializeFn func(value interface{}) interface{} // ParseValueFn is a function type for parsing the value of a GraphQLScalar type type ParseValueFn func(value interface{}) interface{} // ParseLiteralFn is a function type for parsing the literal value of a GraphQLScalar type type ParseLiteralFn func(valueAST ast.Value) interface{} // ScalarConfig options for creating a new GraphQLScalar type ScalarConfig struct { Name string `json:"name"` Description string `json:"description"` Serialize SerializeFn ParseValue ParseValueFn ParseLiteral ParseLiteralFn } // NewScalar creates a new GraphQLScalar func NewScalar(config ScalarConfig) *Scalar { st := &Scalar{} err := invariant(config.Name != "", "Type must be named.") if err != nil { st.err = err return st } err = assertValidName(config.Name) if err != nil { st.err = err return st } st.PrivateName = config.Name st.PrivateDescription = config.Description err = invariantf( config.Serialize != nil, `%v must provide "serialize" function. If this custom Scalar is `+ `also used as an input type, ensure "parseValue" and "parseLiteral" `+ `functions are also provided.`, st, ) if err != nil { st.err = err return st } if config.ParseValue != nil || config.ParseLiteral != nil { err = invariantf( config.ParseValue != nil && config.ParseLiteral != nil, `%v must provide both "parseValue" and "parseLiteral" functions.`, st, ) if err != nil { st.err = err return st } } st.scalarConfig = config return st } func (st *Scalar) Serialize(value interface{}) interface{} { if st.scalarConfig.Serialize == nil { return value } return st.scalarConfig.Serialize(value) } func (st *Scalar) ParseValue(value interface{}) interface{} { if st.scalarConfig.ParseValue == nil { return value } return st.scalarConfig.ParseValue(value) } func (st *Scalar) ParseLiteral(valueAST ast.Value) interface{} { if st.scalarConfig.ParseLiteral == nil { return nil } return st.scalarConfig.ParseLiteral(valueAST) } func (st *Scalar) Name() string { return st.PrivateName } func (st *Scalar) Description() string { return st.PrivateDescription } func (st *Scalar) String() string { return st.PrivateName } func (st *Scalar) Error() error { return st.err } // Object Type Definition // // Almost all of the GraphQL types you define will be object Object types // have a name, but most importantly describe their fields. // Example: // // var AddressType = new Object({ // name: 'Address', // fields: { // street: { type: String }, // number: { type: Int }, // formatted: { // type: String, // resolve(obj) { // return obj.number + ' ' + obj.street // } // } // } // }); // // When two types need to refer to each other, or a type needs to refer to // itself in a field, you can use a function expression (aka a closure or a // thunk) to supply the fields lazily. // // Example: // // var PersonType = new Object({ // name: 'Person', // fields: () => ({ // name: { type: String }, // bestFriend: { type: PersonType }, // }) // }); // // / type Object struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` IsTypeOf IsTypeOfFn typeConfig ObjectConfig initialisedFields bool fields FieldDefinitionMap initialisedInterfaces bool interfaces []*Interface // Interim alternative to throwing an error during schema definition at run-time err error } // IsTypeOfParams Params for IsTypeOfFn() type IsTypeOfParams struct { // Value that needs to be resolve. // Use this to decide which GraphQLObject this value maps to. Value interface{} // Info is a collection of information about the current execution state. Info ResolveInfo // Context argument is a context value that is provided to every resolve function within an execution. // It is commonly // used to represent an authenticated user, or request-specific caches. Context context.Context } type IsTypeOfFn func(p IsTypeOfParams) bool type InterfacesThunk func() []*Interface type ObjectConfig struct { Name string `json:"name"` Interfaces interface{} `json:"interfaces"` Fields interface{} `json:"fields"` IsTypeOf IsTypeOfFn `json:"isTypeOf"` Description string `json:"description"` } type FieldsThunk func() Fields func NewObject(config ObjectConfig) *Object { objectType := &Object{} err := invariant(config.Name != "", "Type must be named.") if err != nil { objectType.err = err return objectType } err = assertValidName(config.Name) if err != nil { objectType.err = err return objectType } objectType.PrivateName = config.Name objectType.PrivateDescription = config.Description objectType.IsTypeOf = config.IsTypeOf objectType.typeConfig = config return objectType } func (gt *Object) AddFieldConfig(fieldName string, fieldConfig *Field) { if fieldName == "" || fieldConfig == nil { return } switch gt.typeConfig.Fields.(type) { case Fields: gt.typeConfig.Fields.(Fields)[fieldName] = fieldConfig gt.initialisedFields = false } } func (gt *Object) Name() string { return gt.PrivateName } func (gt *Object) Description() string { return "" } func (gt *Object) String() string { return gt.PrivateName } func (gt *Object) Fields() FieldDefinitionMap { if gt.initialisedFields { return gt.fields } var configureFields Fields switch gt.typeConfig.Fields.(type) { case Fields: configureFields = gt.typeConfig.Fields.(Fields) case FieldsThunk: configureFields = gt.typeConfig.Fields.(FieldsThunk)() } fields, err := defineFieldMap(gt, configureFields) gt.err = err gt.fields = fields gt.initialisedFields = true return gt.fields } func (gt *Object) Interfaces() []*Interface { if gt.initialisedInterfaces { return gt.interfaces } var configInterfaces []*Interface switch gt.typeConfig.Interfaces.(type) { case InterfacesThunk: configInterfaces = gt.typeConfig.Interfaces.(InterfacesThunk)() case []*Interface: configInterfaces = gt.typeConfig.Interfaces.([]*Interface) case nil: default: gt.err = fmt.Errorf("Unknown Object.Interfaces type: %T", gt.typeConfig.Interfaces) gt.initialisedInterfaces = true return nil } interfaces, err := defineInterfaces(gt, configInterfaces) gt.err = err gt.interfaces = interfaces gt.initialisedInterfaces = true return gt.interfaces } func (gt *Object) Error() error { return gt.err } func defineInterfaces(ttype *Object, interfaces []*Interface) ([]*Interface, error) { ifaces := []*Interface{} if len(interfaces) == 0 { return ifaces, nil } for _, iface := range interfaces { err := invariantf( iface != nil, `%v may only implement Interface types, it cannot implement: %v.`, ttype, iface, ) if err != nil { return ifaces, err } if iface.ResolveType != nil { err = invariantf( iface.ResolveType != nil, `Interface Type %v does not provide a "resolveType" function `+ `and implementing Type %v does not provide a "isTypeOf" `+ `function. There is no way to resolve this implementing type `+ `during execution.`, iface, ttype, ) if err != nil { return ifaces, err } } ifaces = append(ifaces, iface) } return ifaces, nil } func defineFieldMap(ttype Named, fieldMap Fields) (FieldDefinitionMap, error) { resultFieldMap := FieldDefinitionMap{} err := invariantf( len(fieldMap) > 0, `%v fields must be an object with field names as keys or a function which return such an object.`, ttype, ) if err != nil { return resultFieldMap, err } for fieldName, field := range fieldMap { if field == nil { continue } err = invariantf( field.Type != nil, `%v.%v field type must be Output Type but got: %v.`, ttype, fieldName, field.Type, ) if err != nil { return resultFieldMap, err } if field.Type.Error() != nil { return resultFieldMap, field.Type.Error() } err = assertValidName(fieldName) if err != nil { return resultFieldMap, err } fieldDef := &FieldDefinition{ Name: fieldName, Description: field.Description, Type: field.Type, Resolve: field.Resolve, DeprecationReason: field.DeprecationReason, } fieldDef.Args = []*Argument{} for argName, arg := range field.Args { err := assertValidName(argName) if err != nil { return resultFieldMap, err } err = invariantf( arg != nil, `%v.%v args must be an object with argument names as keys.`, ttype, fieldName, ) if err != nil { return resultFieldMap, err } err = invariantf( arg.Type != nil, `%v.%v(%v:) argument type must be Input Type but got: %v.`, ttype, fieldName, argName, arg.Type, ) if err != nil { return resultFieldMap, err } fieldArg := &Argument{ PrivateName: argName, PrivateDescription: arg.Description, Type: arg.Type, DefaultValue: arg.DefaultValue, } fieldDef.Args = append(fieldDef.Args, fieldArg) } resultFieldMap[fieldName] = fieldDef } return resultFieldMap, nil } // ResolveParams Params for FieldResolveFn() type ResolveParams struct { // Source is the source value Source interface{} // Args is a map of arguments for current GraphQL request Args map[string]interface{} // Info is a collection of information about the current execution state. Info ResolveInfo // Context argument is a context value that is provided to every resolve function within an execution. // It is commonly // used to represent an authenticated user, or request-specific caches. Context context.Context } type FieldResolveFn func(p ResolveParams) (interface{}, error) type ResolveInfo struct { FieldName string FieldASTs []*ast.Field ReturnType Output ParentType Composite Schema Schema Fragments map[string]ast.Definition RootValue interface{} Operation ast.Definition VariableValues map[string]interface{} } type Fields map[string]*Field type Field struct { Name string `json:"name"` // used by graphlql-relay Type Output `json:"type"` Args FieldConfigArgument `json:"args"` Resolve FieldResolveFn `json:"-"` DeprecationReason string `json:"deprecationReason"` Description string `json:"description"` } type FieldConfigArgument map[string]*ArgumentConfig type ArgumentConfig struct { Type Input `json:"type"` DefaultValue interface{} `json:"defaultValue"` Description string `json:"description"` } type FieldDefinitionMap map[string]*FieldDefinition type FieldDefinition struct { Name string `json:"name"` Description string `json:"description"` Type Output `json:"type"` Args []*Argument `json:"args"` Resolve FieldResolveFn `json:"-"` DeprecationReason string `json:"deprecationReason"` } type FieldArgument struct { Name string `json:"name"` Type Type `json:"type"` DefaultValue interface{} `json:"defaultValue"` Description string `json:"description"` } type Argument struct { PrivateName string `json:"name"` Type Input `json:"type"` DefaultValue interface{} `json:"defaultValue"` PrivateDescription string `json:"description"` } func (st *Argument) Name() string { return st.PrivateName } func (st *Argument) Description() string { return st.PrivateDescription } func (st *Argument) String() string { return st.PrivateName } func (st *Argument) Error() error { return nil } // Interface Type Definition // // When a field can return one of a heterogeneous set of types, a Interface type // is used to describe what types are possible, what fields are in common across // all types, as well as a function to determine which type is actually used // when the field is resolved. // // Example: // // var EntityType = new Interface({ // name: 'Entity', // fields: { // name: { type: String } // } // }); // // type Interface struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` ResolveType ResolveTypeFn typeConfig InterfaceConfig initialisedFields bool fields FieldDefinitionMap err error } type InterfaceConfig struct { Name string `json:"name"` Fields interface{} `json:"fields"` ResolveType ResolveTypeFn Description string `json:"description"` } // ResolveTypeParams Params for ResolveTypeFn() type ResolveTypeParams struct { // Value that needs to be resolve. // Use this to decide which GraphQLObject this value maps to. Value interface{} // Info is a collection of information about the current execution state. Info ResolveInfo // Context argument is a context value that is provided to every resolve function within an execution. // It is commonly // used to represent an authenticated user, or request-specific caches. Context context.Context } type ResolveTypeFn func(p ResolveTypeParams) *Object func NewInterface(config InterfaceConfig) *Interface { it := &Interface{} err := invariant(config.Name != "", "Type must be named.") if err != nil { it.err = err return it } err = assertValidName(config.Name) if err != nil { it.err = err return it } it.PrivateName = config.Name it.PrivateDescription = config.Description it.ResolveType = config.ResolveType it.typeConfig = config return it } func (it *Interface) AddFieldConfig(fieldName string, fieldConfig *Field) { if fieldName == "" || fieldConfig == nil { return } switch it.typeConfig.Fields.(type) { case Fields: it.typeConfig.Fields.(Fields)[fieldName] = fieldConfig it.initialisedFields = false } } func (it *Interface) Name() string { return it.PrivateName } func (it *Interface) Description() string { return it.PrivateDescription } func (it *Interface) Fields() (fields FieldDefinitionMap) { if it.initialisedFields { return it.fields } var configureFields Fields switch it.typeConfig.Fields.(type) { case Fields: configureFields = it.typeConfig.Fields.(Fields) case FieldsThunk: configureFields = it.typeConfig.Fields.(FieldsThunk)() } fields, err := defineFieldMap(it, configureFields) it.err = err it.fields = fields it.initialisedFields = true return it.fields } func (it *Interface) String() string { return it.PrivateName } func (it *Interface) Error() error { return it.err } // Union Type Definition // // When a field can return one of a heterogeneous set of types, a Union type // is used to describe what types are possible as well as providing a function // to determine which type is actually used when the field is resolved. // // Example: // // var PetType = new Union({ // name: 'Pet', // types: [ DogType, CatType ], // resolveType(value) { // if (value instanceof Dog) { // return DogType; // } // if (value instanceof Cat) { // return CatType; // } // } // }); type Union struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` ResolveType ResolveTypeFn typeConfig UnionConfig types []*Object possibleTypes map[string]bool err error } type UnionConfig struct { Name string `json:"name"` Types []*Object `json:"types"` ResolveType ResolveTypeFn Description string `json:"description"` } func NewUnion(config UnionConfig) *Union { objectType := &Union{} err := invariant(config.Name != "", "Type must be named.") if err != nil { objectType.err = err return objectType } err = assertValidName(config.Name) if err != nil { objectType.err = err return objectType } objectType.PrivateName = config.Name objectType.PrivateDescription = config.Description objectType.ResolveType = config.ResolveType err = invariantf( len(config.Types) > 0, `Must provide Array of types for Union %v.`, config.Name, ) if err != nil { objectType.err = err return objectType } for _, ttype := range config.Types { err := invariantf( ttype != nil, `%v may only contain Object types, it cannot contain: %v.`, objectType, ttype, ) if err != nil { objectType.err = err return objectType } if objectType.ResolveType == nil { err = invariantf( ttype.IsTypeOf != nil, `Union Type %v does not provide a "resolveType" function `+ `and possible Type %v does not provide a "isTypeOf" `+ `function. There is no way to resolve this possible type `+ `during execution.`, objectType, ttype, ) if err != nil { objectType.err = err return objectType } } } objectType.types = config.Types objectType.typeConfig = config return objectType } func (ut *Union) Types() []*Object { return ut.types } func (ut *Union) String() string { return ut.PrivateName } func (ut *Union) Name() string { return ut.PrivateName } func (ut *Union) Description() string { return ut.PrivateDescription } func (ut *Union) Error() error { return ut.err } // Enum Type Definition // // Some leaf values of requests and input values are Enums. GraphQL serializes // Enum values as strings, however internally Enums can be represented by any // kind of type, often integers. // // Example: // // var RGBType = new Enum({ // name: 'RGB', // values: { // RED: { value: 0 }, // GREEN: { value: 1 }, // BLUE: { value: 2 } // } // }); // // Note: If a value is not provided in a definition, the name of the enum value // will be used as its internal value. type Enum struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` enumConfig EnumConfig values []*EnumValueDefinition valuesLookup map[interface{}]*EnumValueDefinition nameLookup map[string]*EnumValueDefinition err error } type EnumValueConfigMap map[string]*EnumValueConfig type EnumValueConfig struct { Value interface{} `json:"value"` DeprecationReason string `json:"deprecationReason"` Description string `json:"description"` } type EnumConfig struct { Name string `json:"name"` Values EnumValueConfigMap `json:"values"` Description string `json:"description"` } type EnumValueDefinition struct { Name string `json:"name"` Value interface{} `json:"value"` DeprecationReason string `json:"deprecationReason"` Description string `json:"description"` } func NewEnum(config EnumConfig) *Enum { gt := &Enum{} gt.enumConfig = config err := assertValidName(config.Name) if err != nil { gt.err = err return gt } gt.PrivateName = config.Name gt.PrivateDescription = config.Description gt.values, err = gt.defineEnumValues(config.Values) if err != nil { gt.err = err return gt } return gt } func (gt *Enum) defineEnumValues(valueMap EnumValueConfigMap) ([]*EnumValueDefinition, error) { values := []*EnumValueDefinition{} err := invariantf( len(valueMap) > 0, `%v values must be an object with value names as keys.`, gt, ) if err != nil { return values, err } for valueName, valueConfig := range valueMap { err := invariantf( valueConfig != nil, `%v.%v must refer to an object with a "value" key `+ `representing an internal value but got: %v.`, gt, valueName, valueConfig, ) if err != nil { return values, err } err = assertValidName(valueName) if err != nil { return values, err } value := &EnumValueDefinition{ Name: valueName, Value: valueConfig.Value, DeprecationReason: valueConfig.DeprecationReason, Description: valueConfig.Description, } if value.Value == nil { value.Value = valueName } values = append(values, value) } return values, nil } func (gt *Enum) Values() []*EnumValueDefinition { return gt.values } func (gt *Enum) Serialize(value interface{}) interface{} { v := value if reflect.ValueOf(v).Kind() == reflect.Ptr { v = reflect.Indirect(reflect.ValueOf(v)).Interface() } if enumValue, ok := gt.getValueLookup()[v]; ok { return enumValue.Name } return nil } func (gt *Enum) ParseValue(value interface{}) interface{} { var v string switch value := value.(type) { case string: v = value case *string: v = *value default: return nil } if enumValue, ok := gt.getNameLookup()[v]; ok { return enumValue.Value } return nil } func (gt *Enum) ParseLiteral(valueAST ast.Value) interface{} { if valueAST, ok := valueAST.(*ast.EnumValue); ok { if enumValue, ok := gt.getNameLookup()[valueAST.Value]; ok { return enumValue.Value } } return nil } func (gt *Enum) Name() string { return gt.PrivateName } func (gt *Enum) Description() string { return gt.PrivateDescription } func (gt *Enum) String() string { return gt.PrivateName } func (gt *Enum) Error() error { return gt.err } func (gt *Enum) getValueLookup() map[interface{}]*EnumValueDefinition { if len(gt.valuesLookup) > 0 { return gt.valuesLookup } valuesLookup := map[interface{}]*EnumValueDefinition{} for _, value := range gt.Values() { valuesLookup[value.Value] = value } gt.valuesLookup = valuesLookup return gt.valuesLookup } func (gt *Enum) getNameLookup() map[string]*EnumValueDefinition { if len(gt.nameLookup) > 0 { return gt.nameLookup } nameLookup := map[string]*EnumValueDefinition{} for _, value := range gt.Values() { nameLookup[value.Name] = value } gt.nameLookup = nameLookup return gt.nameLookup } // InputObject Type Definition // // An input object defines a structured collection of fields which may be // supplied to a field argument. // // Using `NonNull` will ensure that a value must be provided by the query // // Example: // // var GeoPoint = new InputObject({ // name: 'GeoPoint', // fields: { // lat: { type: new NonNull(Float) }, // lon: { type: new NonNull(Float) }, // alt: { type: Float, defaultValue: 0 }, // } // }); type InputObject struct { PrivateName string `json:"name"` PrivateDescription string `json:"description"` typeConfig InputObjectConfig fields InputObjectFieldMap init bool err error } type InputObjectFieldConfig struct { Type Input `json:"type"` DefaultValue interface{} `json:"defaultValue"` Description string `json:"description"` } type InputObjectField struct { PrivateName string `json:"name"` Type Input `json:"type"` DefaultValue interface{} `json:"defaultValue"` PrivateDescription string `json:"description"` } func (st *InputObjectField) Name() string { return st.PrivateName } func (st *InputObjectField) Description() string { return st.PrivateDescription } func (st *InputObjectField) String() string { return st.PrivateName } func (st *InputObjectField) Error() error { return nil } type InputObjectConfigFieldMap map[string]*InputObjectFieldConfig type InputObjectFieldMap map[string]*InputObjectField type InputObjectConfigFieldMapThunk func() InputObjectConfigFieldMap type InputObjectConfig struct { Name string `json:"name"` Fields interface{} `json:"fields"` Description string `json:"description"` } func NewInputObject(config InputObjectConfig) *InputObject { gt := &InputObject{} err := invariant(config.Name != "", "Type must be named.") if err != nil { gt.err = err return gt } gt.PrivateName = config.Name gt.PrivateDescription = config.Description gt.typeConfig = config //gt.fields = gt.defineFieldMap() return gt } func (gt *InputObject) defineFieldMap() InputObjectFieldMap { var fieldMap InputObjectConfigFieldMap switch gt.typeConfig.Fields.(type) { case InputObjectConfigFieldMap: fieldMap = gt.typeConfig.Fields.(InputObjectConfigFieldMap) case InputObjectConfigFieldMapThunk: fieldMap = gt.typeConfig.Fields.(InputObjectConfigFieldMapThunk)() } resultFieldMap := InputObjectFieldMap{} err := invariantf( len(fieldMap) > 0, `%v fields must be an object with field names as keys or a function which return such an object.`, gt, ) if err != nil { gt.err = err return resultFieldMap } for fieldName, fieldConfig := range fieldMap { if fieldConfig == nil { continue } err := assertValidName(fieldName) if err != nil { continue } err = invariantf( fieldConfig.Type != nil, `%v.%v field type must be Input Type but got: %v.`, gt, fieldName, fieldConfig.Type, ) if err != nil { gt.err = err return resultFieldMap } field := &InputObjectField{} field.PrivateName = fieldName field.Type = fieldConfig.Type field.PrivateDescription = fieldConfig.Description field.DefaultValue = fieldConfig.DefaultValue resultFieldMap[fieldName] = field } gt.init = true return resultFieldMap } func (gt *InputObject) Fields() InputObjectFieldMap { if !gt.init { gt.fields = gt.defineFieldMap() } return gt.fields } func (gt *InputObject) Name() string { return gt.PrivateName } func (gt *InputObject) Description() string { return gt.PrivateDescription } func (gt *InputObject) String() string { return gt.PrivateName } func (gt *InputObject) Error() error { return gt.err } // List Modifier // // A list is a kind of type marker, a wrapping type which points to another // type. Lists are often created within the context of defining the fields of // an object type. // // Example: // // var PersonType = new Object({ // name: 'Person', // fields: () => ({ // parents: { type: new List(Person) }, // children: { type: new List(Person) }, // }) // }) // type List struct { OfType Type `json:"ofType"` err error } func NewList(ofType Type) *List { gl := &List{} err := invariantf(ofType != nil, `Can only create List of a Type but got: %v.`, ofType) if err != nil { gl.err = err return gl } gl.OfType = ofType return gl } func (gl *List) Name() string { return fmt.Sprintf("%v", gl.OfType) } func (gl *List) Description() string { return "" } func (gl *List) String() string { if gl.OfType != nil { return fmt.Sprintf("[%v]", gl.OfType) } return "" } func (gl *List) Error() error { return gl.err } // NonNull Modifier // // A non-null is a kind of type marker, a wrapping type which points to another // type. Non-null types enforce that their values are never null and can ensure // an error is raised if this ever occurs during a request. It is useful for // fields which you can make a strong guarantee on non-nullability, for example // usually the id field of a database row will never be null. // // Example: // // var RowType = new Object({ // name: 'Row', // fields: () => ({ // id: { type: new NonNull(String) }, // }) // }) // // Note: the enforcement of non-nullability occurs within the executor. type NonNull struct { OfType Type `json:"ofType"` err error } func NewNonNull(ofType Type) *NonNull { gl := &NonNull{} _, isOfTypeNonNull := ofType.(*NonNull) err := invariantf(ofType != nil && !isOfTypeNonNull, `Can only create NonNull of a Nullable Type but got: %v.`, ofType) if err != nil { gl.err = err return gl } gl.OfType = ofType return gl } func (gl *NonNull) Name() string { return fmt.Sprintf("%v!", gl.OfType) } func (gl *NonNull) Description() string { return "" } func (gl *NonNull) String() string { if gl.OfType != nil { return gl.Name() } return "" } func (gl *NonNull) Error() error { return gl.err } var NameRegExp, _ = regexp.Compile("^[_a-zA-Z][_a-zA-Z0-9]*$") func assertValidName(name string) error { return invariantf( NameRegExp.MatchString(name), `Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "%v" does not.`, name) }