Ejemplo n.º 1
0
        /// <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));
        }
Ejemplo n.º 2
0
        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);
                        }
                    }
                }
            }
        }
Ejemplo n.º 3
0
        private static IEnumerable <ValidationError> ValidateNonNullScalar(string selectionFieldPath, GraphQLField selection, bool isListType, bool isNonNull, GraphQLIntrospectionFullType graphQLType)
        {
            var type = selection.BaseType;

            if (isListType)
            {
                type = GetIEnumerableType(type);
            }

            // If type is class it is always nullable
            if (type.GetTypeInfo().IsClass)
            {
                yield break;
            }

            var isNullable = IsNullableType(type);

            // Validate nullable
            if (!isNullable != isNonNull)
            {
                if (isNonNull)
                {
                    yield return(new ValidationError(selectionFieldPath, ValidationType.Field_Should_Be_NonNull, selection));
                }
                else
                {
                    yield return(new ValidationError(selectionFieldPath, ValidationType.Field_Should_Be_Nullable, selection));
                }
            }
        }
Ejemplo n.º 4
0
        private static IEnumerable <ValidationError> ValidateEnum(string selectionFieldPath, GraphQLField selection, bool isListType, bool isNonNull, GraphQLIntrospectionFullType graphQLType)
        {
            var type = GetSpecificType(selection, isListType);

            if (type.GetTypeInfo().IsEnum)
            {
                // Get all enum names
                var memberNames = Enum.GetNames(type);

                // Get enum values
                foreach (var member in type.GetTypeInfo().DeclaredMembers)
                {
                    if (memberNames.Contains(member.Name))
                    {
                        var enumMemberValue = member.Name;

                        // Detect if an attribute overrides the name
                        var attribute = member.GetCustomAttribute <EnumMemberAttribute>();
                        if (attribute != null)
                        {
                            enumMemberValue = attribute.Value;
                        }

                        // Validate if the enum member could be found
                        var enumValue = graphQLType.EnumValues.SingleOrDefault(introspectionMember => string.Equals(introspectionMember.Name, enumMemberValue, StringComparison.OrdinalIgnoreCase));
                        if (enumValue == null)
                        {
                            yield return(new ValidationError($"{selectionFieldPath}[{enumMemberValue}]", ValidationType.EnumValue_Not_Found, selection));

                            continue;
                        }

                        // Validate that if the enum member is deprecated
                        if (enumValue.IsDeprecated)
                        {
                            yield return(new ValidationError($"{selectionFieldPath}[{enumMemberValue}]", ValidationType.EnumValue_Deprecated, selection));

                            continue;
                        }
                    }
                }
            }
            else
            {
                yield return(new ValidationError(selectionFieldPath, ValidationType.Field_Type_Not_Enum, selection));
            }
        }
Ejemplo n.º 5
0
        private static IEnumerable <ValidationError> ValidateScalar(string selectionFieldPath, GraphQLField selection, bool isListType, bool isNonNull, GraphQLIntrospectionFullType graphQLType)
        {
            var type = GetSpecificType(selection, isListType);

            switch (graphQLType.Name)
            {
            case "String":
                if (!IsValidStringType(type))
                {
                    yield return(new ValidationError(
                                     selectionFieldPath,
                                     ValidationType.Field_Invalid_Type,
                                     selection,
                                     typeof(string).Name,
                                     type.Name));
                }
                break;

            case "Boolean":
                if (!IsValidBooleanType(type, isNonNull))
                {
                    yield return(new ValidationError(
                                     selectionFieldPath,
                                     ValidationType.Field_Invalid_Type,
                                     selection,
                                     typeof(bool).Name,
                                     type.Name));
                }
                break;

            case "Float":
            case "Decimal":
                if (!IsValidFloatOrDecimalType(type, isNonNull))
                {
                    yield return(new ValidationError(
                                     selectionFieldPath,
                                     ValidationType.Field_Invalid_Type,
                                     selection,
                                     $"{typeof(float).Name} or {typeof(double).Name} or {typeof(decimal).Name}",
                                     type.Name));
                }
                break;
            }
        }
Ejemplo n.º 6
0
        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
            });
        }