diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs
index eff687253..b062741b2 100644
--- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.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.Collections.Generic;
@@ -17,7 +17,8 @@ namespace Microsoft.OpenApi
public static class OpenApiSchemaRules
{
///
- /// Validates Schema Property has value
+ /// Validates that each property defined in a schema's properties dictionary has a non-null schema value.
+ /// When a property entry value is null, reports a validation error with a JSON pointer anchored under the properties segment.
///
public static ValidationRule ValidateSchemaPropertyHasValue =>
new(nameof(ValidateSchemaPropertyHasValue),
@@ -28,6 +29,7 @@ public static class OpenApiSchemaRules
foreach (var property in schema.Properties
.Where(entry => entry.Value is null))
{
+ context.Enter(OpenApiConstants.Properties);
context.Enter(property.Key);
context.CreateError(nameof(ValidateSchemaPropertyHasValue),
string.Format(SRResource.Validation_SchemaPropertyObjectRequired,
@@ -35,6 +37,7 @@ public static class OpenApiSchemaRules
? schemaReference.Reference.Id
: string.Empty, property.Key));
context.Exit();
+ context.Exit();
}
}
});
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs
index 7f5cd47e2..c0ecc7888 100644
--- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs
@@ -1,9 +1,10 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net.Http;
using System.Text.Json.Nodes;
using Xunit;
@@ -209,7 +210,7 @@ public void ValidateNullProperty()
Assert.NotEmpty(validator.Errors);
Assert.Equivalent(new List
{
- new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateSchemaPropertyHasValue),"#/schemas/schema1/property1",
+ new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateSchemaPropertyHasValue),"#/schemas/schema1/properties/property1",
string.Format(SRResource.Validation_SchemaPropertyObjectRequired,
string.Empty, "property1"))
}, validator.Errors);
@@ -557,6 +558,146 @@ public void ValidateSchemaDiscriminatorChecksOneOfAnyOfAndAllOf()
Assert.Empty(validator.Errors);
}
+ [Fact]
+ public void ValidateSchemaPropertyHasValueShouldReportErrorWithPropertiesPath()
+ {
+ // Arrange
+ var schema = new OpenApiSchema
+ {
+ Type = JsonSchemaType.Object,
+ Properties = new Dictionary
+ {
+ ["id"] = new OpenApiSchema { Type = JsonSchemaType.Integer },
+ ["name"] = null!
+ }
+ };
+
+ // Act
+ var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
+ var walker = new OpenApiWalker(validator);
+ walker.Walk((IOpenApiSchema)schema);
+
+ // Assert
+ var error = Assert.Single(validator.Errors);
+ Assert.Equal(nameof(OpenApiSchemaRules.ValidateSchemaPropertyHasValue), error.RuleName);
+ Assert.Equal("#/properties/name", error.Pointer);
+ }
+
+ [Fact]
+ public void ValidateSchemaPropertyHasValueWithMultipleNullPropertiesShouldIncludePropertiesSegmentForEach()
+ {
+ // Arrange
+ var schema = new OpenApiSchema
+ {
+ Type = JsonSchemaType.Object,
+ Properties = new Dictionary
+ {
+ ["first"] = null!,
+ ["valid"] = new OpenApiSchema { Type = JsonSchemaType.String },
+ ["second"] = null!
+ }
+ };
+
+ // Act
+ var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
+ var walker = new OpenApiWalker(validator);
+ walker.Walk((IOpenApiSchema)schema);
+
+ // Assert
+ var errors = validator.Errors.ToList();
+ Assert.Equal(2, errors.Count);
+ Assert.All(errors, e => Assert.Equal(nameof(OpenApiSchemaRules.ValidateSchemaPropertyHasValue), e.RuleName));
+ Assert.Contains(errors, e => e.Pointer == "#/properties/first");
+ Assert.Contains(errors, e => e.Pointer == "#/properties/second");
+ }
+
+ [Fact]
+ public void ValidateSchemaPropertyHasValueInDocumentComponentsShouldReportFullPointer()
+ {
+ // Arrange
+ var document = new OpenApiDocument
+ {
+ Info = new OpenApiInfo { Title = "Test API", Version = "1.0.0" },
+ Components = new OpenApiComponents
+ {
+ Schemas = new Dictionary
+ {
+ ["User"] = new OpenApiSchema
+ {
+ Type = JsonSchemaType.Object,
+ Properties = new Dictionary
+ {
+ ["nullProp"] = null!
+ }
+ }
+ }
+ }
+ };
+
+ // Act
+ var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
+ var walker = new OpenApiWalker(validator);
+ walker.Walk(document);
+
+ // Assert
+ var error = Assert.Single(validator.Errors);
+ Assert.Equal(nameof(OpenApiSchemaRules.ValidateSchemaPropertyHasValue), error.RuleName);
+ Assert.Equal("#/components/schemas/User/properties/nullProp", error.Pointer);
+ }
+
+ [Fact]
+ public void ValidateSchemaPropertyHasValueInOperationResponseShouldReportFullPointer()
+ {
+ // Arrange
+ var document = new OpenApiDocument
+ {
+ Info = new OpenApiInfo { Title = "Test API", Version = "1.0.0" },
+ Paths = new OpenApiPaths
+ {
+ ["/items"] = new OpenApiPathItem
+ {
+ Operations = new Dictionary
+ {
+ [HttpMethod.Get] = new OpenApiOperation
+ {
+ Responses = new OpenApiResponses
+ {
+ ["200"] = new OpenApiResponse
+ {
+ Description = "OK",
+ Content = new Dictionary
+ {
+ ["application/json"] = new OpenApiMediaType
+ {
+ Schema = new OpenApiSchema
+ {
+ Type = JsonSchemaType.Object,
+ Properties = new Dictionary
+ {
+ ["missingSchema"] = null!
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+
+ // Act
+ var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
+ var walker = new OpenApiWalker(validator);
+ walker.Walk(document);
+
+ // Assert
+ var error = Assert.Single(validator.Errors);
+ Assert.Equal(nameof(OpenApiSchemaRules.ValidateSchemaPropertyHasValue), error.RuleName);
+ Assert.Equal("#/paths/~1items/get/responses/200/content/application~1json/schema/properties/missingSchema", error.Pointer);
+ }
+
private static OpenApiSchema CreateSchemaChain(int depth, string discriminatorName)
{
var root = new OpenApiSchema();