/// <summary> /// Generates a text snippet of arguments to be used in a query /// </summary> /// <typeparam name="T">The model to serialize</typeparam> /// <param name="query">The queryable chain</param> /// <param name="propertyName">The property to derive arguments from the queryable with</param> /// <returns>A string snippet that is appended onto a line in the query, or blank if no arguments were found</returns> public string GenerateArguments <T>(Interfaces.IQueryable query, string propertyName) { var arguments = query.GetArguments(propertyName); if (arguments == null) { return(""); } var sb = new StringBuilder(); sb.Append("("); foreach (var argument in query.GetArguments(propertyName)) { if (argument.Type == ArgumentType.Unknown) { continue; } sb.Append($"{argument.ArgumentName}: "); switch (argument.Type) { case ArgumentType.String: sb.Append($"\"{argument.Value}\""); break; case ArgumentType.Number: sb.Append(argument.Value); break; case ArgumentType.Enum: sb.Append(Enum.GetName(argument.Value.GetType(), argument.Value)); break; } sb.Append(", "); } sb.Remove(sb.Length - 2, 2); sb.Append(")"); return(sb.ToString()); }
/// <summary> /// Generates a snippet for a schema type to be used in query serialization /// </summary> /// <typeparam name="T">The model to serialize</typeparam> /// <param name="query">The queryable chain</param> /// <param name="property">The property to serialize</param> /// <param name="indentationLevel">The indentation level, used for formatting</param> /// <returns>A string snippet to be used when building the complete query</returns> public string GenerateProperty <T>(Interfaces.IQueryable query, GraphQLProperty property, int indentationLevel) { var sb = new StringBuilder(); switch (property.Type) { case PropertyType.GraphQLType: sb.AppendLine($"{Indent(indentationLevel)}{property.FieldName}{GenerateArguments<T>(query, property.OriginalPropertyName)} {{"); foreach (var subProperty in property.Properties) { sb.Append(GenerateProperty <T>(query, subProperty, indentationLevel + 1)); } sb.AppendLine($"{Indent(indentationLevel)}}}"); break; case PropertyType.String: case PropertyType.Number: case PropertyType.Enum: sb.AppendLine($"{Indent(indentationLevel)}{property.FieldName}{GenerateArguments<T>(query, property.OriginalPropertyName)}"); break; } return(sb.ToString()); }
/// <summary> /// Used to create an alias for a query section if there is multiple queries for the same schema type /// </summary> /// <param name="alias">The name to alias the query with</param> public Interfaces.IQueryable With(Interfaces.IQueryable other) { siblings.Add(other); return(this); }
public IQueryable Queryable(IQueryable queryable) { ((Queryable)queryable).SetParent(this); return(queryable); }
/// <summary> /// Generates a full GraphQL query from the provided GraphQLQueryable /// </summary> /// <typeparam name="T">The model type to serialize</typeparam> /// <param name="query">The structured GraphQLQueryable</param> /// <returns>An instance of model T</returns> public string GenerateQuery <T>(Interfaces.IQueryableOf <T> query) { var allQueries = new Interfaces.IQueryable[] { query }.Concat(query.GetSiblings()); var queryStringBuilder = new StringBuilder(); int rootIndentation = 1; if (query.GetOperationName() != null) { queryStringBuilder.AppendLine($"query {query.GetOperationName()} {{"); } else if (allQueries.Count() > 1) { queryStringBuilder.AppendLine("{"); } else { rootIndentation = 0; } if (allQueries.Count() > 1) { //Search for multiple queries that share the same schema type name and do not have different aliases var ambiguousFields = allQueries .GroupBy(x => x.GetTypeName()) .Where(group => group.SelectMany(q => q.GetAlias() ?? "") .Distinct() .Count() < group.Count() ) .ToArray(); //If any were found, throw an exception. In GraphQL, you must alias field queries that are the same if (ambiguousFields.Length > 0) { throw new AmbiguousFieldsException(ambiguousFields[0].Select(x => x.GetTypeName()).ToArray()); } } //Loop through all of the IQueryable instances. This may be more than one if a With() was used. foreach (var subQuery in allQueries) { //Convert the known GraphQL properties of this type to GraphQLProperty models var propFieldMapping = subQuery.GetProperties() .Select(x => { var propertyInfo = typeof(T).GetProperty(x.Key); return(new GraphQLProperty(x.Key, x.Value, propertyInfo)); }) .ToList(); var queryType = subQuery.GetTypeName(); queryStringBuilder.Append(Indent(rootIndentation)); //If an alias was provided for this query, insert it if (subQuery.GetAlias() != null) { queryStringBuilder.Append($"{subQuery.GetAlias()}: "); } //Add the query type name along with any arguments that may be attached queryStringBuilder.Append(queryType); queryStringBuilder.AppendLine($"{GenerateArguments<T>(subQuery, string.Empty)} {{"); //Loop through each property of the type and generate markup for it foreach (var property in propFieldMapping) { queryStringBuilder.Append(GenerateProperty <T>(subQuery, property, rootIndentation + 1)); } queryStringBuilder.AppendLine($"{Indent(rootIndentation)}}}"); } if (query.GetOperationName() != null || allQueries.Count() > 1) { queryStringBuilder.AppendLine("}"); } return(queryStringBuilder.ToString()); }