From 9bf3f0869166631840759c906abf5112c8505ae2 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 23 Oct 2024 17:36:08 +0300 Subject: [PATCH 1/7] remove depracated validation rule --- .../Validations/Rules/OpenApiHeaderRules.cs | 57 ---- .../Rules/OpenApiMediaTypeRules.cs | 63 ----- .../Rules/OpenApiParameterRules.cs | 39 --- .../Validations/Rules/OpenApiSchemaRules.cs | 43 --- .../Validations/Rules/RuleHelpers.cs | 249 ------------------ .../Validations/ValidationRuleSet.cs | 2 - 6 files changed, 453 deletions(-) delete mode 100644 src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs delete mode 100644 src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs deleted file mode 100644 index 4bc5aa94a..000000000 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Validations.Rules -{ - /// - /// The validation rules for . - /// - //Removed from Default Rules as this is not a MUST in OpenAPI - [OpenApiRule] - public static class OpenApiHeaderRules - { - /// - /// Validate the data matches with the given data type. - /// - public static ValidationRule HeaderMismatchedDataType => - new(nameof(HeaderMismatchedDataType), - (context, header) => - { - // example - context.Enter("example"); - - if (header.Example != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, - nameof(HeaderMismatchedDataType), header.Example, header.Schema); - } - - context.Exit(); - - // examples - context.Enter("examples"); - - if (header.Examples != null) - { - foreach (var key in header.Examples.Keys) - { - if (header.Examples[key] != null) - { - context.Enter(key); - context.Enter("value"); - RuleHelpers.ValidateDataTypeMismatch(context, - nameof(HeaderMismatchedDataType), header.Examples[key]?.Value, header.Schema); - context.Exit(); - context.Exit(); - } - } - } - - context.Exit(); - }); - - // add more rule. - } -} diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs deleted file mode 100644 index 7ac09cbbf..000000000 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Validations.Rules -{ - /// - /// The validation rules for . - /// - /// - /// Removed this in v1.3 as a default rule as the OpenAPI specification does not require that example - /// values validate against the schema. Validating examples against the schema is particularly difficult - /// as it requires parsing of the example using the schema as a guide. This is not possible when the schema - /// is referenced. Even if we fix this issue, this rule should be treated as a warning, not an error - /// Future versions of the validator should make that distinction. - /// Future versions of the example parsers should not try an infer types. - /// Example validation should be done as a separate post reading step so all schemas can be fully available. - /// - [OpenApiRule] - public static class OpenApiMediaTypeRules - { - /// - /// Validate the data matches with the given data type. - /// - public static ValidationRule MediaTypeMismatchedDataType => - new(nameof(MediaTypeMismatchedDataType), - (context, mediaType) => - { - // example - context.Enter("example"); - - if (mediaType.Example != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(MediaTypeMismatchedDataType), mediaType.Example, mediaType.Schema); - } - - context.Exit(); - - // enum - context.Enter("examples"); - - if (mediaType.Examples != null) - { - foreach (var key in mediaType.Examples.Keys) - { - if (mediaType.Examples[key] != null) - { - context.Enter(key); - context.Enter("value"); - RuleHelpers.ValidateDataTypeMismatch(context, nameof(MediaTypeMismatchedDataType), mediaType.Examples[key]?.Value, mediaType.Schema); - context.Exit(); - context.Exit(); - } - } - } - - context.Exit(); - }); - - // add more rule. - } -} diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs index c6ad7835d..812bc7f12 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs @@ -58,45 +58,6 @@ public static class OpenApiParameterRules context.Exit(); }); - /// - /// Validate the data matches with the given data type. - /// - public static ValidationRule ParameterMismatchedDataType => - new(nameof(ParameterMismatchedDataType), - (context, parameter) => - { - // example - context.Enter("example"); - - if (parameter.Example != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(ParameterMismatchedDataType), parameter.Example, parameter.Schema); - } - - context.Exit(); - - // examples - context.Enter("examples"); - - if (parameter.Examples != null) - { - foreach (var key in parameter.Examples.Keys) - { - if (parameter.Examples[key] != null) - { - context.Enter(key); - context.Enter("value"); - RuleHelpers.ValidateDataTypeMismatch(context, - nameof(ParameterMismatchedDataType), parameter.Examples[key]?.Value, parameter.Schema); - context.Exit(); - context.Exit(); - } - } - } - - context.Exit(); - }); - /// /// Validate that a path parameter should always appear in the path /// diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs index e768e8d42..054c79c6b 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs @@ -13,49 +13,6 @@ namespace Microsoft.OpenApi.Validations.Rules [OpenApiRule] public static class OpenApiSchemaRules { - /// - /// Validate the data matches with the given data type. - /// - public static ValidationRule SchemaMismatchedDataType => - new(nameof(SchemaMismatchedDataType), - (context, schema) => - { - // default - context.Enter("default"); - - if (schema.Default != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Default, schema); - } - - context.Exit(); - - // example - context.Enter("example"); - - if (schema.Example != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Example, schema); - } - - context.Exit(); - - // enum - context.Enter("enum"); - - if (schema.Enum != null) - { - for (var i = 0; i < schema.Enum.Count; i++) - { - context.Enter(i.ToString()); - RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Enum[i], schema); - context.Exit(); - } - } - - context.Exit(); - }); - /// /// Validates Schema Discriminator /// diff --git a/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs index 9902360ec..f6891c043 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs @@ -1,18 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; -using System.Text.Json; -using System.Text.Json.Nodes; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; - namespace Microsoft.OpenApi.Validations.Rules { internal static class RuleHelpers { - internal const string DataTypeMismatchedErrorMessage = "Data and type mismatch found."; - /// /// Input string must be in the format of an email address /// @@ -40,246 +32,5 @@ public static bool IsEmailAddress(this string input) return true; } - - public static void ValidateDataTypeMismatch( - IValidationContext context, - string ruleName, - JsonNode value, - OpenApiSchema schema) - { - if (schema == null) - { - return; - } - - // convert value to JsonElement and access the ValueKind property to determine the type. - var jsonElement = JsonDocument.Parse(JsonSerializer.Serialize(value)).RootElement; - - var type = (string)schema.Type; - var format = schema.Format; - var nullable = schema.Nullable; - - // Before checking the type, check first if the schema allows null. - // If so and the data given is also null, this is allowed for any type. - if (nullable && jsonElement.ValueKind is JsonValueKind.Null) - { - return; - } - - if (type == "object") - { - // It is not against the spec to have a string representing an object value. - // To represent examples of media types that cannot naturally be represented in JSON or YAML, - // a string value can contain the example with escaping where necessary - if (jsonElement.ValueKind is JsonValueKind.String) - { - return; - } - - // If value is not a string and also not an object, there is a data mismatch. - if (value is not JsonObject anyObject) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - return; - } - - foreach (var kvp in anyObject) - { - var key = kvp.Key; - context.Enter(key); - - if (schema.Properties != null && - schema.Properties.TryGetValue(key, out var property)) - { - ValidateDataTypeMismatch(context, ruleName, anyObject[key], property); - } - else - { - ValidateDataTypeMismatch(context, ruleName, anyObject[key], schema.AdditionalProperties); - } - - context.Exit(); - } - - return; - } - - if (type == "array") - { - // It is not against the spec to have a string representing an array value. - // To represent examples of media types that cannot naturally be represented in JSON or YAML, - // a string value can contain the example with escaping where necessary - if (jsonElement.ValueKind is JsonValueKind.String) - { - return; - } - - // If value is not a string and also not an array, there is a data mismatch. - if (value is not JsonArray anyArray) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - return; - } - - for (var i = 0; i < anyArray.Count; i++) - { - context.Enter(i.ToString()); - - ValidateDataTypeMismatch(context, ruleName, anyArray[i], schema.Items); - - context.Exit(); - } - - return; - } - - if (type == "integer" && format == "int32") - { - if (jsonElement.ValueKind is not JsonValueKind.Number) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - } - - return; - } - - if (type == "integer" && format == "int64") - { - if (jsonElement.ValueKind is not JsonValueKind.Number) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - } - - return; - } - - if (type == "integer" && jsonElement.ValueKind is not JsonValueKind.Number) - { - if (jsonElement.ValueKind is not JsonValueKind.Number) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - } - - return; - } - - if (type == "number" && format == "float") - { - if (jsonElement.ValueKind is not JsonValueKind.Number) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - } - - return; - } - - if (type == "number" && format == "double") - { - if (jsonElement.ValueKind is not JsonValueKind.Number) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - } - - return; - } - - if (type == "number") - { - if (jsonElement.ValueKind is not JsonValueKind.Number) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - } - - return; - } - - if (type == "string" && format == "byte") - { - if (jsonElement.ValueKind is not JsonValueKind.String) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - } - - return; - } - - if (type == "string" && format == "date") - { - if (jsonElement.ValueKind is not JsonValueKind.String) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - } - - return; - } - - if (type == "string" && format == "date-time") - { - if (jsonElement.ValueKind is not JsonValueKind.String) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - } - - return; - } - - if (type == "string" && format == "password") - { - if (jsonElement.ValueKind is not JsonValueKind.String) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - } - - return; - } - - if (type == "string") - { - if (jsonElement.ValueKind is not JsonValueKind.String) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - } - - return; - } - - if (type == "boolean") - { - if (jsonElement.ValueKind is not JsonValueKind.True && jsonElement.ValueKind is not JsonValueKind.False) - { - context.CreateWarning( - ruleName, - DataTypeMismatchedErrorMessage); - } - - return; - } - } } } diff --git a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs index 67b84f0be..c818e0d6b 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs @@ -329,13 +329,11 @@ internal static PropertyInfo[] GetValidationRuleTypes() ..typeof(OpenApiExternalDocsRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiInfoRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiLicenseRules).GetProperties(BindingFlags.Static | BindingFlags.Public), - ..typeof(OpenApiMediaTypeRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiOAuthFlowRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiServerRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiResponseRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiResponsesRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiSchemaRules).GetProperties(BindingFlags.Static | BindingFlags.Public), - ..typeof(OpenApiHeaderRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiTagRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiPathsRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiParameterRules).GetProperties(BindingFlags.Static | BindingFlags.Public), From ad3b65d2b74bb4f1dae2ee326c00cf02d28bf4c9 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Wed, 23 Oct 2024 17:36:24 +0300 Subject: [PATCH 2/7] clean up tests and update public API interface --- .../PublicApi/PublicApi.approved.txt | 12 ----- .../OpenApiHeaderValidationTests.cs | 26 +--------- .../OpenApiMediaTypeValidationTests.cs | 26 +--------- .../OpenApiParameterValidationTests.cs | 26 +--------- .../OpenApiSchemaValidationTests.cs | 50 ++----------------- .../Validations/ValidationRuleSetTests.cs | 6 +-- 6 files changed, 13 insertions(+), 133 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 3a7fdbd57..ec2f9a6dd 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1682,11 +1682,6 @@ namespace Microsoft.OpenApi.Validations.Rules public static Microsoft.OpenApi.Validations.ValidationRule UrlIsRequired { get; } } [Microsoft.OpenApi.Validations.Rules.OpenApiRule] - public static class OpenApiHeaderRules - { - public static Microsoft.OpenApi.Validations.ValidationRule HeaderMismatchedDataType { get; } - } - [Microsoft.OpenApi.Validations.Rules.OpenApiRule] public static class OpenApiInfoRules { public static Microsoft.OpenApi.Validations.ValidationRule InfoRequiredFields { get; } @@ -1697,11 +1692,6 @@ namespace Microsoft.OpenApi.Validations.Rules public static Microsoft.OpenApi.Validations.ValidationRule LicenseRequiredFields { get; } } [Microsoft.OpenApi.Validations.Rules.OpenApiRule] - public static class OpenApiMediaTypeRules - { - public static Microsoft.OpenApi.Validations.ValidationRule MediaTypeMismatchedDataType { get; } - } - [Microsoft.OpenApi.Validations.Rules.OpenApiRule] public static class OpenApiOAuthFlowRules { public static Microsoft.OpenApi.Validations.ValidationRule OAuthFlowRequiredFields { get; } @@ -1709,7 +1699,6 @@ namespace Microsoft.OpenApi.Validations.Rules [Microsoft.OpenApi.Validations.Rules.OpenApiRule] public static class OpenApiParameterRules { - public static Microsoft.OpenApi.Validations.ValidationRule ParameterMismatchedDataType { get; } public static Microsoft.OpenApi.Validations.ValidationRule ParameterRequiredFields { get; } public static Microsoft.OpenApi.Validations.ValidationRule PathParameterShouldBeInThePath { get; } public static Microsoft.OpenApi.Validations.ValidationRule RequiredMustBeTrueWhenInIsPath { get; } @@ -1739,7 +1728,6 @@ namespace Microsoft.OpenApi.Validations.Rules [Microsoft.OpenApi.Validations.Rules.OpenApiRule] public static class OpenApiSchemaRules { - public static Microsoft.OpenApi.Validations.ValidationRule SchemaMismatchedDataType { get; } public static Microsoft.OpenApi.Validations.ValidationRule ValidateSchemaDiscriminator { get; } public static bool TraverseSchemaElements(string discriminatorName, System.Collections.Generic.IList childSchema) { } public static bool ValidateChildSchemaAgainstDiscriminator(Microsoft.OpenApi.Models.OpenApiSchema schema, string discriminatorName) { } diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs index bbc9dfe35..e8a66e351 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs @@ -40,15 +40,7 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - "#/example", - }); + result.Should().BeTrue(); } [Fact] @@ -107,21 +99,7 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - // #enum/0 is not an error since the spec allows - // representing an object using a string. - "#/examples/example1/value/y", - "#/examples/example1/value/z", - "#/examples/example2/value" - }); + result.Should().BeTrue(); } } } diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiMediaTypeValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiMediaTypeValidationTests.cs index 9f42cb21b..29bd199e1 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiMediaTypeValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiMediaTypeValidationTests.cs @@ -39,15 +39,7 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - "#/example", - }); + result.Should().BeTrue(); } [Fact] @@ -106,21 +98,7 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - // #enum/0 is not an error since the spec allows - // representing an object using a string. - "#/examples/example1/value/y", - "#/examples/example1/value/z", - "#/examples/example2/value" - }); + result.Should().BeTrue(); } } } diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs index beac66d74..b21ddb7eb 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs @@ -88,15 +88,7 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - "#/{parameter1}/example", - }); + result.Should().BeTrue(); } [Fact] @@ -158,21 +150,7 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - // #enum/0 is not an error since the spec allows - // representing an object using a string. - "#/{parameter1}/examples/example1/value/y", - "#/{parameter1}/examples/example1/value/z", - "#/{parameter1}/examples/example2/value" - }); + result.Should().BeTrue(); } [Fact] diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs index 5885377ed..f6b42c91d 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs @@ -39,15 +39,7 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - "#/default", - }); + result.Should().BeTrue(); } [Fact] @@ -72,15 +64,7 @@ public void ValidateExampleAndDefaultShouldNotHaveDataTypeMismatchForSimpleSchem var expectedWarnings = warnings.Select(e => e.Message).ToList(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - "#/example", - }); + result.Should().BeTrue(); } [Fact] @@ -122,21 +106,7 @@ public void ValidateEnumShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - // #enum/0 is not an error since the spec allows - // representing an object using a string. - "#/enum/1/y", - "#/enum/1/z", - "#/enum/2" - }); + result.Should().BeTrue(); } [Fact] @@ -212,19 +182,7 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForComplexSchema() bool result = !warnings.Any(); // Assert - result.Should().BeFalse(); - warnings.Select(e => e.Message).Should().BeEquivalentTo(new[] - { - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage, - RuleHelpers.DataTypeMismatchedErrorMessage - }); - warnings.Select(e => e.Pointer).Should().BeEquivalentTo(new[] - { - "#/default/property1/2", - "#/default/property2/0", - "#/default/property2/1/z" - }); + result.Should().BeTrue(); } [Fact] diff --git a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs index 15ef6b07f..6b4a920cf 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -55,8 +55,8 @@ public void RuleSetConstructorsReturnsTheCorrectRules() Assert.Empty(ruleSet_4.Rules); // Update the number if you add new default rule(s). - Assert.Equal(23, ruleSet_1.Rules.Count); - Assert.Equal(23, ruleSet_2.Rules.Count); + Assert.Equal(19, ruleSet_1.Rules.Count); + Assert.Equal(19, ruleSet_2.Rules.Count); Assert.Equal(3, ruleSet_3.Rules.Count); } From 3c5e279e220be3760569a5f7608f295403a66073 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 24 Oct 2024 10:33:52 +0300 Subject: [PATCH 3/7] Revert "remove depracated validation rule" This reverts commit 9bf3f0869166631840759c906abf5112c8505ae2. --- .../Validations/Rules/OpenApiHeaderRules.cs | 57 ++++ .../Rules/OpenApiMediaTypeRules.cs | 63 +++++ .../Rules/OpenApiParameterRules.cs | 39 +++ .../Validations/Rules/OpenApiSchemaRules.cs | 43 +++ .../Validations/Rules/RuleHelpers.cs | 249 ++++++++++++++++++ .../Validations/ValidationRuleSet.cs | 2 + 6 files changed, 453 insertions(+) create mode 100644 src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs create mode 100644 src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs new file mode 100644 index 000000000..4bc5aa94a --- /dev/null +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Validations.Rules +{ + /// + /// The validation rules for . + /// + //Removed from Default Rules as this is not a MUST in OpenAPI + [OpenApiRule] + public static class OpenApiHeaderRules + { + /// + /// Validate the data matches with the given data type. + /// + public static ValidationRule HeaderMismatchedDataType => + new(nameof(HeaderMismatchedDataType), + (context, header) => + { + // example + context.Enter("example"); + + if (header.Example != null) + { + RuleHelpers.ValidateDataTypeMismatch(context, + nameof(HeaderMismatchedDataType), header.Example, header.Schema); + } + + context.Exit(); + + // examples + context.Enter("examples"); + + if (header.Examples != null) + { + foreach (var key in header.Examples.Keys) + { + if (header.Examples[key] != null) + { + context.Enter(key); + context.Enter("value"); + RuleHelpers.ValidateDataTypeMismatch(context, + nameof(HeaderMismatchedDataType), header.Examples[key]?.Value, header.Schema); + context.Exit(); + context.Exit(); + } + } + } + + context.Exit(); + }); + + // add more rule. + } +} diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs new file mode 100644 index 000000000..7ac09cbbf --- /dev/null +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Validations.Rules +{ + /// + /// The validation rules for . + /// + /// + /// Removed this in v1.3 as a default rule as the OpenAPI specification does not require that example + /// values validate against the schema. Validating examples against the schema is particularly difficult + /// as it requires parsing of the example using the schema as a guide. This is not possible when the schema + /// is referenced. Even if we fix this issue, this rule should be treated as a warning, not an error + /// Future versions of the validator should make that distinction. + /// Future versions of the example parsers should not try an infer types. + /// Example validation should be done as a separate post reading step so all schemas can be fully available. + /// + [OpenApiRule] + public static class OpenApiMediaTypeRules + { + /// + /// Validate the data matches with the given data type. + /// + public static ValidationRule MediaTypeMismatchedDataType => + new(nameof(MediaTypeMismatchedDataType), + (context, mediaType) => + { + // example + context.Enter("example"); + + if (mediaType.Example != null) + { + RuleHelpers.ValidateDataTypeMismatch(context, nameof(MediaTypeMismatchedDataType), mediaType.Example, mediaType.Schema); + } + + context.Exit(); + + // enum + context.Enter("examples"); + + if (mediaType.Examples != null) + { + foreach (var key in mediaType.Examples.Keys) + { + if (mediaType.Examples[key] != null) + { + context.Enter(key); + context.Enter("value"); + RuleHelpers.ValidateDataTypeMismatch(context, nameof(MediaTypeMismatchedDataType), mediaType.Examples[key]?.Value, mediaType.Schema); + context.Exit(); + context.Exit(); + } + } + } + + context.Exit(); + }); + + // add more rule. + } +} diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs index 812bc7f12..c6ad7835d 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs @@ -58,6 +58,45 @@ public static class OpenApiParameterRules context.Exit(); }); + /// + /// Validate the data matches with the given data type. + /// + public static ValidationRule ParameterMismatchedDataType => + new(nameof(ParameterMismatchedDataType), + (context, parameter) => + { + // example + context.Enter("example"); + + if (parameter.Example != null) + { + RuleHelpers.ValidateDataTypeMismatch(context, nameof(ParameterMismatchedDataType), parameter.Example, parameter.Schema); + } + + context.Exit(); + + // examples + context.Enter("examples"); + + if (parameter.Examples != null) + { + foreach (var key in parameter.Examples.Keys) + { + if (parameter.Examples[key] != null) + { + context.Enter(key); + context.Enter("value"); + RuleHelpers.ValidateDataTypeMismatch(context, + nameof(ParameterMismatchedDataType), parameter.Examples[key]?.Value, parameter.Schema); + context.Exit(); + context.Exit(); + } + } + } + + context.Exit(); + }); + /// /// Validate that a path parameter should always appear in the path /// diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs index 054c79c6b..e768e8d42 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs @@ -13,6 +13,49 @@ namespace Microsoft.OpenApi.Validations.Rules [OpenApiRule] public static class OpenApiSchemaRules { + /// + /// Validate the data matches with the given data type. + /// + public static ValidationRule SchemaMismatchedDataType => + new(nameof(SchemaMismatchedDataType), + (context, schema) => + { + // default + context.Enter("default"); + + if (schema.Default != null) + { + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Default, schema); + } + + context.Exit(); + + // example + context.Enter("example"); + + if (schema.Example != null) + { + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Example, schema); + } + + context.Exit(); + + // enum + context.Enter("enum"); + + if (schema.Enum != null) + { + for (var i = 0; i < schema.Enum.Count; i++) + { + context.Enter(i.ToString()); + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Enum[i], schema); + context.Exit(); + } + } + + context.Exit(); + }); + /// /// Validates Schema Discriminator /// diff --git a/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs index f6891c043..9902360ec 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs @@ -1,10 +1,18 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; +using System.Text.Json; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; + namespace Microsoft.OpenApi.Validations.Rules { internal static class RuleHelpers { + internal const string DataTypeMismatchedErrorMessage = "Data and type mismatch found."; + /// /// Input string must be in the format of an email address /// @@ -32,5 +40,246 @@ public static bool IsEmailAddress(this string input) return true; } + + public static void ValidateDataTypeMismatch( + IValidationContext context, + string ruleName, + JsonNode value, + OpenApiSchema schema) + { + if (schema == null) + { + return; + } + + // convert value to JsonElement and access the ValueKind property to determine the type. + var jsonElement = JsonDocument.Parse(JsonSerializer.Serialize(value)).RootElement; + + var type = (string)schema.Type; + var format = schema.Format; + var nullable = schema.Nullable; + + // Before checking the type, check first if the schema allows null. + // If so and the data given is also null, this is allowed for any type. + if (nullable && jsonElement.ValueKind is JsonValueKind.Null) + { + return; + } + + if (type == "object") + { + // It is not against the spec to have a string representing an object value. + // To represent examples of media types that cannot naturally be represented in JSON or YAML, + // a string value can contain the example with escaping where necessary + if (jsonElement.ValueKind is JsonValueKind.String) + { + return; + } + + // If value is not a string and also not an object, there is a data mismatch. + if (value is not JsonObject anyObject) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + return; + } + + foreach (var kvp in anyObject) + { + var key = kvp.Key; + context.Enter(key); + + if (schema.Properties != null && + schema.Properties.TryGetValue(key, out var property)) + { + ValidateDataTypeMismatch(context, ruleName, anyObject[key], property); + } + else + { + ValidateDataTypeMismatch(context, ruleName, anyObject[key], schema.AdditionalProperties); + } + + context.Exit(); + } + + return; + } + + if (type == "array") + { + // It is not against the spec to have a string representing an array value. + // To represent examples of media types that cannot naturally be represented in JSON or YAML, + // a string value can contain the example with escaping where necessary + if (jsonElement.ValueKind is JsonValueKind.String) + { + return; + } + + // If value is not a string and also not an array, there is a data mismatch. + if (value is not JsonArray anyArray) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + return; + } + + for (var i = 0; i < anyArray.Count; i++) + { + context.Enter(i.ToString()); + + ValidateDataTypeMismatch(context, ruleName, anyArray[i], schema.Items); + + context.Exit(); + } + + return; + } + + if (type == "integer" && format == "int32") + { + if (jsonElement.ValueKind is not JsonValueKind.Number) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + } + + return; + } + + if (type == "integer" && format == "int64") + { + if (jsonElement.ValueKind is not JsonValueKind.Number) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + } + + return; + } + + if (type == "integer" && jsonElement.ValueKind is not JsonValueKind.Number) + { + if (jsonElement.ValueKind is not JsonValueKind.Number) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + } + + return; + } + + if (type == "number" && format == "float") + { + if (jsonElement.ValueKind is not JsonValueKind.Number) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + } + + return; + } + + if (type == "number" && format == "double") + { + if (jsonElement.ValueKind is not JsonValueKind.Number) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + } + + return; + } + + if (type == "number") + { + if (jsonElement.ValueKind is not JsonValueKind.Number) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + } + + return; + } + + if (type == "string" && format == "byte") + { + if (jsonElement.ValueKind is not JsonValueKind.String) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + } + + return; + } + + if (type == "string" && format == "date") + { + if (jsonElement.ValueKind is not JsonValueKind.String) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + } + + return; + } + + if (type == "string" && format == "date-time") + { + if (jsonElement.ValueKind is not JsonValueKind.String) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + } + + return; + } + + if (type == "string" && format == "password") + { + if (jsonElement.ValueKind is not JsonValueKind.String) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + } + + return; + } + + if (type == "string") + { + if (jsonElement.ValueKind is not JsonValueKind.String) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + } + + return; + } + + if (type == "boolean") + { + if (jsonElement.ValueKind is not JsonValueKind.True && jsonElement.ValueKind is not JsonValueKind.False) + { + context.CreateWarning( + ruleName, + DataTypeMismatchedErrorMessage); + } + + return; + } + } } } diff --git a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs index c818e0d6b..67b84f0be 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs @@ -329,11 +329,13 @@ internal static PropertyInfo[] GetValidationRuleTypes() ..typeof(OpenApiExternalDocsRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiInfoRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiLicenseRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiMediaTypeRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiOAuthFlowRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiServerRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiResponseRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiResponsesRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiSchemaRules).GetProperties(BindingFlags.Static | BindingFlags.Public), + ..typeof(OpenApiHeaderRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiTagRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiPathsRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiParameterRules).GetProperties(BindingFlags.Static | BindingFlags.Public), From 9c19f930e723a2f1b27b62748ad2548247fd52ee Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 24 Oct 2024 12:50:46 +0300 Subject: [PATCH 4/7] Isolate the data mismatch rule into a separate class to allow clients to opt in --- .../Validations/Rules/OpenApiHeaderRules.cs | 57 -------- .../Rules/OpenApiMediaTypeRules.cs | 63 --------- .../Rules/OpenApiNonDefaultRules.cs | 125 ++++++++++++++++++ .../Rules/OpenApiParameterRules.cs | 39 ------ .../Validations/Rules/OpenApiSchemaRules.cs | 43 ------ .../Validations/ValidationRuleSet.cs | 8 +- 6 files changed, 128 insertions(+), 207 deletions(-) delete mode 100644 src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs delete mode 100644 src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs create mode 100644 src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs deleted file mode 100644 index 4bc5aa94a..000000000 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiHeaderRules.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Validations.Rules -{ - /// - /// The validation rules for . - /// - //Removed from Default Rules as this is not a MUST in OpenAPI - [OpenApiRule] - public static class OpenApiHeaderRules - { - /// - /// Validate the data matches with the given data type. - /// - public static ValidationRule HeaderMismatchedDataType => - new(nameof(HeaderMismatchedDataType), - (context, header) => - { - // example - context.Enter("example"); - - if (header.Example != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, - nameof(HeaderMismatchedDataType), header.Example, header.Schema); - } - - context.Exit(); - - // examples - context.Enter("examples"); - - if (header.Examples != null) - { - foreach (var key in header.Examples.Keys) - { - if (header.Examples[key] != null) - { - context.Enter(key); - context.Enter("value"); - RuleHelpers.ValidateDataTypeMismatch(context, - nameof(HeaderMismatchedDataType), header.Examples[key]?.Value, header.Schema); - context.Exit(); - context.Exit(); - } - } - } - - context.Exit(); - }); - - // add more rule. - } -} diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs deleted file mode 100644 index 7ac09cbbf..000000000 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiMediaTypeRules.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Validations.Rules -{ - /// - /// The validation rules for . - /// - /// - /// Removed this in v1.3 as a default rule as the OpenAPI specification does not require that example - /// values validate against the schema. Validating examples against the schema is particularly difficult - /// as it requires parsing of the example using the schema as a guide. This is not possible when the schema - /// is referenced. Even if we fix this issue, this rule should be treated as a warning, not an error - /// Future versions of the validator should make that distinction. - /// Future versions of the example parsers should not try an infer types. - /// Example validation should be done as a separate post reading step so all schemas can be fully available. - /// - [OpenApiRule] - public static class OpenApiMediaTypeRules - { - /// - /// Validate the data matches with the given data type. - /// - public static ValidationRule MediaTypeMismatchedDataType => - new(nameof(MediaTypeMismatchedDataType), - (context, mediaType) => - { - // example - context.Enter("example"); - - if (mediaType.Example != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(MediaTypeMismatchedDataType), mediaType.Example, mediaType.Schema); - } - - context.Exit(); - - // enum - context.Enter("examples"); - - if (mediaType.Examples != null) - { - foreach (var key in mediaType.Examples.Keys) - { - if (mediaType.Examples[key] != null) - { - context.Enter(key); - context.Enter("value"); - RuleHelpers.ValidateDataTypeMismatch(context, nameof(MediaTypeMismatchedDataType), mediaType.Examples[key]?.Value, mediaType.Schema); - context.Exit(); - context.Exit(); - } - } - } - - context.Exit(); - }); - - // add more rule. - } -} diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs new file mode 100644 index 000000000..1edd130f1 --- /dev/null +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.Models; + +namespace Microsoft.OpenApi.Validations.Rules +{ + /// + /// Defines a non-default set of rules for validating examples in header, media type and parameter objects against the schema + /// + public static class OpenApiNonDefaultRules + { + /// + /// Validate the data matches with the given data type. + /// + public static ValidationRule HeaderMismatchedDataType => + new(nameof(HeaderMismatchedDataType), + (context, header) => + { + ValidateMismatchedDataType(context, nameof(HeaderMismatchedDataType), header.Example, header.Examples, header.Schema); + }); + + /// + /// Validate the data matches with the given data type. + /// + public static ValidationRule MediaTypeMismatchedDataType => + new(nameof(MediaTypeMismatchedDataType), + (context, mediaType) => + { + ValidateMismatchedDataType(context, nameof(MediaTypeMismatchedDataType), mediaType.Example, mediaType.Examples, mediaType.Schema); + }); + + /// + /// Validate the data matches with the given data type. + /// + public static ValidationRule ParameterMismatchedDataType => + new(nameof(ParameterMismatchedDataType), + (context, parameter) => + { + ValidateMismatchedDataType(context, nameof(ParameterMismatchedDataType), parameter.Example, parameter.Examples, parameter.Schema); + }); + + /// + /// Validate the data matches with the given data type. + /// + public static ValidationRule SchemaMismatchedDataType => + new(nameof(SchemaMismatchedDataType), + (context, schema) => + { + // default + context.Enter("default"); + + if (schema.Default != null) + { + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Default, schema); + } + + context.Exit(); + + // example + context.Enter("example"); + + if (schema.Example != null) + { + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Example, schema); + } + + context.Exit(); + + // enum + context.Enter("enum"); + + if (schema.Enum != null) + { + for (var i = 0; i < schema.Enum.Count; i++) + { + context.Enter(i.ToString()); + RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Enum[i], schema); + context.Exit(); + } + } + + context.Exit(); + }); + + private static void ValidateMismatchedDataType(IValidationContext context, + string ruleName, + JsonNode example, + IDictionary examples, + OpenApiSchema schema) + { + // example + context.Enter("example"); + + if (example != null) + { + RuleHelpers.ValidateDataTypeMismatch(context, ruleName, example, schema); + } + + context.Exit(); + + // enum + context.Enter("examples"); + + if (examples != null) + { + foreach (var key in examples.Keys) + { + if (examples[key] != null) + { + context.Enter(key); + context.Enter("value"); + RuleHelpers.ValidateDataTypeMismatch(context, ruleName, examples[key]?.Value, schema); + context.Exit(); + context.Exit(); + } + } + } + + context.Exit(); + } + } +} diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs index c6ad7835d..812bc7f12 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs @@ -58,45 +58,6 @@ public static class OpenApiParameterRules context.Exit(); }); - /// - /// Validate the data matches with the given data type. - /// - public static ValidationRule ParameterMismatchedDataType => - new(nameof(ParameterMismatchedDataType), - (context, parameter) => - { - // example - context.Enter("example"); - - if (parameter.Example != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(ParameterMismatchedDataType), parameter.Example, parameter.Schema); - } - - context.Exit(); - - // examples - context.Enter("examples"); - - if (parameter.Examples != null) - { - foreach (var key in parameter.Examples.Keys) - { - if (parameter.Examples[key] != null) - { - context.Enter(key); - context.Enter("value"); - RuleHelpers.ValidateDataTypeMismatch(context, - nameof(ParameterMismatchedDataType), parameter.Examples[key]?.Value, parameter.Schema); - context.Exit(); - context.Exit(); - } - } - } - - context.Exit(); - }); - /// /// Validate that a path parameter should always appear in the path /// diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs index e768e8d42..054c79c6b 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs @@ -13,49 +13,6 @@ namespace Microsoft.OpenApi.Validations.Rules [OpenApiRule] public static class OpenApiSchemaRules { - /// - /// Validate the data matches with the given data type. - /// - public static ValidationRule SchemaMismatchedDataType => - new(nameof(SchemaMismatchedDataType), - (context, schema) => - { - // default - context.Enter("default"); - - if (schema.Default != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Default, schema); - } - - context.Exit(); - - // example - context.Enter("example"); - - if (schema.Example != null) - { - RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Example, schema); - } - - context.Exit(); - - // enum - context.Enter("enum"); - - if (schema.Enum != null) - { - for (var i = 0; i < schema.Enum.Count; i++) - { - context.Enter(i.ToString()); - RuleHelpers.ValidateDataTypeMismatch(context, nameof(SchemaMismatchedDataType), schema.Enum[i], schema); - context.Exit(); - } - } - - context.Exit(); - }); - /// /// Validates Schema Discriminator /// diff --git a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs index 67b84f0be..6544b22b9 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs @@ -1,4 +1,4 @@ - + // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. @@ -329,17 +329,15 @@ internal static PropertyInfo[] GetValidationRuleTypes() ..typeof(OpenApiExternalDocsRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiInfoRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiLicenseRules).GetProperties(BindingFlags.Static | BindingFlags.Public), - ..typeof(OpenApiMediaTypeRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiOAuthFlowRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiServerRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiResponseRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiResponsesRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiSchemaRules).GetProperties(BindingFlags.Static | BindingFlags.Public), - ..typeof(OpenApiHeaderRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiTagRules).GetProperties(BindingFlags.Static | BindingFlags.Public), ..typeof(OpenApiPathsRules).GetProperties(BindingFlags.Static | BindingFlags.Public), - ..typeof(OpenApiParameterRules).GetProperties(BindingFlags.Static | BindingFlags.Public), - ]; + ..typeof(OpenApiParameterRules).GetProperties(BindingFlags.Static | BindingFlags.Public) + ]; } } } From 2d8640a76ca79104516c7ca936e073217624e981 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 24 Oct 2024 12:51:10 +0300 Subject: [PATCH 5/7] code cleanup --- src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs | 2 -- src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs | 4 +--- .../PublicApi/PublicApi.approved.txt | 7 +++++++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs index 9902360ec..097d61ace 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/RuleHelpers.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; using System.Text.Json; using System.Text.Json.Nodes; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Validations.Rules diff --git a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs index 6544b22b9..3e38d65b2 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs @@ -1,5 +1,4 @@ - -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -9,7 +8,6 @@ using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Properties; using Microsoft.OpenApi.Validations.Rules; -using System.Data; namespace Microsoft.OpenApi.Validations { diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index ec2f9a6dd..255717a65 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1691,6 +1691,13 @@ namespace Microsoft.OpenApi.Validations.Rules { public static Microsoft.OpenApi.Validations.ValidationRule LicenseRequiredFields { get; } } + public static class OpenApiNonDefaultRules + { + public static Microsoft.OpenApi.Validations.ValidationRule HeaderMismatchedDataType { get; } + public static Microsoft.OpenApi.Validations.ValidationRule MediaTypeMismatchedDataType { get; } + public static Microsoft.OpenApi.Validations.ValidationRule ParameterMismatchedDataType { get; } + public static Microsoft.OpenApi.Validations.ValidationRule SchemaMismatchedDataType { get; } + } [Microsoft.OpenApi.Validations.Rules.OpenApiRule] public static class OpenApiOAuthFlowRules { From ae0c5a061899cff25b5bffb1ba3fcc38f5f29953 Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 24 Oct 2024 15:46:24 +0300 Subject: [PATCH 6/7] Add test case for opting into using the data mismatch validation rule --- .../Validations/OpenApiHeaderValidationTests.cs | 7 +++++-- .../Validations/OpenApiParameterValidationTests.cs | 7 +++++-- .../Validations/OpenApiSchemaValidationTests.cs | 6 ++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs index e8a66e351..356a233a1 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs @@ -31,7 +31,10 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() }; // Act - var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); + var defaultRuleSet = ValidationRuleSet.GetDefaultRuleSet(); + defaultRuleSet.Add(typeof(OpenApiHeader), OpenApiNonDefaultRules.HeaderMismatchedDataType); + var validator = new OpenApiValidator(defaultRuleSet); + var walker = new OpenApiWalker(validator); walker.Walk(header); @@ -40,7 +43,7 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeTrue(); + result.Should().BeFalse(); } [Fact] diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs index b21ddb7eb..ef25808d2 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs @@ -141,7 +141,10 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() }; // Act - var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); + var defaultRuleSet = ValidationRuleSet.GetDefaultRuleSet(); + defaultRuleSet.Add(typeof(OpenApiParameter), OpenApiNonDefaultRules.ParameterMismatchedDataType); + + var validator = new OpenApiValidator(defaultRuleSet); validator.Enter("{parameter1}"); var walker = new OpenApiWalker(validator); walker.Walk(parameter); @@ -150,7 +153,7 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() var result = !warnings.Any(); // Assert - result.Should().BeTrue(); + result.Should().BeFalse(); } [Fact] diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs index f6b42c91d..5f4ba5d6b 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs @@ -174,7 +174,9 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForComplexSchema() }; // Act - var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); + var defaultRuleSet = ValidationRuleSet.GetDefaultRuleSet(); + defaultRuleSet.Add(typeof(OpenApiSchema), OpenApiNonDefaultRules.SchemaMismatchedDataType); + var validator = new OpenApiValidator(defaultRuleSet); var walker = new OpenApiWalker(validator); walker.Walk(schema); @@ -182,7 +184,7 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForComplexSchema() bool result = !warnings.Any(); // Assert - result.Should().BeTrue(); + result.Should().BeFalse(); } [Fact] From a478bd232d23f8e9a9b414df5726ad9c414f592c Mon Sep 17 00:00:00 2001 From: Maggiekimani1 Date: Thu, 24 Oct 2024 15:57:27 +0300 Subject: [PATCH 7/7] Use Where for sequence filtering --- .../Validations/Rules/OpenApiNonDefaultRules.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs index 1edd130f1..f02be33ee 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiNonDefaultRules.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System.Collections.Generic; +using System.Linq; using System.Text.Json.Nodes; using Microsoft.OpenApi.Models; @@ -106,16 +107,13 @@ private static void ValidateMismatchedDataType(IValidationContext context, if (examples != null) { - foreach (var key in examples.Keys) + foreach (var key in examples.Keys.Where(k => examples[k] != null)) { - if (examples[key] != null) - { - context.Enter(key); - context.Enter("value"); - RuleHelpers.ValidateDataTypeMismatch(context, ruleName, examples[key]?.Value, schema); - context.Exit(); - context.Exit(); - } + context.Enter(key); + context.Enter("value"); + RuleHelpers.ValidateDataTypeMismatch(context, ruleName, examples[key]?.Value, schema); + context.Exit(); + context.Exit(); } }