From 82f84e072d9f6b4b5907e9435212fbfc8f82d9c7 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 9 Jun 2026 12:08:14 -0400 Subject: [PATCH 1/9] feat(library): add missing json schema properties Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Models/Interfaces/IOpenApiSchema.cs | 2 +- .../IOpenApiSchemaMissingProperties.cs | 86 ++++++++ ...IOpenApiSchemaWithUnevaluatedProperties.cs | 3 + .../Models/OpenApiConstants.cs | 99 ++++++++- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 92 +++++++- .../References/OpenApiSchemaReference.cs | 24 ++- src/Microsoft.OpenApi/PublicAPI.Shipped.txt | 2 +- src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 58 +++++ .../Reader/V3/OpenApiSchemaDeserializer.cs | 72 +++++++ .../Reader/V31/OpenApiSchemaDeserializer.cs | 36 ++++ .../Reader/V32/OpenApiSchemaDeserializer.cs | 36 ++++ .../V31Tests/OpenApiSchemaTests.cs | 44 ++++ .../V32Tests/OpenApiSchemaTests.cs | 45 +++- .../V3Tests/OpenApiSchemaTests.cs | 46 ++++ .../Models/OpenApiSchemaTests.cs | 203 ++++++++++++++++-- .../References/OpenApiSchemaReferenceTests.cs | 45 ++++ 16 files changed, 862 insertions(+), 31 deletions(-) create mode 100644 src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchemaMissingProperties.cs diff --git a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs index 6d43a087a..7cb85f100 100644 --- a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs @@ -260,7 +260,7 @@ public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiReadOnlyExte /// /// Indicates whether unevaluated properties are allowed. When false, no unevaluated properties are permitted. /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-unevaluatedproperties - /// Only serialized when false and UnevaluatedPropertiesSchema (from IOpenApiSchemaWithUnevaluatedProperties) is null. + /// Only serialized when false and UnevaluatedPropertiesSchema (from IOpenApiSchemaMissingProperties) is null. /// /// /// NOTE: This property differs from the naming pattern of AdditionalPropertiesAllowed for binary compatibility reasons. diff --git a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchemaMissingProperties.cs b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchemaMissingProperties.cs new file mode 100644 index 000000000..be2d6fbb1 --- /dev/null +++ b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchemaMissingProperties.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; + +namespace Microsoft.OpenApi; + +/// +/// Compatibility interface for schema properties that cannot be added to +/// in the current major version without a breaking change. +/// This interface provides access to those properties in contexts where callers need a typed model surface. +/// +/// +/// TODO: Remove this interface in the next major version and merge its content into IOpenApiSchema. +/// +public interface IOpenApiSchemaMissingProperties +{ + /// + /// $anchor - identifies a plain-name location-independent fragment within the schema resource. + /// + public string? Anchor { get; } + + /// + /// Indicates whether unevaluated properties are allowed. When false, no unevaluated properties are permitted. + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-unevaluatedproperties + /// Only serialized when false and is null. + /// + /// + /// NOTE: This property differs from the naming pattern of AdditionalPropertiesAllowed for binary compatibility reasons. + /// In the next major version, this will be renamed to UnevaluatedPropertiesAllowed. + /// TODO: Rename to UnevaluatedPropertiesAllowed in the next major version. + /// + public bool UnevaluatedProperties { get; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-unevaluatedproperties + /// This is a schema that unevaluated properties must validate against. + /// When serialized, this takes precedence over the boolean property. + /// + /// + /// NOTE: This property differs from the naming pattern of AdditionalProperties/AdditionalPropertiesAllowed + /// for binary compatibility reasons. In the next major version: + /// - This property will be renamed to UnevaluatedProperties + /// - The current boolean UnevaluatedProperties property will be renamed to UnevaluatedPropertiesAllowed + /// + /// TODO: Rename this property to UnevaluatedProperties in the next major version. + /// + public IOpenApiSchema? UnevaluatedPropertiesSchema { get; } + + /// + /// contentEncoding - identifies the encoding of string content. + /// + public string? ContentEncoding { get; } + + /// + /// contentMediaType - identifies the media type of string content. + /// + public string? ContentMediaType { get; } + + /// + /// contentSchema - provides a schema that describes the decoded string content. + /// + public IOpenApiSchema? ContentSchema { get; } + + /// + /// propertyNames - provides a schema that validates property names. + /// + public IOpenApiSchema? PropertyNames { get; } + + /// + /// dependentSchemas - maps property names to schemas that are applied when that property is present. + /// + public IDictionary? DependentSchemas { get; } + + /// + /// if - applies a conditional schema that determines whether or should be evaluated. + /// + public IOpenApiSchema? If { get; } + + /// + /// then - applies when evaluates successfully. + /// + public IOpenApiSchema? Then { get; } + + /// + /// else - applies when does not evaluate successfully. + /// + public IOpenApiSchema? Else { get; } +} diff --git a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchemaWithUnevaluatedProperties.cs b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchemaWithUnevaluatedProperties.cs index 3379a0837..6fe7e9f9a 100644 --- a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchemaWithUnevaluatedProperties.cs +++ b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchemaWithUnevaluatedProperties.cs @@ -1,3 +1,5 @@ +using System; + namespace Microsoft.OpenApi; /// @@ -13,6 +15,7 @@ namespace Microsoft.OpenApi; /// /// TODO: Remove this interface in the next major version and merge its content into IOpenApiSchema. /// +[Obsolete("Use IOpenApiSchemaMissingProperties instead.")] public interface IOpenApiSchemaWithUnevaluatedProperties { /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index a54758002..07ad81923 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -90,6 +90,11 @@ public static class OpenApiConstants /// public const string Vocabulary = "$vocabulary"; + /// + /// Field: Anchor + /// + public const string Anchor = "$anchor"; + /// /// Field: DynamicRef /// @@ -131,9 +136,14 @@ public static class OpenApiConstants public const string UnevaluatedProperties = "unevaluatedProperties"; /// - /// Extension: x-jsonschema-unevaluatedProperties + /// Extension: x-oai-unevaluatedProperties + /// + public const string UnevaluatedPropertiesExtension = "x-oai-unevaluatedProperties"; + + /// + /// Legacy extension: x-jsonschema-unevaluatedProperties /// - public const string UnevaluatedPropertiesExtension = "x-jsonschema-unevaluatedProperties"; + public const string LegacyUnevaluatedPropertiesExtension = "x-jsonschema-unevaluatedProperties"; /// /// Field: Version @@ -535,11 +545,51 @@ public static class OpenApiConstants /// public const string PatternProperties = "patternProperties"; + /// + /// Field: PropertyNames + /// + public const string PropertyNames = "propertyNames"; + /// /// Extension: x-jsonschema-patternProperties /// public const string PatternPropertiesExtension = "x-jsonschema-patternProperties"; + /// + /// Field: DependentSchemas + /// + public const string DependentSchemas = "dependentSchemas"; + + /// + /// Field: If + /// + public const string If = "if"; + + /// + /// Field: Then + /// + public const string Then = "then"; + + /// + /// Field: Else + /// + public const string Else = "else"; + + /// + /// Field: ContentEncoding + /// + public const string ContentEncoding = "contentEncoding"; + + /// + /// Field: ContentMediaType + /// + public const string ContentMediaType = "contentMediaType"; + + /// + /// Field: ContentSchema + /// + public const string ContentSchema = "contentSchema"; + /// /// Field: AdditionalProperties /// @@ -790,6 +840,51 @@ public static class OpenApiConstants /// public const string DependentRequired = "dependentRequired"; + /// + /// Extension: x-oai-$anchor + /// + public const string AnchorExtension = "x-oai-$anchor"; + + /// + /// Extension: x-oai-propertyNames + /// + public const string PropertyNamesExtension = "x-oai-propertyNames"; + + /// + /// Extension: x-oai-dependentSchemas + /// + public const string DependentSchemasExtension = "x-oai-dependentSchemas"; + + /// + /// Extension: x-oai-if + /// + public const string IfExtension = "x-oai-if"; + + /// + /// Extension: x-oai-then + /// + public const string ThenExtension = "x-oai-then"; + + /// + /// Extension: x-oai-else + /// + public const string ElseExtension = "x-oai-else"; + + /// + /// Extension: x-oai-contentEncoding + /// + public const string ContentEncodingExtension = "x-oai-contentEncoding"; + + /// + /// Extension: x-oai-contentMediaType + /// + public const string ContentMediaTypeExtension = "x-oai-contentMediaType"; + + /// + /// Extension: x-oai-contentSchema + /// + public const string ContentSchemaExtension = "x-oai-contentSchema"; + #region V2.0 /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 40f24bdd0..e94481caa 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -9,6 +9,7 @@ namespace Microsoft.OpenApi { +#pragma warning disable CS0618 /// /// The Schema Object allows the definition of input and output data types. /// @@ -18,7 +19,7 @@ namespace Microsoft.OpenApi /// - Serialization: To produce something functionally equivalent to boolean schemas, create an empty /// for "true" behavior, or create a schema with only set to an empty schema for "false" behavior. /// - public class OpenApiSchema : IOpenApiExtensible, IOpenApiSchema, IOpenApiSchemaWithUnevaluatedProperties, IMetadataContainer + public class OpenApiSchema : IOpenApiExtensible, IOpenApiSchema, IOpenApiSchemaMissingProperties, IOpenApiSchemaWithUnevaluatedProperties, IMetadataContainer { /// public string? Title { get; set; } @@ -44,6 +45,9 @@ public class OpenApiSchema : IOpenApiExtensible, IOpenApiSchema, IOpenApiSchemaW /// public IDictionary? Definitions { get; set; } + /// + public string? Anchor { get; set; } + private string? _exclusiveMaximum; /// public string? ExclusiveMaximum @@ -243,6 +247,30 @@ public string? Minimum /// public IOpenApiSchema? UnevaluatedPropertiesSchema { get; set; } + /// + public string? ContentEncoding { get; set; } + + /// + public string? ContentMediaType { get; set; } + + /// + public IOpenApiSchema? ContentSchema { get; set; } + + /// + public IOpenApiSchema? PropertyNames { get; set; } + + /// + public IDictionary? DependentSchemas { get; set; } + + /// + public IOpenApiSchema? If { get; set; } + + /// + public IOpenApiSchema? Then { get; set; } + + /// + public IOpenApiSchema? Else { get; set; } + /// public OpenApiExternalDocs? ExternalDocs { get; set; } @@ -282,13 +310,32 @@ internal OpenApiSchema(IOpenApiSchema schema) Schema = schema.Schema ?? Schema; Comment = schema.Comment ?? Comment; Vocabulary = schema.Vocabulary != null ? new Dictionary(schema.Vocabulary) : null; + if (schema is IOpenApiSchemaMissingProperties { Anchor: not null } missingPropertiesWithAnchor) + { + Anchor = missingPropertiesWithAnchor.Anchor; + } DynamicAnchor = schema.DynamicAnchor ?? DynamicAnchor; DynamicRef = schema.DynamicRef ?? DynamicRef; Definitions = schema.Definitions != null ? new Dictionary(schema.Definitions) : null; - UnevaluatedProperties = schema.UnevaluatedProperties; - if (schema is IOpenApiSchemaWithUnevaluatedProperties { UnevaluatedPropertiesSchema: { } unevaluatedSchema }) + if (schema is IOpenApiSchemaMissingProperties missingProperties) { - UnevaluatedPropertiesSchema = unevaluatedSchema.CreateShallowCopy(); + UnevaluatedProperties = missingProperties.UnevaluatedProperties; + if (missingProperties.UnevaluatedPropertiesSchema is { } unevaluatedSchema) + { + UnevaluatedPropertiesSchema = unevaluatedSchema.CreateShallowCopy(); + } + ContentEncoding = missingProperties.ContentEncoding ?? ContentEncoding; + ContentMediaType = missingProperties.ContentMediaType ?? ContentMediaType; + ContentSchema = missingProperties.ContentSchema?.CreateShallowCopy(); + PropertyNames = missingProperties.PropertyNames?.CreateShallowCopy(); + DependentSchemas = missingProperties.DependentSchemas != null ? new Dictionary(missingProperties.DependentSchemas) : null; + If = missingProperties.If?.CreateShallowCopy(); + Then = missingProperties.Then?.CreateShallowCopy(); + Else = missingProperties.Else?.CreateShallowCopy(); + } + else + { + UnevaluatedProperties = schema.UnevaluatedProperties; } ExclusiveMaximum = schema.ExclusiveMaximum ?? ExclusiveMaximum; ExclusiveMinimum = schema.ExclusiveMinimum ?? ExclusiveMinimum; @@ -557,18 +604,22 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version // 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) { + var unevaluatedPropertiesExtensionName = version == OpenApiSpecVersion.OpenApi3_0 + ? OpenApiConstants.UnevaluatedPropertiesExtension + : OpenApiConstants.LegacyUnevaluatedPropertiesExtension; + // Write UnevaluatedPropertiesSchema as extension if present if (UnevaluatedPropertiesSchema is not null) { writer.WriteOptionalObject( - OpenApiConstants.UnevaluatedPropertiesExtension, + unevaluatedPropertiesExtensionName, UnevaluatedPropertiesSchema, callback); } // Write boolean false as extension if explicitly set to false else if (!UnevaluatedProperties) { - writer.WritePropertyName(OpenApiConstants.UnevaluatedPropertiesExtension); + writer.WritePropertyName(unevaluatedPropertiesExtensionName); writer.WriteValue(false); } } @@ -578,6 +629,8 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version { writer.WriteOptionalMap(OpenApiConstants.PatternPropertiesExtension, PatternProperties, callback); } + + WriteV3CompatibilityKeywords(writer, callback); } // extensions @@ -607,6 +660,7 @@ internal void WriteJsonSchemaKeywords(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.Const, Const); writer.WriteOptionalMap(OpenApiConstants.Vocabulary, Vocabulary, (w, s) => w.WriteValue(s)); writer.WriteOptionalMap(OpenApiConstants.Defs, Definitions, (w, s) => s.SerializeAsV31(w)); + writer.WriteProperty(OpenApiConstants.Anchor, Anchor); writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef); writer.WriteProperty(OpenApiConstants.DynamicAnchor, DynamicAnchor); @@ -630,6 +684,27 @@ internal void WriteJsonSchemaKeywords(IOpenApiWriter writer) 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)); + writer.WriteProperty(OpenApiConstants.ContentEncoding, ContentEncoding); + writer.WriteProperty(OpenApiConstants.ContentMediaType, ContentMediaType); + writer.WriteOptionalObject(OpenApiConstants.ContentSchema, ContentSchema, (w, s) => s.SerializeAsV31(w)); + writer.WriteOptionalObject(OpenApiConstants.PropertyNames, PropertyNames, (w, s) => s.SerializeAsV31(w)); + writer.WriteOptionalMap(OpenApiConstants.DependentSchemas, DependentSchemas, (w, s) => s.SerializeAsV31(w)); + writer.WriteOptionalObject(OpenApiConstants.If, If, (w, s) => s.SerializeAsV31(w)); + writer.WriteOptionalObject(OpenApiConstants.Then, Then, (w, s) => s.SerializeAsV31(w)); + writer.WriteOptionalObject(OpenApiConstants.Else, Else, (w, s) => s.SerializeAsV31(w)); + } + + private void WriteV3CompatibilityKeywords(IOpenApiWriter writer, Action callback) + { + writer.WriteProperty(OpenApiConstants.AnchorExtension, Anchor); + writer.WriteProperty(OpenApiConstants.ContentEncodingExtension, ContentEncoding); + writer.WriteProperty(OpenApiConstants.ContentMediaTypeExtension, ContentMediaType); + writer.WriteOptionalObject(OpenApiConstants.ContentSchemaExtension, ContentSchema, callback); + writer.WriteOptionalObject(OpenApiConstants.PropertyNamesExtension, PropertyNames, callback); + writer.WriteOptionalMap(OpenApiConstants.DependentSchemasExtension, DependentSchemas, callback); + writer.WriteOptionalObject(OpenApiConstants.IfExtension, If, callback); + writer.WriteOptionalObject(OpenApiConstants.ThenExtension, Then, callback); + writer.WriteOptionalObject(OpenApiConstants.ElseExtension, Else, callback); } internal void WriteAsItemsProperties(IOpenApiWriter writer) @@ -799,6 +874,7 @@ private void SerializeAsV2( // oneOf (Not Supported in V2) - Write the first schema only as an allOf. writer.WriteOptionalCollection(OpenApiConstants.AllOf, OneOf?.Take(1), (w, s) => s.SerializeAsV2(w)); } + #pragma warning restore CS0618 } // properties @@ -855,14 +931,14 @@ private void SerializeAsV2( if (UnevaluatedPropertiesSchema is not null) { writer.WriteOptionalObject( - OpenApiConstants.UnevaluatedPropertiesExtension, + OpenApiConstants.LegacyUnevaluatedPropertiesExtension, UnevaluatedPropertiesSchema, (w, s) => s.SerializeAsV2(w)); } // Write boolean false as extension if explicitly set to false else if (!UnevaluatedProperties) { - writer.WritePropertyName(OpenApiConstants.UnevaluatedPropertiesExtension); + writer.WritePropertyName(OpenApiConstants.LegacyUnevaluatedPropertiesExtension); writer.WriteValue(false); } } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs index 67eb79645..570e395c8 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs @@ -7,10 +7,11 @@ namespace Microsoft.OpenApi { +#pragma warning disable CS0618 /// /// Schema reference object /// - public class OpenApiSchemaReference : BaseOpenApiReferenceHolder, IOpenApiSchema, IOpenApiSchemaWithUnevaluatedProperties, IOpenApiExtensible + public class OpenApiSchemaReference : BaseOpenApiReferenceHolder, IOpenApiSchema, IOpenApiSchemaMissingProperties, IOpenApiSchemaWithUnevaluatedProperties, IOpenApiExtensible { /// @@ -62,6 +63,8 @@ public string? Title /// public IDictionary? Definitions { get => Target?.Definitions; } /// + public string? Anchor { get => (Target as IOpenApiSchemaMissingProperties)?.Anchor; } + /// public string? ExclusiveMaximum { get => Target?.ExclusiveMaximum; } /// public string? ExclusiveMinimum { get => Target?.ExclusiveMinimum; } @@ -146,7 +149,23 @@ public IList? Examples /// public bool UnevaluatedProperties { get => Target?.UnevaluatedProperties ?? true; } /// - public IOpenApiSchema? UnevaluatedPropertiesSchema { get => (Target as IOpenApiSchemaWithUnevaluatedProperties)?.UnevaluatedPropertiesSchema; } + public IOpenApiSchema? UnevaluatedPropertiesSchema { get => (Target as IOpenApiSchemaMissingProperties)?.UnevaluatedPropertiesSchema; } + /// + public string? ContentEncoding { get => (Target as IOpenApiSchemaMissingProperties)?.ContentEncoding; } + /// + public string? ContentMediaType { get => (Target as IOpenApiSchemaMissingProperties)?.ContentMediaType; } + /// + public IOpenApiSchema? ContentSchema { get => (Target as IOpenApiSchemaMissingProperties)?.ContentSchema; } + /// + public IOpenApiSchema? PropertyNames { get => (Target as IOpenApiSchemaMissingProperties)?.PropertyNames; } + /// + public IDictionary? DependentSchemas { get => (Target as IOpenApiSchemaMissingProperties)?.DependentSchemas; } + /// + public IOpenApiSchema? If { get => (Target as IOpenApiSchemaMissingProperties)?.If; } + /// + public IOpenApiSchema? Then { get => (Target as IOpenApiSchemaMissingProperties)?.Then; } + /// + public IOpenApiSchema? Else { get => (Target as IOpenApiSchemaMissingProperties)?.Else; } /// public OpenApiExternalDocs? ExternalDocs { get => Target?.ExternalDocs; } /// @@ -226,5 +245,6 @@ protected override JsonSchemaReference CopyReference(JsonSchemaReference sourceR { return new JsonSchemaReference(sourceReference); } + #pragma warning restore CS0618 } } diff --git a/src/Microsoft.OpenApi/PublicAPI.Shipped.txt b/src/Microsoft.OpenApi/PublicAPI.Shipped.txt index 4424e5862..4c197c380 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Shipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Shipped.txt @@ -2022,7 +2022,7 @@ virtual Microsoft.OpenApi.OpenApiXml.SerializeAsV3(Microsoft.OpenApi.IOpenApiWri virtual Microsoft.OpenApi.OpenApiXml.SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter! writer) -> void virtual Microsoft.OpenApi.OpenApiXml.SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter! writer) -> void const Microsoft.OpenApi.OpenApiConstants.OAuth2MetadataUrl = "oauth2MetadataUrl" -> string! -const Microsoft.OpenApi.OpenApiConstants.UnevaluatedPropertiesExtension = "x-jsonschema-unevaluatedProperties" -> string! +const Microsoft.OpenApi.OpenApiConstants.UnevaluatedPropertiesExtension = "x-oai-unevaluatedProperties" -> string! Microsoft.OpenApi.IOAuth2MetadataProvider Microsoft.OpenApi.IOAuth2MetadataProvider.OAuth2MetadataUrl.get -> System.Uri? Microsoft.OpenApi.IOpenApiSchemaWithUnevaluatedProperties diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index 7dc5c5811..2c1dde11f 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -1 +1,59 @@ #nullable enable +const Microsoft.OpenApi.OpenApiConstants.Anchor = "$anchor" -> string! +const Microsoft.OpenApi.OpenApiConstants.AnchorExtension = "x-oai-$anchor" -> string! +const Microsoft.OpenApi.OpenApiConstants.ContentEncoding = "contentEncoding" -> string! +const Microsoft.OpenApi.OpenApiConstants.ContentEncodingExtension = "x-oai-contentEncoding" -> string! +const Microsoft.OpenApi.OpenApiConstants.ContentMediaType = "contentMediaType" -> string! +const Microsoft.OpenApi.OpenApiConstants.ContentMediaTypeExtension = "x-oai-contentMediaType" -> string! +const Microsoft.OpenApi.OpenApiConstants.ContentSchema = "contentSchema" -> string! +const Microsoft.OpenApi.OpenApiConstants.ContentSchemaExtension = "x-oai-contentSchema" -> string! +const Microsoft.OpenApi.OpenApiConstants.DependentSchemas = "dependentSchemas" -> string! +const Microsoft.OpenApi.OpenApiConstants.DependentSchemasExtension = "x-oai-dependentSchemas" -> string! +const Microsoft.OpenApi.OpenApiConstants.Else = "else" -> string! +const Microsoft.OpenApi.OpenApiConstants.ElseExtension = "x-oai-else" -> string! +const Microsoft.OpenApi.OpenApiConstants.If = "if" -> string! +const Microsoft.OpenApi.OpenApiConstants.IfExtension = "x-oai-if" -> string! +const Microsoft.OpenApi.OpenApiConstants.LegacyUnevaluatedPropertiesExtension = "x-jsonschema-unevaluatedProperties" -> string! +const Microsoft.OpenApi.OpenApiConstants.PropertyNames = "propertyNames" -> string! +const Microsoft.OpenApi.OpenApiConstants.PropertyNamesExtension = "x-oai-propertyNames" -> string! +const Microsoft.OpenApi.OpenApiConstants.Then = "then" -> string! +const Microsoft.OpenApi.OpenApiConstants.ThenExtension = "x-oai-then" -> string! +Microsoft.OpenApi.IOpenApiSchemaMissingProperties +Microsoft.OpenApi.IOpenApiSchemaMissingProperties.Anchor.get -> string? +Microsoft.OpenApi.IOpenApiSchemaMissingProperties.ContentEncoding.get -> string? +Microsoft.OpenApi.IOpenApiSchemaMissingProperties.ContentMediaType.get -> string? +Microsoft.OpenApi.IOpenApiSchemaMissingProperties.ContentSchema.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.IOpenApiSchemaMissingProperties.DependentSchemas.get -> System.Collections.Generic.IDictionary? +Microsoft.OpenApi.IOpenApiSchemaMissingProperties.Else.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.IOpenApiSchemaMissingProperties.If.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.IOpenApiSchemaMissingProperties.PropertyNames.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.IOpenApiSchemaMissingProperties.Then.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.IOpenApiSchemaMissingProperties.UnevaluatedProperties.get -> bool +Microsoft.OpenApi.IOpenApiSchemaMissingProperties.UnevaluatedPropertiesSchema.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.OpenApiSchema.Anchor.get -> string? +Microsoft.OpenApi.OpenApiSchema.Anchor.set -> void +Microsoft.OpenApi.OpenApiSchema.ContentEncoding.get -> string? +Microsoft.OpenApi.OpenApiSchema.ContentEncoding.set -> void +Microsoft.OpenApi.OpenApiSchema.ContentMediaType.get -> string? +Microsoft.OpenApi.OpenApiSchema.ContentMediaType.set -> void +Microsoft.OpenApi.OpenApiSchema.ContentSchema.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.OpenApiSchema.ContentSchema.set -> void +Microsoft.OpenApi.OpenApiSchema.DependentSchemas.get -> System.Collections.Generic.IDictionary? +Microsoft.OpenApi.OpenApiSchema.DependentSchemas.set -> void +Microsoft.OpenApi.OpenApiSchema.Else.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.OpenApiSchema.Else.set -> void +Microsoft.OpenApi.OpenApiSchema.If.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.OpenApiSchema.If.set -> void +Microsoft.OpenApi.OpenApiSchema.PropertyNames.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.OpenApiSchema.PropertyNames.set -> void +Microsoft.OpenApi.OpenApiSchema.Then.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.OpenApiSchema.Then.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Anchor.get -> string? +Microsoft.OpenApi.OpenApiSchemaReference.ContentEncoding.get -> string? +Microsoft.OpenApi.OpenApiSchemaReference.ContentMediaType.get -> string? +Microsoft.OpenApi.OpenApiSchemaReference.ContentSchema.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.OpenApiSchemaReference.DependentSchemas.get -> System.Collections.Generic.IDictionary? +Microsoft.OpenApi.OpenApiSchemaReference.Else.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.OpenApiSchemaReference.If.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.OpenApiSchemaReference.PropertyNames.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.OpenApiSchemaReference.Then.get -> Microsoft.OpenApi.IOpenApiSchema? diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs index 0ad8e747c..9fdd69702 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs @@ -282,6 +282,78 @@ internal static partial class OpenApiV3Deserializer OpenApiConstants.PatternPropertiesExtension, (o, n, t, c) => o.PatternProperties = n.CreateMap(LoadSchema, t, c) }, + { + OpenApiConstants.UnevaluatedPropertiesExtension, + (o, n, t, c) => + { + if (n is JsonValue) + { + var value = n.GetScalarValue(); + if (value is not null) + { + o.UnevaluatedProperties = bool.Parse(value); + } + } + else + { + o.UnevaluatedPropertiesSchema = LoadSchema(n, t, c); + } + } + }, + { + OpenApiConstants.LegacyUnevaluatedPropertiesExtension, + (o, n, t, c) => + { + if (n is JsonValue) + { + var value = n.GetScalarValue(); + if (value is not null) + { + o.UnevaluatedProperties = bool.Parse(value); + } + } + else + { + o.UnevaluatedPropertiesSchema = LoadSchema(n, t, c); + } + } + }, + { + OpenApiConstants.AnchorExtension, + (o, n, _, _) => o.Anchor = n.GetScalarValue() + }, + { + OpenApiConstants.ContentEncodingExtension, + (o, n, _, _) => o.ContentEncoding = n.GetScalarValue() + }, + { + OpenApiConstants.ContentMediaTypeExtension, + (o, n, _, _) => o.ContentMediaType = n.GetScalarValue() + }, + { + OpenApiConstants.ContentSchemaExtension, + (o, n, doc, c) => o.ContentSchema = LoadSchema(n, doc, c) + }, + { + OpenApiConstants.PropertyNamesExtension, + (o, n, doc, c) => o.PropertyNames = LoadSchema(n, doc, c) + }, + { + OpenApiConstants.DependentSchemasExtension, + (o, n, t, c) => o.DependentSchemas = n.CreateMap(LoadSchema, t, c) + }, + { + OpenApiConstants.IfExtension, + (o, n, doc, c) => o.If = LoadSchema(n, doc, c) + }, + { + OpenApiConstants.ThenExtension, + (o, n, doc, c) => o.Then = LoadSchema(n, doc, c) + }, + { + OpenApiConstants.ElseExtension, + (o, n, doc, c) => o.Else = LoadSchema(n, doc, c) + }, }; private static readonly PatternFieldMap _openApiSchemaPatternFields = new() diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs index 14deab765..f4b98234f 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -43,6 +43,10 @@ internal static partial class OpenApiV31Deserializer { "$defs", (o, n, t, c) => o.Definitions = n.CreateMap(LoadSchema, t, c) + }, + { + "$anchor", + (o, n, _, _) => o.Anchor = n.GetScalarValue() }, { "multipleOf", @@ -164,6 +168,18 @@ internal static partial class OpenApiV31Deserializer } } }, + { + "contentEncoding", + (o, n, _, _) => o.ContentEncoding = n.GetScalarValue() + }, + { + "contentMediaType", + (o, n, _, _) => o.ContentMediaType = n.GetScalarValue() + }, + { + "contentSchema", + (o, n, doc, c) => o.ContentSchema = LoadSchema(n, doc, c) + }, { "maxProperties", (o, n, _, _) => @@ -249,6 +265,10 @@ internal static partial class OpenApiV31Deserializer "patternProperties", (o, n, t, c) => o.PatternProperties = n.CreateMap(LoadSchema, t, c) }, + { + "propertyNames", + (o, n, doc, c) => o.PropertyNames = LoadSchema(n, doc, c) + }, { "additionalProperties", (o, n, doc, c) => { @@ -356,6 +376,22 @@ internal static partial class OpenApiV31Deserializer o.DependentRequired = n.CreateArrayMap((n2, _) => n2.GetScalarValue()!, doc, c); } }, + { + "dependentSchemas", + (o, n, t, c) => o.DependentSchemas = n.CreateMap(LoadSchema, t, c) + }, + { + "if", + (o, n, doc, c) => o.If = LoadSchema(n, doc, c) + }, + { + "then", + (o, n, doc, c) => o.Then = LoadSchema(n, doc, c) + }, + { + "else", + (o, n, doc, c) => o.Else = LoadSchema(n, doc, c) + }, }; private static readonly PatternFieldMap _openApiSchemaPatternFields = new() diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs index f0e07724f..dca4f339c 100644 --- a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs @@ -43,6 +43,10 @@ internal static partial class OpenApiV32Deserializer { "$defs", (o, n, t, c) => o.Definitions = n.CreateMap(LoadSchema, t, c) + }, + { + "$anchor", + (o, n, _, _) => o.Anchor = n.GetScalarValue() }, { "multipleOf", @@ -164,6 +168,18 @@ internal static partial class OpenApiV32Deserializer } } }, + { + "contentEncoding", + (o, n, _, _) => o.ContentEncoding = n.GetScalarValue() + }, + { + "contentMediaType", + (o, n, _, _) => o.ContentMediaType = n.GetScalarValue() + }, + { + "contentSchema", + (o, n, doc, c) => o.ContentSchema = LoadSchema(n, doc, c) + }, { "maxProperties", (o, n, _, _) => @@ -249,6 +265,10 @@ internal static partial class OpenApiV32Deserializer "patternProperties", (o, n, t, c) => o.PatternProperties = n.CreateMap(LoadSchema, t, c) }, + { + "propertyNames", + (o, n, doc, c) => o.PropertyNames = LoadSchema(n, doc, c) + }, { "additionalProperties", (o, n, doc, c) => { @@ -356,6 +376,22 @@ internal static partial class OpenApiV32Deserializer o.DependentRequired = n.CreateArrayMap((n2, _) => n2.GetScalarValue()!, doc, c); } }, + { + "dependentSchemas", + (o, n, t, c) => o.DependentSchemas = n.CreateMap(LoadSchema, t, c) + }, + { + "if", + (o, n, doc, c) => o.If = LoadSchema(n, doc, c) + }, + { + "then", + (o, n, doc, c) => o.Then = LoadSchema(n, doc, c) + }, + { + "else", + (o, n, doc, c) => o.Else = LoadSchema(n, doc, c) + }, }; private static readonly PatternFieldMap _openApiSchemaPatternFields = new() diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs index ec66dcbb9..e112eb1ae 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs @@ -857,6 +857,50 @@ public void ParseSchemaWithoutUnevaluatedPropertiesDefaultsToTrue() Assert.True(actual.UnevaluatedProperties); // Explicitly verify the default } + [Fact] + public void ParseSchemaWithMissingJsonSchemaProperties() + { + var schema = @"{ + ""$anchor"": ""root"", + ""contentEncoding"": ""base64"", + ""contentMediaType"": ""application/jwt"", + ""contentSchema"": { + ""type"": ""array"" + }, + ""propertyNames"": { + ""pattern"": ""^[a-z]+$"" + }, + ""dependentSchemas"": { + ""token"": { + ""type"": ""string"" + } + }, + ""if"": { + ""required"": [""token""] + }, + ""then"": { + ""minProperties"": 1 + }, + ""else"": { + ""maxProperties"": 0 + } +}"; + + var actual = OpenApiModelFactory.Parse(schema, OpenApiSpecVersion.OpenApi3_1, new(), out _); + var missingProperties = Assert.IsAssignableFrom(actual); + + Assert.Equal("root", missingProperties.Anchor); + Assert.Equal("base64", missingProperties.ContentEncoding); + Assert.Equal("application/jwt", missingProperties.ContentMediaType); + Assert.Equal(JsonSchemaType.Array, missingProperties.ContentSchema?.Type); + Assert.Equal("^[a-z]+$", missingProperties.PropertyNames?.Pattern); + Assert.Equal(JsonSchemaType.String, missingProperties.DependentSchemas?["token"].Type); + Assert.NotNull(missingProperties.If?.Required); + Assert.Contains("token", missingProperties.If.Required); + Assert.Equal(1, missingProperties.Then?.MinProperties); + Assert.Equal(0, missingProperties.Else?.MaxProperties); + } + [Theory] [InlineData("{}")] [InlineData("true")] diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs index 621cd156c..804117d5c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs @@ -708,6 +708,50 @@ public void ParseSchemaWithUnevaluatedPropertiesComplexSchema() Assert.Equivalent(expected, actual); } + [Fact] + public void ParseSchemaWithMissingJsonSchemaProperties() + { + var schema = @"{ + ""$anchor"": ""root"", + ""contentEncoding"": ""base64"", + ""contentMediaType"": ""application/jwt"", + ""contentSchema"": { + ""type"": ""array"" + }, + ""propertyNames"": { + ""pattern"": ""^[a-z]+$"" + }, + ""dependentSchemas"": { + ""token"": { + ""type"": ""string"" + } + }, + ""if"": { + ""required"": [""token""] + }, + ""then"": { + ""minProperties"": 1 + }, + ""else"": { + ""maxProperties"": 0 + } +}"; + + var actual = OpenApiModelFactory.Parse(schema, OpenApiSpecVersion.OpenApi3_2, new(), out _); + var missingProperties = Assert.IsAssignableFrom(actual); + + Assert.Equal("root", missingProperties.Anchor); + Assert.Equal("base64", missingProperties.ContentEncoding); + Assert.Equal("application/jwt", missingProperties.ContentMediaType); + Assert.Equal(JsonSchemaType.Array, missingProperties.ContentSchema?.Type); + Assert.Equal("^[a-z]+$", missingProperties.PropertyNames?.Pattern); + Assert.Equal(JsonSchemaType.String, missingProperties.DependentSchemas?["token"].Type); + Assert.NotNull(missingProperties.If?.Required); + Assert.Contains("token", missingProperties.If.Required); + Assert.Equal(1, missingProperties.Then?.MinProperties); + Assert.Equal(0, missingProperties.Else?.MaxProperties); + } + [Theory] [InlineData("{}")] [InlineData("true")] @@ -738,4 +782,3 @@ public void DeserializeFalseSchemaParsesAsNotEmptySchema() } } } - diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs index 9b0f0b94b..110e2c342 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -178,6 +178,52 @@ public void ParseDictionarySchemaShouldSucceed() } } + [Fact] + public void ParseSchemaWithOaiCompatibilityKeywordsShouldSucceed() + { + var schemaJson = @"{ + ""x-oai-$anchor"": ""root"", + ""x-oai-unevaluatedProperties"": false, + ""x-oai-contentEncoding"": ""base64"", + ""x-oai-contentMediaType"": ""application/jwt"", + ""x-oai-contentSchema"": { + ""type"": ""array"" + }, + ""x-oai-propertyNames"": { + ""pattern"": ""^[a-z]+$"" + }, + ""x-oai-dependentSchemas"": { + ""token"": { + ""type"": ""string"" + } + }, + ""x-oai-if"": { + ""required"": [""token""] + }, + ""x-oai-then"": { + ""minProperties"": 1 + }, + ""x-oai-else"": { + ""maxProperties"": 0 + } +}"; + + var schema = OpenApiModelFactory.Parse(schemaJson, OpenApiSpecVersion.OpenApi3_0, new(), out _, "json", SettingsFixture.ReaderSettings); + var missingProperties = Assert.IsAssignableFrom(schema); + + Assert.Equal("root", missingProperties.Anchor); + Assert.False(missingProperties.UnevaluatedProperties); + Assert.Equal("base64", missingProperties.ContentEncoding); + Assert.Equal("application/jwt", missingProperties.ContentMediaType); + Assert.Equal(JsonSchemaType.Array, missingProperties.ContentSchema?.Type); + Assert.Equal("^[a-z]+$", missingProperties.PropertyNames?.Pattern); + Assert.Equal(JsonSchemaType.String, missingProperties.DependentSchemas?["token"].Type); + Assert.NotNull(missingProperties.If?.Required); + Assert.Contains("token", missingProperties.If.Required); + Assert.Equal(1, missingProperties.Then?.MinProperties); + Assert.Equal(0, missingProperties.Else?.MaxProperties); + } + [Fact] public void ParseBasicSchemaWithExampleShouldSucceed() { diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 40034eded..06ade24a1 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -523,6 +523,48 @@ public void OpenApiSchemaCopyConstructorWithUnevaluatedPropertiesSchemaSucceeds( Assert.Equal(100, baseSchema.UnevaluatedPropertiesSchema.MaxLength); } + [Fact] + public void OpenApiSchemaCopyConstructorWithMissingPropertiesSucceeds() + { + var baseSchema = new OpenApiSchema + { + Anchor = "root", + UnevaluatedProperties = false, + UnevaluatedPropertiesSchema = new OpenApiSchema { Type = JsonSchemaType.String }, + ContentEncoding = "base64", + ContentMediaType = "application/jwt", + ContentSchema = new OpenApiSchema { Type = JsonSchemaType.Array }, + PropertyNames = new OpenApiSchema { Pattern = "^[a-z]+$" }, + DependentSchemas = new Dictionary + { + ["token"] = new OpenApiSchema { Type = JsonSchemaType.String } + }, + If = new OpenApiSchema { Required = new HashSet { "token" } }, + Then = new OpenApiSchema { MinProperties = 1 }, + Else = new OpenApiSchema { MaxProperties = 0 } + }; + + var actualSchema = Assert.IsType(baseSchema.CreateShallowCopy()); + var actualMissingProperties = Assert.IsAssignableFrom(actualSchema); + + Assert.Equal("root", actualMissingProperties.Anchor); + Assert.False(actualMissingProperties.UnevaluatedProperties); + Assert.NotNull(actualMissingProperties.UnevaluatedPropertiesSchema); + Assert.Equal("base64", actualMissingProperties.ContentEncoding); + Assert.Equal("application/jwt", actualMissingProperties.ContentMediaType); + Assert.NotNull(actualMissingProperties.ContentSchema); + Assert.NotNull(actualMissingProperties.PropertyNames); + Assert.NotNull(actualMissingProperties.DependentSchemas); + Assert.NotNull(actualMissingProperties.If); + Assert.NotNull(actualMissingProperties.Then); + Assert.NotNull(actualMissingProperties.Else); + Assert.NotSame(baseSchema.ContentSchema, actualMissingProperties.ContentSchema); + Assert.NotSame(baseSchema.PropertyNames, actualMissingProperties.PropertyNames); + Assert.NotSame(baseSchema.If, actualMissingProperties.If); + Assert.NotSame(baseSchema.Then, actualMissingProperties.Then); + Assert.NotSame(baseSchema.Else, actualMissingProperties.Else); + } + public static TheoryData SchemaExamples() { return new() @@ -1256,32 +1298,38 @@ public async Task SerializeUnevaluatedPropertiesSchemaTakesPrecedenceOverBoolean Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); } - [Theory] - [InlineData(OpenApiSpecVersion.OpenApi2_0)] - [InlineData(OpenApiSpecVersion.OpenApi3_0)] - public async Task SerializeUnevaluatedPropertiesAsExtensionInEarlierVersions(OpenApiSpecVersion version) + [Fact] + public async Task SerializeUnevaluatedPropertiesAsExtensionInV2() { var expected = @"{ ""x-jsonschema-unevaluatedProperties"": false }"; - // Given - UnevaluatedProperties should be emitted as extension in versions < 3.1 var schema = new OpenApiSchema { UnevaluatedProperties = false }; - // When - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); - // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); } - [Theory] - [InlineData(OpenApiSpecVersion.OpenApi2_0)] - [InlineData(OpenApiSpecVersion.OpenApi3_0)] - public async Task SerializeUnevaluatedPropertiesSchemaAsExtensionInEarlierVersions(OpenApiSpecVersion version) + [Fact] + public async Task SerializeUnevaluatedPropertiesAsExtensionInV3() + { + var expected = @"{ ""x-oai-unevaluatedProperties"": false }"; + var schema = new OpenApiSchema + { + UnevaluatedProperties = false + }; + + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Fact] + public async Task SerializeUnevaluatedPropertiesSchemaAsExtensionInV2() { var expected = @"{ ""x-jsonschema-unevaluatedProperties"": { ""type"": ""string"" } }"; - // Given - UnevaluatedPropertiesSchema should be emitted as extension in versions < 3.1 var schema = new OpenApiSchema { UnevaluatedPropertiesSchema = new OpenApiSchema @@ -1290,10 +1338,25 @@ public async Task SerializeUnevaluatedPropertiesSchemaAsExtensionInEarlierVersio } }; - // When - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); + } + + [Fact] + public async Task SerializeUnevaluatedPropertiesSchemaAsExtensionInV3() + { + var expected = @"{ ""x-oai-unevaluatedProperties"": { ""type"": ""string"" } }"; + var schema = new OpenApiSchema + { + UnevaluatedPropertiesSchema = new OpenApiSchema + { + Type = JsonSchemaType.String + } + }; + + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); - // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); } @@ -1316,6 +1379,114 @@ public async Task SerializeUnevaluatedPropertiesTrueNotEmittedInEarlierVersions( Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); } + [Fact] + public async Task SerializeMissingPropertiesEmitsJsonSchemaKeywordsInV31() + { + var expected = JsonNode.Parse(""" + { + "$anchor": "root", + "contentEncoding": "base64", + "contentMediaType": "application/jwt", + "contentSchema": { + "type": "array" + }, + "propertyNames": { + "pattern": "^[a-z]+$" + }, + "dependentSchemas": { + "token": { + "type": "string" + } + }, + "if": { + "required": [ + "token" + ] + }, + "then": { + "minProperties": 1 + }, + "else": { + "maxProperties": 0 + } + } + """); + + var schema = new OpenApiSchema + { + Anchor = "root", + ContentEncoding = "base64", + ContentMediaType = "application/jwt", + ContentSchema = new OpenApiSchema { Type = JsonSchemaType.Array }, + PropertyNames = new OpenApiSchema { Pattern = "^[a-z]+$" }, + DependentSchemas = new Dictionary + { + ["token"] = new OpenApiSchema { Type = JsonSchemaType.String } + }, + If = new OpenApiSchema { Required = new HashSet { "token" } }, + Then = new OpenApiSchema { MinProperties = 1 }, + Else = new OpenApiSchema { MaxProperties = 0 } + }; + + var actual = JsonNode.Parse(await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1)); + + Assert.True(JsonNode.DeepEquals(expected, actual)); + } + + [Fact] + public async Task SerializeMissingPropertiesEmitsOaiExtensionsInV3() + { + var expected = JsonNode.Parse(""" + { + "x-oai-$anchor": "root", + "x-oai-contentEncoding": "base64", + "x-oai-contentMediaType": "application/jwt", + "x-oai-contentSchema": { + "type": "array" + }, + "x-oai-propertyNames": { + "pattern": "^[a-z]+$" + }, + "x-oai-dependentSchemas": { + "token": { + "type": "string" + } + }, + "x-oai-if": { + "required": [ + "token" + ] + }, + "x-oai-then": { + "minProperties": 1 + }, + "x-oai-else": { + "maxProperties": 0 + } + } + """); + + var schema = new OpenApiSchema + { + Anchor = "root", + ContentEncoding = "base64", + ContentMediaType = "application/jwt", + ContentSchema = new OpenApiSchema { Type = JsonSchemaType.Array }, + PropertyNames = new OpenApiSchema { Pattern = "^[a-z]+$" }, + DependentSchemas = new Dictionary + { + ["token"] = new OpenApiSchema { Type = JsonSchemaType.String } + }, + If = new OpenApiSchema { Required = new HashSet { "token" } }, + Then = new OpenApiSchema { MinProperties = 1 }, + Else = new OpenApiSchema { MaxProperties = 0 } + }; + + var actual = JsonNode.Parse(await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0)); + + Assert.True(JsonNode.DeepEquals(expected, actual)); + } + [Theory] [InlineData(JsonSchemaType.Array, "array")] [InlineData(JsonSchemaType.String, "string")] diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs index 488488518..ebf8423d1 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs @@ -119,6 +119,51 @@ public void SchemaReferenceWithoutAnnotationsShouldFallbackToTarget() Assert.Equal("target example", schemaReference.Examples.First()?.GetValue()); } + [Fact] + public void SchemaReferenceExposesMissingPropertiesFromTarget() + { + var workingDocument = new OpenApiDocument + { + Components = new OpenApiComponents(), + }; + const string referenceId = "targetSchema"; + workingDocument.Components.Schemas = new Dictionary + { + [referenceId] = new OpenApiSchema + { + Anchor = "root", + UnevaluatedProperties = false, + ContentEncoding = "base64", + ContentMediaType = "application/jwt", + ContentSchema = new OpenApiSchema { Type = JsonSchemaType.Array }, + PropertyNames = new OpenApiSchema { Pattern = "^[a-z]+$" }, + DependentSchemas = new Dictionary + { + ["token"] = new OpenApiSchema { Type = JsonSchemaType.String } + }, + If = new OpenApiSchema { Required = new HashSet { "token" } }, + Then = new OpenApiSchema { MinProperties = 1 }, + Else = new OpenApiSchema { MaxProperties = 0 } + } + }; + workingDocument.Workspace.RegisterComponents(workingDocument); + + var schemaReference = new OpenApiSchemaReference(referenceId, workingDocument); + var missingProperties = Assert.IsAssignableFrom(schemaReference); + + Assert.Equal("root", missingProperties.Anchor); + Assert.False(missingProperties.UnevaluatedProperties); + Assert.Equal("base64", missingProperties.ContentEncoding); + Assert.Equal("application/jwt", missingProperties.ContentMediaType); + Assert.Equal(JsonSchemaType.Array, missingProperties.ContentSchema?.Type); + Assert.Equal("^[a-z]+$", missingProperties.PropertyNames?.Pattern); + Assert.Equal(JsonSchemaType.String, missingProperties.DependentSchemas?["token"].Type); + Assert.NotNull(missingProperties.If?.Required); + Assert.Contains("token", missingProperties.If.Required); + Assert.Equal(1, missingProperties.Then?.MinProperties); + Assert.Equal(0, missingProperties.Else?.MaxProperties); + } + [Theory] [InlineData(true)] [InlineData(false)] From 6e22ec6948509d2e256932ee55f1781a544cb53f Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 9 Jun 2026 12:09:17 -0400 Subject: [PATCH 2/9] fix(library): use version-specific schema keyword callbacks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 22 ++++++------- .../Mocks/OpenApiSchemaSerializationTests.cs | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index e94481caa..4f44bb504 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -451,7 +451,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version if (version >= OpenApiSpecVersion.OpenApi3_1) { - WriteJsonSchemaKeywords(writer); + WriteJsonSchemaKeywords(writer, callback); } // title @@ -652,14 +652,14 @@ public virtual void SerializeAsV2(IOpenApiWriter writer) SerializeAsV2(writer: writer, parentRequiredProperties: new HashSet(), propertyName: null); } - internal void WriteJsonSchemaKeywords(IOpenApiWriter writer) + internal void WriteJsonSchemaKeywords(IOpenApiWriter writer, Action callback) { writer.WriteProperty(OpenApiConstants.Id, Id); writer.WriteProperty(OpenApiConstants.DollarSchema, Schema?.ToString()); writer.WriteProperty(OpenApiConstants.Comment, Comment); writer.WriteProperty(OpenApiConstants.Const, Const); writer.WriteOptionalMap(OpenApiConstants.Vocabulary, Vocabulary, (w, s) => w.WriteValue(s)); - writer.WriteOptionalMap(OpenApiConstants.Defs, Definitions, (w, s) => s.SerializeAsV31(w)); + writer.WriteOptionalMap(OpenApiConstants.Defs, Definitions, callback); writer.WriteProperty(OpenApiConstants.Anchor, Anchor); writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef); writer.WriteProperty(OpenApiConstants.DynamicAnchor, DynamicAnchor); @@ -674,7 +674,7 @@ internal void WriteJsonSchemaKeywords(IOpenApiWriter writer) writer.WriteOptionalObject( OpenApiConstants.UnevaluatedProperties, UnevaluatedPropertiesSchema, - (w, s) => s.SerializeAsV31(w)); + callback); } else if (!UnevaluatedProperties) { @@ -682,16 +682,16 @@ internal void WriteJsonSchemaKeywords(IOpenApiWriter writer) } } writer.WriteOptionalCollection(OpenApiConstants.Examples, Examples, (nodeWriter, s) => nodeWriter.WriteAny(s)); - writer.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, (w, s) => s.SerializeAsV31(w)); + writer.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, callback); writer.WriteOptionalMap(OpenApiConstants.DependentRequired, DependentRequired, (w, s) => w.WriteValue(s)); writer.WriteProperty(OpenApiConstants.ContentEncoding, ContentEncoding); writer.WriteProperty(OpenApiConstants.ContentMediaType, ContentMediaType); - writer.WriteOptionalObject(OpenApiConstants.ContentSchema, ContentSchema, (w, s) => s.SerializeAsV31(w)); - writer.WriteOptionalObject(OpenApiConstants.PropertyNames, PropertyNames, (w, s) => s.SerializeAsV31(w)); - writer.WriteOptionalMap(OpenApiConstants.DependentSchemas, DependentSchemas, (w, s) => s.SerializeAsV31(w)); - writer.WriteOptionalObject(OpenApiConstants.If, If, (w, s) => s.SerializeAsV31(w)); - writer.WriteOptionalObject(OpenApiConstants.Then, Then, (w, s) => s.SerializeAsV31(w)); - writer.WriteOptionalObject(OpenApiConstants.Else, Else, (w, s) => s.SerializeAsV31(w)); + writer.WriteOptionalObject(OpenApiConstants.ContentSchema, ContentSchema, callback); + writer.WriteOptionalObject(OpenApiConstants.PropertyNames, PropertyNames, callback); + writer.WriteOptionalMap(OpenApiConstants.DependentSchemas, DependentSchemas, callback); + writer.WriteOptionalObject(OpenApiConstants.If, If, callback); + writer.WriteOptionalObject(OpenApiConstants.Then, Then, callback); + writer.WriteOptionalObject(OpenApiConstants.Else, Else, callback); } private void WriteV3CompatibilityKeywords(IOpenApiWriter writer, Action callback) diff --git a/test/Microsoft.OpenApi.Tests/Mocks/OpenApiSchemaSerializationTests.cs b/test/Microsoft.OpenApi.Tests/Mocks/OpenApiSchemaSerializationTests.cs index 8a402fb74..2d9c5f766 100644 --- a/test/Microsoft.OpenApi.Tests/Mocks/OpenApiSchemaSerializationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Mocks/OpenApiSchemaSerializationTests.cs @@ -45,5 +45,37 @@ public void SerializeAsV3_DoesNotCallV31OrV2Serialization() _xmlMock.Verify(c => c.SerializeAsV2(It.IsAny()), Times.Never, "V2 method should not be called"); _xmlMock.Verify(c => c.SerializeAsV31(It.IsAny()), Times.Never, "V31 method should not be called"); } + + [Fact] + public void SerializeAsV31_UsesV31CallbackForJsonSchemaKeywords() + { + using var stringWriter = new StringWriter(); + var writer = new OpenApiJsonWriter(stringWriter); + var childSchemaMock = new Mock { CallBase = true }; + childSchemaMock.Object.Type = JsonSchemaType.String; + _schema.ContentSchema = childSchemaMock.Object; + + _schema.SerializeAsV31(writer); + + childSchemaMock.Verify(c => c.SerializeAsV31(It.IsAny()), Times.AtLeastOnce); + childSchemaMock.Verify(c => c.SerializeAsV32(It.IsAny()), Times.Never); + childSchemaMock.Verify(c => c.SerializeAsV3(It.IsAny()), Times.Never); + } + + [Fact] + public void SerializeAsV32_UsesV32CallbackForJsonSchemaKeywords() + { + using var stringWriter = new StringWriter(); + var writer = new OpenApiJsonWriter(stringWriter); + var childSchemaMock = new Mock { CallBase = true }; + childSchemaMock.Object.Type = JsonSchemaType.String; + _schema.ContentSchema = childSchemaMock.Object; + + _schema.SerializeAsV32(writer); + + childSchemaMock.Verify(c => c.SerializeAsV32(It.IsAny()), Times.AtLeastOnce); + childSchemaMock.Verify(c => c.SerializeAsV31(It.IsAny()), Times.Never); + childSchemaMock.Verify(c => c.SerializeAsV3(It.IsAny()), Times.Never); + } } } From c62769a6fac6ae354bf5554d6d2e4648e917b99a Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 9 Jun 2026 12:17:09 -0400 Subject: [PATCH 3/9] docs(library): add json schema spec links Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Models/Interfaces/IOpenApiSchemaMissingProperties.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchemaMissingProperties.cs b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchemaMissingProperties.cs index be2d6fbb1..a144f751e 100644 --- a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchemaMissingProperties.cs +++ b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchemaMissingProperties.cs @@ -14,6 +14,7 @@ public interface IOpenApiSchemaMissingProperties { /// /// $anchor - identifies a plain-name location-independent fragment within the schema resource. + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-anchor /// public string? Anchor { get; } @@ -45,41 +46,49 @@ public interface IOpenApiSchemaMissingProperties public IOpenApiSchema? UnevaluatedPropertiesSchema { get; } /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation#name-contentencoding /// contentEncoding - identifies the encoding of string content. /// public string? ContentEncoding { get; } /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation#name-contentmediatype /// contentMediaType - identifies the media type of string content. /// public string? ContentMediaType { get; } /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation#name-contentschema /// contentSchema - provides a schema that describes the decoded string content. /// public IOpenApiSchema? ContentSchema { get; } /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-propertynames /// propertyNames - provides a schema that validates property names. /// public IOpenApiSchema? PropertyNames { get; } /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-dependentschemas /// dependentSchemas - maps property names to schemas that are applied when that property is present. /// public IDictionary? DependentSchemas { get; } /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-if /// if - applies a conditional schema that determines whether or should be evaluated. /// public IOpenApiSchema? If { get; } /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-then /// then - applies when evaluates successfully. /// public IOpenApiSchema? Then { get; } /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-else /// else - applies when does not evaluate successfully. /// public IOpenApiSchema? Else { get; } From eb1891a8d77915add1cdd88949b40aa43cd525b8 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 9 Jun 2026 12:22:22 -0400 Subject: [PATCH 4/9] fix(library): use x-jsonschema schema extensions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Models/OpenApiConstants.cs | 44 +++++++++---------- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 4 +- src/Microsoft.OpenApi/PublicAPI.Shipped.txt | 2 +- src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 20 ++++----- .../V3Tests/OpenApiSchemaTests.cs | 20 ++++----- .../Models/OpenApiSchemaTests.cs | 22 +++++----- 6 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index 07ad81923..e66287fb2 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -136,14 +136,14 @@ public static class OpenApiConstants public const string UnevaluatedProperties = "unevaluatedProperties"; /// - /// Extension: x-oai-unevaluatedProperties + /// Extension: x-jsonschema-unevaluatedProperties /// - public const string UnevaluatedPropertiesExtension = "x-oai-unevaluatedProperties"; + public const string UnevaluatedPropertiesExtension = "x-jsonschema-unevaluatedProperties"; /// - /// Legacy extension: x-jsonschema-unevaluatedProperties + /// Legacy extension: x-oai-unevaluatedProperties /// - public const string LegacyUnevaluatedPropertiesExtension = "x-jsonschema-unevaluatedProperties"; + public const string LegacyUnevaluatedPropertiesExtension = "x-oai-unevaluatedProperties"; /// /// Field: Version @@ -841,49 +841,49 @@ public static class OpenApiConstants public const string DependentRequired = "dependentRequired"; /// - /// Extension: x-oai-$anchor + /// Extension: x-jsonschema-$anchor /// - public const string AnchorExtension = "x-oai-$anchor"; + public const string AnchorExtension = "x-jsonschema-$anchor"; /// - /// Extension: x-oai-propertyNames + /// Extension: x-jsonschema-propertyNames /// - public const string PropertyNamesExtension = "x-oai-propertyNames"; + public const string PropertyNamesExtension = "x-jsonschema-propertyNames"; /// - /// Extension: x-oai-dependentSchemas + /// Extension: x-jsonschema-dependentSchemas /// - public const string DependentSchemasExtension = "x-oai-dependentSchemas"; + public const string DependentSchemasExtension = "x-jsonschema-dependentSchemas"; /// - /// Extension: x-oai-if + /// Extension: x-jsonschema-if /// - public const string IfExtension = "x-oai-if"; + public const string IfExtension = "x-jsonschema-if"; /// - /// Extension: x-oai-then + /// Extension: x-jsonschema-then /// - public const string ThenExtension = "x-oai-then"; + public const string ThenExtension = "x-jsonschema-then"; /// - /// Extension: x-oai-else + /// Extension: x-jsonschema-else /// - public const string ElseExtension = "x-oai-else"; + public const string ElseExtension = "x-jsonschema-else"; /// - /// Extension: x-oai-contentEncoding + /// Extension: x-jsonschema-contentEncoding /// - public const string ContentEncodingExtension = "x-oai-contentEncoding"; + public const string ContentEncodingExtension = "x-jsonschema-contentEncoding"; /// - /// Extension: x-oai-contentMediaType + /// Extension: x-jsonschema-contentMediaType /// - public const string ContentMediaTypeExtension = "x-oai-contentMediaType"; + public const string ContentMediaTypeExtension = "x-jsonschema-contentMediaType"; /// - /// Extension: x-oai-contentSchema + /// Extension: x-jsonschema-contentSchema /// - public const string ContentSchemaExtension = "x-oai-contentSchema"; + public const string ContentSchemaExtension = "x-jsonschema-contentSchema"; #region V2.0 diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 4f44bb504..ac21dc7b9 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -931,14 +931,14 @@ private void SerializeAsV2( if (UnevaluatedPropertiesSchema is not null) { writer.WriteOptionalObject( - OpenApiConstants.LegacyUnevaluatedPropertiesExtension, + OpenApiConstants.UnevaluatedPropertiesExtension, UnevaluatedPropertiesSchema, (w, s) => s.SerializeAsV2(w)); } // Write boolean false as extension if explicitly set to false else if (!UnevaluatedProperties) { - writer.WritePropertyName(OpenApiConstants.LegacyUnevaluatedPropertiesExtension); + writer.WritePropertyName(OpenApiConstants.UnevaluatedPropertiesExtension); writer.WriteValue(false); } } diff --git a/src/Microsoft.OpenApi/PublicAPI.Shipped.txt b/src/Microsoft.OpenApi/PublicAPI.Shipped.txt index 4c197c380..4424e5862 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Shipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Shipped.txt @@ -2022,7 +2022,7 @@ virtual Microsoft.OpenApi.OpenApiXml.SerializeAsV3(Microsoft.OpenApi.IOpenApiWri virtual Microsoft.OpenApi.OpenApiXml.SerializeAsV31(Microsoft.OpenApi.IOpenApiWriter! writer) -> void virtual Microsoft.OpenApi.OpenApiXml.SerializeAsV32(Microsoft.OpenApi.IOpenApiWriter! writer) -> void const Microsoft.OpenApi.OpenApiConstants.OAuth2MetadataUrl = "oauth2MetadataUrl" -> string! -const Microsoft.OpenApi.OpenApiConstants.UnevaluatedPropertiesExtension = "x-oai-unevaluatedProperties" -> string! +const Microsoft.OpenApi.OpenApiConstants.UnevaluatedPropertiesExtension = "x-jsonschema-unevaluatedProperties" -> string! Microsoft.OpenApi.IOAuth2MetadataProvider Microsoft.OpenApi.IOAuth2MetadataProvider.OAuth2MetadataUrl.get -> System.Uri? Microsoft.OpenApi.IOpenApiSchemaWithUnevaluatedProperties diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index 2c1dde11f..cde35ac89 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -1,23 +1,23 @@ #nullable enable const Microsoft.OpenApi.OpenApiConstants.Anchor = "$anchor" -> string! -const Microsoft.OpenApi.OpenApiConstants.AnchorExtension = "x-oai-$anchor" -> string! +const Microsoft.OpenApi.OpenApiConstants.AnchorExtension = "x-jsonschema-$anchor" -> string! const Microsoft.OpenApi.OpenApiConstants.ContentEncoding = "contentEncoding" -> string! -const Microsoft.OpenApi.OpenApiConstants.ContentEncodingExtension = "x-oai-contentEncoding" -> string! +const Microsoft.OpenApi.OpenApiConstants.ContentEncodingExtension = "x-jsonschema-contentEncoding" -> string! const Microsoft.OpenApi.OpenApiConstants.ContentMediaType = "contentMediaType" -> string! -const Microsoft.OpenApi.OpenApiConstants.ContentMediaTypeExtension = "x-oai-contentMediaType" -> string! +const Microsoft.OpenApi.OpenApiConstants.ContentMediaTypeExtension = "x-jsonschema-contentMediaType" -> string! const Microsoft.OpenApi.OpenApiConstants.ContentSchema = "contentSchema" -> string! -const Microsoft.OpenApi.OpenApiConstants.ContentSchemaExtension = "x-oai-contentSchema" -> string! +const Microsoft.OpenApi.OpenApiConstants.ContentSchemaExtension = "x-jsonschema-contentSchema" -> string! const Microsoft.OpenApi.OpenApiConstants.DependentSchemas = "dependentSchemas" -> string! -const Microsoft.OpenApi.OpenApiConstants.DependentSchemasExtension = "x-oai-dependentSchemas" -> string! +const Microsoft.OpenApi.OpenApiConstants.DependentSchemasExtension = "x-jsonschema-dependentSchemas" -> string! const Microsoft.OpenApi.OpenApiConstants.Else = "else" -> string! -const Microsoft.OpenApi.OpenApiConstants.ElseExtension = "x-oai-else" -> string! +const Microsoft.OpenApi.OpenApiConstants.ElseExtension = "x-jsonschema-else" -> string! const Microsoft.OpenApi.OpenApiConstants.If = "if" -> string! -const Microsoft.OpenApi.OpenApiConstants.IfExtension = "x-oai-if" -> string! -const Microsoft.OpenApi.OpenApiConstants.LegacyUnevaluatedPropertiesExtension = "x-jsonschema-unevaluatedProperties" -> string! +const Microsoft.OpenApi.OpenApiConstants.IfExtension = "x-jsonschema-if" -> string! +const Microsoft.OpenApi.OpenApiConstants.LegacyUnevaluatedPropertiesExtension = "x-oai-unevaluatedProperties" -> string! const Microsoft.OpenApi.OpenApiConstants.PropertyNames = "propertyNames" -> string! -const Microsoft.OpenApi.OpenApiConstants.PropertyNamesExtension = "x-oai-propertyNames" -> string! +const Microsoft.OpenApi.OpenApiConstants.PropertyNamesExtension = "x-jsonschema-propertyNames" -> string! const Microsoft.OpenApi.OpenApiConstants.Then = "then" -> string! -const Microsoft.OpenApi.OpenApiConstants.ThenExtension = "x-oai-then" -> string! +const Microsoft.OpenApi.OpenApiConstants.ThenExtension = "x-jsonschema-then" -> string! Microsoft.OpenApi.IOpenApiSchemaMissingProperties Microsoft.OpenApi.IOpenApiSchemaMissingProperties.Anchor.get -> string? Microsoft.OpenApi.IOpenApiSchemaMissingProperties.ContentEncoding.get -> string? diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs index 110e2c342..df2f0d6eb 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -182,28 +182,28 @@ public void ParseDictionarySchemaShouldSucceed() public void ParseSchemaWithOaiCompatibilityKeywordsShouldSucceed() { var schemaJson = @"{ - ""x-oai-$anchor"": ""root"", - ""x-oai-unevaluatedProperties"": false, - ""x-oai-contentEncoding"": ""base64"", - ""x-oai-contentMediaType"": ""application/jwt"", - ""x-oai-contentSchema"": { + ""x-jsonschema-$anchor"": ""root"", + ""x-jsonschema-unevaluatedProperties"": false, + ""x-jsonschema-contentEncoding"": ""base64"", + ""x-jsonschema-contentMediaType"": ""application/jwt"", + ""x-jsonschema-contentSchema"": { ""type"": ""array"" }, - ""x-oai-propertyNames"": { + ""x-jsonschema-propertyNames"": { ""pattern"": ""^[a-z]+$"" }, - ""x-oai-dependentSchemas"": { + ""x-jsonschema-dependentSchemas"": { ""token"": { ""type"": ""string"" } }, - ""x-oai-if"": { + ""x-jsonschema-if"": { ""required"": [""token""] }, - ""x-oai-then"": { + ""x-jsonschema-then"": { ""minProperties"": 1 }, - ""x-oai-else"": { + ""x-jsonschema-else"": { ""maxProperties"": 0 } }"; diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 06ade24a1..ccb9d7109 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -1315,7 +1315,7 @@ public async Task SerializeUnevaluatedPropertiesAsExtensionInV2() [Fact] public async Task SerializeUnevaluatedPropertiesAsExtensionInV3() { - var expected = @"{ ""x-oai-unevaluatedProperties"": false }"; + var expected = @"{ ""x-jsonschema-unevaluatedProperties"": false }"; var schema = new OpenApiSchema { UnevaluatedProperties = false @@ -1346,7 +1346,7 @@ public async Task SerializeUnevaluatedPropertiesSchemaAsExtensionInV2() [Fact] public async Task SerializeUnevaluatedPropertiesSchemaAsExtensionInV3() { - var expected = @"{ ""x-oai-unevaluatedProperties"": { ""type"": ""string"" } }"; + var expected = @"{ ""x-jsonschema-unevaluatedProperties"": { ""type"": ""string"" } }"; var schema = new OpenApiSchema { UnevaluatedPropertiesSchema = new OpenApiSchema @@ -1438,29 +1438,29 @@ public async Task SerializeMissingPropertiesEmitsOaiExtensionsInV3() { var expected = JsonNode.Parse(""" { - "x-oai-$anchor": "root", - "x-oai-contentEncoding": "base64", - "x-oai-contentMediaType": "application/jwt", - "x-oai-contentSchema": { + "x-jsonschema-$anchor": "root", + "x-jsonschema-contentEncoding": "base64", + "x-jsonschema-contentMediaType": "application/jwt", + "x-jsonschema-contentSchema": { "type": "array" }, - "x-oai-propertyNames": { + "x-jsonschema-propertyNames": { "pattern": "^[a-z]+$" }, - "x-oai-dependentSchemas": { + "x-jsonschema-dependentSchemas": { "token": { "type": "string" } }, - "x-oai-if": { + "x-jsonschema-if": { "required": [ "token" ] }, - "x-oai-then": { + "x-jsonschema-then": { "minProperties": 1 }, - "x-oai-else": { + "x-jsonschema-else": { "maxProperties": 0 } } From cf54bb3e2746c0f6c7a60fde5bcc7fa1139dd8b6 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 9 Jun 2026 12:26:35 -0400 Subject: [PATCH 5/9] fix(library): remove unshipped schema extension fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Models/OpenApiConstants.cs | 5 ----- src/Microsoft.OpenApi/Models/OpenApiSchema.cs | 8 ++------ src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 1 - .../Reader/V3/OpenApiSchemaDeserializer.cs | 18 ------------------ 4 files changed, 2 insertions(+), 30 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index e66287fb2..80ef1ae59 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -140,11 +140,6 @@ public static class OpenApiConstants /// public const string UnevaluatedPropertiesExtension = "x-jsonschema-unevaluatedProperties"; - /// - /// Legacy extension: x-oai-unevaluatedProperties - /// - public const string LegacyUnevaluatedPropertiesExtension = "x-oai-unevaluatedProperties"; - /// /// Field: Version /// diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index ac21dc7b9..7dbe09d9f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -604,22 +604,18 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version // 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) { - var unevaluatedPropertiesExtensionName = version == OpenApiSpecVersion.OpenApi3_0 - ? OpenApiConstants.UnevaluatedPropertiesExtension - : OpenApiConstants.LegacyUnevaluatedPropertiesExtension; - // Write UnevaluatedPropertiesSchema as extension if present if (UnevaluatedPropertiesSchema is not null) { writer.WriteOptionalObject( - unevaluatedPropertiesExtensionName, + OpenApiConstants.UnevaluatedPropertiesExtension, UnevaluatedPropertiesSchema, callback); } // Write boolean false as extension if explicitly set to false else if (!UnevaluatedProperties) { - writer.WritePropertyName(unevaluatedPropertiesExtensionName); + writer.WritePropertyName(OpenApiConstants.UnevaluatedPropertiesExtension); writer.WriteValue(false); } } diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index cde35ac89..30d861e31 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -13,7 +13,6 @@ const Microsoft.OpenApi.OpenApiConstants.Else = "else" -> string! const Microsoft.OpenApi.OpenApiConstants.ElseExtension = "x-jsonschema-else" -> string! const Microsoft.OpenApi.OpenApiConstants.If = "if" -> string! const Microsoft.OpenApi.OpenApiConstants.IfExtension = "x-jsonschema-if" -> string! -const Microsoft.OpenApi.OpenApiConstants.LegacyUnevaluatedPropertiesExtension = "x-oai-unevaluatedProperties" -> string! const Microsoft.OpenApi.OpenApiConstants.PropertyNames = "propertyNames" -> string! const Microsoft.OpenApi.OpenApiConstants.PropertyNamesExtension = "x-jsonschema-propertyNames" -> string! const Microsoft.OpenApi.OpenApiConstants.Then = "then" -> string! diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs index 9fdd69702..12eb631ea 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs @@ -300,24 +300,6 @@ internal static partial class OpenApiV3Deserializer } } }, - { - OpenApiConstants.LegacyUnevaluatedPropertiesExtension, - (o, n, t, c) => - { - if (n is JsonValue) - { - var value = n.GetScalarValue(); - if (value is not null) - { - o.UnevaluatedProperties = bool.Parse(value); - } - } - else - { - o.UnevaluatedPropertiesSchema = LoadSchema(n, t, c); - } - } - }, { OpenApiConstants.AnchorExtension, (o, n, _, _) => o.Anchor = n.GetScalarValue() From 9672f95f2622761f88337c5a8804f92285eff2a9 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 9 Jun 2026 12:42:15 -0400 Subject: [PATCH 6/9] chore(library): use constants for new schema keywords Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Reader/V31/OpenApiSchemaDeserializer.cs | 18 +++++++++--------- .../Reader/V32/OpenApiSchemaDeserializer.cs | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs index f4b98234f..2c309d63e 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -45,7 +45,7 @@ internal static partial class OpenApiV31Deserializer (o, n, t, c) => o.Definitions = n.CreateMap(LoadSchema, t, c) }, { - "$anchor", + OpenApiConstants.Anchor, (o, n, _, _) => o.Anchor = n.GetScalarValue() }, { @@ -169,15 +169,15 @@ internal static partial class OpenApiV31Deserializer } }, { - "contentEncoding", + OpenApiConstants.ContentEncoding, (o, n, _, _) => o.ContentEncoding = n.GetScalarValue() }, { - "contentMediaType", + OpenApiConstants.ContentMediaType, (o, n, _, _) => o.ContentMediaType = n.GetScalarValue() }, { - "contentSchema", + OpenApiConstants.ContentSchema, (o, n, doc, c) => o.ContentSchema = LoadSchema(n, doc, c) }, { @@ -266,7 +266,7 @@ internal static partial class OpenApiV31Deserializer (o, n, t, c) => o.PatternProperties = n.CreateMap(LoadSchema, t, c) }, { - "propertyNames", + OpenApiConstants.PropertyNames, (o, n, doc, c) => o.PropertyNames = LoadSchema(n, doc, c) }, { @@ -377,19 +377,19 @@ internal static partial class OpenApiV31Deserializer } }, { - "dependentSchemas", + OpenApiConstants.DependentSchemas, (o, n, t, c) => o.DependentSchemas = n.CreateMap(LoadSchema, t, c) }, { - "if", + OpenApiConstants.If, (o, n, doc, c) => o.If = LoadSchema(n, doc, c) }, { - "then", + OpenApiConstants.Then, (o, n, doc, c) => o.Then = LoadSchema(n, doc, c) }, { - "else", + OpenApiConstants.Else, (o, n, doc, c) => o.Else = LoadSchema(n, doc, c) }, }; diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs index dca4f339c..03bac2785 100644 --- a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs @@ -45,7 +45,7 @@ internal static partial class OpenApiV32Deserializer (o, n, t, c) => o.Definitions = n.CreateMap(LoadSchema, t, c) }, { - "$anchor", + OpenApiConstants.Anchor, (o, n, _, _) => o.Anchor = n.GetScalarValue() }, { @@ -169,15 +169,15 @@ internal static partial class OpenApiV32Deserializer } }, { - "contentEncoding", + OpenApiConstants.ContentEncoding, (o, n, _, _) => o.ContentEncoding = n.GetScalarValue() }, { - "contentMediaType", + OpenApiConstants.ContentMediaType, (o, n, _, _) => o.ContentMediaType = n.GetScalarValue() }, { - "contentSchema", + OpenApiConstants.ContentSchema, (o, n, doc, c) => o.ContentSchema = LoadSchema(n, doc, c) }, { @@ -266,7 +266,7 @@ internal static partial class OpenApiV32Deserializer (o, n, t, c) => o.PatternProperties = n.CreateMap(LoadSchema, t, c) }, { - "propertyNames", + OpenApiConstants.PropertyNames, (o, n, doc, c) => o.PropertyNames = LoadSchema(n, doc, c) }, { @@ -377,19 +377,19 @@ internal static partial class OpenApiV32Deserializer } }, { - "dependentSchemas", + OpenApiConstants.DependentSchemas, (o, n, t, c) => o.DependentSchemas = n.CreateMap(LoadSchema, t, c) }, { - "if", + OpenApiConstants.If, (o, n, doc, c) => o.If = LoadSchema(n, doc, c) }, { - "then", + OpenApiConstants.Then, (o, n, doc, c) => o.Then = LoadSchema(n, doc, c) }, { - "else", + OpenApiConstants.Else, (o, n, doc, c) => o.Else = LoadSchema(n, doc, c) }, }; From 68f9bd2fbf55fe85831b1ccd5cb51ff25920ad75 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 9 Jun 2026 12:59:10 -0400 Subject: [PATCH 7/9] chore(benchmark): refresh performance reports Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../performance.Descriptions-report-github.md | 16 ++--- .../performance.Descriptions-report.csv | 12 ++-- .../performance.Descriptions-report.html | 14 ++--- .../performance.Descriptions-report.json | 2 +- .../performance.EmptyModels-report-github.md | 60 +++++++++---------- .../performance.EmptyModels-report.csv | 56 ++++++++--------- .../performance.EmptyModels-report.html | 58 +++++++++--------- .../performance.EmptyModels-report.json | 2 +- 8 files changed, 110 insertions(+), 110 deletions(-) diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md index 9b5931f81..1b2eb289d 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md @@ -10,11 +10,11 @@ Job=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3 ``` -| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|------------- |-------------:|--------------:|-------------:|-----------:|-----------:|----------:|-------------:| -| PetStoreYaml | 276.3 μs | 38.27 μs | 2.10 μs | 74.2188 | 11.7188 | - | 305.91 KB | -| PetStoreJson | 112.8 μs | 2.80 μs | 0.15 μs | 41.0156 | 0.4883 | - | 168.05 KB | -| GHESYaml | 608,668.3 μs | 188,763.29 μs | 10,346.75 μs | 44000.0000 | 18000.0000 | 3000.0000 | 250121.85 KB | -| GHESJson | 244,147.6 μs | 361,794.79 μs | 19,831.19 μs | 17000.0000 | 9000.0000 | 2000.0000 | 107293.42 KB | -| GHESNextYaml | 765,440.1 μs | 23,162.26 μs | 1,269.60 μs | 79000.0000 | 20000.0000 | 3000.0000 | 443655.46 KB | -| GHESNextJson | 435,329.2 μs | 241,612.89 μs | 13,243.62 μs | 51000.0000 | 11000.0000 | 2000.0000 | 305423.41 KB | +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|------------- |-------------:|--------------:|------------:|-----------:|-----------:|----------:|-------------:| +| PetStoreYaml | 371.5 μs | 35.60 μs | 1.95 μs | 74.2188 | 11.7188 | - | 307.17 KB | +| PetStoreJson | 155.7 μs | 10.23 μs | 0.56 μs | 41.0156 | 6.8359 | - | 169.31 KB | +| GHESYaml | 771,340.7 μs | 72,493.09 μs | 3,973.59 μs | 44000.0000 | 18000.0000 | 3000.0000 | 252535.98 KB | +| GHESJson | 308,100.8 μs | 132,615.87 μs | 7,269.12 μs | 17000.0000 | 9000.0000 | 2000.0000 | 109706.91 KB | +| GHESNextYaml | 999,238.5 μs | 116,421.98 μs | 6,381.48 μs | 80000.0000 | 20000.0000 | 3000.0000 | 446197.67 KB | +| GHESNextJson | 565,582.8 μs | 54,146.09 μs | 2,967.93 μs | 52000.0000 | 14000.0000 | 3000.0000 | 307956.73 KB | diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv index 6ca713e4b..655f0f4b4 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv @@ -1,7 +1,7 @@ Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,LargeAddressAware,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,Mean,Error,StdDev,Gen0,Gen1,Gen2,Allocated -PetStoreYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,Arm64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,276.3 μs,38.27 μs,2.10 μs,74.2188,11.7188,0.0000,305.91 KB -PetStoreJson,ShortRun,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,Arm64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,112.8 μs,2.80 μs,0.15 μs,41.0156,0.4883,0.0000,168.05 KB -GHESYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,Arm64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"608,668.3 μs","188,763.29 μs","10,346.75 μs",44000.0000,18000.0000,3000.0000,250121.85 KB -GHESJson,ShortRun,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,Arm64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"244,147.6 μs","361,794.79 μs","19,831.19 μs",17000.0000,9000.0000,2000.0000,107293.42 KB -GHESNextYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,Arm64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"765,440.1 μs","23,162.26 μs","1,269.60 μs",79000.0000,20000.0000,3000.0000,443655.46 KB -GHESNextJson,ShortRun,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,Arm64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"435,329.2 μs","241,612.89 μs","13,243.62 μs",51000.0000,11000.0000,2000.0000,305423.41 KB +PetStoreYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,Arm64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,371.5 μs,35.60 μs,1.95 μs,74.2188,11.7188,0.0000,307.17 KB +PetStoreJson,ShortRun,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,Arm64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,155.7 μs,10.23 μs,0.56 μs,41.0156,6.8359,0.0000,169.31 KB +GHESYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,Arm64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"771,340.7 μs","72,493.09 μs","3,973.59 μs",44000.0000,18000.0000,3000.0000,252535.98 KB +GHESJson,ShortRun,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,Arm64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"308,100.8 μs","132,615.87 μs","7,269.12 μs",17000.0000,9000.0000,2000.0000,109706.91 KB +GHESNextYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,Arm64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"999,238.5 μs","116,421.98 μs","6,381.48 μs",80000.0000,20000.0000,3000.0000,446197.67 KB +GHESNextJson,ShortRun,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,Arm64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"565,582.8 μs","54,146.09 μs","2,967.93 μs",52000.0000,14000.0000,3000.0000,307956.73 KB diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html index a6a592c7b..b45bbfcc7 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html @@ -2,7 +2,7 @@ -performance.Descriptions-20260526-120411 +performance.Descriptions-20260609-124950