diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 534c8ff8b..1f73031b8 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "3.1.3"
+ ".": "3.2.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 44e6eba95..a863b9118 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## [3.2.0](https://github.com/microsoft/OpenAPI.NET/compare/v3.1.3...v3.2.0) (2026-01-19)
+
+
+### Features
+
+* hidi validate command now logs warnings ([76a3c0f](https://github.com/microsoft/OpenAPI.NET/commit/76a3c0fe33a6c953263d9d91669b2f1bab562a79))
+* hidi validate command now logs warnings ([62e7d56](https://github.com/microsoft/OpenAPI.NET/commit/62e7d56ac0863875999240d68a2766d2cc2d594c))
+
+
+### Bug Fixes
+
+* discriminator property validation fails any/allOf cases when it shouldn't ([fb6cecc](https://github.com/microsoft/OpenAPI.NET/commit/fb6cecccafd5713bc1eb22e0cf07619cf495ebb5))
+* discriminator property validation fails any/allOf cases when it shouldn't ([a8fb81c](https://github.com/microsoft/OpenAPI.NET/commit/a8fb81cf9524a3f2f721aa808db244434e1cd177))
+
## [3.1.3](https://github.com/microsoft/OpenAPI.NET/compare/v3.1.2...v3.1.3) (2026-01-16)
diff --git a/Directory.Build.props b/Directory.Build.props
index b0681cf72..028c4e6c5 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -12,7 +12,7 @@
https://github.com/Microsoft/OpenAPI.NET
© Microsoft Corporation. All rights reserved.
OpenAPI .NET
- 3.1.3
+ 3.2.0
diff --git a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs
index 48f1d1c3e..a494b4f2e 100644
--- a/src/Microsoft.OpenApi.Hidi/OpenApiService.cs
+++ b/src/Microsoft.OpenApi.Hidi/OpenApiService.cs
@@ -398,6 +398,7 @@ private static async Task ParseOpenApiAsync(string openApiFile, bool
logger.LogTrace("{Timestamp}ms: Completed parsing.", stopwatch.ElapsedMilliseconds);
LogErrors(logger, result);
+ LogWarnings(logger, result);
stopwatch.Stop();
}
@@ -652,7 +653,7 @@ private static string GetInputPathExtension(string? openapi = null, string? csdl
private static void LogErrors(ILogger logger, ReadResult result)
{
var context = result.Diagnostic;
- if (context is not null && context.Errors.Count != 0)
+ if (context is { Errors.Count: > 0 })
{
using (logger.BeginScope("Detected errors"))
{
@@ -664,6 +665,21 @@ private static void LogErrors(ILogger logger, ReadResult result)
}
}
+ private static void LogWarnings(ILogger logger, ReadResult result)
+ {
+ var context = result.Diagnostic;
+ if (context is { Warnings.Count: > 0 })
+ {
+ using (logger.BeginScope("Detected warnings"))
+ {
+ foreach (var warning in context.Warnings)
+ {
+ logger.LogWarning("Detected warning during parsing: {Warning}", warning.ToString());
+ }
+ }
+ }
+ }
+
internal static void WriteTreeDocumentAsMarkdown(string openapiUrl, OpenApiDocument document, StreamWriter writer)
{
var rootNode = OpenApiUrlTreeNode.Create(document, "main");
diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiReadOnlyExtensible.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiReadOnlyExtensible.cs
index fac742d7d..c059842c0 100644
--- a/src/Microsoft.OpenApi/Interfaces/IOpenApiReadOnlyExtensible.cs
+++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiReadOnlyExtensible.cs
@@ -3,7 +3,7 @@
namespace Microsoft.OpenApi;
///
-/// Represents an Extensible Open API element elements can be rad from.
+/// Represents an Extensible Open API element elements can be read from.
///
public interface IOpenApiReadOnlyExtensible
{
diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs
index 70d558a13..4f2a122a9 100644
--- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs
@@ -5,6 +5,8 @@
namespace Microsoft.OpenApi
{
+ using System;
+ using System.ComponentModel;
using System.Linq;
///
@@ -48,6 +50,7 @@ public static class OpenApiSchemaRules
{
var discriminatorName = schema.Discriminator?.PropertyName;
+#pragma warning disable CS0618 // Type or member is obsolete
if (!ValidateChildSchemaAgainstDiscriminator(schema, discriminatorName))
{
context.Enter("discriminator");
@@ -56,6 +59,7 @@ public static class OpenApiSchemaRules
schema is OpenApiSchemaReference { Reference: not null} schemaReference ? schemaReference.Reference.Id : string.Empty, discriminatorName));
context.Exit();
}
+#pragma warning restore CS0618 // Type or member is obsolete
}
});
@@ -65,6 +69,8 @@ public static class OpenApiSchemaRules
/// The parent schema.
/// Adds support for polymorphism. The discriminator is an object name that is used to differentiate
/// between other schemas which may satisfy the payload description.
+ [Obsolete("This method will be made private in future versions.")]
+ [Browsable(false)]
public static bool ValidateChildSchemaAgainstDiscriminator(IOpenApiSchema schema, string? discriminatorName)
{
if (discriminatorName is not null)
@@ -72,15 +78,15 @@ public static bool ValidateChildSchemaAgainstDiscriminator(IOpenApiSchema schema
if (schema.Required is null || !schema.Required.Contains(discriminatorName))
{
// recursively check nested schema.OneOf, schema.AnyOf or schema.AllOf and their required fields for the discriminator
- if (schema.OneOf?.Count != 0)
+ if (schema.OneOf is { Count: > 0})
{
return TraverseSchemaElements(discriminatorName, schema.OneOf);
}
- if (schema.AnyOf?.Count != 0)
+ if (schema.AnyOf is { Count: > 0})
{
return TraverseSchemaElements(discriminatorName, schema.AnyOf);
}
- if (schema.AllOf?.Count != 0)
+ if (schema.AllOf is { Count: > 0})
{
return TraverseSchemaElements(discriminatorName, schema.AllOf);
}
@@ -102,25 +108,26 @@ public static bool ValidateChildSchemaAgainstDiscriminator(IOpenApiSchema schema
/// between other schemas which may satisfy the payload description.
/// The child schema.
///
+ [Obsolete("This method will be made private in future versions.")]
+ [Browsable(false)]
public static bool TraverseSchemaElements(string discriminatorName, IList? childSchema)
{
- if (childSchema is not null)
+ if (childSchema is null)
{
- foreach (var childItem in childSchema)
+ return false;
+ }
+ foreach (var childItem in childSchema)
+ {
+ if ((!childItem.Properties?.ContainsKey(discriminatorName) ?? false) &&
+ (!childItem.Required?.Contains(discriminatorName) ?? false))
{
- if ((!childItem.Properties?.ContainsKey(discriminatorName) ?? false) &&
- (!childItem.Required?.Contains(discriminatorName) ?? false))
- {
- return ValidateChildSchemaAgainstDiscriminator(childItem, discriminatorName);
- }
- else
- {
- return true;
- }
+ return ValidateChildSchemaAgainstDiscriminator(childItem, discriminatorName);
}
- return false;
- }
-
+ else
+ {
+ return true;
+ }
+ }
return false;
}
}
diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
index 1446cb8d7..6a8ad3955 100644
--- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
+++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
@@ -1352,7 +1352,11 @@ namespace Microsoft.OpenApi
{
public static Microsoft.OpenApi.ValidationRule ValidateSchemaDiscriminator { get; }
public static Microsoft.OpenApi.ValidationRule ValidateSchemaPropertyHasValue { get; }
+ [System.ComponentModel.Browsable(false)]
+ [System.Obsolete("This method will be made private in future versions.")]
public static bool TraverseSchemaElements(string discriminatorName, System.Collections.Generic.IList? childSchema) { }
+ [System.ComponentModel.Browsable(false)]
+ [System.Obsolete("This method will be made private in future versions.")]
public static bool ValidateChildSchemaAgainstDiscriminator(Microsoft.OpenApi.IOpenApiSchema schema, string? discriminatorName) { }
}
public class OpenApiSecurityRequirement : System.Collections.Generic.Dictionary>, Microsoft.OpenApi.IOpenApiElement, Microsoft.OpenApi.IOpenApiSerializable
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs
index f22806825..fdf36e0b4 100644
--- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs
@@ -246,7 +246,7 @@ public void ValidateSchemaRequiredFieldListMustContainThePropertySpecifiedInTheD
}
[Fact]
- public void ValidateOneOfSchemaPropertyNameContainsPropertySpecifiedInTheDiscriminator()
+ public void ValidateOneOfSchemaPropertyNameContainsPropertySpecifiedInTheDiscriminatorOneOf()
{
// Arrange
var components = new OpenApiComponents
@@ -293,5 +293,102 @@ public void ValidateOneOfSchemaPropertyNameContainsPropertySpecifiedInTheDiscrim
//Assert
Assert.Empty(errors);
}
+
+ [Fact]
+ public void ValidateOneOfSchemaPropertyNameContainsPropertySpecifiedInTheDiscriminatorAnyOf()
+ {
+ // Arrange
+ var components = new OpenApiComponents
+ {
+ Schemas = new Dictionary
+ {
+ {
+ "Person",
+ new OpenApiSchema
+ {
+ Type = JsonSchemaType.Array,
+ Discriminator = new()
+ {
+ PropertyName = "type"
+ },
+ AnyOf =
+ [
+ new OpenApiSchema()
+ {
+ Properties = new Dictionary
+ {
+ {
+ "type",
+ new OpenApiSchema
+ {
+ Type = JsonSchemaType.Array
+ }
+ }
+ },
+ }
+ ],
+ }
+ }
+ }
+ };
+
+ // Act
+ var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
+ var walker = new OpenApiWalker(validator);
+ walker.Walk(components);
+
+ var errors = validator.Errors;
+
+ //Assert
+ Assert.Empty(errors);
+ }
+ [Fact]
+ public void ValidateOneOfSchemaPropertyNameContainsPropertySpecifiedInTheDiscriminatorAllOf()
+ {
+ // Arrange
+ var components = new OpenApiComponents
+ {
+ Schemas = new Dictionary
+ {
+ {
+ "Person",
+ new OpenApiSchema
+ {
+ Type = JsonSchemaType.Array,
+ Discriminator = new()
+ {
+ PropertyName = "type"
+ },
+ AllOf =
+ [
+ new OpenApiSchema()
+ {
+ Properties = new Dictionary
+ {
+ {
+ "type",
+ new OpenApiSchema
+ {
+ Type = JsonSchemaType.Array
+ }
+ }
+ },
+ }
+ ],
+ }
+ }
+ }
+ };
+
+ // Act
+ var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
+ var walker = new OpenApiWalker(validator);
+ walker.Walk(components);
+
+ var errors = validator.Errors;
+
+ //Assert
+ Assert.Empty(errors);
+ }
}
}