예제 #1
0
        IEnumerable <ValidationResult> IValidatableDocument.Validate(SaveType saveType)
        {
            var parentPropertyName = EmbeddedDocumentUtility.GetParentPropertyName <T, TEmbeddedIn>((T)this);
            var validationContext  = DocumentValidationContext <T> .Create((T)this, Parent, parentPropertyName, saveType);

            return(ValidationUtility.Validate(Validators, validationContext));
        }
예제 #2
0
        public void SucceedsForValidDataWithReference()
        {
            // Arrange.
            var validator = new DocumentUniquenessValidator <TestEntityWithReference, string>(x => x.Title)
            {
                Scope = new Expression <Func <TestEntityWithReference, object> >[] { x => x.Site }
            };

            TestEntityWithReference.Create(new TestEntityWithReference
            {
                Site = Site.Create(new Site {
                    Title = "Baz"
                }),
                Title = "Foo"
            });
            var documentInstance = new TestEntityWithReference
            {
                Site = Site.Create(new Site {
                    Title = "Bar"
                }),
                Title = "Foo"
            };
            var context = DocumentValidationContext <TestEntityWithReference> .Create(documentInstance, SaveType.Any);

            // Act.
            var results = validator.Validate("Foo", context).ToList();

            // Assert.
            Assert.That(results, Is.Empty);
        }
예제 #3
0
        /// <summary>
        /// Validates the completed document context to ensure it is "correct" against the specification before generating
        /// the final document.
        /// </summary>
        /// <param name="context">The context containing the parsed sections of a query document..</param>
        /// <returns><c>true</c> if the rule passes, <c>false</c> otherwise.</returns>
        public override bool Execute(DocumentValidationContext context)
        {
            var fieldSelection = (FieldSelection)context.ActivePart;

            // inspect all declared arguments from the schema
            var allArgsValid = true;

            foreach (var argument in fieldSelection.Field.Arguments)
            {
                // when the argument is required but the schema defines no value
                // and it was not on the user query document this rule fails
                if (argument.TypeExpression.IsRequired &&
                    argument.DefaultValue == null &&
                    !fieldSelection.Arguments.ContainsKey(argument.Name.AsMemory()))
                {
                    this.ValidationError(
                        context,
                        fieldSelection.Node,
                        $"Missing Input Argument. The field '{fieldSelection.Name}' requires an input argument named '{argument.Name}'");
                    allArgsValid = false;
                }
            }

            return(allArgsValid);
        }
        /// <summary>
        /// Validates the completed document context to ensure it is "correct" against the specification before generating
        /// the final document.
        /// </summary>
        /// <param name="context">The context containing the parsed sections of a query document..</param>
        /// <returns><c>true</c> if the rule passes, <c>false</c> otherwise.</returns>
        public override bool Execute(DocumentValidationContext context)
        {
            var ivdp           = context.ActivePart as IInputValueDocumentPart;
            var graphType      = ivdp.GraphType as IInputObjectGraphType;
            var requiredFields = graphType?.Fields.Where(x => x.TypeExpression.IsRequired).ToList();
            var complexValue   = ivdp.Value as QueryComplexInputValue;

            if (complexValue == null || requiredFields == null || !requiredFields.Any())
            {
                this.ValidationError(
                    context,
                    ivdp.Node,
                    $"Input type mismatch. The {ivdp.InputType} '{ivdp.Name}' was used like an {TypeKind.INPUT_OBJECT.ToString()} " +
                    $"but contained no fields to evaluate. Check the schema definition for {ivdp.TypeExpression.TypeName}.");
                return(false);
            }

            var allFieldsAccountedFor = true;

            foreach (var field in requiredFields)
            {
                if (!complexValue.Arguments.ContainsKey(field.Name))
                {
                    this.ValidationError(
                        context,
                        ivdp.Node,
                        $"The {ivdp.InputType} '{ivdp.Name}' requires a field named '{field.Name}'.");
                    allFieldsAccountedFor = false;
                }
            }

            return(allFieldsAccountedFor);
        }
예제 #5
0
        public void FailsForInvalidData()
        {
            // Arrange.
            var validator = new EmbeddedDocumentUniquenessValidator <TestBook, TestAuthor, string>(x => x.Title);
            var testBook  = new TestBook {
                Title = "Bar"
            };
            var documentInstance = new TestAuthor
            {
                Books = new List <TestBook>
                {
                    new TestBook {
                        Title = "Foo"
                    },
                    testBook
                }
            };
            var context = DocumentValidationContext <TestBook> .Create(testBook, documentInstance, "Books", SaveType.Any);

            // Act.
            var results = validator.Validate("Foo", context).ToList();

            // Assert.
            Assert.That(results.Count, Is.EqualTo(1));
        }
예제 #6
0
        /// <summary>
        /// Registers a validation error with the local message collection as a critical error. The validation
        /// message will automatically be appended with the appropriate message extensions to reference the error being validated.
        /// </summary>
        /// <param name="context">The validation context in scope.</param>
        /// <param name="node">The node in scope when the error was generated.</param>
        /// <param name="message">The error message.</param>
        protected void ValidationError(DocumentValidationContext context, SyntaxNode node, string message)
        {
            var graphMessage = GraphExecutionMessage.FromValidationRule(
                this,
                message,
                node.Location.AsOrigin());

            context.Messages.Add(graphMessage);
        }
        public int TestValidation(string value)
        {
            // Arrange.
            var validator = new PresenceValidator <TestEntity>();
            var instance  = new TestEntity();
            var context   = DocumentValidationContext <TestEntity> .Create(instance, SaveType.Any);

            // Act.
            return(validator.Validate(value, context).Count());
        }
예제 #8
0
        /// <summary>
        /// Interpretes the syntax tree and generates a contextual document that can be transformed into
        /// a query plan.
        /// </summary>
        /// <param name="syntaxTree">The syntax tree to create a document for.</param>
        /// <returns>IGraphQueryDocument.</returns>
        public IGraphQueryDocument CreateDocument(ISyntaxTree syntaxTree)
        {
            Validation.ThrowIfNull(syntaxTree, nameof(syntaxTree));

            // --------------------------------------------
            // Step 1: Parse the syntax tree
            // --------------------------------------------
            // Walk all nodes of the tree and on a "per node" basis perform actions
            // that are required of that node be it a specification validation rule or
            // an incremental addition to the document context being built.
            //
            // Note: All packages are rendered and then named fragment nodes are processed first
            //       as they are required by any operations referenced elsewhere in the document
            // --------------------------------------------
            var nodeProcessor = new DocumentConstructionRuleProcessor();
            var docContext    = new DocumentContext(_schema);
            var nodeContexts  = new List <DocumentConstructionContext>();

            foreach (var node in syntaxTree.Nodes)
            {
                var nodeContext = docContext.ForTopLevelNode(node);
                nodeContexts.Add(nodeContext);
            }

            nodeContexts.Sort(new TopLevelNodeProcessingOrder());
            var completedAllSteps = nodeProcessor.Execute(nodeContexts);

            // --------------------------------------------
            // Step 2: Validate the document parts
            // --------------------------------------------
            // Inspect the document parts that were generated during part one and, as a whole, run additional
            // validation rules and perform final changes before constructing the final document.
            // e.g. ensure all fragments were called, all variables were referenced at least once etc.
            // --------------------------------------------
            if (completedAllSteps)
            {
                var documentProcessor  = new DocumentValidationRuleProcessor();
                var validationContexts = new List <DocumentValidationContext>();
                foreach (var part in docContext.Children)
                {
                    var partContext = new DocumentValidationContext(docContext, part);
                    validationContexts.Add(partContext);
                }

                documentProcessor.Execute(validationContexts);
            }

            // --------------------------------------------
            // Step 3: Build out the final document
            // --------------------------------------------
            return(docContext.ConstructDocument());
        }
예제 #9
0
        public int TestValidation(string value, int minimumLength, int maximumLength)
        {
            // Arrange.
            var validator = new StringLengthValidator <TestEntity>(minimumLength, maximumLength)
            {
                AllowNull = true
            };
            var instance = new TestEntity();
            var context  = DocumentValidationContext <TestEntity> .Create(instance, SaveType.Any);

            // Act.
            return(validator.Validate(value, context).Count());
        }
예제 #10
0
 IEnumerable <ValidationResult> IValidatableDocument.Validate(SaveType saveType)
 {
     Errors.AddRange(ValidationUtility.Validate(PropertyValidators, DocumentValidationContext <T> .Create((T)this, saveType)));
     foreach (IValidatableDocument embeddedDocument in EmbeddedDocumentUtility.GetEmbeddedDocuments(this))
     {
         Errors.AddRange(embeddedDocument.Validate(saveType));
     }
     foreach (var objectValidator in ObjectValidators)
     {
         Errors.AddRange(objectValidator((T)this));
     }
     return(Errors);
 }
예제 #11
0
        /// <summary>
        /// Validates the completed document context to ensure it is "correct" against the specification before generating
        /// the final document.
        /// </summary>
        /// <param name="context">The context containing the parsed sections of a query document..</param>
        /// <returns><c>true</c> if the rule passes, <c>false</c> otherwise.</returns>
        public override bool Execute(DocumentValidationContext context)
        {
            var variable = (QueryVariable)context.ActivePart;

            if (!variable.IsReferenced)
            {
                this.ValidationError(
                    context,
                    variable.Node,
                    $"The variable '{variable.Name}' was not used within the target operation. " +
                    "All declared variables must be used at least once.");
                return(false);
            }

            return(true);
        }
예제 #12
0
        /// <summary>
        /// Validates the completed document context to ensure it is "correct" against the specification before generating
        /// the final document.
        /// </summary>
        /// <param name="context">The context containing the parsed sections of a query document..</param>
        /// <returns><c>true</c> if the rule passes, <c>false</c> otherwise.</returns>
        public override bool Execute(DocumentValidationContext context)
        {
            var fragment = (QueryFragment)context.ActivePart;

            if (!fragment.IsReferenced)
            {
                this.ValidationError(
                    context,
                    fragment.Node,
                    $"The named fragment '{fragment.Name}' was not referenced by an operation. " +
                    "All declared fragments must be used at least once.");
                return(false);
            }

            return(true);
        }
예제 #13
0
        /// <summary>
        /// Validates the specified node to ensure it is "correct" in the context of the rule doing the valdiation.
        /// </summary>
        /// <param name="context">The validation context encapsulating a <see cref="SyntaxNode"/> that needs to be validated.</param>
        /// <returns><c>true</c> if the node is valid, <c>false</c> otherwise.</returns>
        public override bool Execute(DocumentValidationContext context)
        {
            // anonymous operations will all present as ReadOnlyMemory<char>.Empty
            var operation = (QueryOperation)context.ActivePart;

            if (context.DocumentContext.Operations.Count > 1)
            {
                this.ValidationError(
                    context,
                    operation.Node,
                    "A query document may declare an anonymous operation only if it exists by itself in a document. This document " +
                    $"contains {context.DocumentContext.Operations.Count} total operation(s). Remove the other operations are " +
                    "provide a name for every operation.");

                return(false);
            }

            return(true);
        }
        /// <summary>
        /// Validates the completed document context to ensure it is "correct" against the specification before generating
        /// the final document.
        /// </summary>
        /// <param name="context">The context containing the parsed sections of a query document..</param>
        /// <returns><c>true</c> if the rule passes, <c>false</c> otherwise.</returns>
        public override bool Execute(DocumentValidationContext context)
        {
            var queryDirective   = (QueryDirective)context.ActivePart;
            var directiveIsValid = true;

            // inspect all declared arguments from the schema
            foreach (var argument in queryDirective.Directive.Arguments)
            {
                if (argument.DefaultValue == null && !queryDirective.Arguments.ContainsKey(argument.Name.AsMemory()))
                {
                    this.ValidationError(
                        context,
                        queryDirective.Node,
                        $"Missing Input Argument. The directive '{queryDirective.Name}' requires an input argument named '{argument.Name}'");
                    directiveIsValid = false;
                }
            }

            return(directiveIsValid);
        }
예제 #15
0
        /// <summary>
        /// Validates the completed document context to ensure it is "correct" against the specification before generating
        /// the final document.
        /// </summary>
        /// <param name="context">The context containing the parsed sections of a query document..</param>
        /// <returns>
        ///   <c>true</c> if the rule passes, <c>false</c> otherwise.</returns>
        public override bool Execute(DocumentValidationContext context)
        {
            var argument = context.ActivePart as QueryInputArgument;
            var qvr      = argument.Value as QueryVariableReferenceInputValue;

            // ensure the type expressions are compatible at the location used
            if (!qvr.Variable.TypeExpression.Equals(argument.TypeExpression))
            {
                this.ValidationError(
                    context,
                    argument.Node,
                    $"Invalid Variable Argument. The type expression for the variable used on the " +
                    $"{argument.InputType} '{argument.Name}' could " +
                    $"not be successfully coerced to the required type. Expected '{argument.TypeExpression}' but got '{qvr.Variable.TypeExpression}'. Double check " +
                    $"the declared graph type of the variable and ensure it matches the required type of '{argument.Name}'.");

                return(false);
            }

            return(true);
        }
예제 #16
0
        public void FailsForInvalidData()
        {
            // Arrange.
            var validator = new DocumentUniquenessValidator <TestEntity, string>(x => x.Title)
            {
                Scope = new Expression <Func <TestEntity, object> >[] { x => x.SiteID }
            };

            TestEntity.Create(new TestEntity {
                SiteID = 1, Title = "Foo"
            });
            var documentInstance = new TestEntity {
                SiteID = 1, Title = "Foo"
            };
            var context = DocumentValidationContext <TestEntity> .Create(documentInstance, SaveType.Any);

            // Act.
            var results = validator.Validate("Foo", context).ToList();

            // Assert.
            Assert.That(results, Has.Count.EqualTo(1));
        }
예제 #17
0
        /// <summary>
        /// Validates the completed document context to ensure it is "correct" against the specification before generating
        /// the final document.
        /// </summary>
        /// <param name="context">The context containing the parsed sections of a query document..</param>
        /// <returns><c>true</c> if the rule passes, <c>false</c> otherwise.</returns>
        public override bool Execute(DocumentValidationContext context)
        {
            var ivdp  = context.ActivePart as IInputValueDocumentPart;
            var value = ivdp.Value;

            // variables do not have to supply a default value
            if (value == null && ivdp is QueryVariable)
            {
                return(true);
            }

            if (!this.EvaluateContextData(value))
            {
                this.ValidationError(
                    context,
                    value.ValueNode,
                    $"Invalid {ivdp.InputType}. The value for the input item named '{ivdp.Name}' could " +
                    $"not be successfully coerced to the required type of '{ivdp.TypeExpression}'.");

                return(false);
            }

            return(true);
        }
 /// <summary>
 /// Determines whether this instance can process the given context. The rule will have no effect on the node if it cannot
 /// process it.
 /// </summary>
 /// <param name="context">The context that may be acted upon.</param>
 /// <returns><c>true</c> if this instance can validate the specified node; otherwise, <c>false</c>.</returns>
 public override bool ShouldExecute(DocumentValidationContext context)
 {
     return(base.ShouldExecute(context) &&
            context.ActivePart is QueryOperation operation && operation.OperationType == GraphCollection.Subscription);
 }
예제 #19
0
 /// <summary>
 /// Determines whether this instance can process the given context. The rule will have no effect on the node if it cannot
 /// process it.
 /// </summary>
 /// <param name="context">The context that may be acted upon.</param>
 /// <returns><c>true</c> if this instance can validate the specified node; otherwise, <c>false</c>.</returns>
 public override bool ShouldExecute(DocumentValidationContext context)
 {
     return(base.ShouldExecute(context) &&
            context.ActivePart is QueryOperation operation && operation.Name == string.Empty);
 }
예제 #20
0
 /// <summary>
 /// Determines whether this instance can process the given context. The rule will have no effect on the input argument if it cannot
 /// process it.
 /// </summary>
 /// <param name="context">The context that may be acted upon.</param>
 /// <returns><c>true</c> if this instance can validate the specified input argument; otherwise, <c>false</c>.</returns>
 public override bool ShouldExecute(DocumentValidationContext context)
 {
     return(context.ActivePart is IInputValueDocumentPart ivdp && !(ivdp.Value is QueryVariableReferenceInputValue));
 }
 /// <summary>
 /// Determines whether this instance can process the given context. The rule will have no effect on the node if it cannot
 /// process it.
 /// </summary>
 /// <param name="context">The context that may be acted upon.</param>
 /// <returns><c>true</c> if this instance can validate the specified document part; otherwise, <c>false</c>.</returns>
 public override bool ShouldExecute(DocumentValidationContext context)
 {
     return context.Contains<TContextItem>();
 }
예제 #22
0
 /// <summary>
 /// Determines whether this instance can process the given context. The rule will have no effect on the input argument if it cannot
 /// process it.
 /// </summary>
 /// <param name="context">The context that may be acted upon.</param>
 /// <returns><c>true</c> if this instance can validate the specified input argument; otherwise, <c>false</c>.</returns>
 public override bool ShouldExecute(DocumentValidationContext context)
 {
     return(context.ActivePart is QueryInputArgument arg && arg.Value is QueryVariableReferenceInputValue);
 }
        /// <summary>
        /// Validates the completed document context to ensure it is "correct" against the specification before generating
        /// the final document.
        /// </summary>
        /// <param name="context">The context containing the parsed sections of a query document..</param>
        /// <returns><c>true</c> if the rule passes, <c>false</c> otherwise.</returns>
        public override bool Execute(DocumentValidationContext context)
        {
            // due to the use of virtual fields used by controllers to make a dynamic schema,
            // this rule extend rule 5.2.3.1 to include the top-level operation and each virtual child field
            // has 1 and only 1 child field declaration up to and including a subscription action being located.
            // that is to say the nested fieldsets must not branch until AFTER a subscription field is encountered
            // as its this field that is registered as the subscription, not the virtual field paths

            /*
             *  -----------------------------------------------------
             *  Valid:
             *  -----------------------------------------------------
             *  subscription {
             *      ctrlPath {
             *          routePath1 {
             *              routePath2 {
             *                  subscriptionAction { }
             *              }
             *          }
             *      }
             *  }
             *
             *
             *  subscription {
             *      subscriptionAction { }
             *  }
             *
             *
             *  -----------------------------------------------------
             *  Invalid:
             *  -----------------------------------------------------
             *  subscription {
             *      ctrlPath {
             *          routePath1 {
             *              routePath2 {
             *                  subscriptionAction1 { }
             *                  subscriptionAction2 { }  // two subscription actions encountered (must be 1)
             *              }
             *          }
             *      }
             *  }
             *
             *  subscription {
             *      ctrlPath {
             *          routePath1 {
             *              routePath2 {
             *                  queryActionField { }   // not a subscription field
             *              }
             *          }
             *      }
             *  }
             *
             *  subscription {
             *      controller {
             *          routePath1 {
             *              routePath2 {
             *                  subscriptionActionField2 { }
             *              }
             *          }
             *          subscriptionActionField2 { }  // split pathing allows for two possible subscription fields
             *                                        //(must encounter only 1)
             *      }
             *  }
             */

            var operation       = context.ActivePart as QueryOperation;
            var fieldCollection = operation?.FieldSelectionSet;

            while (fieldCollection != null && fieldCollection.Count == 1)
            {
                // did we encounter a field collection with exactly one child that is not virtual?
                // i.e. one "top-level user action" to be called for the subscription?
                var childField = fieldCollection[0];
                if (!childField.GraphType.IsVirtual)
                {
                    return(true);
                }

                fieldCollection = childField.FieldSelectionSet;
            }

            this.ValidationError(
                context,
                operation.Node,
                "Invalid Subscription. Expected exactly 1 child field, " +
                $"recieved {fieldCollection?.Count ?? 0} child fields at {fieldCollection?.RootPath.DotString() ?? "-null-"}.");
            return(false);
        }
 /// <summary>
 /// Determines whether this instance can process the given context. The rule will have no effect on the input argument if it cannot
 /// process it.
 /// </summary>
 /// <param name="context">The context that may be acted upon.</param>
 /// <returns><c>true</c> if this instance can validate the specified input argument; otherwise, <c>false</c>.</returns>
 public override bool ShouldExecute(DocumentValidationContext context)
 {
     return(context.ActivePart is IInputValueDocumentPart ivdp &&
            ivdp.Value is QueryComplexInputValue);
 }