/// <summary> /// Initializes a top level context for rules /// </summary> /// <param name="root"></param> public RuleContext(ServiceDefinition root, Uri file, ServiceDefinitionMetadata metadata) : this(null) { this.Root = root; this.Value = root; this.File = file; PopulateResourceTypes(root); }
private IEnumerable <ValidationMessage> ValidateOpenAPISpec(string input, ServiceDefinitionMetadata metadata) { var validator = new RecursiveObjectValidator(PropertyNameResolver.JsonName); var serviceDefinition = SwaggerParser.Parse(input, File.ReadAllText(input)); return(validator.GetValidationExceptions(new Uri(input, UriKind.RelativeOrAbsolute), serviceDefinition, metadata).OfType <ValidationMessage>()); }
private IEnumerable <ValidationMessage> ValidateSwagger(string input, ServiceDefinitionMetadata metadata) { // Most rules are to be applied for ARM documents // Also, most rules need to be run over the composite document (i.e. AFTER merge state) // hence the defaults using (NewContext) { var validator = new RecursiveObjectValidator(PropertyNameResolver.JsonName); var serviceDefinition = SwaggerParser.Parse(input, File.ReadAllText(input)); return(validator.GetValidationExceptions(new Uri(input, UriKind.RelativeOrAbsolute), serviceDefinition, metadata).OfType <ValidationMessage>()); } }
/// <summary> /// Validates an object value /// </summary> /// <param name="entity"></param> /// <param name="collectionRules"></param> /// <param name="parentContext"></param> /// <param name="metaData">The metadata associated with corresponding serviceDefinition</param> /// <returns></returns> private IEnumerable <LogMessage> ValidateObjectValue(object entity, IEnumerable <Rule> collectionRules, RuleContext parentContext, ServiceDefinitionMetadata metaData) { // Get any rules defined for the class of the entity var classRules = GetFilteredRules(entity.GetType().GetValidationRules(), metaData); // Combine the class rules with any rules that apply to the collection that the entity is part of classRules = collectionRules.Concat(classRules); // Apply each rule for the entity return(classRules.SelectMany(rule => rule.GetValidationMessages(entity, parentContext))); }
protected override async Task <bool> ProcessInternal() { var docStateInput = await GetValue <string>("merge-state"); ServiceDefinitionDocumentState docState; if (!Enum.TryParse <ServiceDefinitionDocumentState>(docStateInput, true, out docState)) { throw new Exception("Invalid Input for merge-state: " + docStateInput + ". Valid values are 'individual' and 'composed'."); } if ((await GetValue <bool>("azure-arm-validator.debugger")) || (docState == ServiceDefinitionDocumentState.Composed && await GetValue <bool>("azure-arm-validator.composed-debugger")) || (docState == ServiceDefinitionDocumentState.Individual && await GetValue <bool>("azure-arm-validator.individual-debugger"))) { Debugger.Await(); } var files = await ListInputs(); foreach (var file in files) { var content = await ReadFile(file); var fs = new MemoryFileSystem(); fs.WriteAllText(file, content); var serviceDefinition = SwaggerParser.Load(file, fs); var validator = new RecursiveObjectValidator(PropertyNameResolver.JsonName); var docTypeInput = (await GetValue <string>("openapi-type")); ServiceDefinitionDocumentType docType; // Convert data-plane to dataplane if (!Enum.TryParse <ServiceDefinitionDocumentType>(docTypeInput.Replace("-", ""), true, out docType)) { throw new Exception("Invalid Input for openapi-type: " + docTypeInput + ". Valid values are 'arm', 'data-plane' or 'default'."); } var metadata = new ServiceDefinitionMetadata { ServiceDefinitionDocumentType = docType, MergeState = docState }; foreach (ValidationMessage validationEx in validator.GetValidationExceptions(new Uri(file, UriKind.RelativeOrAbsolute), serviceDefinition, metadata)) { LogValidationMessage(validationEx); } } return(true); }
/// <summary> /// Recursively validates <paramref name="entity"/> by traversing all of its properties /// </summary> /// <param name="entity">The object to validate</param> /// <param name="metaData">metaData associated with the serviceDefinition</param> public IEnumerable <Rule> GetFilteredRules(IEnumerable <Rule> rules, ServiceDefinitionMetadata metaData) { // Filter by document type // By default select all rules, then add the doc type specific rules var serviceDefTypeRules = rules.Where(rule => rule.ServiceDefinitionDocumentType == ServiceDefinitionDocumentType.Default); if (metaData.ServiceDefinitionDocumentType != ServiceDefinitionDocumentType.Default) { serviceDefTypeRules = serviceDefTypeRules.Concat(rules.Where(rule => (rule.ServiceDefinitionDocumentType & metaData.ServiceDefinitionDocumentType) != 0)); } // Filter by the current merge state, and return return(serviceDefTypeRules.Where(rule => rule.ValidationRuleMergeState == metaData.MergeState)); }
/// <summary> /// Recursively validates <paramref name="entity"/> by traversing all of its properties /// </summary> /// <param name="entity">The object to validate</param> /// <param name="parentContext">The rule context of the object that <paramref name="entity"/> belongs to</param> /// <param name="rules">The set of rules from the parent object to apply to <paramref name="entity"/></param> /// <param name="metaData">The metadata associated with serviceDefinition to which <paramref name="entity"/> belongs to</param> /// <param name="traverseProperties">Whether or not to traverse this <paramref name="entity"/>'s properties</param> /// <returns></returns> private IEnumerable <LogMessage> RecursiveValidate(object entity, ObjectPath entityPath, RuleContext parentContext, IEnumerable <Rule> rules, ServiceDefinitionMetadata metaData, bool traverseProperties = true) { var messages = Enumerable.Empty <LogMessage>(); if (entity == null) { return(messages); } // Ensure that the rules can be re-enumerated without re-evaluating the enumeration. var collectionRules = rules.ReEnumerable(); var list = entity as IList; var dictionary = entity as IDictionary; if (traverseProperties && list != null) { // Recursively validate each list item and add the // item index to the location of each validation message var listMessages = list.SelectMany((item, index) => RecursiveValidate(item, entityPath.AppendIndex(index), parentContext.CreateChild(item, index), collectionRules, metaData)); messages = messages.Concat(listMessages); } else if (traverseProperties && dictionary != null) { // Dictionaries that don't provide any type info cannot be traversed, since it result in infinite iteration var shouldTraverseEntries = dictionary.IsTraversableDictionary(); // Recursively validate each dictionary entry and add the entry // key to the location of each validation message var dictMessages = dictionary.SelectMany((key, value) => RecursiveValidate(value, entityPath.AppendProperty((string)key), parentContext.CreateChild(value, (string)key), collectionRules, metaData, shouldTraverseEntries)); messages = messages.Concat(dictMessages); } // If this is a class, validate its value and its properties. else if (traverseProperties && entity.GetType().IsClass() && entity.GetType() != typeof(string)) { // Validate each property of the object var propertyMessages = entity.GetValidatableProperties() .SelectMany(p => ValidateProperty(p, p.GetValue(entity), entityPath.AppendProperty(p.Name), parentContext, metaData)); messages = messages.Concat(propertyMessages); } // Validate the value of the object itself var valueMessages = ValidateObjectValue(entity, collectionRules, parentContext, metaData); return(messages.Concat(valueMessages)); }
/// <summary> /// Recursively validates <paramref name="entity"/> by traversing all of its properties /// </summary> /// <param name="filePath">uri path to the servicedefinition document <paramref name="entity"/></param> /// <param name="entity">The object to validate</param><param name="entity">The object to validate</param> /// <param name="metaData">The metadata associated with serviceDefinition to which <paramref name="metaData"/> belongs to</param> public IEnumerable <LogMessage> GetValidationExceptions(Uri filePath, ServiceDefinition entity, ServiceDefinitionMetadata metadata) { return(RecursiveValidate(entity, ObjectPath.Empty, new RuleContext(entity, filePath), Enumerable.Empty <Rule>(), metadata)); }
/// <summary> /// Validates an object property /// </summary> /// <param name="prop">The property metadata</param> /// <param name="value">The property object to validate</param> /// <param name="entityPath">Path to the property object in the serviceDefinition</param> /// <param name="metaData">The metadata associated with corresponding serviceDefinition</param> /// <returns></returns> private IEnumerable <LogMessage> ValidateProperty(PropertyInfo prop, object value, ObjectPath entityPath, RuleContext parentContext, ServiceDefinitionMetadata metaData) { // Uses the property name resolver to get the name to use in the path of messages var propName = resolver(prop); // Determine if anything about this property indicates that it shouldn't be traversed further var shouldTraverseObject = prop.IsTraversableProperty(); // Create the context that's available to rules that validate this value var ruleContext = prop.GetCustomAttribute <JsonExtensionDataAttribute>(true) == null ? parentContext.CreateChild(value, propName) : parentContext.CreateChild(value, -1); // Get any rules defined on this property and any defined as applying to the collection var propertyRules = GetFilteredRules(prop.GetValidationRules(), metaData); var collectionRules = GetFilteredRules(prop.GetValidationCollectionRules(), metaData); // Validate the value of this property against any rules for it var propertyMessages = propertyRules.SelectMany(r => r.GetValidationMessages(value, ruleContext)); // Recursively validate the property (e.g. its properties or any list/dictionary entries), // passing any rules that apply to this collection) var childrenMessages = RecursiveValidate(value, entityPath, ruleContext, collectionRules, metaData, shouldTraverseObject); return(propertyMessages.Concat(childrenMessages)); }