From 59718f9c120bc96e6167ff8cfa0ef52809e023eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 21:24:38 +0000 Subject: [PATCH 01/39] chore(deps): bump dotnet-sdk from 8.0.418 to 8.0.419 Bumps [dotnet-sdk](https://github.com/dotnet/sdk) from 8.0.418 to 8.0.419. - [Release notes](https://github.com/dotnet/sdk/releases) - [Commits](https://github.com/dotnet/sdk/commits) --- updated-dependencies: - dependency-name: dotnet-sdk dependency-version: 8.0.419 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 459fe3e6e..d72c3f669 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0.418" + "version": "8.0.419" } } \ No newline at end of file From 937f47ed6e2c31dae680555fcf67f884f7a8c5e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 21:26:27 +0000 Subject: [PATCH 02/39] Bump the microsoftextensions group with 6 updates Bumps Microsoft.Extensions.DependencyInjection from 10.0.3 to 10.0.4 Bumps Microsoft.Extensions.Logging from 10.0.3 to 10.0.4 Bumps Microsoft.Extensions.Logging.Abstractions from 10.0.3 to 10.0.4 Bumps Microsoft.Extensions.Logging.Console from 10.0.3 to 10.0.4 Bumps Microsoft.Extensions.Logging.Debug from 10.0.3 to 10.0.4 Bumps System.Text.Json from 10.0.3 to 10.0.4 --- updated-dependencies: - dependency-name: Microsoft.Extensions.DependencyInjection dependency-version: 10.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging dependency-version: 10.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging dependency-version: 10.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging.Abstractions dependency-version: 10.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging.Console dependency-version: 10.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: System.Text.Json dependency-version: 10.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging.Console dependency-version: 10.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging.Debug dependency-version: 10.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging.Debug dependency-version: 10.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions ... Signed-off-by: dependabot[bot] --- performance/resultsComparer/resultsComparer.csproj | 10 +++++----- .../Microsoft.OpenApi.Hidi.csproj | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/performance/resultsComparer/resultsComparer.csproj b/performance/resultsComparer/resultsComparer.csproj index 892a4b627..8b701c7bd 100644 --- a/performance/resultsComparer/resultsComparer.csproj +++ b/performance/resultsComparer/resultsComparer.csproj @@ -8,12 +8,12 @@ - - - - + + + + - + diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index 475f4ce67..00ed9dcac 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -29,10 +29,10 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all From fdf8d12c1fa3b7fc407a26505d346e61b14a6285 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 21:29:27 +0000 Subject: [PATCH 03/39] Bump Microsoft.SourceLink.GitHub from 10.0.103 to 10.0.200 --- updated-dependencies: - dependency-name: Microsoft.SourceLink.GitHub dependency-version: 10.0.200 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 9129d4c5e..022908802 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -19,6 +19,6 @@ true - + \ No newline at end of file From 24a002e0feef8033a21ad999303b30bcd8697263 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 11 Mar 2026 09:08:12 -0400 Subject: [PATCH 04/39] chore: upgrades STJ to align verions --- .../Microsoft.OpenApi.Readers.Tests.csproj | 2 +- test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index d56e5157f..6ed651216 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 44fe71dc6..4cce4b8af 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -13,7 +13,7 @@ - + From 7d5bd1ad0214afa1e86f06d8814198c927f7aa5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:26:33 +0000 Subject: [PATCH 05/39] Bump the microsoftextensions group with 6 updates Bumps Microsoft.Extensions.DependencyInjection from 10.0.4 to 10.0.5 Bumps Microsoft.Extensions.Logging from 10.0.4 to 10.0.5 Bumps Microsoft.Extensions.Logging.Abstractions from 10.0.4 to 10.0.5 Bumps Microsoft.Extensions.Logging.Console from 10.0.4 to 10.0.5 Bumps Microsoft.Extensions.Logging.Debug from 10.0.4 to 10.0.5 Bumps System.Text.Json from 10.0.4 to 10.0.5 --- updated-dependencies: - dependency-name: Microsoft.Extensions.DependencyInjection dependency-version: 10.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging dependency-version: 10.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging dependency-version: 10.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging.Abstractions dependency-version: 10.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging.Console dependency-version: 10.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: System.Text.Json dependency-version: 10.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging.Console dependency-version: 10.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging.Debug dependency-version: 10.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions - dependency-name: Microsoft.Extensions.Logging.Debug dependency-version: 10.0.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: microsoftextensions ... Signed-off-by: dependabot[bot] --- performance/resultsComparer/resultsComparer.csproj | 10 +++++----- .../Microsoft.OpenApi.Hidi.csproj | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/performance/resultsComparer/resultsComparer.csproj b/performance/resultsComparer/resultsComparer.csproj index 8b701c7bd..ea80cad30 100644 --- a/performance/resultsComparer/resultsComparer.csproj +++ b/performance/resultsComparer/resultsComparer.csproj @@ -8,12 +8,12 @@ - - - - + + + + - + diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index 00ed9dcac..7e3db37d3 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -29,10 +29,10 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all From f3690b713d66537b6dad4c29092e7acaff353c0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:29:32 +0000 Subject: [PATCH 06/39] Bump Microsoft.SourceLink.GitHub from 10.0.200 to 10.0.201 --- updated-dependencies: - dependency-name: Microsoft.SourceLink.GitHub dependency-version: 10.0.201 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 022908802..3587cea9d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -19,6 +19,6 @@ true - + \ No newline at end of file From 12ae9504beb63f183e3ec8dd9d59c575a5dc9f16 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 13 Mar 2026 08:56:32 -0400 Subject: [PATCH 07/39] chore: upgrades STJ to align verions --- .../Microsoft.OpenApi.Readers.Tests.csproj | 2 +- test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index 6ed651216..c5e8d0991 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 4cce4b8af..38a5937d5 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -13,7 +13,7 @@ - + From d0ece383ceb871daca57ca4932ff4a7e18b2ba3f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Mar 2026 13:07:23 +0000 Subject: [PATCH 08/39] Initial plan From 19538aa169b708dac3baf04a1315bed1dd25eac6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Mar 2026 13:16:58 +0000 Subject: [PATCH 09/39] fix(library): do not emit unevaluatedProperties for non-object schemas When `Type` is explicitly set and does not include `Object`, skip emitting `unevaluatedProperties` in all serialization paths (v3.1+ native property, v2.0/v3.0 extension, and direct v2.0 serialization). Closes: unevaluatedProperties emitted for array/string/number/integer/boolean/null schemas Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> --- global.json | 2 +- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 84 ++++++++------ .../Models/OpenApiSchemaTests.cs | 105 ++++++++++++++++++ 3 files changed, 155 insertions(+), 36 deletions(-) diff --git a/global.json b/global.json index d72c3f669..459fe3e6e 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0.419" + "version": "8.0.418" } } \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 795ee18ee..11252d3c4 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -547,19 +547,24 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version // For versions < 3.1, write unevaluatedProperties as an extension if (version < OpenApiSpecVersion.OpenApi3_1) { - // Write UnevaluatedPropertiesSchema as extension if present - if (UnevaluatedPropertiesSchema is not null) + // Only emit unevaluatedProperties when the type could include objects. + // Skip when type is explicitly set to a non-object type (array, string, number, integer, boolean, null). + if (!Type.HasValue || (Type.Value & JsonSchemaType.Object) != 0) { - writer.WriteOptionalObject( - OpenApiConstants.UnevaluatedPropertiesExtension, - UnevaluatedPropertiesSchema, - callback); - } - // Write boolean false as extension if explicitly set to false - else if (!UnevaluatedProperties) - { - writer.WritePropertyName(OpenApiConstants.UnevaluatedPropertiesExtension); - writer.WriteValue(false); + // Write UnevaluatedPropertiesSchema as extension if present + if (UnevaluatedPropertiesSchema is not null) + { + writer.WriteOptionalObject( + OpenApiConstants.UnevaluatedPropertiesExtension, + UnevaluatedPropertiesSchema, + callback); + } + // Write boolean false as extension if explicitly set to false + else if (!UnevaluatedProperties) + { + writer.WritePropertyName(OpenApiConstants.UnevaluatedPropertiesExtension); + writer.WriteValue(false); + } } // Write patternProperties as an extension @@ -599,19 +604,23 @@ internal void WriteJsonSchemaKeywords(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef); writer.WriteProperty(OpenApiConstants.DynamicAnchor, DynamicAnchor); - // UnevaluatedProperties: similar to AdditionalProperties, serialize as schema if present, else as boolean - if (UnevaluatedPropertiesSchema is not null) - { - writer.WriteOptionalObject( - OpenApiConstants.UnevaluatedProperties, - UnevaluatedPropertiesSchema, - (w, s) => s.SerializeAsV31(w)); - } - else if (!UnevaluatedProperties) + // UnevaluatedProperties: similar to AdditionalProperties, serialize as schema if present, else as boolean. + // Only emit when the type could include objects. + // Skip when type is explicitly set to a non-object type (array, string, number, integer, boolean, null). + if (!Type.HasValue || (Type.Value & JsonSchemaType.Object) != 0) { - writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties); + if (UnevaluatedPropertiesSchema is not null) + { + writer.WriteOptionalObject( + OpenApiConstants.UnevaluatedProperties, + UnevaluatedPropertiesSchema, + (w, s) => s.SerializeAsV31(w)); + } + else if (!UnevaluatedProperties) + { + writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties); + } } - // true is the default, no need to write it out writer.WriteOptionalCollection(OpenApiConstants.Examples, Examples, (nodeWriter, s) => nodeWriter.WriteAny(s)); writer.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, (w, s) => s.SerializeAsV31(w)); writer.WriteOptionalMap(OpenApiConstants.DependentRequired, DependentRequired, (w, s) => w.WriteValue(s)); @@ -832,19 +841,24 @@ private void SerializeAsV2( // x-nullable extension SerializeNullable(writer, OpenApiSpecVersion.OpenApi2_0); - // Write UnevaluatedPropertiesSchema as extension if present - if (UnevaluatedPropertiesSchema is not null) + // Write UnevaluatedPropertiesSchema as extension if present. + // Only emit when the type could include objects. + // Skip when type is explicitly set to a non-object type (array, string, number, integer, boolean, null). + if (!Type.HasValue || (Type.Value & JsonSchemaType.Object) != 0) { - writer.WriteOptionalObject( - OpenApiConstants.UnevaluatedPropertiesExtension, - UnevaluatedPropertiesSchema, - (w, s) => s.SerializeAsV2(w)); - } - // Write boolean false as extension if explicitly set to false - else if (!UnevaluatedProperties) - { - writer.WritePropertyName(OpenApiConstants.UnevaluatedPropertiesExtension); - writer.WriteValue(false); + if (UnevaluatedPropertiesSchema is not null) + { + writer.WriteOptionalObject( + OpenApiConstants.UnevaluatedPropertiesExtension, + UnevaluatedPropertiesSchema, + (w, s) => s.SerializeAsV2(w)); + } + // Write boolean false as extension if explicitly set to false + else if (!UnevaluatedProperties) + { + writer.WritePropertyName(OpenApiConstants.UnevaluatedPropertiesExtension); + writer.WriteValue(false); + } } // Write patternProperties as an extension diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 3cb5af201..64874fb37 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -1312,6 +1312,111 @@ public async Task SerializeUnevaluatedPropertiesTrueNotEmittedInEarlierVersions( Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); } + [Theory] + [InlineData(JsonSchemaType.Array, "array")] + [InlineData(JsonSchemaType.String, "string")] + [InlineData(JsonSchemaType.Number, "number")] + [InlineData(JsonSchemaType.Integer, "integer")] + [InlineData(JsonSchemaType.Boolean, "boolean")] + [InlineData(JsonSchemaType.Null, "null")] + public async Task SerializeUnevaluatedPropertiesFalseNotEmittedForNonObjectType(JsonSchemaType nonObjectType, string typeName) + { + var expected = $@"{{ ""type"": ""{typeName}"" }}"; + // Given - unevaluatedProperties should not be emitted when type is explicitly set to a non-object type + var schema = new OpenApiSchema + { + Type = nonObjectType, + UnevaluatedProperties = false + }; + + // When + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Theory] + [InlineData(JsonSchemaType.Array, "array")] + [InlineData(JsonSchemaType.String, "string")] + [InlineData(JsonSchemaType.Number, "number")] + [InlineData(JsonSchemaType.Integer, "integer")] + [InlineData(JsonSchemaType.Boolean, "boolean")] + [InlineData(JsonSchemaType.Null, "null")] + public async Task SerializeUnevaluatedPropertiesSchemaNotEmittedForNonObjectType(JsonSchemaType nonObjectType, string typeName) + { + var expected = $@"{{ ""type"": ""{typeName}"" }}"; + // Given - unevaluatedProperties schema should not be emitted when type is explicitly set to a non-object type + var schema = new OpenApiSchema + { + Type = nonObjectType, + UnevaluatedPropertiesSchema = new OpenApiSchema { Type = JsonSchemaType.String } + }; + + // When + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi2_0, JsonSchemaType.Array)] + [InlineData(OpenApiSpecVersion.OpenApi2_0, JsonSchemaType.String)] + [InlineData(OpenApiSpecVersion.OpenApi3_0, JsonSchemaType.Array)] + [InlineData(OpenApiSpecVersion.OpenApi3_0, JsonSchemaType.String)] + public async Task SerializeUnevaluatedPropertiesNotEmittedAsExtensionForNonObjectType(OpenApiSpecVersion version, JsonSchemaType nonObjectType) + { + // Given - unevaluatedProperties should not be emitted as extension when type is a non-object type + var schema = new OpenApiSchema + { + Type = nonObjectType, + UnevaluatedProperties = false + }; + + // When + var actual = await schema.SerializeAsJsonAsync(version); + + // Then - should not contain unevaluatedProperties extension + var parsed = JsonNode.Parse(actual)!.AsObject(); + Assert.False(parsed.ContainsKey(OpenApiConstants.UnevaluatedPropertiesExtension)); + } + + [Fact] + public async Task SerializeUnevaluatedPropertiesFalseStillEmittedForObjectType() + { + var expected = @"{ ""type"": ""object"", ""unevaluatedProperties"": false }"; + // Given - unevaluatedProperties should still be emitted for object type + var schema = new OpenApiSchema + { + Type = JsonSchemaType.Object, + UnevaluatedProperties = false + }; + + // When + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Fact] + public async Task SerializeUnevaluatedPropertiesFalseStillEmittedWhenTypeNotSet() + { + var expected = @"{ ""unevaluatedProperties"": false }"; + // Given - unevaluatedProperties should still be emitted when type is not explicitly set + var schema = new OpenApiSchema + { + UnevaluatedProperties = false + }; + + // When + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + + // Then + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + // PatternProperties tests [Theory] [InlineData(OpenApiSpecVersion.OpenApi3_1)] From b89f503da59ecf837cf63c1f90397185208a26f9 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 13 Mar 2026 09:41:47 -0400 Subject: [PATCH 10/39] chore: reverts sdk version change --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 459fe3e6e..d72c3f669 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0.418" + "version": "8.0.419" } } \ No newline at end of file From 632e259641aefd89570ae69c3472928b2d37fcda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 21:32:08 +0000 Subject: [PATCH 11/39] chore(deps): bump actions/create-github-app-token from 2 to 3 Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 2 to 3. - [Release notes](https://github.com/actions/create-github-app-token/releases) - [Commits](https://github.com/actions/create-github-app-token/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/create-github-app-token dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/promote-shipped-apis.yml | 2 +- .github/workflows/release-please-gha.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/promote-shipped-apis.yml b/.github/workflows/promote-shipped-apis.yml index aa611b7a0..d3f2df683 100644 --- a/.github/workflows/promote-shipped-apis.yml +++ b/.github/workflows/promote-shipped-apis.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3 with: app-id: ${{ vars.RELEASE_PLEASE_TOKEN_PROVIDER_APP_ID }} private-key: ${{ secrets.RELEASE_PLEASE_TOKEN_PROVIDER_PEM }} diff --git a/.github/workflows/release-please-gha.yml b/.github/workflows/release-please-gha.yml index 6e2c9825d..f20403d3a 100644 --- a/.github/workflows/release-please-gha.yml +++ b/.github/workflows/release-please-gha.yml @@ -53,7 +53,7 @@ jobs: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@v3 with: app-id: ${{ vars.RELEASE_PLEASE_TOKEN_PROVIDER_APP_ID }} private-key: ${{ secrets.RELEASE_PLEASE_TOKEN_PROVIDER_PEM }} From 27f07b92b2bbb087dbf64867aafb2e69b5a70704 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 21:36:28 +0000 Subject: [PATCH 12/39] Bump FluentAssertions from 7.2.1 to 7.2.2 --- updated-dependencies: - dependency-name: FluentAssertions dependency-version: 7.2.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../Microsoft.OpenApi.Readers.Tests.csproj | 2 +- test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index c5e8d0991..c75ad07e2 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 38a5937d5..dfe424818 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -10,7 +10,7 @@ - + From 6dac23b96069119e73d8514bb921c2ca708df915 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 17 Mar 2026 15:08:11 -0400 Subject: [PATCH 13/39] draft: fix location key encoding Signed-off-by: Vincent Biret --- .../Services/OpenApiVisitorBaseTests.cs | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 test/Microsoft.OpenApi.Tests/Services/OpenApiVisitorBaseTests.cs diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiVisitorBaseTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiVisitorBaseTests.cs new file mode 100644 index 000000000..ce92819f3 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiVisitorBaseTests.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Services; + +public class OpenApiVisitorBaseTests +{ + [Fact] + public void EncodesReservedCharacters() + { + // Given + var openApiDocument = new OpenApiDocument + { + Info = new() + { + Title = "foo", + Version = "1.2.2" + }, + Paths = new() + { + }, + Components = new() + { + Schemas = new Dictionary() + { + ["Pet~"] = new OpenApiSchema() + { + Type = JsonSchemaType.Object + }, + ["Pet/"] = new OpenApiSchema() + { + Type = JsonSchemaType.Object + }, + } + } + }; + var visitor = new LocatorVisitor(); + + // When + visitor.Visit(openApiDocument); + + // Then + Assert.Equivalent( + new List + { + "#/components/schemas/Pet~0", + "#/components/schemas/Pet~1" + }, visitor.Locations); + } + + private class LocatorVisitor : OpenApiVisitorBase + { + public List Locations { get; } = new List(); + + public override void Visit(IOpenApiSchema openApiSchema) + { + Locations.Add(this.PathString); + } + public override void Visit(OpenApiComponents components) + { + Enter("schemas"); + if (components.Schemas != null) + { + foreach (var schemaKvp in components.Schemas) + { + Enter(schemaKvp.Key); + this.Visit(schemaKvp.Value); + Exit(); + } + } + Exit(); + } + public override void Visit(OpenApiDocument doc) + { + Enter("components"); + Visit(doc.Components); + Exit(); + } + } +} From 4c757e1e9806f95ed598f349e4fc35a0307b9342 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 17 Mar 2026 16:09:29 -0400 Subject: [PATCH 14/39] fix: encoding of special characters for JSON paths Signed-off-by: Vincent Biret --- src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs index 397062aca..15010c0e5 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Net.Http; using System.Text.Json.Nodes; @@ -27,7 +28,11 @@ public abstract class OpenApiVisitorBase /// Identifier for context public virtual void Enter(string segment) { - this._path.Push(segment); +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP1_0_OR_GREATER + this._path.Push(segment.Replace("~", "~0", StringComparison.Ordinal).Replace("/", "~1", StringComparison.OrdinalIgnoreCase)); +#else + this._path.Push(segment.Replace("~", "~0").Replace("/", "~1")); +#endif } /// @@ -41,7 +46,7 @@ public virtual void Exit() /// /// Pointer to source of validation error in document /// - public string PathString { get => "#/" + String.Join("/", _path.Reverse()); } + public string PathString { get => "#/" + string.Join("/", _path.Reverse()); } /// /// Visits From 471a61a2567bb517ede94f0bec1a53ef806b2db3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 17 Mar 2026 16:14:18 -0400 Subject: [PATCH 15/39] fix: potential double encoding of paths Signed-off-by: Vincent Biret --- src/Microsoft.OpenApi/Services/OpenApiWalker.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs index c8e5d72d7..01172cdd1 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs @@ -1297,15 +1297,6 @@ internal void Walk(IOpenApiElement element) } } - private static string ReplaceSlashes(string value) - { -#if NET8_0_OR_GREATER - return value.Replace("/", "~1", StringComparison.Ordinal); -#else - return value.Replace("/", "~1"); -#endif - } - /// /// Adds a segment to the context path to enable pointing to the current location in the document /// @@ -1315,7 +1306,7 @@ private static string ReplaceSlashes(string value) /// An action that walks objects within the context. private void WalkItem(string context, T state, Action walk) { - _visitor.Enter(ReplaceSlashes(context)); + _visitor.Enter(context); walk(this, state); _visitor.Exit(); } @@ -1330,7 +1321,7 @@ private void WalkItem(string context, T state, Action walk) /// An action that walks objects within the context. private void WalkItem(string context, T state, Action walk, bool isComponent) { - _visitor.Enter(ReplaceSlashes(context)); + _visitor.Enter(context); walk(this, state, isComponent); _visitor.Exit(); } @@ -1351,7 +1342,7 @@ private void WalkDictionary( { if (state != null && state.Count > 0) { - _visitor.Enter(ReplaceSlashes(context)); + _visitor.Enter(context); foreach (var item in state) { From 6e16cbc84b7bab8438d3876b49e145a9af5c1760 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 17 Mar 2026 16:14:28 -0400 Subject: [PATCH 16/39] fix null reference exception Signed-off-by: Vincent Biret --- src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs index 15010c0e5..45a85d3ab 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs @@ -28,6 +28,11 @@ public abstract class OpenApiVisitorBase /// Identifier for context public virtual void Enter(string segment) { + if (string.IsNullOrEmpty(segment)) + { + this._path.Push(string.Empty); + return; + } #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP1_0_OR_GREATER this._path.Push(segment.Replace("~", "~0", StringComparison.Ordinal).Replace("/", "~1", StringComparison.OrdinalIgnoreCase)); #else From b246cd0bafde78892aa22e6ffe046cf5a438eb7d Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 17 Mar 2026 16:34:03 -0400 Subject: [PATCH 17/39] fix: double encoding of json pointer for invalid reference rule Signed-off-by: Vincent Biret --- .../Validations/Rules/OpenApiDocumentRules.cs | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs index a1f166ee3..38767035f 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. using System; +using System.Collections.Generic; +using System.Linq; namespace Microsoft.OpenApi { @@ -74,28 +76,49 @@ private void ValidateSchemaReference(OpenApiSchemaReference reference) { if (reference.RecursiveTarget is null) { + var segments = GetSegments().ToArray(); + EnterSegments(segments); // The reference was not followed to a valid schema somewhere in the document - context.Enter(GetSegment()); context.CreateWarning(ruleName, string.Format(SRResource.Validation_SchemaReferenceDoesNotExist, reference.Reference.ReferenceV3)); - context.Exit(); + ExitSegments(segments.Length); } } catch (InvalidOperationException ex) { - context.Enter(GetSegment()); + var segments = GetSegments().ToArray(); + EnterSegments(segments); context.CreateWarning(ruleName, ex.Message); - context.Exit(); + ExitSegments(segments.Length); } - string GetSegment() + void ExitSegments(int length) { - // Trim off the leading "#/" as the context is already at the root of the document - return -#if NET8_0_OR_GREATER - $"{PathString[2..]}/$ref"; + for (var i = 0; i < length; i++) + { + context.Exit(); + } + } + + void EnterSegments(string[] segments) + { + foreach (var segment in segments) + { + context.Enter(segment); + } + } + + IEnumerable GetSegments() + { + foreach (var segment in this.PathString.Substring(2).Split('/')) + { +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP1_0_OR_GREATER + yield return segment.Replace("~1", "/", StringComparison.OrdinalIgnoreCase).Replace("~0", "~", StringComparison.OrdinalIgnoreCase); #else - PathString.Substring(2) + "/$ref"; + yield return segment.Replace("~1", "/").Replace("~0", "~"); #endif + } + yield return "$ref"; + // Trim off the leading "#/" as the context is already at the root of the document } } } From bdb6c0d2b6b0eae4c0af59a27e3e964d0f84ca02 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 17 Mar 2026 16:34:21 -0400 Subject: [PATCH 18/39] fix missing encoding for invalid request body rule Signed-off-by: Vincent Biret --- .../Validations/OpenApiRecommendedRulesTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiRecommendedRulesTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiRecommendedRulesTests.cs index c5aa3533d..13d710909 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiRecommendedRulesTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiRecommendedRulesTests.cs @@ -200,7 +200,7 @@ public static void GetOperationWithRequestBodyIsInvalid() Assert.NotNull(warnings); var warning = Assert.Single(warnings); Assert.Equal("GET operations should not have a request body.", warning.Message); - Assert.Equal("#/paths//people/get/requestBody", warning.Pointer); + Assert.Equal("#/paths/~1people/get/requestBody", warning.Pointer); } [Fact] From 2559e7f6d7df0f2948039c59daccc5d25c8a5148 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:42:59 +0000 Subject: [PATCH 19/39] Initial plan From 8dd2b130a0c1846a129f294e27d23cc5d299eaa9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:50:21 +0000 Subject: [PATCH 20/39] chore(dependabot): add coverlet group to reduce PRs Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> --- .github/dependabot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 87b987112..c3da60bcb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,6 +16,9 @@ updates: MicrosoftExtensions: patterns: - "Microsoft.Extensions.*" + coverlet: + patterns: + - "coverlet.*" - package-ecosystem: dotnet-sdk directory: / schedule: From 35e787f77fdf9ea32dedbf484d7e6c56c0426cbd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Mar 2026 15:41:59 +0000 Subject: [PATCH 21/39] Bump the coverlet group with 2 updates Bumps coverlet.collector from 6.0.4 to 8.0.1 Bumps coverlet.msbuild from 8.0.0 to 8.0.1 --- updated-dependencies: - dependency-name: coverlet.collector dependency-version: 8.0.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: coverlet - dependency-name: coverlet.collector dependency-version: 8.0.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: coverlet - dependency-name: coverlet.msbuild dependency-version: 8.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: coverlet - dependency-name: coverlet.msbuild dependency-version: 8.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: coverlet ... Signed-off-by: dependabot[bot] --- .../Microsoft.OpenApi.Hidi.Tests.csproj | 4 ++-- .../Microsoft.OpenApi.Readers.Tests.csproj | 4 ++-- test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj b/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj index 0e9bad64c..b2cb4de59 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj +++ b/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj @@ -13,12 +13,12 @@ - + - + diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index c75ad07e2..9e7baf988 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index dfe424818..c4ac11c4a 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -8,8 +8,8 @@ - - + + From b2ee215098635c0dc313a4894000f3a47adf8f8d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:52:35 +0000 Subject: [PATCH 22/39] Initial plan From bffc173788125651f20967388f3e7c169f22d554 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 18:03:53 +0000 Subject: [PATCH 23/39] chore: initial plan for fix-openapi-schema-reference-description Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index d72c3f669..459fe3e6e 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0.419" + "version": "8.0.418" } } \ No newline at end of file From 9b422bfbe1459588821ab011ba470336c63e4958 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 18:15:58 +0000 Subject: [PATCH 24/39] feat(library): add Extensions support for schema references in v3.1/v3.2; add SerializeAsV32 with loop detection - Add Extensions property to JsonSchemaReference (serialize in v3.1/v3.2 only) - Add Extensions setter to OpenApiSchemaReference (checks reference-level extensions first) - Add SerializeAsV32 override to OpenApiSchemaReference with loop detection - Read extension properties from $ref sibling keywords during v3.1 deserialization - Add tests for extensions in v3.1/v3.2 and verify extensions dropped in v3.0/v2 Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> --- .../Models/JsonSchemaReference.cs | 19 +++ .../References/OpenApiSchemaReference.cs | 12 +- src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 4 + ...orks_produceTerseOutput=False.verified.txt | 1 + ...Works_produceTerseOutput=True.verified.txt | 2 +- ...orks_produceTerseOutput=False.verified.txt | 12 ++ ...Works_produceTerseOutput=True.verified.txt | 1 + .../References/OpenApiSchemaReferenceTests.cs | 161 +++++++++++++++++- 8 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV32JsonWorks_produceTerseOutput=False.verified.txt create mode 100644 test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV32JsonWorks_produceTerseOutput=True.verified.txt diff --git a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs index 933a4a85b..443a47ab6 100644 --- a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs @@ -52,6 +52,12 @@ public class JsonSchemaReference : OpenApiReferenceWithDescription /// public IList? Examples { get; set; } + /// + /// Extension data for this schema reference. Only allowed in OpenAPI 3.1 and later. + /// Extensions are NOT written when serializing for OpenAPI 2.0 or 3.0. + /// + public IDictionary? Extensions { get; set; } + /// /// Parameterless constructor /// @@ -69,6 +75,7 @@ public JsonSchemaReference(JsonSchemaReference reference) : base(reference) ReadOnly = reference.ReadOnly; WriteOnly = reference.WriteOnly; Examples = reference.Examples; + Extensions = reference.Extensions != null ? new Dictionary(reference.Extensions) : null; } /// @@ -106,6 +113,7 @@ private void SerializeAdditionalV3XProperties(IOpenApiWriter writer, Action w.WriteAny(e)); } + writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_1); } /// @@ -146,5 +154,16 @@ protected override void SetAdditional31MetadataFromMapNode(JsonObject jsonObject { Examples = examplesArray.OfType().ToList(); } + + // Extensions (properties starting with "x-") + foreach (var property in jsonObject) + { + if (property.Key.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase) + && property.Value is JsonNode extensionValue) + { + Extensions ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + Extensions[property.Key] = new JsonNodeExtension(extensionValue.DeepClone()); + } + } } } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs index 55be30a51..1e5970f3a 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs @@ -158,7 +158,11 @@ public bool Deprecated /// public OpenApiXml? Xml { get => Target?.Xml; } /// - public IDictionary? Extensions { get => Target?.Extensions; } + public IDictionary? Extensions + { + get => Reference.Extensions ?? Target?.Extensions; + set => Reference.Extensions = value; + } /// public IDictionary? UnrecognizedKeywords { get => Target?.UnrecognizedKeywords; } @@ -172,6 +176,12 @@ public override void SerializeAsV31(IOpenApiWriter writer) SerializeAsWithoutLoops(writer, (w, element) => (element is IOpenApiSchema s ? CopyReferenceAsTargetElementWithOverrides(s) : element).SerializeAsV31(w)); } + /// + public override void SerializeAsV32(IOpenApiWriter writer) + { + SerializeAsWithoutLoops(writer, (w, element) => (element is IOpenApiSchema s ? CopyReferenceAsTargetElementWithOverrides(s) : element).SerializeAsV32(w)); + } + /// public override void SerializeAsV3(IOpenApiWriter writer) { diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index 7dc5c5811..7b7b54aca 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ #nullable enable +Microsoft.OpenApi.JsonSchemaReference.Extensions.get -> System.Collections.Generic.IDictionary? +Microsoft.OpenApi.JsonSchemaReference.Extensions.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Extensions.set -> void +override Microsoft.OpenApi.OpenApiSchemaReference.SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter! writer) -> void diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt index 3d7372e1b..87dbbe91f 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV31JsonWorks_produceTerseOutput=False.verified.txt @@ -7,5 +7,6 @@ "examples": [ "reference example" ], + "x-custom": "custom value", "$ref": "#/components/schemas/Pet" } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt index 2a7cc8e44..c7f7f3cd0 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV31JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"description":"Reference Description","default":"reference default","title":"Reference Title","deprecated":true,"readOnly":true,"examples":["reference example"],"$ref":"#/components/schemas/Pet"} \ No newline at end of file +{"description":"Reference Description","default":"reference default","title":"Reference Title","deprecated":true,"readOnly":true,"examples":["reference example"],"x-custom":"custom value","$ref":"#/components/schemas/Pet"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV32JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV32JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..87dbbe91f --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV32JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,12 @@ +{ + "description": "Reference Description", + "default": "reference default", + "title": "Reference Title", + "deprecated": true, + "readOnly": true, + "examples": [ + "reference example" + ], + "x-custom": "custom value", + "$ref": "#/components/schemas/Pet" +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV32JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV32JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..c7f7f3cd0 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV32JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"description":"Reference Description","default":"reference default","title":"Reference Title","deprecated":true,"readOnly":true,"examples":["reference example"],"x-custom":"custom value","$ref":"#/components/schemas/Pet"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs index 57ccae0cb..f82391b12 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs @@ -133,7 +133,11 @@ public async Task SerializeSchemaReferenceAsV31JsonWorks(bool produceTerseOutput WriteOnly = false, Deprecated = true, Default = JsonValue.Create("reference default"), - Examples = new List { JsonValue.Create("reference example") } + Examples = new List { JsonValue.Create("reference example") }, + Extensions = new Dictionary + { + ["x-custom"] = new JsonNodeExtension(JsonValue.Create("custom value")) + } }; var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); @@ -150,7 +154,7 @@ public async Task SerializeSchemaReferenceAsV31JsonWorks(bool produceTerseOutput [Theory] [InlineData(true)] [InlineData(false)] - public async Task SerializeSchemaReferenceAsV3JsonWorks(bool produceTerseOutput) + public async Task SerializeSchemaReferenceAsV32JsonWorks(bool produceTerseOutput) { // Arrange var reference = new OpenApiSchemaReference("Pet", null) @@ -161,7 +165,43 @@ public async Task SerializeSchemaReferenceAsV3JsonWorks(bool produceTerseOutput) WriteOnly = false, Deprecated = true, Default = JsonValue.Create("reference default"), - Examples = new List { JsonValue.Create("reference example") } + Examples = new List { JsonValue.Create("reference example") }, + Extensions = new Dictionary + { + ["x-custom"] = new JsonNodeExtension(JsonValue.Create("custom value")) + } + }; + + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); + + // Act + reference.SerializeAsV32(writer); + await writer.FlushAsync(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeSchemaReferenceAsV3JsonWorks(bool produceTerseOutput) + { + // Arrange - Extensions should NOT appear in v3.0 output + var reference = new OpenApiSchemaReference("Pet", null) + { + Title = "Reference Title", + Description = "Reference Description", + ReadOnly = true, + WriteOnly = false, + Deprecated = true, + Default = JsonValue.Create("reference default"), + Examples = new List { JsonValue.Create("reference example") }, + Extensions = new Dictionary + { + ["x-custom"] = new JsonNodeExtension(JsonValue.Create("custom value")) + } }; var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); @@ -256,5 +296,120 @@ public void ParseSchemaReferenceWithAnnotationsWorks() Assert.Equal("Original Pet Title", targetSchema.Title); Assert.Equal("Original Pet Description", targetSchema.Description); } + + [Fact] + public void ParseSchemaReferenceWithExtensionsWorks() + { + // Arrange + var jsonContent = @"{ + ""openapi"": ""3.1.0"", + ""info"": { + ""title"": ""Test API"", + ""version"": ""1.0.0"" + }, + ""paths"": { + ""/test"": { + ""get"": { + ""responses"": { + ""200"": { + ""description"": ""OK"", + ""content"": { + ""application/json"": { + ""schema"": { + ""$ref"": ""#/components/schemas/Pet"", + ""description"": ""A pet object"", + ""x-custom-extension"": ""custom value"", + ""x-another-extension"": 42 + } + } + } + } + } + } + } + }, + ""components"": { + ""schemas"": { + ""Pet"": { + ""type"": ""object"", + ""properties"": { + ""name"": { + ""type"": ""string"" + } + } + } + } + } +}"; + + // Act + var readResult = OpenApiDocument.Parse(jsonContent, "json"); + var document = readResult.Document; + + // Assert + Assert.NotNull(document); + Assert.Empty(readResult.Diagnostic.Errors); + + var schema = document.Paths["/test"].Operations[HttpMethod.Get] + .Responses["200"].Content["application/json"].Schema; + + Assert.IsType(schema); + var schemaRef = (OpenApiSchemaReference)schema; + + // Test that reference-level extensions are parsed + Assert.NotNull(schemaRef.Extensions); + Assert.Contains("x-custom-extension", schemaRef.Extensions.Keys); + Assert.Contains("x-another-extension", schemaRef.Extensions.Keys); + } + + [Fact] + public async Task SchemaReferenceExtensionsNotWrittenInV30() + { + // Arrange + var reference = new OpenApiSchemaReference("Pet", null) + { + Description = "Local description", + Extensions = new Dictionary + { + ["x-custom"] = new JsonNodeExtension(JsonValue.Create("custom value")) + } + }; + + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = true }); + + // Act + reference.SerializeAsV3(writer); + await writer.FlushAsync(); + var output = outputStringWriter.ToString(); + + // Assert: In v3.0, ONLY $ref should appear - no description, no extensions + Assert.Equal(@"{""$ref"":""#/components/schemas/Pet""}", output); + } + + [Fact] + public async Task SchemaReferenceExtensionsNotWrittenInV2() + { + // Arrange + var reference = new OpenApiSchemaReference("Pet", null) + { + Description = "Local description", + Extensions = new Dictionary + { + ["x-custom"] = new JsonNodeExtension(JsonValue.Create("custom value")) + } + }; + + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = true }); + + // Act + reference.SerializeAsV2(writer); + await writer.FlushAsync(); + var output = outputStringWriter.ToString(); + + // Assert: In v2, ONLY $ref should appear - no description, no extensions + Assert.Equal(@"{""$ref"":""#/definitions/Pet""}", output); + } } } From fa9c408c2ea8178423b2a67ebb3b4e26c23677fc Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 17 Mar 2026 14:31:39 -0400 Subject: [PATCH 25/39] chore: adds missing interface implementation to OpenAPISchemaReference Signed-off-by: Vincent Biret --- .../Models/References/OpenApiSchemaReference.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs index 1e5970f3a..67eb79645 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs @@ -10,7 +10,7 @@ namespace Microsoft.OpenApi /// /// Schema reference object /// - public class OpenApiSchemaReference : BaseOpenApiReferenceHolder, IOpenApiSchema, IOpenApiSchemaWithUnevaluatedProperties + public class OpenApiSchemaReference : BaseOpenApiReferenceHolder, IOpenApiSchema, IOpenApiSchemaWithUnevaluatedProperties, IOpenApiExtensible { /// From 27779300f388b3aae0d245bc173d3d9c49a6fac5 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 17 Mar 2026 14:35:56 -0400 Subject: [PATCH 26/39] chore: reverts global.json changes --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 459fe3e6e..d72c3f669 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0.418" + "version": "8.0.419" } } \ No newline at end of file From 88cbeaa16633aacad7882f5474bf8c5f9401d538 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 17 Mar 2026 14:40:01 -0400 Subject: [PATCH 27/39] tests: adds a negative test for v2 peer keywords serialization of schema references Signed-off-by: Vincent Biret --- ...orks_produceTerseOutput=False.verified.txt | 3 ++ ...Works_produceTerseOutput=True.verified.txt | 1 + .../References/OpenApiSchemaReferenceTests.cs | 32 +++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt create mode 100644 test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt new file mode 100644 index 000000000..ddb324ad6 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt @@ -0,0 +1,3 @@ +{ + "$ref": "#/definitions/Pet" +} diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt new file mode 100644 index 000000000..b36112c01 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt @@ -0,0 +1 @@ +{"$ref":"#/definitions/Pet"} diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs index f82391b12..488488518 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs @@ -215,6 +215,38 @@ public async Task SerializeSchemaReferenceAsV3JsonWorks(bool produceTerseOutput) await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task SerializeSchemaReferenceAsV2JsonWorks(bool produceTerseOutput) + { + // Arrange - Extensions should NOT appear in v2 output + var reference = new OpenApiSchemaReference("Pet", null) + { + Title = "Reference Title", + Description = "Reference Description", + ReadOnly = true, + WriteOnly = false, + Deprecated = true, + Default = JsonValue.Create("reference default"), + Examples = new List { JsonValue.Create("reference example") }, + Extensions = new Dictionary + { + ["x-custom"] = new JsonNodeExtension(JsonValue.Create("custom value")) + } + }; + + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = produceTerseOutput }); + + // Act + reference.SerializeAsV2(writer); + await writer.FlushAsync(); + + // Assert + await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); + } + [Fact] public void ParseSchemaReferenceWithAnnotationsWorks() { From cba3dda0e518500deb966803c54be51725289423 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 20 Mar 2026 09:07:14 -0400 Subject: [PATCH 28/39] chore: linting Signed-off-by: Vincent Biret --- src/Microsoft.OpenApi/Models/JsonSchemaReference.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs index 443a47ab6..b3856a3e5 100644 --- a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/JsonSchemaReference.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; @@ -156,10 +156,10 @@ protected override void SetAdditional31MetadataFromMapNode(JsonObject jsonObject } // Extensions (properties starting with "x-") - foreach (var property in jsonObject) + foreach (var property in jsonObject + .Where(static p => p.Key.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase))) { - if (property.Key.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase) - && property.Value is JsonNode extensionValue) + if (property.Value is JsonNode extensionValue) { Extensions ??= new Dictionary(StringComparer.OrdinalIgnoreCase); Extensions[property.Key] = new JsonNodeExtension(extensionValue.DeepClone()); From 4b3164a76b316951cb043aebf950663a17e51c90 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 20 Mar 2026 09:25:05 -0400 Subject: [PATCH 29/39] fix: a bug where path parameter validation would fail if they contained forbidden JSON pointer characters Signed-off-by: Vincent Biret --- .../Services/OpenApiVisitorBase.cs | 13 ++++++- .../Rules/OpenApiParameterRules.cs | 2 +- .../OpenApiParameterValidationTests.cs | 37 ++++++++++++++++++- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs index 45a85d3ab..8e0699ebc 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs @@ -33,10 +33,19 @@ public virtual void Enter(string segment) this._path.Push(string.Empty); return; } + this._path.Push(EncodeJsonPointerSegment(segment)); + } + + internal static string EncodeJsonPointerSegment(string? segment) + { + if (string.IsNullOrEmpty(segment)) + { + return string.Empty; + } #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP1_0_OR_GREATER - this._path.Push(segment.Replace("~", "~0", StringComparison.Ordinal).Replace("/", "~1", StringComparison.OrdinalIgnoreCase)); + return segment.Replace("~", "~0", StringComparison.Ordinal).Replace("/", "~1", StringComparison.OrdinalIgnoreCase); #else - this._path.Push(segment.Replace("~", "~0").Replace("/", "~1")); + return segment!.Replace("~", "~0").Replace("/", "~1"); #endif } diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs index a84ea3255..c00e1dcce 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs @@ -61,7 +61,7 @@ public static class OpenApiParameterRules (context, parameter) => { if (parameter.In == ParameterLocation.Path && - !(context.PathString.Contains("{" + parameter.Name + "}") || context.PathString.Contains("#/components"))) + !(context.PathString.Contains("{" + OpenApiVisitorBase.EncodeJsonPointerSegment(parameter.Name) + "}") || context.PathString.Contains("#/components"))) { context.Enter("in"); context.CreateError( diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs index 207db74b6..e12216c05 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs @@ -203,7 +203,42 @@ public void PathParameterInThePathShouldBeOk() validator.Enter("1"); var walker = new OpenApiWalker(validator); - walker.Walk(parameter); + walker.Walk((IOpenApiParameter)parameter); + + errors = validator.Errors; + var result = errors.Any(); + + // Assert + Assert.False(result); + } + + [Fact] + public void PathParameterInThePathShouldBeOkWithSlashInParameterName() + { + // Arrange + IEnumerable errors; + + var parameter = new OpenApiParameter + { + Name = "parameter/1", + In = ParameterLocation.Path, + Required = true, + Schema = new OpenApiSchema() + { + Type = JsonSchemaType.String, + } + }; + + // Act + var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); + validator.Enter("paths"); + validator.Enter("/{parameter/1}"); + validator.Enter("get"); + validator.Enter("parameters"); + validator.Enter("1"); + + var walker = new OpenApiWalker(validator); + walker.Walk((IOpenApiParameter)parameter); errors = validator.Errors; var result = errors.Any(); From 06fb78ff7b3e28c5c46807013e289b026fadacb7 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 20 Mar 2026 09:36:23 -0400 Subject: [PATCH 30/39] chore: further linting to reduce looping Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- src/Microsoft.OpenApi/Models/JsonSchemaReference.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs index b3856a3e5..824a3ebe1 100644 --- a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs @@ -157,13 +157,12 @@ protected override void SetAdditional31MetadataFromMapNode(JsonObject jsonObject // Extensions (properties starting with "x-") foreach (var property in jsonObject - .Where(static p => p.Key.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase))) + .Where(static p => p.Key.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase)) + .Where(static p => p.Value is JsonNode)) { - if (property.Value is JsonNode extensionValue) - { - Extensions ??= new Dictionary(StringComparer.OrdinalIgnoreCase); - Extensions[property.Key] = new JsonNodeExtension(extensionValue.DeepClone()); - } + var extensionValue = (JsonNode)property.Value!; + Extensions ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + Extensions[property.Key] = new JsonNodeExtension(extensionValue.DeepClone()); } } } From 83719d784a1eaf1cc0c559555b011bcbca35a438 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 20 Mar 2026 09:43:25 -0400 Subject: [PATCH 31/39] tests: increases coverage by testing for null parameter names Signed-off-by: Vincent Biret --- .../OpenApiParameterValidationTests.cs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs index e12216c05..1d2882033 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs @@ -75,7 +75,7 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); validator.Enter("{parameter1}"); var walker = new OpenApiWalker(validator); - walker.Walk(parameter); + walker.Walk((IOpenApiParameter)parameter); warnings = validator.Warnings; var result = !warnings.Any(); @@ -246,5 +246,35 @@ public void PathParameterInThePathShouldBeOkWithSlashInParameterName() // Assert Assert.False(result); } + + [Fact] + public void PathParameterValidationShouldNotThrowWithEmptyParameterName() + { + // Arrange + var parameter = new OpenApiParameter + { + Name = string.Empty, + In = ParameterLocation.Path, + Required = true, + Schema = new OpenApiSchema() + { + Type = JsonSchemaType.String, + } + }; + + // Act + var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); + validator.Enter("paths"); + validator.Enter("/{}"); + validator.Enter("get"); + validator.Enter("parameters"); + validator.Enter("1"); + + var walker = new OpenApiWalker(validator); + var exception = Record.Exception(() => walker.Walk((IOpenApiParameter)parameter)); + + // Assert + Assert.Null(exception); + } } } From 7d1de9ed2b7c97d518258452afd378be11aa070c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 20 Mar 2026 09:44:29 -0400 Subject: [PATCH 32/39] tests: adds missing cast to ensure the right walker method is being called Signed-off-by: Vincent Biret --- .../Validations/OpenApiHeaderValidationTests.cs | 2 +- .../Validations/OpenApiSchemaValidationTests.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs index a7a0cba0a..98e59928c 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiHeaderValidationTests.cs @@ -86,7 +86,7 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema() // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); var walker = new OpenApiWalker(validator); - walker.Walk(header); + walker.Walk((IOpenApiHeader)header); warnings = validator.Warnings; var result = !warnings.Any(); diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs index fdf36e0b4..0cb229534 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs @@ -26,7 +26,7 @@ public void ValidateDefaultShouldNotHaveDataTypeMismatchForSimpleSchema() // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); var walker = new OpenApiWalker(validator); - walker.Walk(schema); + walker.Walk((IOpenApiSchema)schema); warnings = validator.Warnings; var result = !warnings.Any(); @@ -50,7 +50,7 @@ public void ValidateExampleAndDefaultShouldNotHaveDataTypeMismatchForSimpleSchem // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); var walker = new OpenApiWalker(validator); - walker.Walk(schema); + walker.Walk((IOpenApiSchema)schema); warnings = validator.Warnings; bool result = !warnings.Any(); @@ -92,7 +92,7 @@ public void ValidateEnumShouldNotHaveDataTypeMismatchForSimpleSchema() // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); var walker = new OpenApiWalker(validator); - walker.Walk(schema); + walker.Walk((IOpenApiSchema)schema); warnings = validator.Warnings; var result = !warnings.Any(); From 45dc6b334ab82f09a951db9388d423a744996bee Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 20 Mar 2026 09:47:39 -0400 Subject: [PATCH 33/39] chore: lints redundant checks Signed-off-by: Vincent Biret --- src/Microsoft.OpenApi/Models/JsonSchemaReference.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs index 824a3ebe1..2893a7d22 100644 --- a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs @@ -157,10 +157,10 @@ protected override void SetAdditional31MetadataFromMapNode(JsonObject jsonObject // Extensions (properties starting with "x-") foreach (var property in jsonObject - .Where(static p => p.Key.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase)) - .Where(static p => p.Value is JsonNode)) + .Where(static p => p.Key.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase) + && p.Value is not null)) { - var extensionValue = (JsonNode)property.Value!; + var extensionValue = property.Value!; Extensions ??= new Dictionary(StringComparer.OrdinalIgnoreCase); Extensions[property.Key] = new JsonNodeExtension(extensionValue.DeepClone()); } From b453c7f33a1acbf5482fe61ae8986fa564be7906 Mon Sep 17 00:00:00 2001 From: "release-please-token-provider[bot]" <225477224+release-please-token-provider[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:31:31 +0000 Subject: [PATCH 34/39] chore(main): release 3.5.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 19 +++++++++++++++++++ Directory.Build.props | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2437b4193..bf0d03617 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.4.0" + ".": "3.5.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 033c3f970..e0cc37e18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [3.5.0](https://github.com/microsoft/OpenAPI.NET/compare/v3.4.0...v3.5.0) (2026-03-20) + + +### Features + +* **library:** add Extensions support for schema references in v3.1/v3.2; add SerializeAsV32 with loop detection ([9b422bf](https://github.com/microsoft/OpenAPI.NET/commit/9b422bfbe1459588821ab011ba470336c63e4958)) + + +### Bug Fixes + +* a bug where path parameter validation would fail if they contained forbidden JSON pointer characters ([ef55b2c](https://github.com/microsoft/OpenAPI.NET/commit/ef55b2ce664014a854dbd4275e3fed783bc53385)) +* a bug where path parameter validation would fail if they contained forbidden JSON pointer characters ([4b3164a](https://github.com/microsoft/OpenAPI.NET/commit/4b3164a76b316951cb043aebf950663a17e51c90)) +* double encoding of json pointer for invalid reference rule ([b246cd0](https://github.com/microsoft/OpenAPI.NET/commit/b246cd0bafde78892aa22e6ffe046cf5a438eb7d)) +* encoding of special characters for JSON paths ([4c757e1](https://github.com/microsoft/OpenAPI.NET/commit/4c757e1e9806f95ed598f349e4fc35a0307b9342)) +* **library:** do not emit unevaluatedProperties for non-object schemas ([852fb4c](https://github.com/microsoft/OpenAPI.NET/commit/852fb4c744c51099a629d90797a2803d3bbdfc2a)) +* **library:** do not emit unevaluatedProperties for non-object schemas ([19538aa](https://github.com/microsoft/OpenAPI.NET/commit/19538aa169b708dac3baf04a1315bed1dd25eac6)) +* **library:** enforce spec-compliant $ref serialization; add Extensions support for schema references in v3.1/v3.2 ([9bf61de](https://github.com/microsoft/OpenAPI.NET/commit/9bf61de9c10092f744ab38fe0a41bd6adb8351d4)) +* potential double encoding of paths ([471a61a](https://github.com/microsoft/OpenAPI.NET/commit/471a61a2567bb517ede94f0bec1a53ef806b2db3)) + ## [3.4.0](https://github.com/microsoft/OpenAPI.NET/compare/v3.3.1...v3.4.0) (2026-03-04) diff --git a/Directory.Build.props b/Directory.Build.props index 3587cea9d..034c59b03 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.4.0 + 3.5.0 From 87fe6c94109589ed5a9b3aa844d0455df6d3e079 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 20 Mar 2026 16:31:59 +0000 Subject: [PATCH 35/39] chore: promote shipped APIs --- src/Microsoft.OpenApi/PublicAPI.Shipped.txt | 4 ++++ src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.OpenApi/PublicAPI.Shipped.txt b/src/Microsoft.OpenApi/PublicAPI.Shipped.txt index e560dba7f..4424e5862 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Shipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Shipped.txt @@ -2034,3 +2034,7 @@ Microsoft.OpenApi.OpenApiSecurityScheme.OAuth2MetadataUrl.get -> System.Uri? Microsoft.OpenApi.OpenApiSecurityScheme.OAuth2MetadataUrl.set -> void Microsoft.OpenApi.OpenApiSecuritySchemeReference.OAuth2MetadataUrl.get -> System.Uri? const Microsoft.OpenApi.OpenApiConstants.PatternPropertiesExtension = "x-jsonschema-patternProperties" -> string! +Microsoft.OpenApi.JsonSchemaReference.Extensions.get -> System.Collections.Generic.IDictionary? +Microsoft.OpenApi.JsonSchemaReference.Extensions.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Extensions.set -> void +override Microsoft.OpenApi.OpenApiSchemaReference.SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter! writer) -> void diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index 7b7b54aca..7dc5c5811 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -1,5 +1 @@ #nullable enable -Microsoft.OpenApi.JsonSchemaReference.Extensions.get -> System.Collections.Generic.IDictionary? -Microsoft.OpenApi.JsonSchemaReference.Extensions.set -> void -Microsoft.OpenApi.OpenApiSchemaReference.Extensions.set -> void -override Microsoft.OpenApi.OpenApiSchemaReference.SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter! writer) -> void From 16d0fab9c71abd6b66a7eb4da8a4a31c6d31238e Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 20 Mar 2026 12:38:06 -0400 Subject: [PATCH 36/39] chore: upgrades system.commandline dependency --- performance/resultsComparer/resultsComparer.csproj | 2 +- src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/performance/resultsComparer/resultsComparer.csproj b/performance/resultsComparer/resultsComparer.csproj index ea80cad30..847f570d5 100644 --- a/performance/resultsComparer/resultsComparer.csproj +++ b/performance/resultsComparer/resultsComparer.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index 7e3db37d3..56a98870b 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -37,7 +37,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + From 08da8fa829728e12126d6b9dee191b5156705ada Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 20 Mar 2026 12:39:12 -0400 Subject: [PATCH 37/39] chore: upgrades yoko --- src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj index 56a98870b..3352b5a21 100644 --- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj +++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj @@ -39,7 +39,7 @@ - + From e73ffb9d423e0854e6380e14b3c276490025d803 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 20 Mar 2026 12:39:57 -0400 Subject: [PATCH 38/39] chore: upgrades verify test dependency --- test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index c4ac11c4a..4ae22970e 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -14,7 +14,7 @@ - + From aeb45ba3482d4d40db58034a5404da2d6370a854 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 20 Mar 2026 12:40:49 -0400 Subject: [PATCH 39/39] chore: upgrades windows compatibility dependency --- .../Microsoft.OpenApi.Workbench.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj b/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj index f00b8d79b..89cf78d37 100644 --- a/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj +++ b/src/Microsoft.OpenApi.Workbench/Microsoft.OpenApi.Workbench.csproj @@ -25,7 +25,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - +