Skip to content

Commit

Permalink
v8.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Samuel Abraham committed Mar 19, 2024
1 parent 7d052d6 commit d2e7cde
Show file tree
Hide file tree
Showing 46 changed files with 1,228 additions and 1,058 deletions.
152 changes: 78 additions & 74 deletions src/TypeCache.GraphQL/Extensions/GraphQLExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using TypeCache.Extensions;
using TypeCache.GraphQL.Data;
using TypeCache.GraphQL.Resolvers;
using TypeCache.GraphQL.SqlApi;
using TypeCache.GraphQL.Types;
using static System.FormattableString;
using static System.Runtime.CompilerServices.MethodImplOptions;
Expand All @@ -25,25 +24,90 @@ namespace TypeCache.GraphQL.Extensions;

public static class GraphQLExtensions
{
public static FieldType AddField<T>(this IComplexGraphType @this, string name, IFieldResolver resolver)
=> @this.AddField(new()
{
Name = name,
Type = typeof(T).ToGraphQLType(false),
Resolver = resolver
});

public static FieldType AddField(this IComplexGraphType @this, string name, IGraphType resolvedType, IFieldResolver resolver)
=> @this.AddField(new()
{
Name = name,
ResolvedType = resolvedType,
Resolver = resolver
});

public static FieldType AddField(this IComplexGraphType @this, MethodInfo methodInfo, IFieldResolver resolver)
{
var fieldType = methodInfo.ToFieldType();
fieldType.Resolver = resolver;
return @this.AddField(fieldType);
}
=> @this.AddField(new()
{
Arguments = new QueryArguments(methodInfo.GetParameters()
.Where(parameterInfo => !parameterInfo.GraphQLIgnore() && !parameterInfo.ParameterType.Is<IResolveFieldContext>())
.Select(parameterInfo => new QueryArgument(parameterInfo.ToGraphQLType())
{
Name = parameterInfo.GraphQLName(),
Description = parameterInfo.GraphQLDescription(),
})),
Name = methodInfo.GraphQLName(),
Description = methodInfo.GraphQLDescription(),
DeprecationReason = methodInfo.GraphQLDeprecationReason(),
Resolver = resolver,
Type = methodInfo.ReturnType.ToGraphQLType(false).ToNonNullGraphType()
});

public static FieldType AddField(this IComplexGraphType @this, MethodInfo methodInfo, ISourceStreamResolver resolver)
{
var fieldType = methodInfo.ToFieldType();
fieldType.StreamResolver = resolver;
return @this.AddField(fieldType);
}
=> @this.AddField(new()
{
Arguments = new QueryArguments(methodInfo.GetParameters()
.Where(parameterInfo => !parameterInfo.GraphQLIgnore() && !parameterInfo.ParameterType.Is<IResolveFieldContext>())
.Select(parameterInfo => new QueryArgument(parameterInfo.ToGraphQLType())
{
Name = parameterInfo.GraphQLName(),
Description = parameterInfo.GraphQLDescription(),
})),
Name = methodInfo.GraphQLName(),
Description = methodInfo.GraphQLDescription(),
DeprecationReason = methodInfo.GraphQLDeprecationReason(),
StreamResolver = resolver,
Type = methodInfo.ReturnType.ToGraphQLType(false).ToNonNullGraphType()
});

public static FieldType AddField(this IComplexGraphType @this, PropertyInfo propertyInfo, IFieldResolver resolver)
{
var fieldType = propertyInfo.ToFieldType();
fieldType.Resolver = resolver;
return @this.AddField(fieldType);
var type = propertyInfo.ToGraphQLType(false);
var arguments = new QueryArguments();

if (type.IsAssignableTo<ScalarGraphType>() && !type.Implements(typeof(NonNullGraphType<>)))
arguments.Add("null", type, description: "Return this if the value is null.");

if (propertyInfo.PropertyType.IsAssignableTo<IFormattable>())
arguments.Add<string>("format", nullable: true, description: "Use .NET format specifiers to format the data.");

if (type.Is<GraphQLScalarType<DateTime>>() || type.Is<NonNullGraphType<GraphQLScalarType<DateTime>>>())
arguments.Add<string>("timeZone", nullable: true, description: Invariant($"{typeof(TimeZoneInfo).Namespace}.{nameof(TimeZoneInfo)}.{nameof(TimeZoneInfo.ConvertTimeBySystemTimeZoneId)}(value, [..., ...] | [UTC, ...])"));
else if (type.Is<GraphQLScalarType<DateTimeOffset>>() || type.Is<NonNullGraphType<GraphQLScalarType<DateTimeOffset>>>())
arguments.Add<string>("timeZone", nullable: true, description: Invariant($"{typeof(TimeZoneInfo).Namespace}.{nameof(TimeZoneInfo)}.{nameof(TimeZoneInfo.ConvertTimeBySystemTimeZoneId)}(value, ...)"));
else if (type.Is<GraphQLScalarType<string>>() || type.Is<NonNullGraphType<GraphQLScalarType<string>>>())
{
arguments.Add<StringCase?>("case", description: "value.ToLower(), value.ToLowerInvariant(), value.ToUpper(), value.ToUpperInvariant()");
arguments.Add<int?>("length", description: "value.Left(length)");
arguments.Add<string>("match", nullable: true, description: "value.ToRegex(RegexOptions.Compiled | RegexOptions.Singleline).Match(match).");
arguments.Add<string>("trim", nullable: true, description: "value.Trim()");
arguments.Add<string>("trimEnd", nullable: true, description: "value.TrimEnd()");
arguments.Add<string>("trimStart", nullable: true, description: "value.TrimStart()");
}

return @this.AddField(new()
{
Arguments = arguments,
Type = type,
Name = propertyInfo.GraphQLName(),
Description = propertyInfo.GraphQLDescription(),
DeprecationReason = propertyInfo.GraphQLDeprecationReason(),
Resolver = resolver
});
}

public static void AddOrderBy(this EnumerationGraphType @this, string column, string? deprecationReason = null)
Expand Down Expand Up @@ -175,64 +239,4 @@ public static Type ToListGraphType(this Type @this)
[MethodImpl(AggressiveInlining), DebuggerHidden]
public static Type ToNonNullGraphType(this Type @this)
=> typeof(NonNullGraphType<>).MakeGenericType(@this);

public static FieldType ToFieldType(this MethodInfo @this)
=> new()
{
Arguments = new QueryArguments(@this.GetParameters()
.Where(parameterInfo => !parameterInfo.GraphQLIgnore() && !parameterInfo.ParameterType.Is<IResolveFieldContext>())
.Select(parameterInfo => new QueryArgument(parameterInfo.ToGraphQLType())
{
Name = parameterInfo.GraphQLName(),
Description = parameterInfo.GraphQLDescription(),
})),
Name = @this.GraphQLName(),
Description = @this.GraphQLDescription(),
DeprecationReason = @this.GraphQLDeprecationReason(),
Type = @this.ReturnType.ToGraphQLType(false).ToNonNullGraphType()
};

public static FieldType ToFieldType(this PropertyInfo @this)
{
var type = @this.ToGraphQLType(false);
var arguments = new QueryArguments();

if (type.IsAssignableTo<ScalarGraphType>() && !type.Implements(typeof(NonNullGraphType<>)))
arguments.Add("null", type, description: "Return this value instead of null.");

if (@this.PropertyType.IsAssignableTo<IFormattable>())
arguments.Add<string>("format", nullable: true, description: "Use .NET format specifiers to format the data.");

if (type.Is<GraphQLScalarType<DateTime>>() || type.Is<NonNullGraphType<GraphQLScalarType<DateTime>>>())
arguments.Add<string>("timeZone", nullable: true, description: Invariant($"{typeof(TimeZoneInfo).Namespace}.{nameof(TimeZoneInfo)}.{nameof(TimeZoneInfo.ConvertTimeBySystemTimeZoneId)}(value, [..., ...] | [UTC, ...])"));
else if (type.Is<GraphQLScalarType<DateTimeOffset>>() || type.Is<NonNullGraphType<GraphQLScalarType<DateTimeOffset>>>())
arguments.Add<string>("timeZone", nullable: true, description: Invariant($"{typeof(TimeZoneInfo).Namespace}.{nameof(TimeZoneInfo)}.{nameof(TimeZoneInfo.ConvertTimeBySystemTimeZoneId)}(value, ...)"));
else if (type.Is<GraphQLScalarType<string>>() || type.Is<NonNullGraphType<GraphQLScalarType<string>>>())
{
arguments.Add<StringCase>("case", nullable: true, description: "Convert string value to upper or lower case.");
arguments.Add<int>("length", nullable: true, description: "Exclude the rest of the string value if it exceeds this length.");
arguments.Add<string>("match", nullable: true, description: "Returns the matching result based on the specified regular expression pattern, null if no match.");
arguments.Add<string>("trim", nullable: true, description: Invariant($"{typeof(string).Namespace}.{nameof(String)}.{nameof(string.Trim)}(value)"));
arguments.Add<string>("trimEnd", nullable: true, description: Invariant($"{typeof(string).Namespace}.{nameof(String)}.{nameof(string.TrimEnd)}(value)"));
arguments.Add<string>("trimStart", nullable: true, description: Invariant($"{typeof(string).Namespace}.{nameof(String)}.{nameof(string.TrimStart)}(value)"));
}

return new()
{
Arguments = arguments,
Type = type,
Name = @this.GraphQLName(),
Description = @this.GraphQLDescription(),
DeprecationReason = @this.GraphQLDeprecationReason(),
};
}

public static FieldType ToInputFieldType(this PropertyInfo @this)
=> new()
{
Type = @this.ToGraphQLType(true),
Name = @this.GraphQLName(),
Description = @this.GraphQLDescription(),
DeprecationReason = @this.GraphQLDeprecationReason()
};
}
22 changes: 19 additions & 3 deletions src/TypeCache.GraphQL/Extensions/QueryArgumentsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace TypeCache.GraphQL.Extensions;

public static class QueryArgumentsExtensions
{
public static void Add<T>(this QueryArguments @this, string name, bool nullable = false, object? defaultValue = null, string? description = null)
public static void Add<T>(this QueryArguments @this, string name, bool? nullable = null, object? defaultValue = null, string? description = null)
{
if (typeof(T).Implements(typeof(IGraphType)))
{
Expand All @@ -31,6 +31,10 @@ public static void Add<T>(this QueryArguments @this, string name, bool nullable
_ => typeof(T)
};

var isValueNullable = type.Is(typeof(Nullable<>));
if (isValueNullable)
type = type.GenericTypeArguments[0];

var graphType = type switch
{
{ IsEnum: true } => typeof(GraphQLEnumType<>),
Expand All @@ -41,10 +45,22 @@ _ when type.Implements(typeof(ISpanParsable<>)) => typeof(GraphQLScalarType<>),
type = graphType.MakeGenericType(type);

if (isList)
{
if (!isValueNullable)
type = type.ToNonNullGraphType();

type = type.ToListGraphType();
nullable ??= defaultValue is not null;

if (!nullable)
type = type.ToNonNullGraphType();
if (nullable is false)
type = type.ToNonNullGraphType();
}
else
{
nullable ??= isValueNullable || defaultValue is not null;
if (nullable is false)
type = type.ToNonNullGraphType();
}

@this.Add(new QueryArgument(type)
{
Expand Down
25 changes: 12 additions & 13 deletions src/TypeCache.GraphQL/Extensions/SchemaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using GraphQL;
Expand Down Expand Up @@ -220,7 +219,7 @@ public static void AddDatabaseEndpoints(this ISchema @this, IDataSource dataSour
var arguments = new QueryArguments();
arguments.Add<Parameter[]>("parameters", nullable: true, description: "Used to reference user input values from the where clause.");
if (dataSource.Type is DataSourceType.SqlServer)
arguments.Add<string>(nameof(SelectQuery.Top));
arguments.Add<string>(nameof(SelectQuery.Top), nullable: true, description: "Accepts integer `n` or `n%`.");
arguments.Add<bool>(nameof(SelectQuery.Distinct), defaultValue: false);
arguments.Add<string>(nameof(SelectQuery.Where), nullable: true, description: "If `where` is omitted, all records will be returned.");
Expand Down Expand Up @@ -308,7 +307,7 @@ public static void AddDatabaseEndpoints(this ISchema @this, IDataSource dataSour
var arguments = new QueryArguments();
arguments.Add<Parameter[]>("parameters", nullable: true, description: "Used to reference user input values from the where clause.");
if (dataSource.Type is DataSourceType.SqlServer)
arguments.Add<GraphQLScalarType<string>>(nameof(SelectQuery.Top));
arguments.Add<string>(nameof(SelectQuery.Top), nullable: true, description: "Accepts integer `n` or `n%`.");
arguments.Add<bool>(nameof(SelectQuery.Distinct), defaultValue: false);
arguments.Add<string>(nameof(SelectQuery.Where), nullable: true, description: "If `where` is omitted, all records will be returned.");
Expand Down Expand Up @@ -840,7 +839,7 @@ public static FieldType AddSqlApiCallProcedureEndpoint<T>(this ISchema @this, ID
dataSource.AssertNotNull();
procedure.AssertNotBlank();

var name = dataSource.CreateName(procedure);
var name = dataSource.Escape(procedure);
var objectSchema = dataSource.ObjectSchemas[name];
var parameters = objectSchema.Parameters
.Where(_ => _.Direction is ParameterDirection.Input || _.Direction is ParameterDirection.InputOutput)
Expand Down Expand Up @@ -885,7 +884,7 @@ public static FieldType AddSqlApiDeleteDataEndpoint<T>(this ISchema @this, IData
dataSource.AssertNotNull();
table.AssertNotBlank();

var name = dataSource.CreateName(table);
var name = dataSource.Escape(table);
var objectSchema = dataSource.ObjectSchemas[name];
var arguments = new QueryArguments();
arguments.Add<T[]>("data", description: "The data to be deleted.");
Expand Down Expand Up @@ -920,7 +919,7 @@ public static FieldType AddSqlApiDeleteEndpoint<T>(this ISchema @this, IDataSour
dataSource.AssertNotNull();
table.AssertNotBlank();

var name = dataSource.CreateName(table);
var name = dataSource.Escape(table);
var objectSchema = dataSource.ObjectSchemas[name];
var arguments = new QueryArguments();
arguments.Add<Parameter[]>("parameters", nullable: true, description: "Used to reference user input values from the where clause.");
Expand Down Expand Up @@ -956,7 +955,7 @@ public static FieldType AddSqlApiInsertDataEndpoint<T>(this ISchema @this, IData
dataSource.AssertNotNull();
table.AssertNotBlank();

var name = dataSource.CreateName(table);
var name = dataSource.Escape(table);
var objectSchema = dataSource.ObjectSchemas[name];
var arguments = new QueryArguments();
arguments.Add<string[]>("columns", description: "The columns to insert data into.");
Expand Down Expand Up @@ -992,7 +991,7 @@ public static FieldType AddSqlApiInsertEndpoint<T>(this ISchema @this, IDataSour
dataSource.AssertNotNull();
table.AssertNotBlank();

var name = dataSource.CreateName(table);
var name = dataSource.Escape(table);
var objectSchema = dataSource.ObjectSchemas[name];
var graphOrderByEnum = new EnumerationGraphType
{
Expand All @@ -1011,7 +1010,7 @@ public static FieldType AddSqlApiInsertEndpoint<T>(this ISchema @this, IDataSour
var arguments = new QueryArguments();
arguments.Add<Parameter[]>("parameters", nullable: true, description: "Used to reference user input values from the where clause.");
if (dataSource.Type is DataSourceType.SqlServer)
arguments.Add<string>(nameof(SelectQuery.Top), nullable: true);
arguments.Add<string>(nameof(SelectQuery.Top), nullable: true, description: "Accepts integer `n` or `n%`.");

arguments.Add<bool>(nameof(SelectQuery.Distinct), defaultValue: false);
arguments.Add<string>(nameof(SelectQuery.From), description: "The table or view to pull the data from to insert.");
Expand Down Expand Up @@ -1050,7 +1049,7 @@ public static FieldType AddSqlApiSelectEndpoint<T>(this ISchema @this, IDataSour
dataSource.AssertNotNull();
table.AssertNotBlank();

var name = dataSource.CreateName(table);
var name = dataSource.Escape(table);
var objectSchema = dataSource.ObjectSchemas[name];
var graphOrderByEnum = new EnumerationGraphType
{
Expand All @@ -1067,7 +1066,7 @@ public static FieldType AddSqlApiSelectEndpoint<T>(this ISchema @this, IDataSour
var arguments = new QueryArguments();
arguments.Add<Parameter[]>("parameters", nullable: true, description: "Used to reference user input values from the where clause.");
if (dataSource.Type is DataSourceType.SqlServer)
arguments.Add<uint>(nameof(SelectQuery.Top), nullable: true);
arguments.Add<string>(nameof(SelectQuery.Top), nullable: true, description: "Accepts integer `n` or `n%`.");

arguments.Add<bool>(nameof(SelectQuery.Distinct), defaultValue: false);
arguments.Add<string>(nameof(SelectQuery.Where), nullable: true, description: "If `where` is omitted, all records will be returned.");
Expand Down Expand Up @@ -1105,7 +1104,7 @@ public static FieldType AddSqlApiUpdateDataEndpoint<T>(this ISchema @this, IData
dataSource.AssertNotNull();
table.AssertNotBlank();

var name = dataSource.CreateName(table);
var name = dataSource.Escape(table);
var objectSchema = dataSource.ObjectSchemas[name];
var arguments = new QueryArguments();
arguments.Add<T[]>("set", description: "The columns to be updated.");
Expand Down Expand Up @@ -1140,7 +1139,7 @@ public static FieldType AddSqlApiUpdateEndpoint<T>(this ISchema @this, IDataSour
dataSource.AssertNotNull();
table.AssertNotBlank();

var name = dataSource.CreateName(table);
var name = dataSource.Escape(table);
var objectSchema = dataSource.ObjectSchemas[name];
var arguments = new QueryArguments();
arguments.Add<Parameter[]>("parameters", nullable: true, description: "Used to reference user input values from the where clause.");
Expand Down
8 changes: 7 additions & 1 deletion src/TypeCache.GraphQL/Resolvers/FieldResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ public abstract class FieldResolver : IFieldResolver
{
return await this.ResolveAsync(context);
}
catch (ExecutionError error)
{
context.Errors.Add(error);
return null;
}
catch (AggregateException error)
{
var executionErrors = error.InnerExceptions.Select(exception => new ExecutionError(exception.Message, exception));
var executionErrors = error.InnerExceptions.Select(exception =>
exception as ExecutionError ?? new ExecutionError(exception.Message, exception));
context.Errors.AddRange(executionErrors);
return null;
}
Expand Down
16 changes: 5 additions & 11 deletions src/TypeCache.GraphQL/Resolvers/PropertyFieldResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,15 @@
using TypeCache.Extensions;
using static System.FormattableString;
using static System.Globalization.CultureInfo;
using static System.Text.RegularExpressions.RegexOptions;

namespace TypeCache.GraphQL.Resolvers;

public sealed class PropertyFieldResolver<T> : FieldResolver
public sealed class PropertyFieldResolver<T>(PropertyInfo propertyInfo) : FieldResolver
{
private readonly PropertyInfo _PropertyInfo;

public PropertyFieldResolver(PropertyInfo propertyInfo)
{
this._PropertyInfo = propertyInfo;
}

protected override async ValueTask<object?> ResolveAsync(IResolveFieldContext context)
{
propertyInfo.AssertNotNull();

var source = context.Source switch
{
null => null,
Expand All @@ -31,7 +25,7 @@ public PropertyFieldResolver(PropertyInfo propertyInfo)
_ => context.Source
};

var value = this._PropertyInfo.GetPropertyValue(source!);
var value = propertyInfo.GetPropertyValue(source!);
if (value is null)
return value ?? context.GetArgument<object>("null");

Expand Down Expand Up @@ -87,7 +81,7 @@ public PropertyFieldResolver(PropertyInfo propertyInfo)
var pattern = context.GetArgument<string>("match");
if (pattern.IsNotBlank())
{
var match = Regex.Match(text, pattern, Compiled | Singleline);
var match = text.ToRegex(RegexOptions.Compiled | RegexOptions.Singleline).Match(text);
if (match.Success)
text = match.Value;
else
Expand Down
Loading

0 comments on commit d2e7cde

Please sign in to comment.