private static IDictionary <Type, IEnumerable <ExecSelection <Info> > > CreateQueryTypeToSelectionsMapping( GraphQLType queryGraphQlType, IList <ExecSelection <Info> > selections) { var abstractFieldSelection = selections .Where(m => !m.SchemaField.Field().IsPost) .Where(s => s.TypeCondition == null); var typeConditionSelection = selections .Where(m => !m.SchemaField.Field().IsPost) .Where(s => s.TypeCondition != null) .GroupBy( s => s.TypeCondition?.Value?.Type().QueryType ?? queryGraphQlType.QueryType ) .ToDictionary(s => s.Key, s => s.AsEnumerable()); return(new List <GraphQLType> { queryGraphQlType }.Concat(queryGraphQlType.PossibleTypes) .ToDictionary( t => t.QueryType, t => abstractFieldSelection .Concat( // Add type condition selection bindings typeConditionSelection.ContainsKey(t.QueryType) ? typeConditionSelection[t.QueryType] : new List <ExecSelection <Info> >()) .Concat( t.Interfaces.SelectMany(i => typeConditionSelection.ContainsKey(i.QueryType) ? typeConditionSelection[i.QueryType] : new List <ExecSelection <Info> >())) )); }
private static LambdaExpression GetSelector(GraphQLSchema <TContext> schema, GraphQLType gqlType, IEnumerable <ExecSelection <Info> > selections, ExpressionOptions options) { var parameter = Expression.Parameter(gqlType.CLRType, "p"); var init = GetMemberInit(schema, gqlType.QueryType, selections, parameter, options); return(Expression.Lambda(init, parameter)); }
private static FieldMap MapField(Field field, GraphQLType type) { var schemaField = type.Fields.First(f => f.Name == field.Name); return(new FieldMap { ParsedField = field, SchemaField = schemaField, Children = field.Fields.Select(f => MapField(f, schemaField.Type)).ToList(), }); }
private static string CreatePropertyName(GraphQLType graphQlType, GraphQLType typeConditionType, string fieldName) { var isObjectAbstractType = graphQlType.TypeKind == TypeKind.UNION || graphQlType.TypeKind == TypeKind.INTERFACE; var isTypeConditionInterface = typeConditionType?.TypeKind == TypeKind.INTERFACE; return(isObjectAbstractType && !isTypeConditionInterface && typeConditionType != null ? GraphQLSchema <TContext> .CreatePossibleTypePropertyName(typeConditionType.Name, fieldName) : fieldName); }
public GraphQLTypeBuilder <TContext, TEntity> AddType <TEntity>(string description = null) { var type = typeof(TEntity); if (_types.Any(t => t.CLRType == type)) { throw new ArgumentException("Type has already been added"); } var gqlType = new GraphQLType(type) { IsScalar = type.IsPrimitive, Description = description ?? "" }; _types.Add(gqlType); return(new GraphQLTypeBuilder <TContext, TEntity>(this, gqlType)); }
public static GraphQLField NewMutation <TContext, TArgs, TMutReturn>(GraphQLSchema schema, string name, Func <TArgs, TMutReturn, LambdaExpression> exprFunc, Type fieldCLRType, GraphQLType definingType, Func <TContext, TArgs, TMutReturn> mutationFunc) => NewInternal <TArgs>(schema, name, exprFunc, fieldCLRType, definingType, mutationFunc);
public static GraphQLField New <TArgs>(GraphQLSchema schema, string name, Func <TArgs, LambdaExpression> exprFunc, Type fieldCLRType, GraphQLType definingType) => NewInternal <TArgs>(schema, name, exprFunc, fieldCLRType, definingType, null);
private static GraphQLField NewInternal <TArgs>(GraphQLSchema schema, string name, Delegate exprFunc, Type fieldCLRType, GraphQLType definingType, Delegate mutationFunc) { var isList = false; if (fieldCLRType.IsGenericType && TypeHelpers.IsAssignableToGenericType(fieldCLRType, typeof(IEnumerable <>))) { fieldCLRType = fieldCLRType.GetGenericArguments()[0]; isList = true; } return(new GraphQLField { Schema = schema, Name = name, FieldCLRType = fieldCLRType, DefiningType = definingType, ArgsCLRType = typeof(TArgs), IsList = isList, ExprFunc = exprFunc, MutationFunc = mutationFunc, IsMutation = mutationFunc != null, }); }
internal GraphQLTypeBuilder(GraphQLSchema <TContext> schema, GraphQLType type) { _schema = schema; _type = type; }
private static bool IsEqualToAnyAncestorType(GraphQLType type, string referenceTypeName) { return(type != null && (type.Name == referenceTypeName || IsEqualToAnyAncestorType(type?.BaseType, referenceTypeName))); }
private static MemberBinding GetBinding(GraphQLSchema <TContext> schema, ExecSelection <Info> map, GraphQLType graphQlType, Expression baseBindingExpr, ExpressionOptions options) { var toType = graphQlType.QueryType; var field = map.SchemaField.Field(); var needsTypeCheck = baseBindingExpr.Type != map.SchemaField.DeclaringType?.Info?.Type?.CLRType; var propertyName = CreatePropertyName(graphQlType, map.TypeCondition?.Value?.Type(), map.SchemaField.FieldName); var toMember = toType.GetProperty( propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); if (toMember == null) { throw new Exception($"The field '{map.SchemaField.FieldName}' does not exist on type '{graphQlType.Name}'."); } // expr is form of: (context, entity) => entity.Field var expr = field.GetExpression(map.Arguments.Values()); // The field might be defined on sub-types of the specified type, so add a type cast if necessary. // If appropriate, `entity.Field` becomes `(entity as TFieldDefiningType).Field` var typeCastedBaseExpression = needsTypeCheck ? Expression.TypeAs(baseBindingExpr, field.DefiningType.CLRType) : baseBindingExpr; // Replace (entity) with baseBindingExpr, note expression is no longer a LambdaExpression // `(context, entity) => entity.Field` becomes `someOtherEntity.Entity.Field` where baseBindingExpr is `someOtherEntity.Entity` var replacedBase = ParameterReplacer.Replace(expr.Body, expr.Parameters[1], typeCastedBaseExpression); // If the entity has to be casted, add a type check: // `(someOtherEntity.Entity as TFieldDefininingType).Field` becomes `(someOtherEntity.Entity is TFieldDefininingType) ? (someOtherEntity.Entity as TFieldDefininingType).Field : null` if (needsTypeCheck && options.TypeCheckInheritance) { // The expression type has to be nullable if (replacedBase.Type.IsValueType) { replacedBase = Expression.Convert(replacedBase, typeof(Nullable <>).MakeGenericType(replacedBase.Type)); } var typeCheck = Expression.TypeIs(baseBindingExpr, field.DefiningType.CLRType); var nullResult = Expression.Constant(null, replacedBase.Type); replacedBase = Expression.Condition(typeCheck, replacedBase, nullResult); } // This just makes sure that the (context) parameter is the same as the one used by the whole query var replacedContext = ParameterReplacer.Replace(replacedBase, expr.Parameters[0], GraphQLSchema <TContext> .DbParam); // If there aren't any children, then we can assume that this is a scalar entity and we don't have to map child fields if (!map.Selections.Any()) { if (!field.IsList) { if (options.CastAssignment && expr.Body.Type != toMember.PropertyType) { replacedContext = Expression.Convert(replacedContext, toMember.PropertyType); } return(Expression.Bind(toMember, replacedContext)); } return(Expression.Bind(toMember, options.NullCheckLists ? NullPropagate(replacedContext, replacedContext) : replacedContext)); } // If binding a single entity, just use the already built selector expression (replaced context) // Otherwise, if binding to a list, introduce a new parameter that will be used in a call to .Select var listParameter = Expression.Parameter (field.Type.CLRType, field.Type.CLRType.Name.Substring(0, 1).ToLower()); // TODO: Name conflicts in expressions? var bindChildrenTo = map.SchemaField.Field().IsList ? listParameter : replacedContext; // Now that we have our new binding parameter, build the tree for the rest of the children var memberInit = GetMemberInit(schema, field.Type, map.Selections.Values(), bindChildrenTo, options); // For single entities, we're done and we can just bind to the memberInit expression if (!field.IsList) { return(Expression.Bind(toMember, memberInit)); } // However for lists, we need to check for null and call .Select() and .ToList() first var selectLambda = Expression.Lambda(memberInit, listParameter); var call = Expression.Call(typeof(Enumerable), "Select", new[] { field.Type.CLRType, field.Type.QueryType }, replacedContext, selectLambda); var toList = Expression.Call(typeof(Enumerable), "ToList", new[] { field.Type.QueryType }, call); return(Expression.Bind(toMember, options.NullCheckLists ? (Expression)NullPropagate(replacedContext, toList) : toList)); }
private static ConditionalExpression GetMemberInit(GraphQLSchema <TContext> schema, GraphQLType graphQlType, IEnumerable <ExecSelection <Info> > selectionsEnumerable, Expression parameterExpression, ExpressionOptions options) { var queryType = graphQlType.QueryType; // Avoid possible multiple enumeration of selections-enumerable var selections = selectionsEnumerable as IList <ExecSelection <Info> > ?? selectionsEnumerable.ToList(); // The '__typename'-field selection has to be added for queries with type conditions var typeConditionButNoTypeNameSelection = selections.Any() && selections.Any(s => s.TypeCondition != null); // Any '__typename' selection have to be replaced by the '__typename' selection of the target type' '__typename'-field. var typeNameConditionHasToBeReplaced = selections.Any(s => s.Name == "__typename"); var bindingsWithPossibleDuplicates = selections.Where(s => !s.SchemaField.Info.Field.IsPost) .Where(s => s.Name != "__typename") .Select(s => GetBinding(schema, s, graphQlType, parameterExpression, options)) .ToList(); var uniqueBindingMemberNames = bindingsWithPossibleDuplicates.Select(b => b.Member.Name).Distinct(); var bindings = uniqueBindingMemberNames.Select(name => bindingsWithPossibleDuplicates.First(b => b.Member.Name == name)); // Add selection for '__typename'-field of the proper type if (typeConditionButNoTypeNameSelection || typeNameConditionHasToBeReplaced) { // Find the base types' `__typename` field var typeNameField = graphQlType?.Fields.Find(f => f.Name == "__typename"); if (typeNameField != null && !typeNameField.IsPost) { var typeNameExecSelection = new ExecSelection <Info>( new SchemaField( schema.Adapter.QueryTypes[graphQlType?.Name], typeNameField, schema.Adapter), new FSharpOption <string>(typeNameField?.Name), null, new WithSource <ExecArgument <Info> >[] {}, new WithSource <ExecDirective <Info> >[] {}, new WithSource <ExecSelection <Info> >[] {} ); bindings = bindings .Where(b => b.Member.Name != "__typename") .Concat(new[] { GetBinding(schema, typeNameExecSelection, graphQlType, parameterExpression, options) }) .ToList(); } } var memberInit = Expression.MemberInit(Expression.New(queryType), bindings); return(NullPropagate(parameterExpression, memberInit)); }
// Set return type, e.g. union types. internal void SetReturnType(GraphQLType type) { _type = type; }