private static IEnumerable <ValidationError> ValidateGraphQLType <T>(this GraphQLIntrospectionSchema graphQLIntrospectionSchema, GraphQLOperationType operationType, IGraphQLFieldBuilder fieldBuilder) { if (fieldBuilder is null) { throw new ArgumentNullException(nameof(fieldBuilder)); } var validator = new GraphQLValidation(); return(validator.ValidateGraphQLSelectionSet(graphQLIntrospectionSchema, operationType, fieldBuilder.GenerateSelectionSet(typeof(T)))); }
/// <summary> /// Returns the GraphQLIntrospectionSchema with the implicit fields __schema, __type and __typename which is defined in the GraphQL specification /// <see cref="https://graphql.github.io/graphql-spec/June2018/#sec-Schema-Introspection"/> /// </summary> /// <param name="schema">The schema without the implicit fields</param> /// <returns>Returns the GraphQLIntrospectionSchema with the implicit fields __schema, __type and __typename which is defined in the GraphQL specification</returns> public static GraphQLIntrospectionSchema WithImplicitFields(this GraphQLIntrospectionSchema schema) { return(new GraphQLIntrospectionSchema { QueryType = schema.QueryType, MutationType = schema.MutationType, SubscriptionType = schema.SubscriptionType, Directives = schema.Directives, Types = schema.Types.Select(type => GetIntrospectionType(type, schema)) }); }
/// <summary> /// Validate if the GraphQL <paramref name="selectionSet"/> is valid for the specified <paramref name="graphQLIntrospectionSchema"/> and <paramref name="operationType"/> /// </summary> /// <param name="graphQLIntrospectionSchema">The introspectionSchema to validate against</param> /// <param name="operationType">The operationType to validate against</param> /// <param name="selectionSet">The selectionSet which should be validated</param> /// <returns>An empty list if no errors were found or a <see cref="ValidationError"/> for each error found</returns> public IEnumerable <ValidationError> ValidateGraphQLSelectionSet(GraphQLIntrospectionSchema graphQLIntrospectionSchema, GraphQLOperationType operationType, IEnumerable <GraphQLField> selectionSet) { if (graphQLIntrospectionSchema is null) { throw new ArgumentNullException(nameof(graphQLIntrospectionSchema)); } if (selectionSet is null) { throw new ArgumentNullException(nameof(selectionSet)); } // Get implicit types var graphQLIntrospectionSchemaWithImplicitTypes = graphQLIntrospectionSchema.WithImplicitFields(); GraphQLIntrospectionFullType type = null; switch (operationType) { case GraphQLOperationType.Query: if (graphQLIntrospectionSchemaWithImplicitTypes.QueryType != null) { type = GetTypeByName(graphQLIntrospectionSchemaWithImplicitTypes, graphQLIntrospectionSchemaWithImplicitTypes.QueryType.Name); } break; case GraphQLOperationType.Mutation: if (graphQLIntrospectionSchemaWithImplicitTypes.MutationType != null) { type = GetTypeByName(graphQLIntrospectionSchemaWithImplicitTypes, graphQLIntrospectionSchemaWithImplicitTypes.MutationType.Name); } break; case GraphQLOperationType.Subscription: if (graphQLIntrospectionSchemaWithImplicitTypes.SubscriptionType != null) { type = GetTypeByName(graphQLIntrospectionSchemaWithImplicitTypes, graphQLIntrospectionSchemaWithImplicitTypes.SubscriptionType.Name); } break; default: throw new ArgumentOutOfRangeException(nameof(operationType), $"Operationtype {operationType} is not supported"); } if (type == null) { return(new List <ValidationError> { new ValidationError(ValidationType.Operation_Type_Not_Found, operationType) }); } return(ValidateSelectionSet(graphQLIntrospectionSchemaWithImplicitTypes, selectionSet, type, operationType, rootLevel: true)); }
private static GraphQLIntrospectionFullType GetSubtype(GraphQLIntrospectionSchema graphQLIntrospectionSchema, GraphQLIntrospectionTypeRef graphQLIntrospectionTypeRef) { if (graphQLIntrospectionSchema is null) { throw new ArgumentNullException(nameof(graphQLIntrospectionSchema)); } if (graphQLIntrospectionTypeRef is null) { throw new ArgumentNullException(nameof(graphQLIntrospectionTypeRef)); } if (HasSubtype(graphQLIntrospectionTypeRef.Kind)) { return(GetSubtype(graphQLIntrospectionSchema, graphQLIntrospectionTypeRef.OfType)); } return(GetTypeByName(graphQLIntrospectionSchema, graphQLIntrospectionTypeRef.Name)); }
/// <summary> /// Validate if the GraphQL query type <typeparamref name="T"/> is valid for the specified <paramref name="graphQLIntrospectionSchema"/> and <paramref name="operationType"/> /// </summary> /// <typeparam name="T">The GraphQL query type to validate against</typeparam> /// <param name="graphQLIntrospectionSchema">The introspectionSchema to validate against</param> /// <param name="operationType">The operationType to validate against</param> /// <returns>An empty list if no errors were found or a <see cref="ValidationError"/> for each error found</returns> public static IEnumerable <ValidationError> ValidateGraphQLType <T>(this GraphQLIntrospectionSchema graphQLIntrospectionSchema, GraphQLOperationType operationType) { return(ValidateGraphQLType <T>(graphQLIntrospectionSchema, operationType, new GraphQLFieldBuilder())); }
private static IEnumerable <ValidationError> ValidateSelectionSet(GraphQLIntrospectionSchema graphQLIntrospectionSchema, IEnumerable <GraphQLField> selectionSet, GraphQLIntrospectionFullType graphQLIntrospectionType, GraphQLOperationType operationType, string fieldPath = null, bool rootLevel = false) { foreach (var selection in selectionSet) { // Get fieldPath var selectionFieldPath = fieldPath == null ? selection.Field : string.Join(".", fieldPath, selection.Field); // Get field from introspection var introspectionField = graphQLIntrospectionType.Fields.SingleOrDefault(e => e.Name == selection.Field); if (introspectionField == null) { yield return(new ValidationError(selectionFieldPath, ValidationType.Field_Not_Found, selection)); continue; } // IsDeprecated if (introspectionField.IsDeprecated) { yield return(new ValidationError(selectionFieldPath, ValidationType.Field_Deprecated, selection)); } // Validate arguments foreach (var argument in selection.Arguments) { // Get argument path var argumentPath = $"{selectionFieldPath}({argument.ArgumentName})"; // Get argument var introspectionArgument = introspectionField.Args.SingleOrDefault(arg => arg.Name == argument.ArgumentName); if (introspectionArgument == null) { yield return(new ValidationError(selectionFieldPath, ValidationType.Argument_Not_Found, selection)); continue; } // Validate type var typeName = GetTypeName(introspectionArgument.Type); if (!string.Equals(typeName, argument.ArgumentType, StringComparison.OrdinalIgnoreCase)) { yield return(new ValidationError(selectionFieldPath, ValidationType.Argument_Invalid_Type, selection, introspectionArgument.Type.Name, argument.ArgumentType)); } } // Switch on type kind (ignore __typename since union can only have __typename as selectionSet) var hasSelectionSet = selection.SelectionSet.Any(f => f.Field != "__typename"); // Get concrete type GraphQLIntrospectionFullType graphQLType = GetSubtype(graphQLIntrospectionSchema, introspectionField.Type); // Get kind switch (graphQLType.Kind) { case GraphQLTypeKind.Scalar: case GraphQLTypeKind.Union: case GraphQLTypeKind.Enum: // If Scalar/Union/Enum we cannot have selectionSet if (hasSelectionSet) { yield return(new ValidationError(selectionFieldPath, ValidationType.Field_Cannot_Have_SelectionSet, selection)); } break; case GraphQLTypeKind.Object: case GraphQLTypeKind.Interface: // Object/Interface should have selectionSet if (!hasSelectionSet) { yield return(new ValidationError(selectionFieldPath, ValidationType.Field_Should_Have_SelectionSet, selection)); } break; default: // Report error throw new NotImplementedException($"{nameof(GraphQLTypeKind)} {introspectionField.Type.Kind} not implemented for fields"); } // TODO: Validation should also validate type is correct for instance if GraphQLString is having the type String // Validate type if (selection.BaseType != null) { if (graphQLType.Kind == GraphQLTypeKind.Enum || graphQLType.Kind == GraphQLTypeKind.Scalar) { foreach (var error in ValidateNonNullScalar(selectionFieldPath, selection, IsListType(introspectionField.Type), IsNonNull(introspectionField.Type), graphQLType)) { yield return(error); } } switch (graphQLType.Kind) { case GraphQLTypeKind.Enum: foreach (var error in ValidateEnum(selectionFieldPath, selection, IsListType(introspectionField.Type), IsNonNull(introspectionField.Type), graphQLType)) { yield return(error); } break; case GraphQLTypeKind.Scalar: foreach (var error in ValidateScalar(selectionFieldPath, selection, IsListType(introspectionField.Type), IsNonNull(introspectionField.Type), graphQLType)) { yield return(error); } break; } } // Validate selectionSet if (hasSelectionSet) { // Validate selectionSet foreach (var result in ValidateSelectionSet(graphQLIntrospectionSchema, selection.SelectionSet, graphQLType, operationType, selectionFieldPath)) { yield return(result); } // Validate possible types foreach (var possibleType in selection.TargetTypes) { // Get fieldPath for possible type var possibleTypeFieldPath = $"{selectionFieldPath}[{possibleType.Key}]"; // Get type var introspectionPossibleType = GetTypeByName(graphQLIntrospectionSchema, possibleType.Key); if (introspectionPossibleType == null) { yield return(new ValidationError(possibleTypeFieldPath, ValidationType.PossibleType_Not_Found, selection)); continue; } // Validate selectionSet foreach (var result in ValidateSelectionSet(graphQLIntrospectionSchema, possibleType.Value.SelectionSet, introspectionPossibleType, operationType, possibleTypeFieldPath)) { yield return(result); } } } } }
private static GraphQLIntrospectionFullType GetTypeByName(GraphQLIntrospectionSchema graphQLIntrospectionSchema, string name) { return(graphQLIntrospectionSchema.Types.SingleOrDefault(type => string.Equals(type.Name, name, StringComparison.OrdinalIgnoreCase))); }
private static GraphQLIntrospectionFullType GetIntrospectionType(GraphQLIntrospectionFullType graphQLType, GraphQLIntrospectionSchema schema) { List <GraphQLIntrospectionField> fields = new List <GraphQLIntrospectionField>(); if (graphQLType.Fields != null) { fields.AddRange(graphQLType.Fields); } // If it's the query type if (graphQLType.Name == schema.QueryType.Name) { // Add schema fields.Add(new GraphQLIntrospectionField { Name = Implicit_Schema_Name, Type = new GraphQLIntrospectionTypeRef { Kind = GraphQLTypeKind.NonNull, OfType = new GraphQLIntrospectionTypeRef { Kind = GraphQLTypeKind.Object, Name = Implicit_Schema_Type } } }); // Add type fields.Add(new GraphQLIntrospectionField { Name = Implicit_Type_Name, Type = new GraphQLIntrospectionTypeRef { Kind = GraphQLTypeKind.Object, Name = Implicit_Type_Type }, Args = new List <GraphQLIntrospectionInputValue> { new GraphQLIntrospectionInputValue { Name = Implicit_Type_Argument_Name, Type = new GraphQLIntrospectionTypeRef { Kind = GraphQLTypeKind.NonNull, OfType = new GraphQLIntrospectionTypeRef { Kind = GraphQLTypeKind.Scalar, Name = Implicit_Type_Argument_Type } } } } }); } // Add typename fields.Add(new GraphQLIntrospectionField { Name = Implicit_TypeName_Name, Type = new GraphQLIntrospectionTypeRef { Kind = GraphQLTypeKind.Scalar, Name = Implicit_TypeName_Type } }); return(new GraphQLIntrospectionFullType { Description = graphQLType.Description, EnumValues = graphQLType.EnumValues, Fields = fields.AsEnumerable(), InputFields = graphQLType.InputFields, Interfaces = graphQLType.Interfaces, Kind = graphQLType.Kind, Name = graphQLType.Name, PossibleTypes = graphQLType.PossibleTypes }); }