/// <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> /// 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> /// 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); }