From 5c9817c92e77aec5b17cb983a2a2931cf601a30f Mon Sep 17 00:00:00 2001 From: Ciaran Liedeman Date: Mon, 18 Aug 2025 22:02:10 +0200 Subject: [PATCH 1/2] fix: Validate schema property is not null --- .../Properties/SRResource.Designer.cs | 11 +++++- .../Properties/SRResource.resx | 3 ++ .../Validations/Rules/OpenApiSchemaRules.cs | 25 ++++++++++++ .../OpenApiSchemaValidationTests.cs | 38 +++++++++++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs b/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs index bd42672a5..eb88eab6f 100644 --- a/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs +++ b/src/Microsoft.OpenApi/Properties/SRResource.Designer.cs @@ -383,7 +383,16 @@ internal static string Validation_RuleAddTwice { return ResourceManager.GetString("Validation_RuleAddTwice", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Schema {0} property {1} is null.. + /// + internal static string Validation_SchemaPropertyObjectRequired { + get { + return ResourceManager.GetString("Validation_SchemaPropertyObjectRequired", resourceCulture); + } + } + /// /// Looks up a localized string similar to The schema reference '{0}' does not point to an existing schema.. /// diff --git a/src/Microsoft.OpenApi/Properties/SRResource.resx b/src/Microsoft.OpenApi/Properties/SRResource.resx index b758ff89b..c2e953ec4 100644 --- a/src/Microsoft.OpenApi/Properties/SRResource.resx +++ b/src/Microsoft.OpenApi/Properties/SRResource.resx @@ -219,6 +219,9 @@ Schema {0} must contain property specified in the discriminator {1} in the required field list. + + Schema {0} property {1} is null. + The string '{0}' MUST be in the format of an email address. diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs index 826eaa031..81a4ab88f 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs @@ -5,12 +5,37 @@ namespace Microsoft.OpenApi { + using System.Linq; + /// /// The validation rules for . /// [OpenApiRule] public static class OpenApiSchemaRules { + /// + /// Validates Schema Property has value + /// + public static ValidationRule ValidateSchemaPropertyHasValue => + new(nameof(ValidateSchemaPropertyHasValue), + (context, schema) => + { + if (schema.Properties is not null) + { + foreach (var property in schema.Properties + .Where(entry => entry.Value is null)) + { + context.Enter(property.Key); + context.CreateError(nameof(ValidateSchemaPropertyHasValue), + string.Format(SRResource.Validation_SchemaPropertyObjectRequired, + schema is OpenApiSchemaReference { Reference: not null } schemaReference + ? schemaReference.Reference.Id + : string.Empty, property.Key)); + context.Exit(); + } + } + }); + /// /// Validates Schema Discriminator /// diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs index 028913e37..f22806825 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs @@ -174,6 +174,44 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForComplexSchema() // Assert Assert.NotEmpty(validator.Warnings); } + + [Fact] + public void ValidateNullProperty() + { + // Arrange + var components = new OpenApiComponents + { + Schemas = new Dictionary + { + { + "schema1", + new OpenApiSchema + { + Properties = new Dictionary + { + ["property1"] = null, + } + } + } + } + }; + + // Act + var defaultRuleSet = ValidationRuleSet.GetDefaultRuleSet(); + defaultRuleSet.Add(typeof(IOpenApiSchema), OpenApiNonDefaultRules.SchemaMismatchedDataType); + var validator = new OpenApiValidator(defaultRuleSet); + var walker = new OpenApiWalker(validator); + walker.Walk(components); + + // Assert + Assert.NotEmpty(validator.Errors); + Assert.Equivalent(new List + { + new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateSchemaPropertyHasValue),"#/schemas/schema1/property1", + string.Format(SRResource.Validation_SchemaPropertyObjectRequired, + string.Empty, "property1")) + }, validator.Errors); + } [Fact] public void ValidateSchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator() From e4ea7f08b419f182f4efd40d8430422e9f74fc21 Mon Sep 17 00:00:00 2001 From: Ciaran Liedeman Date: Tue, 19 Aug 2025 22:10:10 +0200 Subject: [PATCH 2/2] fixed tests --- test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt | 1 + .../Validations/ValidationRuleSetTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index f8d83d291..5733eb735 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1248,6 +1248,7 @@ namespace Microsoft.OpenApi public static class OpenApiSchemaRules { public static Microsoft.OpenApi.ValidationRule ValidateSchemaDiscriminator { get; } + public static Microsoft.OpenApi.ValidationRule ValidateSchemaPropertyHasValue { get; } public static bool TraverseSchemaElements(string discriminatorName, System.Collections.Generic.IList? childSchema) { } public static bool ValidateChildSchemaAgainstDiscriminator(Microsoft.OpenApi.IOpenApiSchema schema, string? discriminatorName) { } } diff --git a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs index e1414232e..50cf77a1f 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs @@ -53,8 +53,8 @@ public void RuleSetConstructorsReturnsTheCorrectRules() Assert.Empty(ruleSet_4.Rules); // Update the number if you add new default rule(s). - Assert.Equal(20, ruleSet_1.Rules.Count); - Assert.Equal(20, ruleSet_2.Rules.Count); + Assert.Equal(21, ruleSet_1.Rules.Count); + Assert.Equal(21, ruleSet_2.Rules.Count); Assert.Equal(3, ruleSet_3.Rules.Count); }