Ejemplo n.º 1
0
        public List <LogKeyValueItem> SetPropertiesAfterValidationsOfProjectReferencesPathAndFiles()
        {
            var logItems = new List <LogKeyValueItem>();

            if (ApiProjectSrcPath.Exists)
            {
                var files = Directory.GetFiles(ApiProjectSrcPath.FullName, "ApiRegistration.cs", SearchOption.AllDirectories);
                if (files.Length == 1)
                {
                    ApiProjectSrcPath = new FileInfo(files[0]).Directory !;
                    files             = Directory.GetFiles(ApiProjectSrcPath.FullName, "*.csproj", SearchOption.AllDirectories);
                    if (files.Length == 1)
                    {
                        ApiProjectSrcCsProj = new FileInfo(files[0]);
                    }
                }
            }

            if (ApiProjectSrcCsProj == null || !ApiProjectSrcCsProj.Exists)
            {
                logItems.Add(LogItemHelper.Create(LogCategoryType.Error, ValidationRuleNameConstants.ProjectHostGenerated04, "Can't find API .csproj file"));
            }

            return(logItems);
        }
Ejemplo n.º 2
0
    /// <summary>
    /// Check for response types according to operation/global parameters.
    /// </summary>
    /// <param name="validationOptions">The validation options.</param>
    /// <param name="path">The path.</param>
    /// <returns>List of possible validation errors</returns>
    public static List <LogKeyValueItem> ValidateGetOperations(
        ApiOptionsValidation validationOptions,
        KeyValuePair <string, OpenApiPathItem> path)
    {
        ArgumentNullException.ThrowIfNull(validationOptions);

        var logItems    = new List <LogKeyValueItem>();
        var logCategory = validationOptions.StrictMode
            ? LogCategoryType.Error
            : LogCategoryType.Warning;

        foreach (var(key, value) in path.Value.Operations)
        {
            if (key != OperationType.Get ||
                (path.Value.Parameters.All(x => x.In != ParameterLocation.Path) &&
                 value.Parameters.All(x => x.In != ParameterLocation.Path)))
            {
                continue;
            }

            var httpStatusCodes = value.Responses.GetHttpStatusCodes();
            if (!httpStatusCodes.Contains(HttpStatusCode.NotFound))
            {
                logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation14, $"Missing NotFound response type for operation '{value.GetOperationName()}', required by url parameter."));
            }
        }

        return(logItems);
    }
Ejemplo n.º 3
0
        public void Create()
        {
            // Act
            var actual = LogItemHelper.Create(LogCategoryType.Error, ValidationRuleNameConstants.Operation04, "Hallo world");

            // Assert
            Assert.NotNull(actual);
            Assert.Equal("Key: CR0204, Value: Operation, LogCategory: Error, Description: Hallo world", actual.ToString());
        }
Ejemplo n.º 4
0
        private static LogKeyValueItem ValidateVersioning(ApiProjectOptions apiProjectOptions)
        {
            if (apiProjectOptions == null)
            {
                throw new ArgumentNullException(nameof(apiProjectOptions));
            }

            if (!Directory.Exists(apiProjectOptions.PathForSrcGenerate.FullName))
            {
                return(LogItemHelper.Create(LogCategoryType.Information, ValidationRuleNameConstants.ProjectApiGenerated01, "Old project don't exist."));
            }

            var apiGeneratedFile = Path.Combine(apiProjectOptions.PathForSrcGenerate.FullName, "ApiRegistration.cs");

            if (!File.Exists(apiGeneratedFile))
            {
                return(LogItemHelper.Create(LogCategoryType.Information, ValidationRuleNameConstants.ProjectApiGenerated02, "Old ApiRegistration.cs in project don't exist."));
            }

            var lines = File.ReadLines(apiGeneratedFile).ToList();

            const string toolName   = "ApiGenerator";
            var          newVersion = GenerateHelper.GetAtcToolVersion();

            foreach (var line in lines)
            {
                var indexOfToolName = line.IndexOf(toolName !, StringComparison.Ordinal);
                if (indexOfToolName == -1)
                {
                    continue;
                }

                var oldVersion = line.Substring(indexOfToolName + toolName !.Length);
                if (oldVersion.EndsWith('.'))
                {
                    oldVersion = oldVersion.Substring(0, oldVersion.Length - 1);
                }

                if (!Version.TryParse(oldVersion, out var oldVersionResult))
                {
                    return(LogItemHelper.Create(LogCategoryType.Error, ValidationRuleNameConstants.ProjectApiGenerated03, "Existing project version is invalid."));
                }

                if (newVersion >= oldVersionResult)
                {
                    return(LogItemHelper.Create(LogCategoryType.Information, ValidationRuleNameConstants.ProjectApiGenerated04, "The generate project version is the same or newer."));
                }

                return(LogItemHelper.Create(LogCategoryType.Error, ValidationRuleNameConstants.ProjectApiGenerated05, "Existing project version is never than this tool version."));
            }

            return(LogItemHelper.Create(LogCategoryType.Error, ValidationRuleNameConstants.ProjectApiGenerated06, "Existing project did not contain a version."));
        }
    private static List <LogKeyValueItem> ValidateSchemaModelPropertyNameCasing(
        ApiOptionsValidation validationOptions,
        string key,
        OpenApiSchema schema)
    {
        var logItems    = new List <LogKeyValueItem>();
        var logCategory = validationOptions.StrictMode
            ? LogCategoryType.Error
            : LogCategoryType.Warning;

        if (!key.IsCasingStyleValid(validationOptions.ModelPropertyNameCasingStyle))
        {
            logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Schema07, $"Object '{schema.Title}' with property '{key}' is not using {validationOptions.ModelPropertyNameCasingStyle}."));
        }

        return(logItems);
    }
Ejemplo n.º 6
0
    /// <summary>
    /// Check for operations that are not defining parameters, which are present in the path.key.
    /// </summary>
    /// <param name="validationOptions">The validation options.</param>
    /// <param name="path">The path.</param>
    /// <returns>List of possible validation errors</returns>
    public static List <LogKeyValueItem> ValidateMissingOperationParameters(
        ApiOptionsValidation validationOptions,
        KeyValuePair <string, OpenApiPathItem> path)
    {
        ArgumentNullException.ThrowIfNull(validationOptions);

        var logItems    = new List <LogKeyValueItem>();
        var logCategory = validationOptions.StrictMode
            ? LogCategoryType.Error
            : LogCategoryType.Warning;

        if (!path.Key.Contains('{', StringComparison.Ordinal) ||
            !path.Key.IsStringFormatParametersBalanced(false))
        {
            return(logItems);
        }

        var parameterNamesToCheckAgainst    = GetParameterListFromPathKey(path.Key);
        var allOperationsParametersFromPath = GetAllOperationsParametersFromPath(path.Value.Operations);
        var distinctOperations = allOperationsParametersFromPath
                                 .Select(x => x.Item1)
                                 .Distinct(StringComparer.Ordinal)
                                 .ToList();

        foreach (var parameterName in parameterNamesToCheckAgainst)
        {
            var allOperationWithTheMatchingParameterName = allOperationsParametersFromPath
                                                           .Where(x => x.Item2.Equals(parameterName, StringComparison.OrdinalIgnoreCase))
                                                           .ToList();

            if (distinctOperations.Count != allOperationWithTheMatchingParameterName.Count)
            {
                var operationsWithMissingParameter = allOperationsParametersFromPath
                                                     .Where(x => string.IsNullOrEmpty(x.Item2))
                                                     .Select(x => x.Item1)
                                                     .ToList();

                logItems.Add(operationsWithMissingParameter.Count == 0
                    ? LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation12, $"The operations in path '{path.Key}' does not define a parameter named '{parameterName}'.")
                    : LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation12, $"The operations '{string.Join(',', operationsWithMissingParameter)}' in path '{path.Key}' does not define a parameter named '{parameterName}'."));
            }
        }

        return(logItems);
    }
    private static List <LogKeyValueItem> ValidateSchemaModelNameCasing(
        ApiOptionsValidation validationOptions,
        OpenApiSchema schema)
    {
        var logItems    = new List <LogKeyValueItem>();
        var logCategory = validationOptions.StrictMode
            ? LogCategoryType.Error
            : LogCategoryType.Warning;

        var modelName = schema.GetModelName(ensureFirstCharacterToUpper: false);

        if (!modelName.IsCasingStyleValid(validationOptions.ModelNameCasingStyle))
        {
            logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Schema06, $"Object '{modelName}' is not using {validationOptions.ModelNameCasingStyle}."));
        }

        return(logItems);
    }
Ejemplo n.º 8
0
    /// <summary>
    /// Check for operations with parameters, that are not present in the path.key.
    /// </summary>
    /// <param name="validationOptions">The validation options.</param>
    /// <param name="path">The path.</param>
    /// <returns>List of possible validation errors</returns>
    public static List <LogKeyValueItem> ValidateOperationsWithParametersNotPresentInPath(
        ApiOptionsValidation validationOptions,
        KeyValuePair <string, OpenApiPathItem> path)
    {
        ArgumentNullException.ThrowIfNull(validationOptions);

        var logItems    = new List <LogKeyValueItem>();
        var logCategory = validationOptions.StrictMode
            ? LogCategoryType.Error
            : LogCategoryType.Warning;

        var openApiOperationsWithPathParameter = path.Value.Operations.Values
                                                 .Where(x => x.Parameters.Any(p => p.In == ParameterLocation.Path))
                                                 .ToList();

        if (!openApiOperationsWithPathParameter.Any())
        {
            return(logItems);
        }

        var operationPathParameterNames = new List <string>();

        foreach (var openApiOperation in openApiOperationsWithPathParameter)
        {
            operationPathParameterNames.AddRange(openApiOperation.Parameters
                                                 .Where(x => x.In == ParameterLocation.Path)
                                                 .Select(openApiParameter => openApiParameter.Name));
        }

        if (!operationPathParameterNames.Any())
        {
            return(logItems);
        }

        foreach (var operationParameterName in operationPathParameterNames)
        {
            if (!path.Key.Contains(operationParameterName, StringComparison.OrdinalIgnoreCase))
            {
                logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation13, $"Defined path parameter '{operationParameterName}' does not exist in route '{path.Key}'."));
            }
        }

        return(logItems);
    }
    private static List <LogKeyValueItem> ValidateServers(
        ApiOptionsValidation validationOptions,
        IEnumerable <OpenApiServer> servers)
    {
        var logItems    = new List <LogKeyValueItem>();
        var logCategory = validationOptions.StrictMode
            ? LogCategoryType.Error
            : LogCategoryType.Warning;

        var server = servers.FirstOrDefault();

        if (server is not null &&
            !IsServerUrlValid(server.Url))
        {
            logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Server01, "Invalid server url."));
        }

        return(logItems);
    }
Ejemplo n.º 10
0
    /// <summary>
    /// Check global parameters.
    /// </summary>
    /// <param name="validationOptions">The validation options.</param>
    /// <param name="globalPathParameterNames">The global path parameter names.</param>
    /// <param name="path">The path.</param>
    /// <returns>List of possible validation errors</returns>
    public static List <LogKeyValueItem> ValidateGlobalParameters(
        ApiOptionsValidation validationOptions,
        IEnumerable <string> globalPathParameterNames,
        KeyValuePair <string, OpenApiPathItem> path)
    {
        ArgumentNullException.ThrowIfNull(validationOptions);
        ArgumentNullException.ThrowIfNull(globalPathParameterNames);

        var logItems    = new List <LogKeyValueItem>();
        var logCategory = validationOptions.StrictMode
            ? LogCategoryType.Error
            : LogCategoryType.Warning;

        foreach (var pathParameterName in globalPathParameterNames)
        {
            if (!path.Key.Contains(pathParameterName, StringComparison.OrdinalIgnoreCase))
            {
                logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation11, $"Defined global path parameter '{pathParameterName}' does not exist in route '{path.Key}'."));
            }
        }

        return(logItems);
    }
    private static List <LogKeyValueItem> ValidatePathsAndOperations(
        ApiOptionsValidation validationOptions,
        OpenApiPaths paths)
    {
        var logItems    = new List <LogKeyValueItem>();
        var logCategory = validationOptions.StrictMode
            ? LogCategoryType.Error
            : LogCategoryType.Warning;

        foreach (var path in paths)
        {
            if (!path.Key.IsStringFormatParametersBalanced(false))
            {
                logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Path01, $"Path parameters are not well-formatted for '{path.Key}'."));
            }

            var globalPathParameterNames = path.Value.Parameters
                                           .Where(x => x.In == ParameterLocation.Path)
                                           .Select(x => x.Name)
                                           .ToList();

            if (globalPathParameterNames.Any())
            {
                logItems.AddRange(ValidatePathsAndOperationsHelper.ValidateGlobalParameters(validationOptions, globalPathParameterNames, path));
            }
            else
            {
                logItems.AddRange(ValidatePathsAndOperationsHelper.ValidateMissingOperationParameters(validationOptions, path));
                logItems.AddRange(ValidatePathsAndOperationsHelper.ValidateOperationsWithParametersNotPresentInPath(validationOptions, path));
            }

            logItems.AddRange(ValidatePathsAndOperationsHelper.ValidateGetOperations(validationOptions, path));
        }

        return(logItems);
    }
    private static List <LogKeyValueItem> ValidateSchemas(
        ApiOptionsValidation validationOptions,
        IEnumerable <OpenApiSchema> schemas)
    {
        var logItems    = new List <LogKeyValueItem>();
        var logCategory = validationOptions.StrictMode
            ? LogCategoryType.Error
            : LogCategoryType.Warning;

        foreach (var schema in schemas)
        {
            switch (schema.Type)
            {
            case OpenApiDataTypeConstants.Array:
            {
                if (string.IsNullOrEmpty(schema.Title))
                {
                    logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Schema01, $"Missing title on array type '{schema.Reference.ReferenceV3}'."));
                }
                else if (schema.Title.IsFirstCharacterLowerCase())
                {
                    logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Schema02, $"Title on array type '{schema.Title}' is not starting with uppercase."));
                }

                logItems.AddRange(ValidateSchemaModelNameCasing(validationOptions, schema));
                break;
            }

            case OpenApiDataTypeConstants.Object:
            {
                if (string.IsNullOrEmpty(schema.Title))
                {
                    logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Schema03, $"Missing title on object type '{schema.Reference.ReferenceV3}'."));
                }
                else if (schema.Title.IsFirstCharacterLowerCase())
                {
                    logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Schema04, $"Title on object type '{schema.Title}' is not starting with uppercase."));
                }

                foreach (var(key, value) in schema.Properties)
                {
                    switch (value.Type)
                    {
                    case OpenApiDataTypeConstants.Object:
                    {
                        if (!value.IsObjectReferenceTypeDeclared())
                        {
                            logItems.Add(LogItemHelper.Create(LogCategoryType.Error, ValidationRuleNameConstants.Schema10, $"Implicit object definition on property '{key}' in type '{schema.Reference.ReferenceV3}' is not supported."));
                        }

                        break;
                    }

                    case OpenApiDataTypeConstants.Array:
                    {
                        if (value.Items is null)
                        {
                            logItems.Add(LogItemHelper.Create(LogCategoryType.Error, ValidationRuleNameConstants.Schema11, $"Not specifying a data type for array property '{key}' in type '{schema.Reference.ReferenceV3}' is not supported."));
                        }
                        else
                        {
                            if (value.Items.Type is null)
                            {
                                logItems.Add(LogItemHelper.Create(LogCategoryType.Error, ValidationRuleNameConstants.Schema09, $"Not specifying a data type for array property '{key}' in type '{schema.Reference.ReferenceV3}' is not supported."));
                            }

                            if (value.Items.Type is not null &&
                                !value.IsArrayReferenceTypeDeclared() &&
                                !value.HasItemsWithSimpleDataType() &&
                                !value.HasItemsWithFormatTypeBinary())
                            {
                                logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Schema05, $"Implicit object definition on property '{key}' in array type '{schema.Reference.ReferenceV3}' is not supported."));
                            }
                        }

                        break;
                    }
                    }

                    logItems.AddRange(ValidateSchemaModelPropertyNameCasing(validationOptions, key, schema));
                }

                logItems.AddRange(ValidateSchemaModelNameCasing(validationOptions, schema));
                break;
            }
            }
        }

        return(logItems);
    }
    private static List <LogKeyValueItem> ValidateOperationsParametersAndResponses(
        ApiOptionsValidation validationOptions,
        Dictionary <string, OpenApiPathItem> .ValueCollection paths)
    {
        var logItems    = new List <LogKeyValueItem>();
        var logCategory = validationOptions.StrictMode
            ? LogCategoryType.Error
            : LogCategoryType.Warning;

        foreach (var path in paths)
        {
            foreach (var(operationType, value) in path.Operations)
            {
                var httpStatusCodes = value.Responses.GetHttpStatusCodes();
                if (httpStatusCodes.Contains(HttpStatusCode.BadRequest) &&
                    !value.HasParametersOrRequestBody() &&
                    !path.HasParameters())
                {
                    logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation10, $"Contains BadRequest response type for operation '{value.OperationId}', but has no parameters."));
                }

                if (httpStatusCodes.Contains(HttpStatusCode.OK) &&
                    httpStatusCodes.Contains(HttpStatusCode.Created))
                {
                    // We do not support both 200 and 201, since our ActionResult - implicit operators only supports 1 type.
                    logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation18, $"The operation '{value.OperationId}' contains both 200 and 201, which is not supported."));
                }

                if (value.HasParametersOrRequestBody())
                {
                    var schema = value.RequestBody?.Content.GetSchemaByFirstMediaType();
                    if (schema is not null &&
                        string.IsNullOrEmpty(schema.GetModelName()) &&
                        !schema.IsFormatTypeBinary() &&
                        !schema.HasItemsWithFormatTypeBinary())
                    {
                        logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation17, $"RequestBody is defined with inline model for operation '{value.OperationId}' - only reference to component-schemas are supported."));
                    }
                }

                foreach (var parameter in value.Parameters)
                {
                    switch (parameter.In)
                    {
                    case ParameterLocation.Path:
                        if (!parameter.Required)
                        {
                            logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation15, $"Path parameter '{parameter.Name}' for operation '{value.OperationId}' is missing required=true."));
                        }

                        if (parameter.Schema.Nullable)
                        {
                            logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation16, $"Path parameter '{parameter.Name}' for operation '{value.OperationId}' must not be nullable."));
                        }

                        break;

                    case ParameterLocation.Query:
                        break;
                    }
                }
            }
        }

        return(logItems);
    }
    private static List <LogKeyValueItem> ValidateOperations(
        ApiOptionsValidation validationOptions,
        OpenApiPaths paths,
        IDictionary <string, OpenApiSchema> modelSchemas)
    {
        var logItems    = new List <LogKeyValueItem>();
        var logCategory = validationOptions.StrictMode
            ? LogCategoryType.Error
            : LogCategoryType.Warning;

        foreach (var(pathKey, pathValue) in paths)
        {
            foreach (var(operationKey, operationValue) in pathValue.Operations)
            {
                if (string.IsNullOrEmpty(operationValue.OperationId))
                {
                    logItems.Add(LogItemHelper.Create(LogCategoryType.Error, ValidationRuleNameConstants.Operation01, $"Missing OperationId in path '{operationKey} # {pathKey}'."));
                }
                else
                {
                    if (!operationValue.OperationId.IsCasingStyleValid(validationOptions.OperationIdCasingStyle))
                    {
                        logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation02, $"OperationId '{operationValue.OperationId}' is not using {validationOptions.OperationIdCasingStyle}."));
                    }

                    if (operationKey == OperationType.Get)
                    {
                        if (!operationValue.OperationId.StartsWith("Get", StringComparison.OrdinalIgnoreCase) &&
                            !operationValue.OperationId.StartsWith("List", StringComparison.OrdinalIgnoreCase))
                        {
                            logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation03, $"OperationId should start with the prefix 'Get' or 'List' for operation '{operationValue.OperationId}'."));
                        }
                    }
                    else if (operationKey == OperationType.Post)
                    {
                        if (operationValue.OperationId.StartsWith("Delete", StringComparison.OrdinalIgnoreCase))
                        {
                            logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation04, $"OperationId should not start with the prefix 'Delete' for operation '{operationValue.OperationId}'."));
                        }
                    }
                    else if (operationKey == OperationType.Put)
                    {
                        if (!operationValue.OperationId.StartsWith("Update", StringComparison.OrdinalIgnoreCase))
                        {
                            logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation05, $"OperationId should start with the prefix 'Update' for operation '{operationValue.OperationId}'."));
                        }
                    }
                    else if (operationKey == OperationType.Patch)
                    {
                        if (!operationValue.OperationId.StartsWith("Patch", StringComparison.OrdinalIgnoreCase) &&
                            !operationValue.OperationId.StartsWith("Update", StringComparison.OrdinalIgnoreCase))
                        {
                            logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation06, $"OperationId should start with the prefix 'Update' for operation '{operationValue.OperationId}'."));
                        }
                    }
                    else if (operationKey == OperationType.Delete &&
                             !operationValue.OperationId.StartsWith("Delete", StringComparison.OrdinalIgnoreCase) &&
                             !operationValue.OperationId.StartsWith("Remove", StringComparison.OrdinalIgnoreCase))
                    {
                        logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation07, $"OperationId should start with the prefix 'Delete' for operation '{operationValue.OperationId}'."));
                    }
                }
            }

            foreach (var(operationKey, operationValue) in pathValue.Operations)
            {
                // Validate Response Schema
                var responseModelSchema = operationValue.GetModelSchemaFromResponse();
                if (responseModelSchema is not null)
                {
                    if (operationValue.IsOperationIdPluralized(operationKey))
                    {
                        if (!IsModelOfTypeArray(responseModelSchema, modelSchemas))
                        {
                            logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation08, $"OperationId '{operationValue.OperationId}' is not singular - Response model is defined as a single item."));
                        }
                    }
                    else
                    {
                        if (IsModelOfTypeArray(responseModelSchema, modelSchemas))
                        {
                            logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Operation09, $"OperationId '{operationValue.OperationId}' is not pluralized - Response model is defined as an array."));
                        }
                    }
                }

                //// TO-DO Validate RequestBody Schema
            }
        }

        return(logItems);
    }