// Verifies if a tracked resource has a corresponding PATCH operation public override IEnumerable <ValidationMessage> GetValidationMessages(Dictionary <string, Schema> definitions, RuleContext context) { ServiceDefinition serviceDefinition = (ServiceDefinition)context.Root; // enumerate all the PATCH operations IEnumerable <Operation> patchOperations = ValidationUtilities.GetOperationsByRequestMethod("patch", serviceDefinition); foreach (var op in patchOperations) { var reqModels = op.Parameters.Where(p => p.In == ParameterLocation.Body).Select(p => p.Schema?.Reference?.StripDefinitionPath()).Where(p => !string.IsNullOrEmpty(p)); foreach (var reqModel in reqModels) { // select all models that have properties set to required var reqProps = ValidationUtilities.EnumerateRequiredProperties(reqModel, definitions); // select all models that have properties with default values var defValProps = ValidationUtilities.EnumerateDefaultValuedProperties(reqModel, definitions); var modelHierarchy = ValidationUtilities.EnumerateModelHierarchy(reqModel, definitions); foreach (var reqProp in reqProps) { var modelContainingReqProp = modelHierarchy.First(model => definitions[model].Required?.Contains(reqProp) == true); yield return(new ValidationMessage(new FileObjectPath(context.File, context.Path.AppendProperty(modelContainingReqProp).AppendProperty("required")), this, "required", op.OperationId, modelContainingReqProp, reqProp)); } foreach (var defValProp in defValProps) { var modelContainingDefValProp = modelHierarchy.First(model => definitions[model].Properties?.Contains(defValProp) == true); yield return(new ValidationMessage(new FileObjectPath(context.File, context.Path.AppendProperty(modelContainingDefValProp).AppendProperty("properties").AppendProperty(defValProp.Key)), this, "default-valued", op.OperationId, modelContainingDefValProp, defValProp.Key)); } } } }
/// <summary> /// Validation fails iof tracked resource fails to meet one of the four required criteria. /// </summary> /// <param name="definitions">Operation Definition to validate</param> /// <param name="formatParameters">The noun to be put in the failure message</param> /// <returns></returns> public override bool IsValid(Dictionary <string, Schema> definitions, RuleContext context, out object[] formatParameters) { IEnumerable <Operation> getOperations = ValidationUtilities.GetOperationsByRequestMethod("get", (ServiceDefinition)context.Root); foreach (KeyValuePair <string, Schema> definition in definitions) { if (!exemptedNames.IsMatch(definition.Key) && ValidationUtilities.IsTrackedResource(definition.Value, definitions)) { bool getCheck = getOperations.Any(operation => operation.Responses.Any(response => response.Key.Equals("200") && response.Value.Schema != null && response.Value.Schema.Reference != null && response.Value.Schema.Reference.EndsWith("/" + definition.Key) ) ); if (!getCheck) { formatParameters = new object[2]; formatParameters[0] = definition.Key; formatParameters[1] = 1; return(false); } bool listByResourceGroupCheck = this.ListByXCheck(getOperations, listByRgRegEx, definition.Key, definitions); if (!listByResourceGroupCheck) { formatParameters = new object[2]; formatParameters[0] = definition.Key; formatParameters[1] = 2; return(false); } bool listBySubscriptionIdCheck = this.ListByXCheck(getOperations, listBySidRegEx, definition.Key, definitions); if (!listBySubscriptionIdCheck) { formatParameters = new object[2]; formatParameters[0] = definition.Key; formatParameters[1] = 3; return(false); } bool schemaResult = this.HandleSchema(definition.Value, definitions); if (!schemaResult) { formatParameters = new object[2]; formatParameters[0] = definition.Key; formatParameters[1] = 4; return(false); } } } formatParameters = new object[0]; return(true); }
/// <summary> /// Verifies if a tracked resource has a corresponding get operation /// </summary> /// <param name="definitions"></param> /// <param name="context"></param> /// <returns></returns> public override IEnumerable <ValidationMessage> GetValidationMessages(Dictionary <string, Schema> definitions, RuleContext context) { // Retrieve the list of TrackedResources IEnumerable <string> trackedResources = context.TrackedResourceModels; // Retrieve the list of getOperations IEnumerable <Operation> getOperations = ValidationUtilities.GetOperationsByRequestMethod("get", context.Root); foreach (string trackedResource in trackedResources) { // check for 200 status response models since they correspond to a successful get operation if (!getOperations.Any(op => op.Responses.ContainsKey("200") && (trackedResource).Equals(op.Responses["200"]?.Schema?.Reference?.StripDefinitionPath()))) { yield return(new ValidationMessage(new FileObjectPath(context.File, context.Path.AppendProperty(trackedResource)), this, trackedResource)); } } }
// Verifies if a tracked resource has a corresponding PATCH operation public override IEnumerable <ValidationMessage> GetValidationMessages(Dictionary <string, Dictionary <string, Operation> > paths, RuleContext context) { var serviceDefinition = context.Root; var ops = ValidationUtilities.GetOperationsByRequestMethod("put", serviceDefinition); var putRespModels = ops.Select(op => (op.Responses?.ContainsKey("200") != true) ? null : op.Responses["200"].Schema?.Reference?.StripDefinitionPath()); putRespModels = putRespModels.Where(modelName => !string.IsNullOrEmpty(modelName)); var xmsResModels = ValidationUtilities.GetXmsAzureResourceModels(serviceDefinition.Definitions); var violatingModels = putRespModels.Where(respModel => !ValidationUtilities.EnumerateModelHierarchy(respModel, serviceDefinition.Definitions).Intersect(xmsResModels).Any()); foreach (var model in violatingModels) { var violatingOp = ops.Where(op => (op.Responses?.ContainsKey("200") == true) && op.Responses["200"]?.Schema?.Reference?.StripDefinitionPath() == model).First(); var violatingPath = ValidationUtilities.GetOperationIdPath(violatingOp.OperationId, paths); var violatingOpVerb = ValidationUtilities.GetOperationIdVerb(violatingOp.OperationId, violatingPath); yield return(new ValidationMessage(new FileObjectPath(context.File, context.Path.AppendProperty(violatingPath.Key).AppendProperty(violatingOpVerb)), this, violatingOp.OperationId, model)); } }
/// <summary> /// Verifies if a tracked resource has a corresponding ListBySubscription operation /// </summary> /// <param name="definitions"></param> /// <param name="context"></param> /// <returns></returns> public override IEnumerable <ValidationMessage> GetValidationMessages(Dictionary <string, Schema> definitions, RuleContext context) { // Retrieve the list of TrackedResources IEnumerable <string> trackedResources = context.TrackedResourceModels; // Retrieve the list of getOperations IEnumerable <Operation> getOperations = ValidationUtilities.GetOperationsByRequestMethod("get", context.Root); foreach (string trackedResource in trackedResources) { bool listBySubscriptionsCheck = ValidationUtilities.ListByXCheck(getOperations, listBySidRegEx, trackedResource, definitions); if (!listBySubscriptionsCheck) { yield return(new ValidationMessage(new FileObjectPath(context.File, context.Path.AppendProperty(trackedResource)), this, trackedResource)); } } }
/// <summary> /// Verifies if a tracked resource has a corresponding patch operation /// </summary> /// <param name="definitions"></param> /// <param name="context"></param> /// <returns></returns> public override IEnumerable<ValidationMessage> GetValidationMessages(Dictionary<string, Schema> definitions, RuleContext context) { ServiceDefinition serviceDefinition = (ServiceDefinition)context.Root; // enumerate all the PATCH operations IEnumerable<Operation> patchOperations = ValidationUtilities.GetOperationsByRequestMethod("patch", serviceDefinition); // enumerate all the models returned by all PATCH operations (200/201 responses) var respModels = patchOperations.Select(op => op.Responses.GetValueOrNull("200")?.Schema?.Reference?.StripDefinitionPath()); respModels.Union(patchOperations.Select(op => op.Responses.GetValueOrNull("201")?.Schema?.Reference?.StripDefinitionPath())).Where(modelName => !string.IsNullOrEmpty(modelName)); // find models that are not being returned by any of the PATCH operations var violatingModels = context.TrackedResourceModels.Except(respModels); foreach (var modelName in violatingModels) { yield return new ValidationMessage(new FileObjectPath(context.File, context.Path.AppendProperty(modelName)), this, modelName); } }
/// Verifies if a PUT operation request and response schemas match public override IEnumerable <ValidationMessage> GetValidationMessages(Dictionary <string, Dictionary <string, Operation> > paths, RuleContext context) { var serviceDefinition = (ServiceDefinition)context.Root; var ops = ValidationUtilities.GetOperationsByRequestMethod("put", serviceDefinition); foreach (var op in ops) { // if PUT operation does not have any request parameters, skip, let some other validation rule handle it // if no 200 response exists, skip, let some other validation rule handle empty PUT response operations if (op.Parameters?.Any() != true || op.Responses?.ContainsKey("200") != true || serviceDefinition.Definitions?.Any() != true) { continue; } // look for the request body schema in the operation parameters section as well as the global parameters section string reqBodySchema = null; if (op.Parameters.Any(p => p.In == ParameterLocation.Body)) { reqBodySchema = op.Parameters.First(p => p.In == ParameterLocation.Body).Schema?.Reference?.StripDefinitionPath(); } else { var opGlobalParams = op.Parameters.Where(p => serviceDefinition.Parameters.ContainsKey(p.Reference?.StripParameterPath() ?? "")); if (opGlobalParams.Any()) { reqBodySchema = opGlobalParams.FirstOrDefault(p => p.In == ParameterLocation.Body)?.Schema?.Reference?.StripDefinitionPath(); } } // if no body parameters were found, skip, let some other validation handle an empty body put operation if (string.IsNullOrEmpty(reqBodySchema) || !serviceDefinition.Definitions.ContainsKey(reqBodySchema)) { continue; } var respModel = op.Responses["200"]?.Schema?.Reference?.StripDefinitionPath() ?? string.Empty; // if the 200 response schema does not match the request body parameter schema, flag violation if (respModel != reqBodySchema) { var violatingPath = ValidationUtilities.GetOperationIdPath(op.OperationId, paths); var violatingOpVerb = ValidationUtilities.GetOperationIdVerb(op.OperationId, violatingPath); yield return(new ValidationMessage(new FileObjectPath(context.File, context.Path.AppendProperty(violatingPath.Key).AppendProperty(violatingOpVerb)), this, op.OperationId, reqBodySchema, respModel)); } } }
// Verifies if a tracked resource has a corresponding get operation public override IEnumerable <ValidationMessage> GetValidationMessages(Dictionary <string, Schema> definitions, RuleContext context) { ServiceDefinition serviceDefinition = context.Root; IEnumerable <Operation> getOperations = ValidationUtilities.GetOperationsByRequestMethod("get", serviceDefinition); foreach (KeyValuePair <string, Schema> definition in definitions) { if (context.TrackedResourceModels.Contains(definition.Key.StripDefinitionPath())) { // check for 200 status response models since they correspond to a successful get operation if (!getOperations.Any(op => op.Responses.ContainsKey("200") && (op.Responses["200"]?.Schema?.Reference?.StripDefinitionPath()) == definition.Key)) { // if no GET operation returns current tracked resource as a response, // the tracked resource does not have a corresponding get operation yield return(new ValidationMessage(new FileObjectPath(context.File, context.Path), this, definition.Key.StripDefinitionPath())); } } } }
// Verifies if a tracked resource has a corresponding patch operation public override IEnumerable <ValidationMessage> GetValidationMessages(Dictionary <string, Schema> definitions, RuleContext context) { ServiceDefinition serviceDefinition = (ServiceDefinition)context.Root; IEnumerable <Operation> patchOperations = ValidationUtilities.GetOperationsByRequestMethod("patch", serviceDefinition); var respDefinitions = ValidationUtilities.GetResponseModelDefinitions(serviceDefinition); foreach (KeyValuePair <string, Schema> definition in definitions) { if (respDefinitions.Contains(definition.Key) && ValidationUtilities.IsTrackedResource(definition.Value, definitions)) { if (!patchOperations.Any(op => (op.Responses["200"]?.Schema?.Reference?.StripDefinitionPath()) == definition.Key)) { // if no patch operation returns current tracked resource as a response, // the tracked resource does not have a corresponding patch operation yield return(new ValidationMessage(new FileObjectPath(context.File, context.Path), this, definition.Key.StripDefinitionPath())); } } } }