Example #1
0
        /// <summary>
        /// Builds out this field template parsing out the required type declarations unions, nameing scheme etc.  This method should be
        /// overridden in any child classes for additional decalration requirements.
        /// </summary>
        protected override void ParseTemplateDefinition()
        {
            // ensure type extension right out of the gate
            // the field can't be built otherwise
            _typeAttrib = this.SingleAttributeOfTypeOrDefault <TypeExtensionAttribute>();
            if (_typeAttrib == null)
            {
                return;
            }

            _sourceType = _typeAttrib.TypeToExtend;

            base.ParseTemplateDefinition();

            var returnType = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredReturnType);

            if (returnType != typeof(IGraphActionResult))
            {
                // inspect the return type, if its a valid dictionary extract the return type from the value
                // and set the type modifiers and method type based on the value of each dictionary entry
                if (_typeAttrib.ExecutionMode == FieldResolutionMode.Batch)
                {
                    returnType          = returnType.GetValueTypeOfDictionary();
                    this.ObjectType     = GraphValidation.EliminateWrappersFromCoreType(returnType);
                    this.TypeExpression = GraphValidation.GenerateTypeExpression(returnType, this);
                    this.PossibleTypes.Insert(0, this.ObjectType);
                }
            }
        }
        /// <summary>
        /// Parses the template contents according to the rules of the template.
        /// </summary>
        public virtual void Parse()
        {
            this.DeclaredArgumentType = this.Parameter.ParameterType;
            this.ObjectType           = GraphValidation.EliminateWrappersFromCoreType(this.Parameter.ParameterType);

            // set the name
            _fieldDeclaration = this.Parameter.SingleAttributeOrDefault <FromGraphQLAttribute>();
            string name = null;

            if (_fieldDeclaration != null)
            {
                name = _fieldDeclaration?.ArgumentName?.Trim();
            }

            if (string.IsNullOrWhiteSpace(name))
            {
                name = Constants.Routing.PARAMETER_META_NAME;
            }

            name       = name.Replace(Constants.Routing.PARAMETER_META_NAME, this.Parameter.Name);
            this.Route = new GraphArgumentFieldPath(this.Parent.Route, name);

            this.Description = this.Parameter.SingleAttributeOrDefault <DescriptionAttribute>()?.Description?.Trim();

            if (this.Parameter.HasDefaultValue && this.Parameter.DefaultValue != null)
            {
                // enums will present their default value as a raw int
                if (this.ObjectType.IsEnum)
                {
                    this.DefaultValue = Enum.ToObject(this.ObjectType, this.Parameter.DefaultValue);
                }
                else
                {
                    this.DefaultValue = this.Parameter.DefaultValue;
                }
            }

            // set appropriate meta data about this parameter for inclusion in the type system
            this.TypeExpression = GraphValidation.GenerateTypeExpression(this.DeclaredArgumentType, this);
            this.TypeExpression = this.TypeExpression.CloneTo(GraphTypeNames.ParseName(this.ObjectType, TypeKind.INPUT_OBJECT));

            // when this argument accepts the same data type as the data returned by its owners target source type
            // i.e. if the source data supplied to the field for resolution is the same as this argument
            // then assume this argument is to contain the source data
            // since the source data will be an OBJECT type (not INPUT_OBJECT) there is no way the user could have supplied it
            if (this.IsSourceDataArgument())
            {
                this.ArgumentModifiers = this.ArgumentModifiers
                                         | GraphArgumentModifiers.ParentFieldResult
                                         | GraphArgumentModifiers.Internal;
            }
        }
        public void GenerateTypeExpression(
            Type type,
            string expectedExpression,
            bool definesDefaultValue,
            GTW[] wrappers)
        {
            var mock = new Mock <IGraphTypeExpressionDeclaration>();

            mock.Setup(x => x.HasDefaultValue).Returns(definesDefaultValue);
            mock.Setup(x => x.TypeWrappers).Returns(wrappers);

            var typeExpression = GraphValidation.GenerateTypeExpression(type, mock.Object);

            Assert.AreEqual(expectedExpression, typeExpression.ToString());
        }
Example #4
0
        /// <summary>
        /// Validates the completed field context to ensure it is "correct" against the specification before finalizing its reslts.
        /// </summary>
        /// <param name="context">The context containing the resolved field.</param>
        /// <returns><c>true</c> if the node is valid, <c>false</c> otherwise.</returns>
        public override bool Execute(FieldValidationContext context)
        {
            var typeExpression = context.TypeExpression;

            if (typeExpression.IsRequired && context.ResultData == null)
            {
                // 6.4.3 section 1c
                this.ValidationError(
                    context,
                    $"Field '{context.FieldPath}' expected a non-null result but received {{null}}.");

                context.DataItem.InvalidateResult();
            }

            // 6.4.3 section 2
            if (context.ResultData == null)
            {
                return(true);
            }

            // 6.4.3 section 3, ensure an IEnumerable for a type expression that is a list
            if (typeExpression.IsListOfItems && !GraphValidation.IsValidListType(context.ResultData.GetType()))
            {
                this.ValidationError(
                    context,
                    $"Field '{context.FieldPath}' was expected to return a list of items but instead returned a single item.");

                context.DataItem.InvalidateResult();
                return(true);
            }

            var graphType = context.Schema?.KnownTypes.FindGraphType(typeExpression?.TypeName);

            if (graphType == null)
            {
                this.ValidationError(
                    context,
                    $"The graph type for field '{context.FieldPath}' ({typeExpression?.TypeName}) does not exist on the target schema. The field" +
                    $"cannot be properly evaluated.");

                context.DataItem.InvalidateResult();
            }
            else if (!typeExpression.Matches(context.ResultData, graphType.ValidateObject))
            {
                // generate a valid, properly cased type expression reference for the data that was provided
                var actualExpression = GraphValidation.GenerateTypeExpression(context.ResultData.GetType());
                var coreType         = GraphValidation.EliminateWrappersFromCoreType(context.ResultData.GetType());
                var actualType       = context.Schema.KnownTypes.FindGraphType(coreType);
                if (actualType != null)
                {
                    actualExpression = actualExpression.CloneTo(actualType.Name);
                }

                // 6.4.3  section 4 & 5
                this.ValidationError(
                    context,
                    $"The resolved value for field '{context.FieldPath}' does not match the required type expression. " +
                    $"Expected {typeExpression} but got {actualExpression}.");

                context.DataItem.InvalidateResult();
            }

            return(true);
        }
Example #5
0
        /// <summary>
        /// When overridden in a child class this method builds out the template according to its own individual requirements.
        /// </summary>
        protected override void ParseTemplateDefinition()
        {
            _fieldDeclaration = this.SingleAttributeOfTypeOrDefault <GraphFieldAttribute>();

            // ------------------------------------
            // Common Metadata
            // ------------------------------------
            this.Route       = this.GenerateFieldPath();
            this.Mode        = _fieldDeclaration?.ExecutionMode ?? FieldResolutionMode.PerSourceItem;
            this.Complexity  = _fieldDeclaration?.Complexity;
            this.Description = this.SingleAttributeOfTypeOrDefault <DescriptionAttribute>()?.Description;
            var depreciated = this.SingleAttributeOfTypeOrDefault <DeprecatedAttribute>();

            if (depreciated != null)
            {
                this.IsDeprecated      = true;
                this.DeprecationReason = depreciated.Reason?.Trim();
            }

            var objectType     = GraphValidation.EliminateWrappersFromCoreType(this.DeclaredReturnType);
            var typeExpression = GraphValidation.GenerateTypeExpression(this.DeclaredReturnType, this);

            typeExpression = typeExpression.CloneTo(GraphTypeNames.ParseName(objectType, this.Parent.Kind));

            // ------------------------------------
            // Gather Possible Types and/or union definition
            // ------------------------------------
            this.BuildUnionProxyInstance();
            if (this.UnionProxy != null)
            {
                this.PossibleTypes = new List <Type>(this.UnionProxy.Types);
            }
            else
            {
                this.PossibleTypes = new List <Type>();

                // the possible types attribte is optional but expects taht the concrete types are added
                // to the schema else where lest a runtime exception occurs of a missing graph type.
                var typesAttrib = this.SingleAttributeOfTypeOrDefault <PossibleTypesAttribute>();
                if (typesAttrib != null)
                {
                    foreach (var type in typesAttrib.PossibleTypes)
                    {
                        this.PossibleTypes.Add(type);
                    }
                }

                // add any types decalred on the primary field declaration
                if (_fieldDeclaration != null)
                {
                    foreach (var type in _fieldDeclaration.Types)
                    {
                        var strippedType = GraphValidation.EliminateWrappersFromCoreType(type);
                        if (strippedType != null)
                        {
                            this.PossibleTypes.Add(strippedType);
                        }
                    }
                }

                if (objectType != null && !Validation.IsCastable <IGraphActionResult>(objectType) && GraphValidation.IsValidGraphType(objectType))
                {
                    this.PossibleTypes.Insert(0, objectType);
                }
            }

            this.PossibleTypes = this.PossibleTypes.Distinct().ToList();

            // ------------------------------------
            // Adjust the action result to the actual return type this field returns
            // ------------------------------------
            if (Validation.IsCastable <IGraphActionResult>(objectType))
            {
                if (this.UnionProxy != null)
                {
                    // if a union was decalred preserve whatever modifer elements
                    // were decalred but alter the return type to object (a known common element among all members of the union)
                    objectType = typeof(object);
                }
                else if (_fieldDeclaration != null && _fieldDeclaration.Types.Count > 0)
                {
                    objectType     = _fieldDeclaration.Types[0];
                    typeExpression = GraphValidation.GenerateTypeExpression(objectType, this)
                                     .CloneTo(GraphTypeNames.ParseName(objectType, this.Parent.Kind));
                    objectType = GraphValidation.EliminateWrappersFromCoreType(objectType);
                }
                else if (this.PossibleTypes.Count > 0)
                {
                    objectType     = this.PossibleTypes[0];
                    typeExpression = GraphValidation.GenerateTypeExpression(objectType, this)
                                     .CloneTo(GraphTypeNames.ParseName(objectType, this.Parent.Kind));
                    objectType = GraphValidation.EliminateWrappersFromCoreType(objectType);
                }
                else
                {
                    objectType = typeof(object);
                }
            }

            this.ObjectType = objectType;

            if (this.UnionProxy != null)
            {
                this.TypeExpression = typeExpression.CloneTo(this.UnionProxy.Name);
            }
            else
            {
                this.TypeExpression = typeExpression;
            }

            // ------------------------------------
            // Async Requirements
            // ------------------------------------
            this.IsAsyncField = Validation.IsCastable <Task>(this.DeclaredReturnType);

            // ------------------------------------
            // Security Policies
            // ------------------------------------
            _securityPolicies = FieldSecurityGroup.FromAttributeCollection(this.AttributeProvider);
        }
Example #6
0
        /// <summary>
        /// Validates the completed field context to ensure it is "correct" against the specification before finalizing its reslts.
        /// </summary>
        /// <param name="context">The context containing the resolved field.</param>
        /// <returns><c>true</c> if the node is valid, <c>false</c> otherwise.</returns>
        public override bool Execute(FieldValidationContext context)
        {
            var dataObject = context.ResultData;

            // 6.4.3 section 1c
            // This is a quick short cut and customed error message for a common top-level null mismatch.
            var dataItemTypeExpression = context.DataItem.TypeExpression;

            if (dataItemTypeExpression.IsRequired && dataObject == null)
            {
                this.ValidationError(
                    context,
                    $"Field '{context.FieldPath}' expected a non-null result but received {{null}}.");

                context.DataItem.InvalidateResult();
            }

            // 6.4.3 section 2
            if (dataObject == null)
            {
                return(true);
            }

            // 6.4.3 section 3, ensure list type in the result object for a type expression that is a list.
            // This is a quick short cut and customed error message for a common top-level list mismatch.
            if (dataItemTypeExpression.IsListOfItems && !GraphValidation.IsValidListType(dataObject.GetType()))
            {
                this.ValidationError(
                    context,
                    $"Field '{context.FieldPath}' was expected to return a list of items but instead returned a single item.");

                context.DataItem.InvalidateResult();
                return(true);
            }

            var expectedGraphType = context.Schema?.KnownTypes.FindGraphType(context.Field);

            if (expectedGraphType == null)
            {
                this.ValidationError(
                    context,
                    $"The graph type for field '{context.FieldPath}' ({dataItemTypeExpression?.TypeName}) does not exist on the target schema. The field" +
                    "cannot be properly evaluated.");

                context.DataItem.InvalidateResult();
                return(true);
            }

            // Perform a deep check of the meta-type chain (list and nullability wrappers) against the result data.
            // For example, if the type expression is [[SomeType]] ensure the result object is List<List<T>> etc.)
            // however, use the type name of the actual data object, not the graph type itself
            // we only want to check the type expression wrappers in this step
            var rootSourceType        = GraphValidation.EliminateWrappersFromCoreType(dataObject.GetType());
            var mangledTypeExpression = dataItemTypeExpression.CloneTo(rootSourceType.Name);

            if (!mangledTypeExpression.Matches(dataObject))
            {
                // generate a valid, properly cased type expression reference for the data that was provided
                var actualExpression = GraphValidation.GenerateTypeExpression(context.ResultData.GetType());

                // Fake the type expression against the real graph type
                // this step only validates the meta graph types the actual type may be different (but castable to the concrete
                // type of the graphType). Don't confuse the user in this step.
                actualExpression = actualExpression.CloneTo(expectedGraphType.Name);

                // 6.4.3  section 4 & 5
                this.ValidationError(
                    context,
                    $"The resolved value for field '{context.FieldPath}' does not match the required type expression. " +
                    $"Expected {dataItemTypeExpression} but got {actualExpression}.");

                context.DataItem.InvalidateResult();
            }

            return(true);
        }