/// <summary> /// Attempts to drill into the supplied type and remove the next found wrapper (given the provided filters) /// to distill it to its core type. (i.e. convert IEnumerable[T] to 'T'). /// </summary> /// <param name="type">The type to inspect.</param> /// <param name="eliminateEnumerables">if set to <c>true</c> this method will attempt to eliminate any wrapper type that declares <see cref="IEnumerable{T}" />.</param> /// <param name="eliminateTask">if set to <c>true</c> this method will attempt to remove a <see cref="Task{T}" /> declaration from the type.</param> /// <param name="eliminateNullableT">if set to <c>true</c> the Nullable{T} wrapper that can exist on value types will be stripped (leaving just T).</param> /// <returns>Type.</returns> public static Type EliminateNextWrapperFromCoreType( Type type, bool eliminateEnumerables = true, bool eliminateTask = true, bool eliminateNullableT = true) { if (type == null) { return(null); } // eliminate Task<T> if (eliminateTask && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task <>)) { return(type.GetGenericArguments()[0]); } // drill into the concrete type if a list was supplied List<T>, IEnumerable<T>, ICollection<T> etc. if (eliminateEnumerables && GraphValidation.IsValidListType(type)) { return(type.GetEnumerableUnderlyingType(false)); } if (eliminateNullableT && Validation.IsNullableOfT(type)) { return(Nullable.GetUnderlyingType(type)); } return(type); }
/// <summary> /// Forciably assigns a graph name to a type, will override any previously assigned name. /// </summary> /// <param name="type">The type.</param> /// <param name="kind">The kind of object for which the name will be assigned.</param> /// <param name="graphTypeName">Name of the graph type.</param> public static void AssignName(Type type, TypeKind kind, string graphTypeName) { Validation.ThrowIfNull(type, nameof(type)); if (!GraphValidation.IsValidGraphName(graphTypeName)) { throw new GraphTypeDeclarationException( $"The type, '{type.FriendlyName()}', declares an invalid graph type name, '{graphTypeName}'. Graph type names " + "can only contain letters A-Z, numbers 0-9 and an underscore. They must also not start with a double underscore."); } var typeNameDictionary = RetrieveTypeDictionary(type); typeNameDictionary.AddOrUpdate(kind, graphTypeName, (_, __) => graphTypeName); }
/// <summary> /// Parses the name of the type as it would exist in the object graph and returns the name. /// </summary> /// <param name="type">The concrete type to parse.</param> /// <param name="kind">The kind of graph type being created.</param> /// <returns>System.String.</returns> public static string ParseName(Type type, TypeKind kind) { Validation.ThrowIfNull(type, nameof(type)); kind = GraphValidation.ResolveTypeKind(type, kind); var typeNameDictionary = RetrieveTypeDictionary(type); if (typeNameDictionary.TryGetValue(kind, out var typeName)) { return(typeName); } type = GraphValidation.EliminateWrappersFromCoreType(type); if (GraphQLProviders.ScalarProvider.IsScalar(type)) { typeName = GraphQLProviders.ScalarProvider.RetrieveScalar(type).Name; } else if (type.IsEnum) { // enums always are their declared name (no changes needed for input types) typeName = type.SingleAttributeOrDefault <GraphTypeAttribute>()?.Name; } else if (kind == TypeKind.INPUT_OBJECT) { var inputNameAttrib = type.SingleAttributeOrDefault <GraphTypeAttribute>(); if (inputNameAttrib != null && !string.IsNullOrWhiteSpace(inputNameAttrib.InputName)) { if (type.IsGenericType) { throw new GraphTypeDeclarationException( $"Generic Types such as '{type.FriendlyName()}', cannot use the '{nameof(GraphTypeAttribute)}'. " + "Doing so would result in a single common name across multiple different instances of the type. " + "Remove the attribute and try again, the runtime will generate an acceptable name automatically."); } typeName = inputNameAttrib.InputName; } else { typeName = GraphTypeNames.ParseName(type, TypeKind.OBJECT); typeName = $"Input_{typeName}"; } } else { var graphTypeNameAttrib = type.SingleAttributeOrDefault <GraphTypeAttribute>(); if (graphTypeNameAttrib != null && !string.IsNullOrWhiteSpace(graphTypeNameAttrib.Name)) { if (type.IsGenericType) { throw new GraphTypeDeclarationException( $"Generic Types such as '{type.FriendlyName()}', cannot use the '{nameof(GraphTypeAttribute)}'. " + "Doing so would result in a single common name across multiple different instances of the type. " + "Remove the declared attribute and try again."); } typeName = graphTypeNameAttrib.Name; } } typeName = typeName ?? type.FriendlyName("_"); typeName = typeName.Replace(Constants.Routing.CLASS_META_NAME, type.Name).Trim(); if (kind == TypeKind.DIRECTIVE && typeName.EndsWith(Constants.CommonSuffix.DIRECTIVE_SUFFIX)) { typeName = typeName.ReplaceLastInstanceOfCaseInvariant(Constants.CommonSuffix.DIRECTIVE_SUFFIX, string.Empty); } AssignName(type, kind, typeName); return(typeName); }
/// <summary> /// Inspects the provided type and generates a type expression to represent it in the object grpah. /// </summary> /// <param name="typeToCheck">The complete type specification to check.</param> /// <param name="typeDefinition">An optional expression declaration to use as a set of overrides on the type provided.</param> /// <returns>GraphFieldOptions.</returns> public static GraphTypeExpression GenerateTypeExpression(Type typeToCheck, IGraphTypeExpressionDeclaration typeDefinition = null) { Validation.ThrowIfNull(typeToCheck, nameof(typeToCheck)); if (typeDefinition?.TypeWrappers != null) { typeToCheck = GraphValidation.EliminateWrappersFromCoreType( typeToCheck, eliminateEnumerables: true, eliminateTask: true, eliminateNullableT: true); return(new GraphTypeExpression(typeToCheck.FriendlyName(), typeDefinition.TypeWrappers)); } // strip out Task{T} before doin any type inspections typeToCheck = GraphValidation.EliminateWrappersFromCoreType( typeToCheck, eliminateEnumerables: false, eliminateTask: true, eliminateNullableT: false); var hasDefinedDefaultValue = typeDefinition?.HasDefaultValue ?? false; List <MetaGraphTypes> wrappers = new List <MetaGraphTypes>(); if (GraphValidation.IsValidListType(typeToCheck)) { // auto generated type expressions will always allow for a nullable list (since class references can be null) // unwrap any nested lists as necessary: e.g. IEnumerable<IEnumerable<IEnumerable<T>>> while (true) { var unwrappedType = EliminateNextWrapperFromCoreType( typeToCheck, eliminateEnumerables: true, eliminateTask: false, eliminateNullableT: false); if (unwrappedType == typeToCheck) { break; } wrappers.Add(MetaGraphTypes.IsList); typeToCheck = unwrappedType; } if (!hasDefinedDefaultValue && GraphValidation.IsNotNullable(typeToCheck)) { wrappers.Add(MetaGraphTypes.IsNotNull); } } else if (!hasDefinedDefaultValue && GraphValidation.IsNotNullable(typeToCheck)) { wrappers.Add(MetaGraphTypes.IsNotNull); } typeToCheck = EliminateWrappersFromCoreType( typeToCheck, eliminateEnumerables: false, eliminateTask: false, eliminateNullableT: true); return(new GraphTypeExpression(typeToCheck.FriendlyName(), wrappers)); }
/// <summary> /// Determines whether the given type CAN be represented in the object graph. /// </summary> /// <param name="type">The type.</param> /// <param name="throwOnFailure">if set to <c>true</c> a context rich <see cref="GraphTypeDeclarationException"/> will be thrown instead of returning false.</param> /// <returns><c>true</c> if the type can be correctly rendered otherwise, <c>false</c>.</returns> public static bool IsValidGraphType(Type type, bool throwOnFailure = false) { if (type == null) { return(false); } if (type == typeof(void)) { return(false); } if (type.IsEnum) { return(true); } if (type == typeof(object)) { if (throwOnFailure) { throw new GraphTypeDeclarationException( $"The type '{typeof(object).FriendlyName()}' cannot be used directly in an object graph. GraphQL requires a complete " + "definition of a schema for validation and introspection. Using a generic object prohibits this library from " + "scanning for fields, interfaces and inputs.", type); } return(false); } // remove any IEnumerable or Task wrappers from the supplied type // so we can validate the core entity type = GraphValidation.EliminateWrappersFromCoreType(type); // explicitly disallow dictionaries // graphQL doesn't allow for arbitrary keyvalue pairs in any type renderings if (typeof(IDictionary).IsAssignableFrom(type)) { if (throwOnFailure) { throw new GraphTypeDeclarationException( $"The type '{type.FriendlyName()}' appears to be a {typeof(IDictionary).FriendlyName()}. Objects " + "which allow for arbitrary key/value pairs of data are not allowed in graphQL. This type cannot be used as a publically" + "available graph type.", type); } return(false); } if (type.IsGenericType) { if (typeof(IDictionary <,>).IsAssignableFrom(type.GetGenericTypeDefinition())) { if (throwOnFailure) { throw new GraphTypeDeclarationException( $"The type '{type.FriendlyName()}' appears to be a {typeof(IDictionary<,>).FriendlyName()}. Objects " + "which allow for arbitrary key/value pairs of data are not allowed in graphQL. This type cannot be used as a publically" + "available graph type.", type); } return(false); } if (typeof(IReadOnlyDictionary <,>).IsAssignableFrom(type.GetGenericTypeDefinition())) { if (throwOnFailure) { throw new GraphTypeDeclarationException( $"The type '{type.FriendlyName()}' appears to be a {typeof(IReadOnlyDictionary<,>).FriendlyName()}. Objects " + "which allow for arbitrary key/value pairs of data are not allowed in graphQL. This type cannot be used as a publically" + "available graph type.", type); } return(false); } } return(true); }
/// <summary> /// Determines whether the given type CAN be represented in the object graph. An exception is thrown if it cannot be. /// </summary> /// <param name="type">The type to check.</param> public static void EnsureValidGraphTypeOrThrow(Type type) { GraphValidation.IsValidGraphType(type, true); }