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:
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 }}
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 9129d4c5e..034c59b03 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -12,13 +12,13 @@
https://github.com/Microsoft/OpenAPI.NET
© Microsoft Corporation. All rights reserved.
OpenAPI .NET
- 3.4.0
+ 3.5.0
true
-
+
\ No newline at end of file
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
diff --git a/performance/resultsComparer/resultsComparer.csproj b/performance/resultsComparer/resultsComparer.csproj
index 892a4b627..847f570d5 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..3352b5a21 100644
--- a/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
+++ b/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj
@@ -29,17 +29,17 @@
-
-
-
-
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
-
+
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
-
+
diff --git a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs
index 933a4a85b..2893a7d22 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;
@@ -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,15 @@ protected override void SetAdditional31MetadataFromMapNode(JsonObject jsonObject
{
Examples = examplesArray.OfType().ToList();
}
+
+ // Extensions (properties starting with "x-")
+ foreach (var property in jsonObject
+ .Where(static p => p.Key.StartsWith(OpenApiConstants.ExtensionFieldNamePrefix, StringComparison.OrdinalIgnoreCase)
+ && p.Value is not null))
+ {
+ var extensionValue = property.Value!;
+ Extensions ??= new Dictionary(StringComparer.OrdinalIgnoreCase);
+ Extensions[property.Key] = new JsonNodeExtension(extensionValue.DeepClone());
+ }
}
}
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/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs
index 55be30a51..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
{
///
@@ -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.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/Services/OpenApiVisitorBase.cs b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs
index 397062aca..8e0699ebc 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,25 @@ public abstract class OpenApiVisitorBase
/// Identifier for context
public virtual void Enter(string segment)
{
- this._path.Push(segment);
+ if (string.IsNullOrEmpty(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
+ return segment.Replace("~", "~0", StringComparison.Ordinal).Replace("/", "~1", StringComparison.OrdinalIgnoreCase);
+#else
+ return segment!.Replace("~", "~0").Replace("/", "~1");
+#endif
}
///
@@ -41,7 +60,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
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)
{
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
}
}
}
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.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 d56e5157f..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,11 +15,11 @@
-
-
+
+
-
-
+
+
diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
index 44fe71dc6..4ae22970e 100644
--- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
+++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj
@@ -8,13 +8,13 @@
-
-
-
+
+
+
-
-
+
+
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)]
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.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..488488518 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);
@@ -175,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()
{
@@ -256,5 +328,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);
+ }
}
}
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();
+ }
+ }
+}
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/OpenApiParameterValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs
index 207db74b6..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();
@@ -203,7 +203,7 @@ 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();
@@ -211,5 +211,70 @@ public void PathParameterInThePathShouldBeOk()
// 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();
+
+ // 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);
+ }
}
}
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]
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();