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