From d2a6719fedf7e9f0ea6ea4484b723a7d39d41424 Mon Sep 17 00:00:00 2001 From: Steven Yeh Date: Fri, 16 Jan 2026 19:32:14 -0600 Subject: [PATCH 1/4] Fix Typo in IOpenApiReadOnlyExtensible.cs --- src/Microsoft.OpenApi/Interfaces/IOpenApiReadOnlyExtensible.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 { From a8fb81cf9524a3f2f721aa808db244434e1cd177 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 19 Jan 2026 11:15:25 -0500 Subject: [PATCH 2/4] fix: discriminator property validation fails any/allOf cases when it shouldn't Signed-off-by: Vincent Biret --- .../Validations/Rules/OpenApiSchemaRules.cs | 41 ++++---- .../PublicApi/PublicApi.approved.txt | 4 + .../OpenApiSchemaValidationTests.cs | 99 ++++++++++++++++++- 3 files changed, 126 insertions(+), 18 deletions(-) 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); + } } } From 62e7d56ac0863875999240d68a2766d2cc2d594c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 19 Jan 2026 13:13:05 -0500 Subject: [PATCH 3/4] feat: hidi validate command now logs warnings Signed-off-by: Vincent Biret --- src/Microsoft.OpenApi.Hidi/OpenApiService.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) 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"); From b0dbb301261a50a0ebe8e47b7680739cd6d024f4 Mon Sep 17 00:00:00 2001 From: "release-please-token-provider[bot]" <225477224+release-please-token-provider[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 19:00:33 +0000 Subject: [PATCH 4/4] chore(main): release 3.2.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 14 ++++++++++++++ Directory.Build.props | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) 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