コード例 #1
0
ファイル: JsonSchema.cs プロジェクト: madansr7/apidoctor
        private static PropertyValidationOutcome ValidateSameDataType(ParameterDefinition expected, ParameterDefinition actual, IssueLogger issues, bool relaxStringValidation)
        {
            if (expected.Type == actual.Type && actual.Type != ParameterDataType.String)
            {
                return(PropertyValidationOutcome.Ok);
            }
            else if (expected.Type == actual.Type && actual.Type == ParameterDataType.String)
            {
                // Perform extra validation to see if the string is the right format (iso date, enum value, url, or just a string)
                if (relaxStringValidation)
                {
                    return(PropertyValidationOutcome.Ok);
                }
                return(ValidateStringFormat(expected, actual, issues));
            }
            else if (expected.Type == ParameterDataType.String && expected.Type.IsLessSpecificThan(actual.Type))
            {
                return(PropertyValidationOutcome.Ok);
            }
            else if (actual.Type.IsLessSpecificThan(expected.Type))
            {
                if (relaxStringValidation)
                {
                    issues.Message(
                        $"Expected type {expected.Type} but actual was {actual.Type}, which is less specific than the expected type. Property: {actual.Name}, actual value: '{actual.OriginalValue}'");
                    return(PropertyValidationOutcome.Ok);
                }

                issues.Error(
                    ValidationErrorCode.ExpectedTypeDifferent,
                    $"Expected type {expected.Type} but actual was {actual.Type}, which is less specific than the expected type. Property: {actual.Name}, actual value: '{actual.OriginalValue}'");
                return(PropertyValidationOutcome.BadStringValue);
            }
            else
            {
                // Type of the inputProperty is mismatched from the expected value.
                issues.Error(
                    ValidationErrorCode.ExpectedTypeDifferent,
                    $"Expected type {expected.Type} but actual was {actual.Type}. Property: {actual.Name}, actual value: '{actual.OriginalValue}'");
                return(PropertyValidationOutcome.InvalidType);
            }
        }
コード例 #2
0
ファイル: ODataParser.cs プロジェクト: madansr7/apidoctor
        private static ResourceDefinition ResourceDefinitionFromType(Schema schema, IEnumerable <Schema> otherSchema, ComplexType ct, IssueLogger issues)
        {
            var annotation = new CodeBlockAnnotation()
            {
                ResourceType = string.Concat(schema.Namespace, ".", ct.Name), BlockType = CodeBlockType.Resource
            };
            var json = BuildJsonExample(ct, otherSchema);
            ResourceDefinition rd = new JsonResourceDefinition(annotation, json, null, issues);

            return(rd);
        }
コード例 #3
0
        private void ValidateCollectionObject(JContainer obj, CodeBlockAnnotation annotation, Dictionary <string, JsonSchema> otherSchemas, string collectionPropertyName, IssueLogger issues, ValidationOptions options)
        {
            // TODO: also validate additional properties on the collection, like nextDataLink
            JToken collection = null;

            if (obj is JArray)
            {
                collection = obj;
            }
            else
            {
                try
                {
                    collection = obj[collectionPropertyName];
                }
                catch (Exception ex)
                {
                    issues.Error(ValidationErrorCode.JsonErrorObjectExpected, $"Unable to find collection parameter or array to validate: {ex.Message}");
                    return;
                }
            }

            if (null == collection)
            {
                issues.Error(ValidationErrorCode.MissingCollectionProperty, $"Failed to locate collection property '{collectionPropertyName}' in response.");
            }
            else
            {
                if (!collection.Any())
                {
                    if (!annotation.IsEmpty)
                    {
                        issues.Warning(
                            ValidationErrorCode.CollectionArrayEmpty,
                            $"Property contained an empty array that was not validated: {collectionPropertyName}");
                    }
                }
                else if (annotation.IsEmpty)
                {
                    issues.Warning(
                        ValidationErrorCode.CollectionArrayNotEmpty,
                        $"Property contained a non-empty array that was expected to be empty: {collectionPropertyName}");
                }

                foreach (var jToken in collection)
                {
                    var container = jToken as JContainer;
                    if (null != container)
                    {
                        var deeperOptions = new ValidationOptions(options)
                        {
                            AllowTruncatedResponses = annotation.TruncatedResult
                        };

                        this.ValidateContainerObject(
                            container,
                            deeperOptions,
                            otherSchemas,
                            issues.For("container", onlyKeepUniqueErrors: true));
                    }
                }
            }
        }
コード例 #4
0
        private static IEnumerable <ParameterDefinition> ParseParameterTable(IMarkdownTable table, ParameterLocation location, TableDecoder decoder, IssueLogger issues, bool navigationProperties = false)
        {
            var records = table.RowValues.Select(r => new ParameterDefinition
            {
                Name          = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["name"]),
                Type          = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["type"]).ParseParameterDataType(defaultValue: ParameterDataType.String),
                Description   = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]),
                Required      = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]).IsRequired(),
                Optional      = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]).IsOptional(),
                Location      = location,
                IsNavigatable = navigationProperties,
            }).ToList();

            var badRows = records.Count(r => string.IsNullOrEmpty(r.Name));

            if (badRows > 0)
            {
                var tableHeaders = $"|{ string.Join("|", table.ColumnHeaders)}|";
                if (badRows == records.Count)
                {
                    issues.Warning(ValidationErrorCode.MarkdownParserError, $"Failed to parse any rows out of table with headers: {tableHeaders}");
                    return(Enumerable.Empty <ParameterDefinition>());
                }

                issues.Warning(ValidationErrorCode.ParameterParserError, $"Failed to parse {badRows} row(s) in table with headers: {tableHeaders}");
                records = records.Where(r => !string.IsNullOrEmpty(r.Name)).ToList();
            }

            return(records);
        }
コード例 #5
0
ファイル: ODataParser.cs プロジェクト: madansr7/apidoctor
        /// <summary>
        /// Convert resources found in the CSDL schema objects into ResourceDefintion instances
        /// that can be tested against the documentation.
        /// </summary>
        /// <param name="schemas"></param>
        /// <returns></returns>
        public static List <ResourceDefinition> GenerateResourcesFromSchemas(IEnumerable <Schema> schemas, IssueLogger issues)
        {
            List <ResourceDefinition> resources = new List <ResourceDefinition>();

            foreach (var schema in schemas)
            {
                resources.AddRange(CreateResourcesFromSchema(schema, schemas, issues));
            }

            return(resources);
        }
コード例 #6
0
 public IODataNavigable NavigateByUriComponent(string component, EntityFramework edmx, IssueLogger issues, bool isLastSegment)
 {
     throw new NotImplementedException();
 }
コード例 #7
0
        /// <summary>
        /// Evaluate the ApiRequirements file and return any inconsistencies
        /// as ValidationErrors.
        /// </summary>
        public static ValidationResult <bool> IsRequestValid(this HttpRequest request, string sourceFile, ApiRequirements apiRequirements, IssueLogger issues)
        {
            if (null == apiRequirements || null == apiRequirements.HttpRequest)
            {
                return(new ValidationResult <bool>(true));
            }

            var reqs = apiRequirements.HttpRequest;

            var requestMimeType = new MultipartMime.MimeContentType(request.ContentType);

            if (null != reqs.ContentTypes && null != requestMimeType.MimeType &&
                !reqs.ContentTypes.Contains(requestMimeType.MimeType))
            {
                issues.Warning(ValidationErrorCode.InvalidContentType, $"Request content-type header value is not in the supported list of content-types: {request.ContentType}");
            }

            if (reqs.HttpMethods != null && !reqs.HttpMethods.Contains(request.Method))
            {
                issues.Error(ValidationErrorCode.InvalidHttpMethod, $"Request HTTP method is not in the supported list of HTTP methods: {request.Method}");
            }

            if (reqs.MaxUrlLength > 0 && request.Url.Length > reqs.MaxUrlLength)
            {
                issues.Error(ValidationErrorCode.UrlLengthExceedsMaximum, $"Request URL is longer than the defined maximum URL length: [{request.Url.Length}] {request.Url}");
            }

            if (reqs.StandardHeaders != null && request.Headers.Count > 0)
            {
                foreach (var headerName in request.Headers.AllKeys)
                {
                    if (!reqs.StandardHeaders.ContainsString(headerName, apiRequirements.CaseSensativeHeaders))
                    {
                        issues.Warning(ValidationErrorCode.NonStandardHeaderUsed, $"Request includes a non-standard header: {headerName}");
                    }
                }
            }

            return(new ValidationResult <bool>(issues.IssuesInCurrentScope == 0));
        }
コード例 #8
0
 public MarkdownValidator(string pathToDocs)
 {
     this.pathToDocs = pathToDocs;
     this.issues     = new IssueLogger();
 }
コード例 #9
0
 public void ClearExistingIssues()
 {
     issues = new IssueLogger();
 }
コード例 #10
0
ファイル: JsonSchema.cs プロジェクト: madansr7/apidoctor
        /// <summary>
        /// Check each member of the actualProperty's array to make sure it matches the resource type specified for the property.
        /// </summary>
        /// <param name="actualProperty"></param>
        /// <param name="schemas"></param>
        /// <param name="detectedErrors"></param>
        /// <param name="options"></param>
        private PropertyValidationOutcome ValidateArrayProperty(ParameterDefinition actualProperty, Dictionary <string, JsonSchema> schemas, IssueLogger issues, ValidationOptions options)
        {
            JArray actualArray = null;

            try
            {
                actualArray = (JArray)JsonConvert.DeserializeObject(actualProperty.OriginalValue);
            }
            catch (InvalidCastException)
            {
                throw new InvalidCastException($"Property {actualProperty.Name} expected to be an array, but failed to cast value to an array: {actualProperty.OriginalValue}");
            }

            var        expectedPropertyDefinition = this.ExpectedProperties[actualProperty.Name];
            JsonSchema memberSchema = null;

            if (actualProperty.Type.CollectionResourceType == SimpleDataType.Object && expectedPropertyDefinition.Type.CustomTypeName != null)
            {
                // We have an ambigious array, but we know what it's supposed to be so let's use that
                schemas.TryGetValue(expectedPropertyDefinition.Type.CustomTypeName, out memberSchema);
            }

            if (memberSchema == null && string.IsNullOrEmpty(actualProperty.Type.CustomTypeName))
            {
                return(this.ValidateSimpleArrayProperty(actualProperty, this.ExpectedProperties[actualProperty.Name], issues));
            }
            else if (memberSchema == null && !schemas.TryGetValue(actualProperty.Type.CustomTypeName, out memberSchema))
            {
                issues.Error(ValidationErrorCode.ResourceTypeNotFound, $"Failed to locate resource definition for: {actualProperty.Type.CustomTypeName}");
                return(PropertyValidationOutcome.MissingResourceType);
            }

            bool hadErrors    = false;
            var  memberIssues = issues.For("member", onlyKeepUniqueErrors: true);

            for (int i = 0; i < actualArray.Count; i++)
            {
                JContainer member = actualArray[i] as JContainer;
                if (member != null)
                {
                    memberSchema.ValidateContainerObject(member, options, schemas, memberIssues);

                    // TODO: Filter out non-unique errors
                    hadErrors |= memberIssues.Issues.Count() > 0;
                }
            }

            return(hadErrors ? PropertyValidationOutcome.GenericError : PropertyValidationOutcome.Ok);
        }
コード例 #11
0
ファイル: JsonSchema.cs プロジェクト: madansr7/apidoctor
        private static ParameterDefinition ParseProperty(JToken token, JsonSchema containerSchema, IssueLogger issues = null)
        {
            ParameterDefinition propertyInfo = null;

            if (token.Type == JTokenType.Property)
            {
                JProperty tokenProperty = (JProperty)token;
                propertyInfo = ParseProperty(tokenProperty.Name, tokenProperty.Value, containerSchema);
            }
            else
            {
                if (issues != null)
                {
                    issues.Warning(ValidationErrorCode.JsonParserException, "Unhandled token type: " + token.Type);
                }
                else
                {
                    Console.WriteLine("Unhandled token type: " + token.Type);
                }
            }
            return(propertyInfo);
        }
コード例 #12
0
ファイル: JsonSchema.cs プロジェクト: madansr7/apidoctor
        private PropertyValidationOutcome ValidateSimpleArrayProperty(ParameterDefinition actualProperty, ParameterDefinition expectedProperty, IssueLogger issues)
        {
            if (!actualProperty.Type.IsCollection || !expectedProperty.Type.IsCollection)
            {
                throw new SchemaBuildException("Cannot use simple array valiation without array types", null);
            }

            if (actualProperty.Type == expectedProperty.Type)
            {
                return(PropertyValidationOutcome.Ok);
            }
            else if (expectedProperty.Type.IsCollection && actualProperty.Type.IsCollection && actualProperty.OriginalValue == "[]")    // Check for the empty array case
            {
                return(PropertyValidationOutcome.Ok);
            }
            else
            {
                issues.Error(ValidationErrorCode.ArrayTypeMismatch, $"Array expected members to be of type {expectedProperty.Type} but found: {actualProperty.Type}");
                return(PropertyValidationOutcome.InvalidType);
            }
        }
コード例 #13
0
ファイル: JsonSchema.cs プロジェクト: madansr7/apidoctor
        private static PropertyValidationOutcome ValidateStringFormat(ParameterDefinition schemaProperty, ParameterDefinition inputProperty, IssueLogger issues)
        {
            switch (schemaProperty.StringFormat())
            {
            case ExpectedStringFormat.Iso8601Date:
            {
                DateTimeOffset?output = inputProperty.OriginalValue.TryParseIso8601Date();
                if (!output.HasValue)
                {
                    issues.Error(ValidationErrorCode.InvalidDateTimeString, $"Invalid ISO 8601 date-time string in property: {schemaProperty.Name}: {inputProperty.OriginalValue}");
                    return(PropertyValidationOutcome.BadStringValue);
                }
                return(PropertyValidationOutcome.Ok);
            }

            case ExpectedStringFormat.AbsoluteUrl:
            {
                try
                {
                    new Uri(inputProperty.OriginalValue, UriKind.Absolute);
                    return(PropertyValidationOutcome.Ok);
                }
                catch (FormatException)
                {
                    issues.Error(ValidationErrorCode.InvalidUrlString, $"Invalid absolute URL value in property {schemaProperty.Name}: {inputProperty.OriginalValue}");
                    return(PropertyValidationOutcome.BadStringValue);
                }
            }

            case ExpectedStringFormat.EnumeratedValue:
            {
                if (!schemaProperty.IsValidEnumValue(inputProperty.OriginalValue))
                {
                    issues.Error(ValidationErrorCode.InvalidEnumeratedValueString, $"Invalid enumerated value in property {schemaProperty.Name}: {inputProperty.OriginalValue}");
                    return(PropertyValidationOutcome.BadStringValue);
                }
                return(PropertyValidationOutcome.Ok);
            }

            case ExpectedStringFormat.Generic:
                return(PropertyValidationOutcome.Ok);

            default:
                throw new NotImplementedException();
            }
        }
コード例 #14
0
ファイル: JsonSchema.cs プロジェクト: madansr7/apidoctor
        /// <summary>
        /// Verify that a property from the json-to-validate matches something in our schema
        /// </summary>
        private PropertyValidationOutcome ValidateProperty(ParameterDefinition inputProperty, Dictionary <string, JsonSchema> schemas, IssueLogger issues, ValidationOptions options)
        {
            if (this.ExpectedProperties.ContainsKey(inputProperty.Name))
            {
                // The property was expected to be found in this schema! Yay.
                var schemaPropertyDef = this.ExpectedProperties[inputProperty.Name];

                // Check for simple value types first
                if (this.SimpleValueTypes(schemaPropertyDef.Type, inputProperty.Type) && this.AllFalse(schemaPropertyDef.Type.IsCollection, inputProperty.Type.IsCollection))
                {
                    return(ValidateSameDataType(schemaPropertyDef, inputProperty, issues, (null != options) ? options.RelaxedStringValidation : false));
                }
                else if (null == inputProperty.OriginalValue)
                {
                    if (null != this.NullableProperties && !this.NullableProperties.Contains(schemaPropertyDef.Name))
                    {
                        issues.Warning(ValidationErrorCode.NullPropertyValue, $"Non-nullable property {schemaPropertyDef.Name} had a null value in the response. Expected {schemaPropertyDef.Type}.");
                    }
                    return(PropertyValidationOutcome.Ok);
                }
                else if (schemaPropertyDef.Type.IsCollection || inputProperty.Type.IsCollection)
                {
                    // Check for an array
                    if (schemaPropertyDef.Type.IsCollection && !inputProperty.Type.IsCollection)
                    {
                        // Expected an array, but didn't get one
                        issues.Error(ValidationErrorCode.ExpectedArrayValue, $"Expected an array but property was not an array: {inputProperty.Name}");
                        return(PropertyValidationOutcome.InvalidType);
                    }
                    else if (!schemaPropertyDef.Type.IsCollection && inputProperty.Type.IsCollection)
                    {
                        issues.Error(ValidationErrorCode.ExpectedNonArrayValue, $"Expected a value of type {schemaPropertyDef.Type} but property was an array: {inputProperty.Name}");
                        return(PropertyValidationOutcome.InvalidType);
                    }

                    return(this.ValidateArrayProperty(inputProperty, schemas, issues, options));
                }
                else if (schemaPropertyDef.Type.IsObject && inputProperty.Type.IsObject)
                {
                    // Compare the ODataType schema to the custom schema
                    if (null == schemaPropertyDef.Type.CustomTypeName || !schemas.ContainsKey(schemaPropertyDef.Type.CustomTypeName))
                    {
                        issues.Error(ValidationErrorCode.ResourceTypeNotFound, $"Missing resource: resource {schemaPropertyDef.Type.CustomTypeName} was not found (property name '{inputProperty.Name}').");
                        return(PropertyValidationOutcome.MissingResourceType);
                    }
                    else if (inputProperty.Type.IsObject)
                    {
                        var odataSchema = schemas[schemaPropertyDef.Type.CustomTypeName];
                        if (null != inputProperty.Type.CustomMembers && !odataSchema.ValidateCustomObject(inputProperty.Type.CustomMembers.ToArray(), issues, schemas, options))
                        {
                            if (issues.Errors.Any())
                            {
                                issues.Error(ValidationErrorCode.ConsolidatedError, $"Schema validation failed on property '{inputProperty.Name}' ['{odataSchema.ResourceName}']");
                            }
                            else
                            {
                                issues.Warning(ValidationErrorCode.ConsolidatedError, $"Schema validation failed on property '{inputProperty.Name}' ['{odataSchema.ResourceName}']");
                            }

                            return(PropertyValidationOutcome.InvalidType);
                        }
                        else if (null == inputProperty.Type.CustomMembers)
                        {
                            issues.Error(ValidationErrorCode.NoCustomMembersFound, $"Property '{inputProperty.Name}' is of type Custom but has no custom members.");
                        }
                        return(PropertyValidationOutcome.Ok);
                    }
                    else
                    {
                        var odataSchema = schemas[schemaPropertyDef.Type.CustomTypeName];
                        if (inputProperty.Type.CustomMembers == null)
                        {
                            issues.Error(ValidationErrorCode.MissingCustomMembers, $"Property {inputProperty.Name} is missing custom members and cannot be validated.");
                            return(PropertyValidationOutcome.InvalidType);
                        }
                        else
                        {
                            odataSchema.ValidateObjectProperties(inputProperty.Type.CustomMembers, options, schemas, issues);
                            return(PropertyValidationOutcome.Ok);
                        }
                    }
                }
                else if (schemaPropertyDef.Type.IsObject)
                {
                    issues.Warning(ValidationErrorCode.CustomValidationNotSupported, $"Schemas type was 'Custom' which is not supported. Add a resource type to the definition of property: {inputProperty.Name}");
                    return(PropertyValidationOutcome.MissingResourceType);
                }
                else
                {
                    issues.Error(ValidationErrorCode.ExpectedTypeDifferent, $"Type mismatch: property '{inputProperty.Name}' [{inputProperty.Type}] doesn't match expected type [{schemaPropertyDef.Type}].");
                    return(PropertyValidationOutcome.InvalidType);
                }
            }
            else
            {
                // Check to see if this property is on the ignorable list
                string[] ignorableUndocumentedProperties = this.OriginalResource?.SourceFile.Parent.Requirements?.IgnorableProperties;

                string propertyName   = inputProperty.Name;
                string annotationName = null;
                var    indexOfAtSign  = propertyName.IndexOf('@');
                if (indexOfAtSign > 0)
                {
                    // [email protected] is an example of what we're looking for here
                    annotationName = propertyName.Substring(indexOfAtSign);
                    propertyName   = propertyName.Substring(0, indexOfAtSign);
                }


                if (null != annotationName)
                {
                    // Check to see if propertyName is known or not. If it isn't known, fail.
                    if (this.Properties.Any(x => x.Name.Equals(propertyName)))
                    {
                        // If the cleaned up propertyName is known, then check to see if the annotation is ignorable
                        if (null != ignorableUndocumentedProperties && ignorableUndocumentedProperties.Contains(annotationName))
                        {
                            // If we know of both the property and the annotation, we're good.
                            return(PropertyValidationOutcome.Ok);
                        }
                    }
                }

                if (null != ignorableUndocumentedProperties && ignorableUndocumentedProperties.Contains(propertyName))
                {
                    return(PropertyValidationOutcome.Ok);
                }

                if (this.OriginalResource?.OriginalMetadata?.IsOpenType == true)
                {
                    return(PropertyValidationOutcome.Ok);
                }

                // This property isn't documented
                issues.Warning(new UndocumentedPropertyWarning(null, inputProperty.Name, inputProperty.Type, ResourceName));
                return(PropertyValidationOutcome.MissingFromSchema);
            }
        }
コード例 #15
0
        private static IEnumerable <ParameterDefinition> ParseParameterTable(IMarkdownTable table, ParameterLocation location, TableDecoder decoder, IssueLogger issues, bool navigationProperties = false)
        {
            // tables sometimes have column spans to delineate different sections of the table. for instance:
            //
            // | Name | Type   | Description
            // |------|--------|--------------
            // | one  | int    | first number
            // | two  | int    | second number
            // | **fancy numbers**
            // | pi   | double | third number
            //
            // our markdown parser captures this as a regular row with all the columns, except with &nbsp; for all the blanks.
            // we try to infer such rows by looking for a **bold** first cell, followed by nbsp in all the other cells.
            // see below.

            var records = table.RowValues.
                          Where(r => !r[0].StartsWith("**") || r.Skip(1).Any(c => c != "&nbsp;")). // see comment above
                          Select(r => new ParameterDefinition
            {
                Name          = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["name"]),
                Type          = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["type"]).ParseParameterDataType(defaultValue: ParameterDataType.String),
                Description   = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]),
                Required      = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]).IsRequired(),
                Optional      = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]).IsOptional(),
                Location      = location,
                IsNavigatable = navigationProperties,
            }).ToList();

            var badRows = records.Count(r => string.IsNullOrEmpty(r.Name));

            if (badRows > 0)
            {
                var tableHeaders = $"|{ string.Join("|", table.ColumnHeaders)}|";
                if (badRows == records.Count)
                {
                    issues.Warning(ValidationErrorCode.MarkdownParserError, $"Failed to parse any rows out of table with headers: {tableHeaders}\nTable was parsed as {decoder.ParseAs}, which requires a name column called one of the following: {{ {String.Join(",", decoder.ParseRule.ColumnNames["name"])} }}");
                    return(Enumerable.Empty <ParameterDefinition>());
                }

                issues.Warning(ValidationErrorCode.ParameterParserError, $"Failed to parse {badRows} row(s) in table with headers: {tableHeaders}");
                records = records.Where(r => !string.IsNullOrEmpty(r.Name)).ToList();
            }

            return(records);
        }
コード例 #16
0
        /// <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="issues"></param>
        /// <param name="expectedJson"></param>
        /// <returns></returns>
        public bool ValidateJsonCompilesWithSchema(JsonSchema schema, JsonExample inputJson, IssueLogger issues, 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();

            var annotationTruncated = (inputJson.Annotation ?? new CodeBlockAnnotation()).TruncatedResult;

            options.AllowTruncatedResponses = annotationTruncated || options.AllowTruncatedResponses;
            options.CollectionPropertyName  = collectionPropertyName;

            return(schema.ValidateJson(inputJson, issues, this.registeredSchema, options, expectedJson));
        }
コード例 #17
0
 public IODataNavigable NavigateByEntityTypeKey(EntityFramework edmx, IssueLogger issues)
 {
     throw new NotImplementedException();
 }
コード例 #18
0
        /// <summary>
        /// Validates the value of json according to an implicit schmea defined by expectedJson
        /// </summary>
        /// <returns></returns>
        public bool ValidateJsonExample(CodeBlockAnnotation expectedResponseAnnotation, string actualResponseBodyJson, IssueLogger issues, ValidationOptions options = null)
        {
            List <ValidationError> newErrors = new List <ValidationError>();

            var resourceType = expectedResponseAnnotation.ResourceType;

            if (resourceType == "stream")
            {
                // No validation since we're streaming data
                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 available in the json
                    schema = new JsonSchema(actualResponseBodyJson, new CodeBlockAnnotation {
                        ResourceType = expectedResponseAnnotation.ResourceType
                    });
                }

                this.ValidateJsonCompilesWithSchema(schema, new JsonExample(actualResponseBodyJson, expectedResponseAnnotation), issues, options: options);
                return(issues.Issues.Count() == 0);
            }
        }
コード例 #19
0
        protected override async Task PublishFileToDestinationAsync(FileInfo sourceFile, DirectoryInfo destinationRoot, DocFile page, IssueLogger issues)
        {
            this.LogMessage(new ValidationMessage(sourceFile.Name, "Publishing file to HTML"));

            var destinationPath = this.GetPublishedFilePath(sourceFile, destinationRoot, HtmlOutputExtension);

            // Create a tag processor
            string tagsInput;

            if (null == PageParameters || !PageParameters.TryGetValue("tags", out tagsInput))
            {
                tagsInput = string.Empty;
            }
            var markdownSource = page.ReadAndPreprocessFileContents(tagsInput, issues);

            var converter = this.GetMarkdownConverter();
            var html      = converter.Transform(markdownSource);

            // Post-process the resulting HTML for any remaining tags
            TagProcessor tagProcessor = new TagProcessor(tagsInput,
                                                         page.Parent.SourceFolderPath, LogMessage);

            html = tagProcessor.PostProcess(html, sourceFile, converter);

            var pageData = page.Annotation ?? new PageAnnotation();

            if (string.IsNullOrEmpty(pageData.Title))
            {
                pageData.Title = (from b in page.OriginalMarkdownBlocks
                                  where b.BlockType == BlockType.h1
                                  select b.Content).FirstOrDefault();
            }
            page.Annotation = pageData;
            await this.WriteHtmlDocumentAsync(html, page, destinationPath, destinationRoot.FullName);
        }
コード例 #20
0
        /// <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>
        internal bool ValidateResponseMatchesSchema(MethodDefinition method, HttpResponse actualResponse, HttpResponse expectedResponse, IssueLogger issues, ValidationOptions options = null)
        {
            var expectedResourceType = method.ExpectedResponseMetadata.ResourceType;

            switch (expectedResourceType)
            {
            case "stream":
            case "Stream":
            case "Edm.stream":
            case "Edm.Stream":
                // No validation since we're streaming data
                return(true);

            case "string":
            case "String":
            case "Edm.String":
            case " Edm.string":
                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, issues, expectedResponseJson);

            if (null == schema)
            {
                issues.Error(ValidationErrorCode.ResourceTypeNotFound, $"Unable to locate a definition for resource type: {expectedResourceType}");
            }
            else
            {
                this.ValidateJsonCompilesWithSchema(schema, new JsonExample(actualResponse.Body, method.ExpectedResponseMetadata), issues, (null != expectedResponseJson) ? new JsonExample(expectedResponseJson) : null, options);
            }

            if (issues.Issues.WereWarningsOrErrors() && method.ExpectedResponseMetadata.ResourceTypeAka != null)
            {
                var expectedResourceTypeAka = method.ExpectedResponseMetadata.ResourceTypeAka;
                schema = this.GetJsonSchema(expectedResourceTypeAka, issues, expectedResponseJson);
                if (null == schema)
                {
                    issues.Error(ValidationErrorCode.ResourceTypeNotFound, $"Unable to locate a definition for resource type: {expectedResourceTypeAka}");
                }
                else
                {
                    this.ValidateJsonCompilesWithSchema(schema, new JsonExample(actualResponse.Body, method.ExpectedResponseMetadata), issues, (null != expectedResponseJson) ? new JsonExample(expectedResponseJson) : null, options);
                }
            }

            return(!issues.Issues.WereWarningsOrErrors());
        }
コード例 #21
0
        public static ValidationResult <bool> IsResponseValid(this HttpResponse response, string sourceFile, ApiRequirements requirements, IssueLogger issues)
        {
            if (null == requirements || null == requirements.HttpResponse)
            {
                return(new ValidationResult <bool>(true));
            }

            List <ValidationError> errors = new List <ValidationError>();

            var reqs = requirements.HttpResponse;

            var responseMimeType = new MultipartMime.MimeContentType(response.ContentType);

            if (reqs.ContentTypes != null && null != responseMimeType.MimeType &&
                !reqs.ContentTypes.Contains(responseMimeType.MimeType))
            {
                issues.Warning(ValidationErrorCode.InvalidContentType, $"Response Content-Type header value is not in the supported list of content-types: {response.ContentType}");
            }

            return(new ValidationResult <bool>(!errors.Any()));
        }
コード例 #22
0
        /// <summary>
        /// Scans the text content of a file and removes any "internal" comments/references
        /// </summary>
        /// <param name="sourceFile">File.</param>
        /// <param name="destinationRoot"></param>
        /// <param name="page"></param>
        protected override async Task PublishFileToDestinationAsync(FileInfo sourceFile, DirectoryInfo destinationRoot, DocFile page, IssueLogger issues)
        {
            this.LogMessage(new ValidationMessage(sourceFile.Name, "Scanning text file for internal content."));

            var outputPath = this.GetPublishedFilePath(sourceFile, destinationRoot);
            var writer     = new StreamWriter(outputPath, false, Encoding.UTF8)
            {
                AutoFlush = true
            };

            StreamReader reader = new StreamReader(sourceFile.OpenRead());

            long   lineNumber = 0;
            string nextLine;

            while ((nextLine = await reader.ReadLineAsync()) != null)
            {
                lineNumber++;
                if (this.IsDoubleBlockQuote(nextLine))
                {
                    this.LogMessage(new ValidationMessage(string.Concat(sourceFile, ":", lineNumber), "Removing DoubleBlockQuote: {0}", nextLine));
                    continue;
                }
                await writer.WriteLineAsync(nextLine);
            }
            writer.Close();
            reader.Close();
        }
コード例 #23
0
        /// <summary>
        /// Convert a tablespec block into one of our internal object model representations
        /// </summary>
        public TableDefinition ParseTableSpec(Block tableSpecBlock, Stack <Config.DocumentHeader> headerStack, IssueLogger issues)
        {
            List <ValidationError> discoveredErrors = new List <ValidationError>();
            List <ItemDefinition>  items            = new List <ItemDefinition>();

            var tableShape = tableSpecBlock.Table;

            TableDecoder decoder = new TableDecoder {
                Type = TableBlockType.Unknown
            };

            var headerText = headerStack.Peek()?.Title;

            // Try matching based on header
            if (headerText != null)
            {
                var matchingDecoder = FindDecoderFromHeaderText(headerStack);
                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, issues.For($"{decoder.Type}Table")));
                break;

            case TableBlockType.ResourcePropertyDescriptions:
            case TableBlockType.RequestObjectProperties:
            case TableBlockType.ResponseObjectProperties:
                items.AddRange(ParseParameterTable(tableShape, ParameterLocation.JsonObject, decoder, issues));
                break;

            case TableBlockType.ResourceNavigationPropertyDescriptions:
                items.AddRange(ParseParameterTable(tableShape, ParameterLocation.JsonObject, decoder, issues, true));
                break;

            case TableBlockType.HttpHeaders:
                items.AddRange(ParseParameterTable(tableShape, ParameterLocation.Header, decoder, issues));
                break;

            case TableBlockType.QueryStringParameters:
                items.AddRange(ParseParameterTable(tableShape, ParameterLocation.QueryString, decoder, issues));
                break;

            case TableBlockType.EnumerationValues:
                items.AddRange(ParseEnumerationTable(tableShape, decoder));
                break;

            case TableBlockType.AuthScopes:
                items.AddRange(ParseAuthScopeTable(tableShape, decoder));
                break;

            case TableBlockType.Unknown:
                var headers = tableShape.ColumnHeaders != null?tableShape.ColumnHeaders.ComponentsJoinedByString(",") : "null";

                issues.Message($"Ignored unclassified table: headerText='{headerText}', tableHeaders='{headers}'");
                break;

            default:
                var hdrs = tableShape.ColumnHeaders != null?tableShape.ColumnHeaders.ComponentsJoinedByString(",") : "null";

                issues.Message($"Ignored table: classification='{decoder.Type}', headerText='{headerText}', tableHeaders='{hdrs}'");
                break;
            }

            return(new TableDefinition(decoder.Type, items, headerText));
        }
コード例 #24
0
 public JsonResourceDefinition(CodeBlockAnnotation sourceAnnotation, string json, DocFile source, IssueLogger issues)
     : base(sourceAnnotation, json, source, "json")
 {
     ParseJsonInput(issues);
 }
コード例 #25
0
ファイル: ODataParser.cs プロジェクト: madansr7/apidoctor
        private static IEnumerable <ResourceDefinition> CreateResourcesFromSchema(Schema schema, IEnumerable <Schema> otherSchema, IssueLogger issues)
        {
            List <ResourceDefinition> resources = new List <ResourceDefinition>();

            resources.AddRange(from ct in schema.ComplexTypes select ResourceDefinitionFromType(schema, otherSchema, ct, issues));
            resources.AddRange(from et in schema.EntityTypes select ResourceDefinitionFromType(schema, otherSchema, et, issues));

            return(resources);
        }
コード例 #26
0
        public static string RequestUriPathOnly(this MethodDefinition method, string[] baseUrlsToRemove, IssueLogger issues)
        {
            if (string.IsNullOrWhiteSpace(method.Request))
            {
                return(string.Empty);
            }

            var path = method.Request.FirstLineOnly().TextBetweenCharacters(' ', '?').TrimEnd('/');

            if (baseUrlsToRemove != null)
            {
                foreach (var baseUrl in baseUrlsToRemove)
                {
                    if (!string.IsNullOrEmpty(baseUrl) && path.StartsWith(baseUrl, StringComparison.OrdinalIgnoreCase))
                    {
                        path = path.Substring(baseUrl.Length);
                    }
                }
            }

            // just in case there's a stray example that doesn't match, at least chop the domain part off.
            foreach (var scheme in new[] { "https://", "http://" })
            {
                if (path.StartsWith(scheme, StringComparison.OrdinalIgnoreCase))
                {
                    int pathStartIndex = path.IndexOf('/', scheme.Length);
                    if (pathStartIndex != -1)
                    {
                        path = path.Substring(pathStartIndex);
                        break;
                    }
                }
            }

            // Normalize variables in the request path
            path = path.ReplaceTextBetweenCharacters('{', '}', "var");

            if (method.RequestMetadata.SampleKeys != null)
            {
                foreach (var key in method.RequestMetadata.SampleKeys)
                {
                    path = path.Replace("/" + key, "/{var}");
                }
            }

            // Normalize function params
            var substitutions = new Dictionary <string, string>();
            var parenIndex    = path.IndexOf('(');

            for (int i = 0; i < path.Length; i++)
            {
                if (path[i] == '(')
                {
                    // this is the start of a function. let's find the closing paren.
                    var close = path.IndexOf(')', i);
                    if (close > -1)
                    {
                        var inner = path.Substring(i + 1, close - i - 1);
                        substitutions[inner] = NormalizeFunctionParameters(inner, issues);
                        i = close;
                    }
                }
            }

            foreach (var sub in substitutions)
            {
                if (string.IsNullOrWhiteSpace(sub.Key))
                {
                    continue;
                }
                path = path.Replace(sub.Key, sub.Value);
            }

            // Rewrite path syntax into what it logically means
            path = path.ReplaceTextBetweenCharacters(':', ':', "/children/{var}", requireSecondChar: false, removeTargetChars: true);

            return(path);
        }
コード例 #27
0
        /// <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="issues"></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, IssueLogger issues, 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)
            {
                issues.Error(ValidationErrorCode.JsonParserException, $"Failed to parse json string: {jsonInput.JsonData}.", ex);
                return(false);
            }

            var annotation = jsonInput.Annotation ?? new CodeBlockAnnotation();

            bool expectErrorObject = (jsonInput.Annotation != null) && jsonInput.Annotation.ExpectError;

            // Check for an error response
            try
            {
                dynamic errorObject = obj["error"];
                if (null != errorObject && !expectErrorObject)
                {
                    string code    = errorObject.code;
                    string message = errorObject.message;

                    issues.Error(ValidationErrorCode.JsonErrorObject, $"Error response received. Code: {code}, Message: {message}");
                    return(false);
                }
                else if (expectErrorObject && null == errorObject)
                {
                    issues.Error(ValidationErrorCode.JsonErrorObjectExpected, "Expected an error object response, but didn't receive one.");
                    return(false);
                }
            }
            catch (Exception ex)
            {
                if (annotation.ExpectError)
                {
                    issues.Error(ValidationErrorCode.JsonErrorObjectExpected, "Expected an error object, but it doesn't look like we got one.", ex);
                }
            }

            // Check to see if this is a "collection" instance
            if (null != annotation && annotation.IsCollection)
            {
                this.ValidateCollectionObject(obj, annotation, otherSchemas, options.CollectionPropertyName, issues, options);
            }
            // 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, issues);
            }

            return(issues.Issues.Count() == 0);
        }
コード例 #28
0
        public IODataNavigable NavigateByUriComponent(string component, EntityFramework edmx, IssueLogger issues, bool isLastSegment)
        {
            string targetType = null;

            var entitySet = (from e in EntitySets
                             where e.Name == component
                             select e).FirstOrDefault();

            if (null != entitySet)
            {
                // EntitySet is always a collection of an item type
                targetType = "Collection(" + entitySet.EntityType + ")";
            }
            else
            {
                var singleton = (from s in Singletons
                                 where s.Name == component
                                 select s).FirstOrDefault();

                if (null != singleton)
                {
                    targetType = singleton.Type;
                }
            }

            if (targetType != null)
            {
                if (targetType.StartsWith("Collection("))
                {
                    var innerId = targetType.Substring(11, targetType.Length - 12);
                    return(new ODataCollection(innerId));
                }
                return(edmx.ResourceWithIdentifier <IODataNavigable>(targetType));
            }

            return(null);
        }
コード例 #29
0
        /// <summary>
        /// Verify that a Json container (object) is valid according to it's resource name (schema).
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="options"></param>
        /// <param name="otherSchemas"></param>
        /// <param name="issues"></param>
        private void ValidateContainerObject(JContainer obj, ValidationOptions options, Dictionary <string, JsonSchema> otherSchemas, IssueLogger issues)
        {
            var explicitType = obj["@odata.type"];

            if (explicitType != null)
            {
                var typeName = explicitType.Value <string>();
                if (!string.IsNullOrEmpty(typeName))
                {
                    if (typeName.StartsWith("#"))
                    {
                        typeName = typeName.Substring(1);
                    }

                    JsonSchema childType;
                    if (this.childTypes.TryGetValue(typeName, out childType))
                    {
                        childType.ValidateContainerObject(obj, options, otherSchemas, issues);
                        return;
                    }
                    else if (!typeName.IEquals(this.ResourceName))
                    {
                        issues.Warning(ValidationErrorCode.ResourceTypeNotFound, $"unrecognized type declaration {typeName}");
                    }
                }
            }

            var containerProperties = from p in obj
                                      select ParseProperty(p, this, issues.For(p.Path));

            this.ValidateObjectProperties(containerProperties.Where(x => null != x), options, otherSchemas, issues);
        }
コード例 #30
0
ファイル: JsonSchema.cs プロジェクト: madansr7/apidoctor
        private void ValidateObjectProperties(IEnumerable <ParameterDefinition> propertiesOnObject, ValidationOptions options, Dictionary <string, JsonSchema> otherSchemas, IssueLogger issues)
        {
            List <string> missingProperties = new List <string>();

            missingProperties.AddRange(from m in this.ExpectedProperties select m.Key);

            foreach (var property in propertiesOnObject)
            {
                missingProperties.Remove(property.Name);

                // This detects bad types, extra properties, etc.
                if (null != options && (property.Type.IsCollection || property.Type.IsObject))
                {
                    var propertyOptions = options.CreateForProperty(property.Name);
                    this.ValidateProperty(property, otherSchemas, issues.For(property.Name), propertyOptions);
                }
                else
                {
                    this.ValidateProperty(property, otherSchemas, issues.For(property.Name), options);
                }
            }

            this.CleanMissingProperties(options, missingProperties);
            if (missingProperties.Count > 0)
            {
                issues.Error(ValidationErrorCode.RequiredPropertiesMissing,
                             $"Missing properties: response was missing these required properties: {missingProperties.ComponentsJoinedByString(", ")}");
            }
        }