/// <summary> /// Examines input json string to ensure that it compiles with the JsonSchema definition. Any errors in the /// validation of the schema are returned via the errors out parameter. /// </summary> /// <param name="schema">Schemas definition used as a reference.</param> /// <param name="inputJson">Input json example to be validated</param> /// <param name="errors">Out parameter that provides any errors, warnings, or messages that were generated</param> /// <param name="expectedJson"></param> /// <returns></returns> public bool ValidateJsonCompilesWithSchema(JsonSchema schema, JsonExample inputJson, out ValidationError[] errors, JsonExample expectedJson = null, ValidationOptions options = null) { if (null == schema) throw new ArgumentNullException("schema"); if (null == inputJson) throw new ArgumentNullException("inputJson"); string collectionPropertyName = "value"; if (null != inputJson.Annotation && null != inputJson.Annotation.CollectionPropertyName) { collectionPropertyName = inputJson.Annotation.CollectionPropertyName; } // If we didn't get an options, create a new one with some defaults provided by the annotation options = options ?? new ValidationOptions(); options.AllowTruncatedResponses = (inputJson.Annotation ?? new CodeBlockAnnotation()).TruncatedResult; options.CollectionPropertyName = collectionPropertyName; return schema.ValidateJson(inputJson, out errors, this.registeredSchema, options, expectedJson); }
/// <summary> /// Validate the input json against the defined scehma when the instance was created. /// </summary> /// <param name="jsonInput">Input json to validate against schema</param> /// <param name="errors">Array of errors if the validation fails</param> /// <param name="otherSchemas"></param> /// <param name="options"></param> /// <param name="expectedJson"></param> /// <returns>True if validation was successful, otherwise false.</returns> public bool ValidateJson(JsonExample jsonInput, out ValidationError[] errors, Dictionary<string, JsonSchema> otherSchemas, ValidationOptions options, JsonExample expectedJson = null) { JContainer obj; try { var settings = new JsonSerializerSettings { DateParseHandling = DateParseHandling.None, NullValueHandling = NullValueHandling.Include, DefaultValueHandling = DefaultValueHandling.Include }; obj = (JContainer)JsonConvert.DeserializeObject(jsonInput.JsonData, settings); } catch (Exception ex) { errors = new ValidationError[] { new ValidationError(ValidationErrorCode.JsonParserException, null, "Failed to parse json string: {0}. Json: {1}", ex.Message, jsonInput.JsonData) }; return false; } var annotation = jsonInput.Annotation ?? new CodeBlockAnnotation(); List<ValidationError> detectedErrors = new List<ValidationError>(); bool expectErrorObject = (jsonInput.Annotation != null) && jsonInput.Annotation.ExpectError; // Check for an error response dynamic errorObject = obj["error"]; if (null != errorObject && !expectErrorObject) { string code = errorObject.code; string message = errorObject.message; detectedErrors.Clear(); detectedErrors.Add(new ValidationError(ValidationErrorCode.JsonErrorObject, null, "Error response received. Code: {0}, Message: {1}", code, message)); errors = detectedErrors.ToArray(); return false; } else if (expectErrorObject && null == errorObject) { detectedErrors.Clear(); detectedErrors.Add(new ValidationError(ValidationErrorCode.JsonErrorObjectExpected, null, "Expected an error object response, but didn't receive one.")); errors = detectedErrors.ToArray(); return false; } // Check to see if this is a "collection" instance if (null != annotation && annotation.IsCollection) { this.ValidateCollectionObject(obj, annotation, otherSchemas, options.CollectionPropertyName, detectedErrors); } // otherwise verify the object matches this schema else { options = options ?? new ValidationOptions(annotation); if (null != expectedJson) { var expectedJsonSchema = new JsonSchema(expectedJson.JsonData, expectedJson.Annotation); options.ExpectedJsonSchema = expectedJsonSchema; options.RequiredPropertyNames = expectedJsonSchema.ExpectedProperties.Keys.ToArray(); } this.ValidateContainerObject(obj, options, otherSchemas, detectedErrors); } errors = detectedErrors.ToArray(); return detectedErrors.Count == 0; }
/// <summary> /// Checks that the expected response of a method definition is valid with this resource. /// </summary> /// <param name="method"></param> /// <param name="errors"></param> /// <returns></returns> public bool ValidateExpectedResponse(MethodDefinition method, out ValidationError[] errors) { HttpParser parser = new HttpParser(); var response = parser.ParseHttpResponse(method.ExpectedResponse); JsonExample example = new JsonExample(response.Body, method.ExpectedResponseMetadata); var otherSchemas = new Dictionary<string, JsonSchema>(); return this.ValidateJson(example, out errors, otherSchemas, null); }
/// <summary> /// Verify that the body of the actual response is consistent with the method definition and expected response parameters /// </summary> /// <param name="method">The MethodDefinition that generated the response.</param> /// <param name="actualResponse">The actual response from the service to validate.</param> /// <param name="expectedResponse">The prototype expected response from the service.</param> /// <param name="detectedErrors">A collection of errors that will be appended with any detected errors</param> private void VerifyResponseBody(HttpResponse actualResponse, HttpResponse expectedResponse, out ValidationError[] errors, ValidationOptions options = null) { List<ValidationError> detectedErrors = new List<ValidationError>(); if (string.IsNullOrEmpty(actualResponse.Body) && (expectedResponse != null && !string.IsNullOrEmpty(expectedResponse.Body))) { detectedErrors.Add(new ValidationError(ValidationErrorCode.HttpBodyExpected, null, "Body missing from response (expected response includes a body or a response type was provided).")); } else if (!string.IsNullOrEmpty(actualResponse.Body)) { ValidationError[] schemaErrors; if (this.ExpectedResponseMetadata == null || (string.IsNullOrEmpty(this.ExpectedResponseMetadata.ResourceType) && (expectedResponse != null && !string.IsNullOrEmpty(expectedResponse.Body)))) { detectedErrors.Add(new ValidationError(ValidationErrorCode.ResponseResourceTypeMissing, null, "Expected a response, but resource type on method is missing: {0}", this.Identifier)); } else { var otherResources = this.SourceFile.Parent.ResourceCollection; if ( !otherResources.ValidateResponseMatchesSchema( this, actualResponse, expectedResponse, out schemaErrors, options)) { detectedErrors.AddRange(schemaErrors); } } var responseValidation = actualResponse.IsResponseValid( this.SourceFile.DisplayName, this.SourceFile.Parent.Requirements); detectedErrors.AddRange(responseValidation.Messages); } errors = detectedErrors.ToArray(); }
public bool ValidateNoBrokenLinks(bool includeWarnings, out ValidationError[] errors) { string[] files; return this.ValidateNoBrokenLinks(includeWarnings, out errors, out files); }
public void AddResults(string actionName, ValidationError[] errors, ValidationOutcome? outcome = null) { this[actionName].AddResults(errors, outcome); }
/// <summary> /// Validates the value of json according to an implicit schmea defined by expectedJson /// </summary> /// <param name="expectedResponseAnnotation"></param> /// <param name="actualResponseBodyJson"></param> /// <param name="errors"></param> /// <returns></returns> public bool ValidateJsonExample(CodeBlockAnnotation expectedResponseAnnotation, string actualResponseBodyJson, out ValidationError[] errors, ValidationOptions options = null) { List<ValidationError> newErrors = new List<ValidationError>(); var resourceType = expectedResponseAnnotation.ResourceType; if (resourceType == "stream") { // No validation since we're streaming data errors = null; return true; } else { JsonSchema schema; if (string.IsNullOrEmpty(resourceType)) { schema = JsonSchema.EmptyResponseSchema; } else if (!this.registeredSchema.TryGetValue(resourceType, out schema)) { newErrors.Add(new ValidationWarning(ValidationErrorCode.ResponseResourceTypeMissing, null, "Missing required resource: {0}. Validation limited to basics only.", resourceType)); // Create a new schema based on what's avaiable in the json schema = new JsonSchema(actualResponseBodyJson, new CodeBlockAnnotation { ResourceType = expectedResponseAnnotation.ResourceType }); } ValidationError[] validationJsonOutput; this.ValidateJsonCompilesWithSchema(schema, new JsonExample(actualResponseBodyJson, expectedResponseAnnotation), out validationJsonOutput, options: options); newErrors.AddRange(validationJsonOutput); errors = newErrors.ToArray(); return errors.Length == 0; } }
public void RecordError(ValidationError error) { if (null != error) System.Diagnostics.Debug.WriteLine(error.ErrorText); }
public new bool Scan(out ValidationError[] errors) { this.HasScanRun = true; this.TransformMarkdownIntoBlocksAndLinks(this.Markdown); return this.ParseMarkdownBlocks(out errors); }
public ValidationMessageEventArgs(ValidationError message) { this.Message = message; }
/// <summary> /// Logs out a validation message. /// </summary> /// <param name="message">Message.</param> protected void LogMessage(ValidationError message) { var eventHandler = this.NewMessage; if (null != eventHandler) { eventHandler(this, new ValidationMessageEventArgs(message)); } this.Messages.Add(message); }
/// <summary> /// Run post processing on the collection of elements found inside this doc file. /// </summary> /// <param name="elements"></param> /// <param name="postProcessingErrors"></param> private void PostProcessFoundElements(List<object> elements, out ValidationError[] postProcessingErrors) { /* if FoundMethods == 1 then Attach all tables found in the document to the method. else if FoundMethods > 1 then Table.Type == ErrorCodes - Attach errors to all methods in the file Table.Type == PathParameters - Find request with matching parameters Table.Type == Query String Parameters - Request may not have matching parameters, because query string parameters may not be part of the request Table.Type == Header Parameters - Find request with matching parameters Table.Type == Body Parameters - Find request with matching parameters */ List<ValidationError> detectedErrors = new List<ValidationError>(); var elementsFoundInDocument = elements as IList<object> ?? elements.ToList(); var foundMethods = from s in elementsFoundInDocument where s is MethodDefinition select (MethodDefinition)s; var foundResources = from s in elementsFoundInDocument where s is ResourceDefinition select (ResourceDefinition)s; var foundTables = from s in elementsFoundInDocument where s is TableDefinition select (TableDefinition)s; this.PostProcessAuthScopes(elementsFoundInDocument); PostProcessResources(foundResources, foundTables); this.PostProcessMethods(foundMethods, foundTables, detectedErrors); postProcessingErrors = detectedErrors.ToArray(); }
/// <summary> /// Convert blocks of text found inside the markdown file into things we know how to work /// with (methods, resources, examples, etc). /// </summary> /// <param name="errors"></param> /// <returns></returns> protected bool ParseMarkdownBlocks(out ValidationError[] errors) { List<ValidationError> detectedErrors = new List<ValidationError>(); string methodTitle = null; string methodDescription = null; Block previousHeaderBlock = null; List<object> foundElements = new List<object>(); Stack<Config.DocumentHeader> headerStack = new Stack<Config.DocumentHeader>(); for (int i = 0; i < this.OriginalMarkdownBlocks.Length; i++) { var block = this.OriginalMarkdownBlocks[i]; this.ContentOutline.Add(string.Format("{0} - {1}", block.BlockType, this.PreviewOfBlockContent(block))); // Capture GitHub Flavored Markdown Bookmarks if (IsHeaderBlock(block, 6)) { this.AddBookmarkForHeader(block.Content); this.AddHeaderToHierarchy(headerStack, block); } // Capture h1 and/or p element to be used as the title and description for items on this page if (IsHeaderBlock(block)) { methodTitle = block.Content; methodDescription = null; // Clear this because we don't want new title + old description detectedErrors.Add(new ValidationMessage(null, "Found title: {0}", methodTitle)); } else if (block.BlockType == BlockType.p) { if (null == previousHeaderBlock) { detectedErrors.Add(new ValidationWarning(ValidationErrorCode.MissingHeaderBlock, null, "Paragraph text found before a valid header: {0}", this.DisplayName)); } else if (IsHeaderBlock(previousHeaderBlock)) { methodDescription = block.Content; detectedErrors.Add(new ValidationMessage(null, "Found description: {0}", methodDescription)); } } else if (block.BlockType == BlockType.html) { // If the next block is a codeblock we've found a metadata + codeblock pair Block nextBlock = null; if (i + 1 < this.OriginalMarkdownBlocks.Length) { nextBlock = this.OriginalMarkdownBlocks[i + 1]; } if (null != nextBlock && nextBlock.BlockType == BlockType.codeblock) { // html + codeblock = likely request or response! ItemDefinition definition = null; try { definition = this.ParseCodeBlock(block, nextBlock); } catch (Exception ex) { detectedErrors.Add(new ValidationError(ValidationErrorCode.MarkdownParserError, this.DisplayName, ex.Message)); } if (null != definition) { detectedErrors.Add(new ValidationMessage(null, "Found code block: {0} [{1}]", definition.Title, definition.GetType().Name)); definition.Title = methodTitle; definition.Description = methodDescription; if (!foundElements.Contains(definition)) { foundElements.Add(definition); } } } else if (null == this.Annotation) { // See if this is the page-level annotation var annotation = this.ParsePageAnnotation(block); if (null != annotation) { this.Annotation = annotation; if (string.IsNullOrEmpty(this.Annotation.Title)) { this.Annotation.Title = (from b in this.OriginalMarkdownBlocks where IsHeaderBlock(b, 1) select b.Content).FirstOrDefault(); } } } } else if (block.BlockType == BlockType.table_spec) { Block blockBeforeTable = (i - 1 >= 0) ? this.OriginalMarkdownBlocks[i - 1] : null; if (null == blockBeforeTable) continue; ValidationError[] parseErrors; var table = TableSpecConverter.ParseTableSpec(block, previousHeaderBlock, out parseErrors); if (null != parseErrors) detectedErrors.AddRange(parseErrors); detectedErrors.Add(new ValidationMessage(null, "Found table: {0}. Rows:\r\n{1}", table.Type, (from r in table.Rows select JsonConvert.SerializeObject(r, Formatting.Indented)).ComponentsJoinedByString(" ,\r\n"))); foundElements.Add(table); } if (block.IsHeaderBlock()) { previousHeaderBlock = block; } } ValidationError[] postProcessingErrors; this.PostProcessFoundElements(foundElements, out postProcessingErrors); detectedErrors.AddRange(postProcessingErrors); errors = detectedErrors.ToArray(); return !detectedErrors.Any(x => x.IsError); }
/// <summary> /// Checks all links detected in the source document to make sure they are valid. /// </summary> /// <param name="includeWarnings"></param> /// <param name="errors">Information about broken links</param> /// <param name="linkedDocFiles"></param> /// <returns>True if all links are valid. Otherwise false</returns> public bool ValidateNoBrokenLinks(bool includeWarnings, out ValidationError[] errors, out string[] linkedDocFiles) { if (!this.HasScanRun) throw new InvalidOperationException("Cannot validate links until Scan() is called."); List<string> linkedPages = new List<string>(); var foundErrors = new List<ValidationError>(); foreach (var link in this.MarkdownLinks) { if (null == link.def) { foundErrors.Add(new ValidationError(ValidationErrorCode.MissingLinkSourceId, this.DisplayName, "Link specifies ID '{0}' which was not found in the document.", link.link_text)); continue; } string relativeFileName; var result = this.VerifyLink(this.FullPath, link.def.url, this.BasePath, out relativeFileName); switch (result) { case LinkValidationResult.BookmarkSkipped: case LinkValidationResult.ExternalSkipped: if (includeWarnings) foundErrors.Add(new ValidationWarning(ValidationErrorCode.LinkValidationSkipped, this.DisplayName, "Skipped validation of link '{1}' to URL '{0}'", link.def.url, link.link_text)); break; case LinkValidationResult.FileNotFound: foundErrors.Add(new ValidationError(ValidationErrorCode.LinkDestinationNotFound, this.DisplayName, "Destination missing for link '{1}' to URL '{0}'", link.def.url, link.link_text)); break; case LinkValidationResult.ParentAboveDocSetPath: foundErrors.Add(new ValidationError(ValidationErrorCode.LinkDestinationOutsideDocSet, this.DisplayName, "Destination outside of doc set for link '{1}' to URL '{0}'", link.def.url, link.link_text)); break; case LinkValidationResult.UrlFormatInvalid: foundErrors.Add(new ValidationError(ValidationErrorCode.LinkFormatInvalid, this.DisplayName, "Invalid URL format for link '{1}' to URL '{0}'", link.def.url, link.link_text)); break; case LinkValidationResult.Valid: foundErrors.Add(new ValidationMessage(this.DisplayName, "Link to URL '{0}' is valid.", link.def.url, link.link_text)); if (null != relativeFileName) { linkedPages.Add(relativeFileName); } break; default: foundErrors.Add(new ValidationError(ValidationErrorCode.Unknown, this.DisplayName, "{2}: for link '{1}' to URL '{0}'", link.def.url, link.link_text, result)); break; } } errors = foundErrors.ToArray(); linkedDocFiles = linkedPages.Distinct().ToArray(); return !(errors.WereErrors() || errors.WereWarnings()); }
private bool ValidateCustomObject(JsonProperty[] properties, out ValidationError[] errors, Dictionary<string, JsonSchema> otherSchemas, ValidationOptions options) { List<string> missingProperties = new List<string>(this.ExpectedProperties.Keys); List<ValidationError> detectedErrors = new List<ValidationError>(); foreach (var inputProperty in properties) { missingProperties.Remove(inputProperty.Name); this.ValidateProperty(inputProperty, otherSchemas, detectedErrors, new ValidationOptions()); } this.CleanMissingProperties(options, missingProperties); errors = detectedErrors.ToArray(); return detectedErrors.Count == 0; }
public static void LogMessage(ValidationError error) { var helper = GetLogger(); helper.RecordError(error); }
public static ValidationError NewConsolidatedError(ValidationErrorCode code, ValidationError[] errors, string message, params object[] parameters) { var error = errors.All(err => err.IsWarning) ? new ValidationWarning(code, null, message, parameters) : new ValidationError(code, null, message, parameters); error.InnerErrors = errors; return error; }
/// <summary> /// Validates that the actual response body matches the schema defined for the response and any additional constraints /// from the expected request (e.g. properties that are included in the expected response are required in the actual /// response even if the metadata defines that the response is truncated) /// </summary> /// <param name="method"></param> /// <param name="actualResponse"></param> /// <param name="expectedResponse"></param> /// <param name="schemaErrors"></param> /// <returns></returns> internal bool ValidateResponseMatchesSchema(MethodDefinition method, HttpResponse actualResponse, HttpResponse expectedResponse, out ValidationError[] schemaErrors) { List<ValidationError> newErrors = new List<ValidationError>(); var expectedResourceType = method.ExpectedResponseMetadata.ResourceType; switch (expectedResourceType) { case "stream": case "Stream": // No validation since we're streaming data schemaErrors = new ValidationError[0]; return true; case "string": case "String": case "Edm.String": case" Edm.string": schemaErrors = new ValidationError[0]; return true; } // Get a reference of our JsonSchema that we're checking the response with var expectedResponseJson = (null != expectedResponse) ? expectedResponse.Body : null; JsonSchema schema = this.GetJsonSchema(expectedResourceType, newErrors, expectedResponseJson); if (null == schema) { newErrors.Add(new ValidationError(ValidationErrorCode.ResourceTypeNotFound, null, "Unable to locate a definition for resource type: {0}", expectedResourceType)); } else { ValidationError[] validationJsonOutput; this.ValidateJsonCompilesWithSchema(schema, new JsonExample(actualResponse.Body, method.ExpectedResponseMetadata), out validationJsonOutput, (null != expectedResponseJson) ? new JsonExample(expectedResponseJson) : null); newErrors.AddRange(validationJsonOutput); } schemaErrors = newErrors.ToArray(); return !schemaErrors.WereWarningsOrErrors(); }
/// <summary> /// Convert a tablespec block into one of our internal object model representations /// </summary> /// <param name="tableSpecBlock"></param> /// <param name="lastHeaderBlock"></param> /// <param name="errors"></param> /// <returns></returns> public TableDefinition ParseTableSpec(Block tableSpecBlock, Block lastHeaderBlock, out ValidationError[] errors) { List<ValidationError> discoveredErrors = new List<ValidationError>(); List<ItemDefinition> items = new List<ItemDefinition>(); var tableShape = tableSpecBlock.Table; TableDecoder decoder = new TableDecoder { Type = TableBlockType.Unknown }; string headerText = null; // Try matching based on header if (null != lastHeaderBlock && null != lastHeaderBlock.Content) { headerText = lastHeaderBlock.Content; var matchingDecoder = FindDecoderFromHeaderText(headerText); if (null != matchingDecoder) decoder = matchingDecoder; } // Try matching based on shape if (decoder.Type == TableBlockType.Unknown && null != tableSpecBlock.Table) { var matchingDecoder = FindDecoderFromShape(tableShape); if (null != matchingDecoder) decoder = matchingDecoder; } switch (decoder.Type) { case TableBlockType.ErrorCodes: items.AddRange(ParseErrorTable(tableShape, decoder)); break; case TableBlockType.PathParameters: items.AddRange(ParseParameterTable(tableShape, ParameterLocation.Path, decoder)); break; case TableBlockType.ResourcePropertyDescriptions: case TableBlockType.RequestObjectProperties: case TableBlockType.ResponseObjectProperties: items.AddRange(ParseParameterTable(tableShape, ParameterLocation.JsonObject, decoder)); break; case TableBlockType.ResourceNavigationPropertyDescriptions: items.AddRange(ParseParameterTable(tableShape, ParameterLocation.JsonObject, decoder, true)); break; case TableBlockType.HttpHeaders: items.AddRange(ParseParameterTable(tableShape, ParameterLocation.Header, decoder)); break; case TableBlockType.QueryStringParameters: items.AddRange(ParseParameterTable(tableShape, ParameterLocation.QueryString, decoder)); break; case TableBlockType.EnumerationValues: items.AddRange(ParseEnumerationTable(tableShape, decoder)); break; case TableBlockType.AuthScopes: items.AddRange(ParseAuthScopeTable(tableShape, decoder)); break; case TableBlockType.Unknown: discoveredErrors.Add(new ValidationMessage(null, "Ignored unclassified table: headerText='{0}', tableHeaders='{1}'", headerText, tableShape.ColumnHeaders != null ? tableShape.ColumnHeaders.ComponentsJoinedByString(",") : "null")); break; default: discoveredErrors.Add(new ValidationMessage(null, "Ignored table: classification='{2}', headerText='{0}', tableHeaders='{1}'", headerText, tableShape.ColumnHeaders != null ? tableShape.ColumnHeaders.ComponentsJoinedByString(",") : "null", decoder.Type)); break; } errors = discoveredErrors.ToArray(); return new TableDefinition(decoder.Type, items, headerText); }
public void AddResult(string actionName, ValidationError error, ValidationOutcome? outcome = null ) { this[actionName].AddResults(new ValidationError[] { error }, outcome); }
/// <summary> /// Validates that a particular HttpResponse matches the method definition and optionally the expected response. /// </summary> /// <param name="method">Method definition that was used to generate a request.</param> /// <param name="actualResponse">Actual response from the service (this is what we validate).</param> /// <param name="expectedResponse">Prototype response (expected) that shows what a valid response should look like.</param> /// <param name="scenario">A test scenario used to generate the response, which may include additional parameters to verify.</param> /// <param name="errors">A collection of errors, warnings, and verbose messages generated by this process.</param> public void ValidateResponse(HttpResponse actualResponse, HttpResponse expectedResponse, ScenarioDefinition scenario, out ValidationError[] errors, ValidationOptions options = null) { if (null == actualResponse) throw new ArgumentNullException("actualResponse"); List<ValidationError> detectedErrors = new List<ValidationError>(); // Verify the request is valid (headers, etc) this.VerifyHttpRequest(detectedErrors); // Verify that the expected response headers match the actual response headers ValidationError[] httpErrors; if (null != expectedResponse && !expectedResponse.ValidateResponseHeaders(actualResponse, out httpErrors, (null != scenario) ? scenario.AllowedStatusCodes : null)) { detectedErrors.AddRange(httpErrors); } // Verify the actual response body is correct according to the schema defined for the response ValidationError[] bodyErrors; this.VerifyResponseBody(actualResponse, expectedResponse, out bodyErrors, options); detectedErrors.AddRange(bodyErrors); // Verify any expectations in the scenario are met if (null != scenario) { scenario.ValidateExpectations(actualResponse, detectedErrors); } errors = detectedErrors.ToArray(); }
public void AddResults(ValidationError[] errors, ValidationOutcome? outcome = null) { if (errors.Length == 0) return; this.Errors.AddRange(errors); if (outcome.HasValue) this.Outcome = outcome.Value; }
/// <summary> /// Read the contents of the file into blocks and generate any resource or method definitions from the contents /// </summary> public bool Scan(out ValidationError[] errors) { this.HasScanRun = true; List<ValidationError> detectedErrors = new List<ValidationError>(); try { this.TransformMarkdownIntoBlocksAndLinks(this.GetContentsOfFile()); } catch (IOException ioex) { detectedErrors.Add(new ValidationError(ValidationErrorCode.ErrorOpeningFile, this.DisplayName, "Error reading file contents: {0}", ioex.Message)); errors = detectedErrors.ToArray(); return false; } catch (Exception ex) { detectedErrors.Add(new ValidationError(ValidationErrorCode.ErrorReadingFile, this.DisplayName, "Error reading file contents: {0}", ex.Message)); errors = detectedErrors.ToArray(); return false; } return this.ParseMarkdownBlocks(out errors); }