-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add DIGraphAttribute, AutoInputObjectGraphType, GraphQLBuilderExtensi…
…ons, DISchemaTypes (#12)
- Loading branch information
Showing
35 changed files
with
1,969 additions
and
731 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.ComponentModel; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Text; | ||
using GraphQL.Types; | ||
|
||
namespace GraphQL.DI | ||
{ | ||
/// <summary> | ||
/// An automatically-generated graph type for public properties on the specified input model. | ||
/// </summary> | ||
public class AutoInputObjectGraphType<TSourceType> : InputObjectGraphType<TSourceType> | ||
{ | ||
private const string ORIGINAL_EXPRESSION_PROPERTY_NAME = nameof(ORIGINAL_EXPRESSION_PROPERTY_NAME); | ||
|
||
/// <summary> | ||
/// Creates a GraphQL type from <typeparamref name="TSourceType"/>. | ||
/// </summary> | ||
public AutoInputObjectGraphType() | ||
{ | ||
var classType = typeof(TSourceType); | ||
|
||
//allow default name / description / obsolete tags to remain if not overridden | ||
var nameAttribute = classType.GetCustomAttribute<NameAttribute>(); | ||
if (nameAttribute != null) | ||
Name = nameAttribute.Name; | ||
else { | ||
var name = GetDefaultName(); | ||
if (name != null) | ||
Name = name; | ||
} | ||
|
||
var descriptionAttribute = classType.GetCustomAttribute<DescriptionAttribute>(); | ||
if (descriptionAttribute != null) | ||
Description = descriptionAttribute.Description; | ||
var obsoleteAttribute = classType.GetCustomAttribute<ObsoleteAttribute>(); | ||
if (obsoleteAttribute != null) | ||
DeprecationReason = obsoleteAttribute.Message; | ||
//pull metadata | ||
foreach (var metadataAttribute in classType.GetCustomAttributes<MetadataAttribute>()) | ||
Metadata.Add(metadataAttribute.Key, metadataAttribute.Value); | ||
|
||
foreach (var property in GetRegisteredProperties()) { | ||
if (property != null) { | ||
var fieldType = ProcessProperty(property); | ||
if (fieldType != null) | ||
AddField(fieldType); | ||
|
||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Returns the default name assigned to the graph, or <see langword="null"/> to leave the default setting set by the <see cref="GraphType"/> constructor. | ||
/// </summary> | ||
protected virtual string? GetDefaultName() | ||
{ | ||
//if this class is inherited, do not set default name | ||
if (GetType() != typeof(AutoInputObjectGraphType<TSourceType>)) | ||
return null; | ||
|
||
//without this code, the name would default to AutoInputObjectGraphType_1 | ||
var name = typeof(TSourceType).Name.Replace('`', '_'); | ||
if (name.EndsWith("Model", StringComparison.InvariantCulture)) | ||
name = name.Substring(0, name.Length - "Model".Length); | ||
return name; | ||
} | ||
|
||
/// <summary> | ||
/// Returns a list of properties that should have fields created for them. | ||
/// </summary> | ||
protected virtual IEnumerable<PropertyInfo> GetRegisteredProperties() | ||
=> typeof(TSourceType).GetProperties(BindingFlags.Public | BindingFlags.Instance) | ||
.Where(x => x.CanWrite); | ||
|
||
/// <summary> | ||
/// Processes the specified property and returns a <see cref="FieldType"/> | ||
/// </summary> | ||
/// <param name="property"></param> | ||
/// <returns></returns> | ||
protected virtual FieldType? ProcessProperty(PropertyInfo property) | ||
{ | ||
//get the field name | ||
string fieldName = property.Name; | ||
var fieldNameAttribute = property.GetCustomAttribute<NameAttribute>(); | ||
if (fieldNameAttribute != null) { | ||
fieldName = fieldNameAttribute.Name; | ||
} | ||
if (fieldName == null) | ||
return null; //ignore field if set to null (or Ignore is specified) | ||
|
||
//determine the graphtype of the field | ||
var graphTypeAttribute = property.GetCustomAttribute<GraphTypeAttribute>(); | ||
Type? graphType = graphTypeAttribute?.Type; | ||
//infer the graphtype if it is not specified | ||
if (graphType == null) { | ||
graphType = InferGraphType(ApplyAttributes(GetTypeInformation(property))); | ||
} | ||
//load the description | ||
string? description = property.GetCustomAttribute<DescriptionAttribute>()?.Description; | ||
//load the deprecation reason | ||
string? obsoleteDescription = property.GetCustomAttribute<ObsoleteAttribute>()?.Message; | ||
//load the default value | ||
object? defaultValue = property.GetCustomAttribute<DefaultValueAttribute>()?.Value; | ||
//create the field | ||
var fieldType = new FieldType() { | ||
Type = graphType, | ||
Name = fieldName, | ||
Description = description, | ||
DeprecationReason = obsoleteDescription, | ||
DefaultValue = defaultValue, | ||
}; | ||
//set name of property | ||
fieldType.WithMetadata(ORIGINAL_EXPRESSION_PROPERTY_NAME, property.Name); | ||
//load the metadata | ||
foreach (var metaAttribute in property.GetCustomAttributes<MetadataAttribute>()) | ||
fieldType.WithMetadata(metaAttribute.Key, metaAttribute.Value); | ||
//return the field | ||
return fieldType; | ||
} | ||
|
||
/// <inheritdoc cref="ReflectionExtensions.GetNullabilityInformation(ParameterInfo)"/> | ||
protected virtual IEnumerable<(Type Type, Nullability Nullable)> GetNullabilityInformation(PropertyInfo parameter) | ||
{ | ||
return parameter.GetNullabilityInformation(); | ||
} | ||
|
||
private static readonly Type[] _listTypes = new Type[] { | ||
typeof(IEnumerable<>), | ||
typeof(IList<>), | ||
typeof(List<>), | ||
typeof(ICollection<>), | ||
typeof(IReadOnlyCollection<>), | ||
typeof(IReadOnlyList<>), | ||
typeof(HashSet<>), | ||
typeof(ISet<>), | ||
}; | ||
|
||
/// <summary> | ||
/// Analyzes a property and returns a <see cref="TypeInformation"/> | ||
/// struct containing type information necessary to select a graph type. | ||
/// </summary> | ||
protected virtual TypeInformation GetTypeInformation(PropertyInfo propertyInfo) | ||
{ | ||
var isList = false; | ||
var isNullableList = false; | ||
var typeTree = GetNullabilityInformation(propertyInfo); | ||
foreach (var type in typeTree) { | ||
if (type.Type.IsArray) { | ||
//unwrap type and mark as list | ||
isList = true; | ||
isNullableList = type.Nullable != Nullability.NonNullable; | ||
continue; | ||
} | ||
if (type.Type.IsGenericType) { | ||
var g = type.Type.GetGenericTypeDefinition(); | ||
if (_listTypes.Contains(g)) { | ||
//unwrap type and mark as list | ||
isList = true; | ||
isNullableList = type.Nullable != Nullability.NonNullable; | ||
continue; | ||
} | ||
} | ||
if (type.Type == typeof(IEnumerable) || type.Type == typeof(ICollection)) { | ||
//assume list of nullable object | ||
isList = true; | ||
isNullableList = type.Nullable != Nullability.NonNullable; | ||
break; | ||
} | ||
//found match | ||
var nullable = type.Nullable != Nullability.NonNullable; | ||
return new TypeInformation(propertyInfo, true, type.Type, nullable, isList, isNullableList, null); | ||
} | ||
//unknown type | ||
return new TypeInformation(propertyInfo, true, typeof(object), true, isList, isNullableList, null); | ||
} | ||
|
||
/// <summary> | ||
/// Apply <see cref="RequiredAttribute"/>, <see cref="OptionalAttribute"/>, <see cref="RequiredListAttribute"/>, | ||
/// <see cref="OptionalListAttribute"/>, <see cref="IdAttribute"/> and <see cref="DIGraphAttribute"/> over | ||
/// the supplied <see cref="TypeInformation"/>. | ||
/// Override this method to enforce specific graph types for specific CLR types, or to implement custom | ||
/// attributes to change graph type selection behavior. | ||
/// </summary> | ||
protected virtual TypeInformation ApplyAttributes(TypeInformation typeInformation) | ||
=> typeInformation.ApplyAttributes(typeInformation.MemberInfo); | ||
|
||
/// <summary> | ||
/// Returns a GraphQL input type for a specified CLR type | ||
/// </summary> | ||
protected virtual Type InferGraphType(TypeInformation typeInformation) | ||
=> typeInformation.InferGraphType(); | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
|
||
namespace GraphQL.DI | ||
{ | ||
/// <summary> | ||
/// Marks a method's return graph type to be a specified DI graph type. | ||
/// Useful when the return type cannot be inferred (often when it is of type <see cref="object"/>). | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Method, Inherited = false)] | ||
public class DIGraphAttribute : Attribute | ||
{ | ||
/// <summary> | ||
/// Marks a method's return graph type to be a specified DI graph type. | ||
/// </summary> | ||
/// <param name="graphBaseType">A type that inherits <see cref="DIObjectGraphBase"/>.</param> | ||
public DIGraphAttribute(Type graphBaseType) | ||
{ | ||
GraphBaseType = graphBaseType; | ||
} | ||
|
||
/// <summary> | ||
/// The DI graph type that inherits <see cref="DIObjectGraphBase"/>. | ||
/// </summary> | ||
public Type GraphBaseType { get; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.