From 2749463f1ec1d452ba2334876d32466ea45ee8d1 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 26 Jun 2026 13:42:19 -0400 Subject: [PATCH 1/5] feat(library): support schema keywords on references Add JSON Schema keyword sibling storage, parsing, accessors, and serialization for schema references across OpenAPI 3.0 extension compatibility and OpenAPI 3.1+ native keywords. Fixes #2903 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Models/JsonSchemaReference.cs | 571 ++++++++++++++++++ .../References/OpenApiSchemaReference.cs | 110 ++-- src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 147 +++++ .../Reader/V3/OpenApiSchemaDeserializer.cs | 25 +- .../Reader/V31/OpenApiSchemaDeserializer.cs | 25 +- ...orks_produceTerseOutput=False.verified.txt | 10 +- ...Works_produceTerseOutput=True.verified.txt | 2 +- .../References/OpenApiSchemaReferenceTests.cs | 290 ++++++++- 8 files changed, 1101 insertions(+), 79 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs index ac7290f38..14f1dcd70 100644 --- a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs @@ -14,6 +14,8 @@ namespace Microsoft.OpenApi; /// public class JsonSchemaReference : OpenApiReferenceWithDescription { + private const string JsonSchemaExtensionPrefix = "x-jsonschema-"; + /// /// A default value which by default SHOULD override that of the referenced component. /// If the referenced object-type does not allow a default field, then this field has no effect. @@ -99,6 +101,236 @@ public class JsonSchemaReference : OpenApiReferenceWithDescription /// public string? Anchor { get; set; } + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public string? ExclusiveMaximum { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public string? ExclusiveMinimum { get; set; } + + /// + /// Schema type override. Named SchemaType to avoid collision with . + /// + public JsonSchemaType? SchemaType { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public string? Const { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public string? Format { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public string? Maximum { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public string? Minimum { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public int? MaxLength { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public int? MinLength { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public string? Pattern { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public decimal? MultipleOf { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public IList? AllOf { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public IList? OneOf { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public IList? AnyOf { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public IOpenApiSchema? Not { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public ISet? Required { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public IOpenApiSchema? Items { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public int? MaxItems { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public int? MinItems { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public bool? UniqueItems { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public IOpenApiSchema? Contains { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public uint? MaxContains { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public uint? MinContains { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public IDictionary? Properties { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public IDictionary? PatternProperties { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public int? MaxProperties { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public int? MinProperties { get; set; } + + /// + /// Indicates if the schema can contain properties other than those defined by the properties map. + /// + public bool? AdditionalPropertiesAllowed { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public IOpenApiSchema? AdditionalProperties { get; set; } + + /// + /// Adds support for polymorphism. + /// + public OpenApiDiscriminator? Discriminator { get; set; } + + /// + /// A free-form property to include an example of an instance for this schema. + /// + public JsonNode? Example { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// + public IList? Enum { get; set; } + + /// + /// Indicates whether unevaluated properties are allowed. + /// + public bool? UnevaluatedProperties { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-unevaluatedproperties + /// + public IOpenApiSchema? UnevaluatedPropertiesSchema { get; set; } + + /// + /// Additional external documentation for this schema. + /// + public OpenApiExternalDocs? ExternalDocs { get; set; } + + /// + /// This MAY be used only on properties schemas. + /// + public OpenApiXml? Xml { get; set; } + + /// + /// This object stores any unrecognized keywords found in the schema. + /// + public IDictionary? UnrecognizedKeywords { get; set; } + + /// + /// Follow JSON Schema definition:https://json-schema.org/draft/2020-12/json-schema-validation#section-6.5.4 + /// + public IDictionary>? DependentRequired { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation#name-contentencoding + /// + public string? ContentEncoding { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation#name-contentmediatype + /// + public string? ContentMediaType { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation#name-contentschema + /// + public IOpenApiSchema? ContentSchema { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-propertynames + /// + public IOpenApiSchema? PropertyNames { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-dependentschemas + /// + public IDictionary? DependentSchemas { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-if + /// + public IOpenApiSchema? If { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-then + /// + public IOpenApiSchema? Then { get; set; } + + /// + /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-else + /// + public IOpenApiSchema? Else { get; set; } + /// /// Parameterless constructor /// @@ -125,6 +357,52 @@ public JsonSchemaReference(JsonSchemaReference reference) : base(reference) DynamicAnchor = reference.DynamicAnchor; Definitions = reference.Definitions != null ? new Dictionary(reference.Definitions) : null; Anchor = reference.Anchor; + ExclusiveMaximum = reference.ExclusiveMaximum; + ExclusiveMinimum = reference.ExclusiveMinimum; + SchemaType = reference.SchemaType; + Const = reference.Const; + Format = reference.Format; + Maximum = reference.Maximum; + Minimum = reference.Minimum; + MaxLength = reference.MaxLength; + MinLength = reference.MinLength; + Pattern = reference.Pattern; + MultipleOf = reference.MultipleOf; + AllOf = reference.AllOf != null ? [.. reference.AllOf] : null; + OneOf = reference.OneOf != null ? [.. reference.OneOf] : null; + AnyOf = reference.AnyOf != null ? [.. reference.AnyOf] : null; + Not = reference.Not; + Required = reference.Required != null ? new HashSet(reference.Required) : null; + Items = reference.Items; + MaxItems = reference.MaxItems; + MinItems = reference.MinItems; + UniqueItems = reference.UniqueItems; + Contains = reference.Contains; + MaxContains = reference.MaxContains; + MinContains = reference.MinContains; + Properties = reference.Properties != null ? new Dictionary(reference.Properties) : null; + PatternProperties = reference.PatternProperties != null ? new Dictionary(reference.PatternProperties) : null; + MaxProperties = reference.MaxProperties; + MinProperties = reference.MinProperties; + AdditionalPropertiesAllowed = reference.AdditionalPropertiesAllowed; + AdditionalProperties = reference.AdditionalProperties; + Discriminator = reference.Discriminator; + Example = reference.Example; + Enum = reference.Enum != null ? [.. reference.Enum] : null; + UnevaluatedProperties = reference.UnevaluatedProperties; + UnevaluatedPropertiesSchema = reference.UnevaluatedPropertiesSchema; + ExternalDocs = reference.ExternalDocs; + Xml = reference.Xml; + UnrecognizedKeywords = reference.UnrecognizedKeywords != null ? new Dictionary(reference.UnrecognizedKeywords) : null; + DependentRequired = reference.DependentRequired != null ? new Dictionary>(reference.DependentRequired) : null; + ContentEncoding = reference.ContentEncoding; + ContentMediaType = reference.ContentMediaType; + ContentSchema = reference.ContentSchema; + PropertyNames = reference.PropertyNames; + DependentSchemas = reference.DependentSchemas != null ? new Dictionary(reference.DependentSchemas) : null; + If = reference.If; + Then = reference.Then; + Else = reference.Else; } /// @@ -145,6 +423,70 @@ protected override void SerializeAdditionalV31Properties(IOpenApiWriter writer) writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef); writer.WriteProperty(OpenApiConstants.DynamicAnchor, DynamicAnchor); + writer.WriteProperty(OpenApiConstants.Const, Const); + WriteSchemaType(writer, OpenApiConstants.Type, SchemaType, allowMultipleTypes: true); + writer.WriteProperty(OpenApiConstants.Format, Format); + writer.WriteProperty(OpenApiConstants.MultipleOf, MultipleOf); + WriteRawProperty(writer, OpenApiConstants.Maximum, Maximum); + WriteRawProperty(writer, OpenApiConstants.ExclusiveMaximum, ExclusiveMaximum); + WriteRawProperty(writer, OpenApiConstants.Minimum, Minimum); + WriteRawProperty(writer, OpenApiConstants.ExclusiveMinimum, ExclusiveMinimum); + writer.WriteProperty(OpenApiConstants.MaxLength, MaxLength); + writer.WriteProperty(OpenApiConstants.MinLength, MinLength); + writer.WriteProperty(OpenApiConstants.Pattern, Pattern); + writer.WriteProperty(OpenApiConstants.MaxItems, MaxItems); + writer.WriteProperty(OpenApiConstants.MinItems, MinItems); + writer.WriteProperty(OpenApiConstants.UniqueItems, UniqueItems); + writer.WriteProperty(OpenApiConstants.MaxProperties, MaxProperties); + writer.WriteProperty(OpenApiConstants.MinProperties, MinProperties); + writer.WriteOptionalCollection(OpenApiConstants.Required, Required, (w, s) => + { + if (!string.IsNullOrEmpty(s)) + { + w.WriteValue(s!); + } + }); + writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (w, e) => w.WriteAny(e)); + writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, serializeCallback); + writer.WriteOptionalCollection(OpenApiConstants.AnyOf, AnyOf, serializeCallback); + writer.WriteOptionalCollection(OpenApiConstants.OneOf, OneOf, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.Not, Not, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.Items, Items, serializeCallback); + writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, serializeCallback); + writer.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, serializeCallback); + if (AdditionalProperties is not null) + { + writer.WriteOptionalObject(OpenApiConstants.AdditionalProperties, AdditionalProperties, serializeCallback); + } + else if (AdditionalPropertiesAllowed.HasValue) + { + writer.WriteProperty(OpenApiConstants.AdditionalProperties, AdditionalPropertiesAllowed.Value); + } + writer.WriteOptionalObject(OpenApiConstants.Discriminator, Discriminator, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); + if (UnevaluatedPropertiesSchema is not null) + { + writer.WriteOptionalObject(OpenApiConstants.UnevaluatedProperties, UnevaluatedPropertiesSchema, serializeCallback); + } + else if (UnevaluatedProperties.HasValue) + { + writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties.Value); + } + writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.Xml, Xml, serializeCallback); + writer.WriteOptionalMap(OpenApiConstants.DependentRequired, DependentRequired, (w, s) => w.WriteValue(s)); + writer.WriteOptionalObject(OpenApiConstants.Contains, Contains, serializeCallback); + writer.WriteProperty(OpenApiConstants.MaxContains, MaxContains); + writer.WriteProperty(OpenApiConstants.MinContains, MinContains); + writer.WriteProperty(OpenApiConstants.ContentEncoding, ContentEncoding); + writer.WriteProperty(OpenApiConstants.ContentMediaType, ContentMediaType); + writer.WriteOptionalObject(OpenApiConstants.ContentSchema, ContentSchema, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.PropertyNames, PropertyNames, serializeCallback); + writer.WriteOptionalMap(OpenApiConstants.DependentSchemas, DependentSchemas, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.If, If, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.Then, Then, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.Else, Else, serializeCallback); + // Additional schema metadata annotations in 3.1 writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d)); writer.WriteProperty(OpenApiConstants.Title, Title); @@ -167,6 +509,146 @@ protected override void SerializeAdditionalV31Properties(IOpenApiWriter writer) writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_1); } + /// + public override void SerializeAsV3(IOpenApiWriter writer) + { + if (Type != ReferenceType.Schema) throw new InvalidOperationException( + $"JsonSchemaReference can only be serialized for ReferenceType.Schema, but was {Type}."); + + writer.WriteStartObject(); + writer.WriteProperty(OpenApiConstants.DollarRef, ReferenceV3); + SerializeV3CompatibilityProperties(writer); + writer.WriteEndObject(); + } + + private void SerializeV3CompatibilityProperties(IOpenApiWriter writer) + { + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Id), SchemaId); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.DollarSchema), Schema?.ToString()); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Comment), Comment); + writer.WriteOptionalMap(GetJsonSchemaExtensionName(OpenApiConstants.Vocabulary), Vocabulary, static (w, s) => w.WriteValue(s)); + writer.WriteOptionalMap(GetJsonSchemaExtensionName(OpenApiConstants.Defs), Definitions, static (w, s) => s.SerializeAsV3(w)); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Anchor), Anchor); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.DynamicRef), DynamicRef); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.DynamicAnchor), DynamicAnchor); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Const), Const); + WriteSchemaType(writer, GetJsonSchemaExtensionName(OpenApiConstants.Type), SchemaType, allowMultipleTypes: true); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Format), Format); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MultipleOf), MultipleOf); + WriteRawProperty(writer, GetJsonSchemaExtensionName(OpenApiConstants.Maximum), Maximum); + WriteRawProperty(writer, GetJsonSchemaExtensionName(OpenApiConstants.ExclusiveMaximum), ExclusiveMaximum); + WriteRawProperty(writer, GetJsonSchemaExtensionName(OpenApiConstants.Minimum), Minimum); + WriteRawProperty(writer, GetJsonSchemaExtensionName(OpenApiConstants.ExclusiveMinimum), ExclusiveMinimum); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MaxLength), MaxLength); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MinLength), MinLength); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Pattern), Pattern); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MaxItems), MaxItems); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MinItems), MinItems); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.UniqueItems), UniqueItems); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MaxProperties), MaxProperties); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MinProperties), MinProperties); + writer.WriteOptionalCollection(GetJsonSchemaExtensionName(OpenApiConstants.Required), Required, static (w, s) => + { + if (!string.IsNullOrEmpty(s)) + { + w.WriteValue(s!); + } + }); + writer.WriteOptionalCollection(GetJsonSchemaExtensionName(OpenApiConstants.Enum), Enum, static (w, e) => w.WriteAny(e)); + writer.WriteOptionalCollection(GetJsonSchemaExtensionName(OpenApiConstants.AllOf), AllOf, static (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalCollection(GetJsonSchemaExtensionName(OpenApiConstants.AnyOf), AnyOf, static (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalCollection(GetJsonSchemaExtensionName(OpenApiConstants.OneOf), OneOf, static (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Not), Not, static (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Items), Items, static (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalMap(GetJsonSchemaExtensionName(OpenApiConstants.Properties), Properties, static (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalMap(GetJsonSchemaExtensionName(OpenApiConstants.PatternProperties), PatternProperties, static (w, s) => s.SerializeAsV3(w)); + if (AdditionalProperties is not null) + { + writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.AdditionalProperties), AdditionalProperties, static (w, s) => s.SerializeAsV3(w)); + } + else if (AdditionalPropertiesAllowed.HasValue) + { + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.AdditionalProperties), AdditionalPropertiesAllowed.Value); + } + writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Example), Example, static (w, e) => w.WriteAny(e)); + writer.WriteOptionalCollection(GetJsonSchemaExtensionName(OpenApiConstants.Examples), Examples, static (w, e) => w.WriteAny(e)); + if (UnevaluatedPropertiesSchema is not null) + { + writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.UnevaluatedProperties), UnevaluatedPropertiesSchema, static (w, s) => s.SerializeAsV3(w)); + } + else if (UnevaluatedProperties.HasValue) + { + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.UnevaluatedProperties), UnevaluatedProperties.Value); + } + writer.WriteOptionalMap(GetJsonSchemaExtensionName(OpenApiConstants.DependentRequired), DependentRequired, static (w, s) => w.WriteValue(s)); + writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Contains), Contains, static (w, s) => s.SerializeAsV3(w)); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MaxContains), MaxContains); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MinContains), MinContains); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.ContentEncoding), ContentEncoding); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.ContentMediaType), ContentMediaType); + writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.ContentSchema), ContentSchema, static (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.PropertyNames), PropertyNames, static (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalMap(GetJsonSchemaExtensionName(OpenApiConstants.DependentSchemas), DependentSchemas, static (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.If), If, static (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Then), Then, static (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Else), Else, static (w, s) => s.SerializeAsV3(w)); + writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Default), Default, static (w, d) => w.WriteAny(d)); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Title), Title); + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Description), Description); + if (Deprecated.HasValue) + { + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Deprecated), Deprecated.Value, false); + } + if (ReadOnly.HasValue) + { + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.ReadOnly), ReadOnly.Value, false); + } + if (WriteOnly.HasValue) + { + writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.WriteOnly), WriteOnly.Value, false); + } + } + + internal static string GetJsonSchemaExtensionName(string keyword) => JsonSchemaExtensionPrefix + keyword; + + private static void WriteRawProperty(IOpenApiWriter writer, string name, string? value) + { + if (!string.IsNullOrEmpty(value)) + { + writer.WritePropertyName(name); + writer.WriteRaw(value!); + } + } + + private static void WriteSchemaType(IOpenApiWriter writer, string name, JsonSchemaType? schemaType, bool allowMultipleTypes) + { + if (!schemaType.HasValue) + { + return; + } + + var values = schemaType.Value.ToIdentifiers(); + if (values is null || values.Length == 0) + { + return; + } + + if (allowMultipleTypes && values.Length > 1) + { + writer.WriteOptionalCollection(name, values, (w, s) => + { + if (!string.IsNullOrEmpty(s)) + { + w.WriteValue(s!); + } + }); + } + else + { + writer.WriteProperty(name, values[0]); + } + } + /// protected override void SetAdditional31MetadataFromMapNode(JsonObject jsonObject) { @@ -270,4 +752,93 @@ protected override void SetAdditional31MetadataFromMapNode(JsonObject jsonObject } } } + + internal void ApplySchemaMetadata(OpenApiSchema schema, JsonObject jsonObject) + { + Title = schema.Title; + Description = schema.Description; + Default = schema.Default; + if (jsonObject.ContainsKey(OpenApiConstants.Deprecated)) + { + Deprecated = schema.Deprecated; + } + if (jsonObject.ContainsKey(OpenApiConstants.ReadOnly)) + { + ReadOnly = schema.ReadOnly; + } + if (jsonObject.ContainsKey(OpenApiConstants.WriteOnly)) + { + WriteOnly = schema.WriteOnly; + } + Examples = schema.Examples; + Extensions = schema.Extensions; + SchemaId = schema.Id; + Schema = schema.Schema; + Comment = schema.Comment; + if (schema.Vocabulary is { Count: > 0 }) + { + Vocabulary = schema.Vocabulary; + } + DynamicRef = schema.DynamicRef; + DynamicAnchor = schema.DynamicAnchor; + if (schema.Definitions is { Count: > 0 }) + { + Definitions = schema.Definitions; + } + Anchor = schema.Anchor; + ExclusiveMaximum = schema.ExclusiveMaximum; + ExclusiveMinimum = schema.ExclusiveMinimum; + SchemaType = schema.Type; + Const = schema.Const; + Format = schema.Format; + Maximum = schema.Maximum; + Minimum = schema.Minimum; + MaxLength = schema.MaxLength; + MinLength = schema.MinLength; + Pattern = schema.Pattern; + MultipleOf = schema.MultipleOf; + AllOf = schema.AllOf; + OneOf = schema.OneOf; + AnyOf = schema.AnyOf; + Not = schema.Not; + Required = schema.Required; + Items = schema.Items; + MaxItems = schema.MaxItems; + MinItems = schema.MinItems; + UniqueItems = schema.UniqueItems; + Contains = schema.Contains; + MaxContains = schema.MaxContains; + MinContains = schema.MinContains; + Properties = schema.Properties; + PatternProperties = schema.PatternProperties; + MaxProperties = schema.MaxProperties; + MinProperties = schema.MinProperties; + if (jsonObject.TryGetPropertyValue(OpenApiConstants.AdditionalProperties, out var additionalPropertiesNode) && + additionalPropertiesNode is JsonValue) + { + AdditionalPropertiesAllowed = schema.AdditionalPropertiesAllowed; + } + AdditionalProperties = schema.AdditionalProperties; + Discriminator = schema.Discriminator; + Example = schema.Example; + Enum = schema.Enum; + if (jsonObject.TryGetPropertyValue(OpenApiConstants.UnevaluatedProperties, out var unevaluatedPropertiesNode) && + unevaluatedPropertiesNode is JsonValue) + { + UnevaluatedProperties = schema.UnevaluatedProperties; + } + UnevaluatedPropertiesSchema = schema.UnevaluatedPropertiesSchema; + ExternalDocs = schema.ExternalDocs; + Xml = schema.Xml; + UnrecognizedKeywords = schema.UnrecognizedKeywords; + DependentRequired = schema.DependentRequired; + ContentEncoding = schema.ContentEncoding; + ContentMediaType = schema.ContentMediaType; + ContentSchema = schema.ContentSchema; + PropertyNames = schema.PropertyNames; + DependentSchemas = schema.DependentSchemas; + If = schema.If; + Then = schema.Then; + Else = schema.Else; + } } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs index e98608988..bff16769a 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs @@ -49,43 +49,43 @@ public string? Title set => Reference.Title = value; } /// - public Uri? Schema { get => Reference.Schema ?? Target?.Schema; } + public Uri? Schema { get => Reference.Schema ?? Target?.Schema; set => Reference.Schema = value; } /// - public string? Id { get => string.IsNullOrEmpty(Reference.SchemaId) ? Target?.Id : Reference.SchemaId; } + public string? Id { get => string.IsNullOrEmpty(Reference.SchemaId) ? Target?.Id : Reference.SchemaId; set => Reference.SchemaId = value; } /// - public string? Comment { get => string.IsNullOrEmpty(Reference.Comment) ? Target?.Comment : Reference.Comment; } + public string? Comment { get => string.IsNullOrEmpty(Reference.Comment) ? Target?.Comment : Reference.Comment; set => Reference.Comment = value; } /// - public IDictionary? Vocabulary { get => Reference.Vocabulary ?? Target?.Vocabulary; } + public IDictionary? Vocabulary { get => Reference.Vocabulary ?? Target?.Vocabulary; set => Reference.Vocabulary = value; } /// - public string? DynamicRef { get => string.IsNullOrEmpty(Reference.DynamicRef) ? Target?.DynamicRef : Reference.DynamicRef; } + public string? DynamicRef { get => string.IsNullOrEmpty(Reference.DynamicRef) ? Target?.DynamicRef : Reference.DynamicRef; set => Reference.DynamicRef = value; } /// - public string? DynamicAnchor { get => string.IsNullOrEmpty(Reference.DynamicAnchor) ? Target?.DynamicAnchor : Reference.DynamicAnchor; } + public string? DynamicAnchor { get => string.IsNullOrEmpty(Reference.DynamicAnchor) ? Target?.DynamicAnchor : Reference.DynamicAnchor; set => Reference.DynamicAnchor = value; } /// - public IDictionary? Definitions { get => Reference.Definitions ?? Target?.Definitions; } + public IDictionary? Definitions { get => Reference.Definitions ?? Target?.Definitions; set => Reference.Definitions = value; } /// - public string? Anchor { get => string.IsNullOrEmpty(Reference.Anchor) ? (Target as IOpenApiSchemaMissingProperties)?.Anchor : Reference.Anchor; } + public string? Anchor { get => string.IsNullOrEmpty(Reference.Anchor) ? (Target as IOpenApiSchemaMissingProperties)?.Anchor : Reference.Anchor; set => Reference.Anchor = value; } /// - public string? ExclusiveMaximum { get => Target?.ExclusiveMaximum; } + public string? ExclusiveMaximum { get => string.IsNullOrEmpty(Reference.ExclusiveMaximum) ? Target?.ExclusiveMaximum : Reference.ExclusiveMaximum; set => Reference.ExclusiveMaximum = value; } /// - public string? ExclusiveMinimum { get => Target?.ExclusiveMinimum; } + public string? ExclusiveMinimum { get => string.IsNullOrEmpty(Reference.ExclusiveMinimum) ? Target?.ExclusiveMinimum : Reference.ExclusiveMinimum; set => Reference.ExclusiveMinimum = value; } /// - public JsonSchemaType? Type { get => Target?.Type; } + public JsonSchemaType? Type { get => Reference.SchemaType ?? Target?.Type; set => Reference.SchemaType = value; } /// - public string? Const { get => Target?.Const; } + public string? Const { get => string.IsNullOrEmpty(Reference.Const) ? Target?.Const : Reference.Const; set => Reference.Const = value; } /// - public string? Format { get => Target?.Format; } + public string? Format { get => string.IsNullOrEmpty(Reference.Format) ? Target?.Format : Reference.Format; set => Reference.Format = value; } /// - public string? Maximum { get => Target?.Maximum; } + public string? Maximum { get => string.IsNullOrEmpty(Reference.Maximum) ? Target?.Maximum : Reference.Maximum; set => Reference.Maximum = value; } /// - public string? Minimum { get => Target?.Minimum; } + public string? Minimum { get => string.IsNullOrEmpty(Reference.Minimum) ? Target?.Minimum : Reference.Minimum; set => Reference.Minimum = value; } /// - public int? MaxLength { get => Target?.MaxLength; } + public int? MaxLength { get => Reference.MaxLength ?? Target?.MaxLength; set => Reference.MaxLength = value; } /// - public int? MinLength { get => Target?.MinLength; } + public int? MinLength { get => Reference.MinLength ?? Target?.MinLength; set => Reference.MinLength = value; } /// - public string? Pattern { get => Target?.Pattern; } + public string? Pattern { get => string.IsNullOrEmpty(Reference.Pattern) ? Target?.Pattern : Reference.Pattern; set => Reference.Pattern = value; } /// - public decimal? MultipleOf { get => Target?.MultipleOf; } + public decimal? MultipleOf { get => Reference.MultipleOf ?? Target?.MultipleOf; set => Reference.MultipleOf = value; } /// public JsonNode? Default { @@ -105,45 +105,45 @@ public bool WriteOnly set => Reference.WriteOnly = value; } /// - public IList? AllOf { get => Target?.AllOf; } + public IList? AllOf { get => Reference.AllOf ?? Target?.AllOf; set => Reference.AllOf = value; } /// - public IList? OneOf { get => Target?.OneOf; } + public IList? OneOf { get => Reference.OneOf ?? Target?.OneOf; set => Reference.OneOf = value; } /// - public IList? AnyOf { get => Target?.AnyOf; } + public IList? AnyOf { get => Reference.AnyOf ?? Target?.AnyOf; set => Reference.AnyOf = value; } /// - public IOpenApiSchema? Not { get => Target?.Not; } + public IOpenApiSchema? Not { get => Reference.Not ?? Target?.Not; set => Reference.Not = value; } /// - public ISet? Required { get => Target?.Required; } + public ISet? Required { get => Reference.Required ?? Target?.Required; set => Reference.Required = value; } /// - public IOpenApiSchema? Items { get => Target?.Items; } + public IOpenApiSchema? Items { get => Reference.Items ?? Target?.Items; set => Reference.Items = value; } /// - public int? MaxItems { get => Target?.MaxItems; } + public int? MaxItems { get => Reference.MaxItems ?? Target?.MaxItems; set => Reference.MaxItems = value; } /// - public int? MinItems { get => Target?.MinItems; } + public int? MinItems { get => Reference.MinItems ?? Target?.MinItems; set => Reference.MinItems = value; } /// - public bool? UniqueItems { get => Target?.UniqueItems; } + public bool? UniqueItems { get => Reference.UniqueItems ?? Target?.UniqueItems; set => Reference.UniqueItems = value; } /// - public IOpenApiSchema? Contains { get => (Target as IOpenApiSchemaMissingProperties)?.Contains; } + public IOpenApiSchema? Contains { get => Reference.Contains ?? (Target as IOpenApiSchemaMissingProperties)?.Contains; set => Reference.Contains = value; } /// - public uint? MaxContains { get => (Target as IOpenApiSchemaMissingProperties)?.MaxContains; } + public uint? MaxContains { get => Reference.MaxContains ?? (Target as IOpenApiSchemaMissingProperties)?.MaxContains; set => Reference.MaxContains = value; } /// - public uint? MinContains { get => (Target as IOpenApiSchemaMissingProperties)?.MinContains; } + public uint? MinContains { get => Reference.MinContains ?? (Target as IOpenApiSchemaMissingProperties)?.MinContains; set => Reference.MinContains = value; } /// - public IDictionary? Properties { get => Target?.Properties; } + public IDictionary? Properties { get => Reference.Properties ?? Target?.Properties; set => Reference.Properties = value; } /// - public IDictionary? PatternProperties { get => Target?.PatternProperties; } + public IDictionary? PatternProperties { get => Reference.PatternProperties ?? Target?.PatternProperties; set => Reference.PatternProperties = value; } /// - public int? MaxProperties { get => Target?.MaxProperties; } + public int? MaxProperties { get => Reference.MaxProperties ?? Target?.MaxProperties; set => Reference.MaxProperties = value; } /// - public int? MinProperties { get => Target?.MinProperties; } + public int? MinProperties { get => Reference.MinProperties ?? Target?.MinProperties; set => Reference.MinProperties = value; } /// - public bool AdditionalPropertiesAllowed { get => Target?.AdditionalPropertiesAllowed ?? true; } + public bool AdditionalPropertiesAllowed { get => Reference.AdditionalPropertiesAllowed ?? Target?.AdditionalPropertiesAllowed ?? true; set => Reference.AdditionalPropertiesAllowed = value; } /// - public IOpenApiSchema? AdditionalProperties { get => Target?.AdditionalProperties; } + public IOpenApiSchema? AdditionalProperties { get => Reference.AdditionalProperties ?? Target?.AdditionalProperties; set => Reference.AdditionalProperties = value; } /// - public OpenApiDiscriminator? Discriminator { get => Target?.Discriminator; } + public OpenApiDiscriminator? Discriminator { get => Reference.Discriminator ?? Target?.Discriminator; set => Reference.Discriminator = value; } /// - public JsonNode? Example { get => Target?.Example; } + public JsonNode? Example { get => Reference.Example ?? Target?.Example; set => Reference.Example = value; } /// public IList? Examples { @@ -151,29 +151,29 @@ public IList? Examples set => Reference.Examples = value; } /// - public IList? Enum { get => Target?.Enum; } + public IList? Enum { get => Reference.Enum ?? Target?.Enum; set => Reference.Enum = value; } /// - public bool UnevaluatedProperties { get => Target?.UnevaluatedProperties ?? true; } + public bool UnevaluatedProperties { get => Reference.UnevaluatedProperties ?? Target?.UnevaluatedProperties ?? true; set => Reference.UnevaluatedProperties = value; } /// - public IOpenApiSchema? UnevaluatedPropertiesSchema { get => (Target as IOpenApiSchemaMissingProperties)?.UnevaluatedPropertiesSchema; } + public IOpenApiSchema? UnevaluatedPropertiesSchema { get => Reference.UnevaluatedPropertiesSchema ?? (Target as IOpenApiSchemaMissingProperties)?.UnevaluatedPropertiesSchema; set => Reference.UnevaluatedPropertiesSchema = value; } /// - public string? ContentEncoding { get => (Target as IOpenApiSchemaMissingProperties)?.ContentEncoding; } + public string? ContentEncoding { get => string.IsNullOrEmpty(Reference.ContentEncoding) ? (Target as IOpenApiSchemaMissingProperties)?.ContentEncoding : Reference.ContentEncoding; set => Reference.ContentEncoding = value; } /// - public string? ContentMediaType { get => (Target as IOpenApiSchemaMissingProperties)?.ContentMediaType; } + public string? ContentMediaType { get => string.IsNullOrEmpty(Reference.ContentMediaType) ? (Target as IOpenApiSchemaMissingProperties)?.ContentMediaType : Reference.ContentMediaType; set => Reference.ContentMediaType = value; } /// - public IOpenApiSchema? ContentSchema { get => (Target as IOpenApiSchemaMissingProperties)?.ContentSchema; } + public IOpenApiSchema? ContentSchema { get => Reference.ContentSchema ?? (Target as IOpenApiSchemaMissingProperties)?.ContentSchema; set => Reference.ContentSchema = value; } /// - public IOpenApiSchema? PropertyNames { get => (Target as IOpenApiSchemaMissingProperties)?.PropertyNames; } + public IOpenApiSchema? PropertyNames { get => Reference.PropertyNames ?? (Target as IOpenApiSchemaMissingProperties)?.PropertyNames; set => Reference.PropertyNames = value; } /// - public IDictionary? DependentSchemas { get => (Target as IOpenApiSchemaMissingProperties)?.DependentSchemas; } + public IDictionary? DependentSchemas { get => Reference.DependentSchemas ?? (Target as IOpenApiSchemaMissingProperties)?.DependentSchemas; set => Reference.DependentSchemas = value; } /// - public IOpenApiSchema? If { get => (Target as IOpenApiSchemaMissingProperties)?.If; } + public IOpenApiSchema? If { get => Reference.If ?? (Target as IOpenApiSchemaMissingProperties)?.If; set => Reference.If = value; } /// - public IOpenApiSchema? Then { get => (Target as IOpenApiSchemaMissingProperties)?.Then; } + public IOpenApiSchema? Then { get => Reference.Then ?? (Target as IOpenApiSchemaMissingProperties)?.Then; set => Reference.Then = value; } /// - public IOpenApiSchema? Else { get => (Target as IOpenApiSchemaMissingProperties)?.Else; } + public IOpenApiSchema? Else { get => Reference.Else ?? (Target as IOpenApiSchemaMissingProperties)?.Else; set => Reference.Else = value; } /// - public OpenApiExternalDocs? ExternalDocs { get => Target?.ExternalDocs; } + public OpenApiExternalDocs? ExternalDocs { get => Reference.ExternalDocs ?? Target?.ExternalDocs; set => Reference.ExternalDocs = value; } /// public bool Deprecated { @@ -181,7 +181,7 @@ public bool Deprecated set => Reference.Deprecated = value; } /// - public OpenApiXml? Xml { get => Target?.Xml; } + public OpenApiXml? Xml { get => Reference.Xml ?? Target?.Xml; set => Reference.Xml = value; } /// public IDictionary? Extensions { @@ -190,10 +190,10 @@ public IDictionary? Extensions } /// - public IDictionary? UnrecognizedKeywords { get => Target?.UnrecognizedKeywords; } + public IDictionary? UnrecognizedKeywords { get => Reference.UnrecognizedKeywords ?? Target?.UnrecognizedKeywords; set => Reference.UnrecognizedKeywords = value; } /// - public IDictionary>? DependentRequired { get => Target?.DependentRequired; } + public IDictionary>? DependentRequired { get => Reference.DependentRequired ?? Target?.DependentRequired; set => Reference.DependentRequired = value; } /// public override void SerializeAsV31(IOpenApiWriter writer) @@ -245,6 +245,6 @@ protected override JsonSchemaReference CopyReference(JsonSchemaReference sourceR { return new JsonSchemaReference(sourceReference); } - #pragma warning restore CS0618 +#pragma warning restore CS0618 } } diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index 7dc5c5811..cf62927a7 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -1 +1,148 @@ #nullable enable +Microsoft.OpenApi.JsonSchemaReference.AdditionalProperties.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.JsonSchemaReference.AdditionalProperties.set -> void +Microsoft.OpenApi.JsonSchemaReference.AdditionalPropertiesAllowed.get -> bool? +Microsoft.OpenApi.JsonSchemaReference.AdditionalPropertiesAllowed.set -> void +Microsoft.OpenApi.JsonSchemaReference.AllOf.get -> System.Collections.Generic.IList? +Microsoft.OpenApi.JsonSchemaReference.AllOf.set -> void +Microsoft.OpenApi.JsonSchemaReference.AnyOf.get -> System.Collections.Generic.IList? +Microsoft.OpenApi.JsonSchemaReference.AnyOf.set -> void +Microsoft.OpenApi.JsonSchemaReference.Const.get -> string? +Microsoft.OpenApi.JsonSchemaReference.Const.set -> void +Microsoft.OpenApi.JsonSchemaReference.Contains.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.JsonSchemaReference.Contains.set -> void +Microsoft.OpenApi.JsonSchemaReference.ContentEncoding.get -> string? +Microsoft.OpenApi.JsonSchemaReference.ContentEncoding.set -> void +Microsoft.OpenApi.JsonSchemaReference.ContentMediaType.get -> string? +Microsoft.OpenApi.JsonSchemaReference.ContentMediaType.set -> void +Microsoft.OpenApi.JsonSchemaReference.ContentSchema.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.JsonSchemaReference.ContentSchema.set -> void +Microsoft.OpenApi.JsonSchemaReference.DependentRequired.get -> System.Collections.Generic.IDictionary!>? +Microsoft.OpenApi.JsonSchemaReference.DependentRequired.set -> void +Microsoft.OpenApi.JsonSchemaReference.DependentSchemas.get -> System.Collections.Generic.IDictionary? +Microsoft.OpenApi.JsonSchemaReference.DependentSchemas.set -> void +Microsoft.OpenApi.JsonSchemaReference.Discriminator.get -> Microsoft.OpenApi.OpenApiDiscriminator? +Microsoft.OpenApi.JsonSchemaReference.Discriminator.set -> void +Microsoft.OpenApi.JsonSchemaReference.Else.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.JsonSchemaReference.Else.set -> void +Microsoft.OpenApi.JsonSchemaReference.Enum.get -> System.Collections.Generic.IList? +Microsoft.OpenApi.JsonSchemaReference.Enum.set -> void +Microsoft.OpenApi.JsonSchemaReference.Example.get -> System.Text.Json.Nodes.JsonNode? +Microsoft.OpenApi.JsonSchemaReference.Example.set -> void +Microsoft.OpenApi.JsonSchemaReference.ExclusiveMaximum.get -> string? +Microsoft.OpenApi.JsonSchemaReference.ExclusiveMaximum.set -> void +Microsoft.OpenApi.JsonSchemaReference.ExclusiveMinimum.get -> string? +Microsoft.OpenApi.JsonSchemaReference.ExclusiveMinimum.set -> void +Microsoft.OpenApi.JsonSchemaReference.ExternalDocs.get -> Microsoft.OpenApi.OpenApiExternalDocs? +Microsoft.OpenApi.JsonSchemaReference.ExternalDocs.set -> void +Microsoft.OpenApi.JsonSchemaReference.Format.get -> string? +Microsoft.OpenApi.JsonSchemaReference.Format.set -> void +Microsoft.OpenApi.JsonSchemaReference.If.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.JsonSchemaReference.If.set -> void +Microsoft.OpenApi.JsonSchemaReference.Items.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.JsonSchemaReference.Items.set -> void +Microsoft.OpenApi.JsonSchemaReference.MaxContains.get -> uint? +Microsoft.OpenApi.JsonSchemaReference.MaxContains.set -> void +Microsoft.OpenApi.JsonSchemaReference.Maximum.get -> string? +Microsoft.OpenApi.JsonSchemaReference.Maximum.set -> void +Microsoft.OpenApi.JsonSchemaReference.MaxItems.get -> int? +Microsoft.OpenApi.JsonSchemaReference.MaxItems.set -> void +Microsoft.OpenApi.JsonSchemaReference.MaxLength.get -> int? +Microsoft.OpenApi.JsonSchemaReference.MaxLength.set -> void +Microsoft.OpenApi.JsonSchemaReference.MaxProperties.get -> int? +Microsoft.OpenApi.JsonSchemaReference.MaxProperties.set -> void +Microsoft.OpenApi.JsonSchemaReference.MinContains.get -> uint? +Microsoft.OpenApi.JsonSchemaReference.MinContains.set -> void +Microsoft.OpenApi.JsonSchemaReference.Minimum.get -> string? +Microsoft.OpenApi.JsonSchemaReference.Minimum.set -> void +Microsoft.OpenApi.JsonSchemaReference.MinItems.get -> int? +Microsoft.OpenApi.JsonSchemaReference.MinItems.set -> void +Microsoft.OpenApi.JsonSchemaReference.MinLength.get -> int? +Microsoft.OpenApi.JsonSchemaReference.MinLength.set -> void +Microsoft.OpenApi.JsonSchemaReference.MinProperties.get -> int? +Microsoft.OpenApi.JsonSchemaReference.MinProperties.set -> void +Microsoft.OpenApi.JsonSchemaReference.MultipleOf.get -> decimal? +Microsoft.OpenApi.JsonSchemaReference.MultipleOf.set -> void +Microsoft.OpenApi.JsonSchemaReference.Not.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.JsonSchemaReference.Not.set -> void +Microsoft.OpenApi.JsonSchemaReference.OneOf.get -> System.Collections.Generic.IList? +Microsoft.OpenApi.JsonSchemaReference.OneOf.set -> void +Microsoft.OpenApi.JsonSchemaReference.Pattern.get -> string? +Microsoft.OpenApi.JsonSchemaReference.Pattern.set -> void +Microsoft.OpenApi.JsonSchemaReference.PatternProperties.get -> System.Collections.Generic.IDictionary? +Microsoft.OpenApi.JsonSchemaReference.PatternProperties.set -> void +Microsoft.OpenApi.JsonSchemaReference.Properties.get -> System.Collections.Generic.IDictionary? +Microsoft.OpenApi.JsonSchemaReference.Properties.set -> void +Microsoft.OpenApi.JsonSchemaReference.PropertyNames.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.JsonSchemaReference.PropertyNames.set -> void +Microsoft.OpenApi.JsonSchemaReference.Required.get -> System.Collections.Generic.ISet? +Microsoft.OpenApi.JsonSchemaReference.Required.set -> void +Microsoft.OpenApi.JsonSchemaReference.SchemaType.get -> Microsoft.OpenApi.JsonSchemaType? +Microsoft.OpenApi.JsonSchemaReference.SchemaType.set -> void +Microsoft.OpenApi.JsonSchemaReference.Then.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.JsonSchemaReference.Then.set -> void +Microsoft.OpenApi.JsonSchemaReference.UnevaluatedProperties.get -> bool? +Microsoft.OpenApi.JsonSchemaReference.UnevaluatedProperties.set -> void +Microsoft.OpenApi.JsonSchemaReference.UnevaluatedPropertiesSchema.get -> Microsoft.OpenApi.IOpenApiSchema? +Microsoft.OpenApi.JsonSchemaReference.UnevaluatedPropertiesSchema.set -> void +Microsoft.OpenApi.JsonSchemaReference.UniqueItems.get -> bool? +Microsoft.OpenApi.JsonSchemaReference.UniqueItems.set -> void +Microsoft.OpenApi.JsonSchemaReference.UnrecognizedKeywords.get -> System.Collections.Generic.IDictionary? +Microsoft.OpenApi.JsonSchemaReference.UnrecognizedKeywords.set -> void +Microsoft.OpenApi.JsonSchemaReference.Xml.get -> Microsoft.OpenApi.OpenApiXml? +Microsoft.OpenApi.JsonSchemaReference.Xml.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.AdditionalProperties.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.AdditionalPropertiesAllowed.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.AllOf.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Anchor.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.AnyOf.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Comment.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Const.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Contains.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.ContentEncoding.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.ContentMediaType.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.ContentSchema.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Definitions.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.DependentRequired.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.DependentSchemas.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Discriminator.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.DynamicAnchor.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.DynamicRef.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Else.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Enum.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Example.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.ExclusiveMaximum.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.ExclusiveMinimum.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.ExternalDocs.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Format.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Id.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.If.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Items.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Maximum.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.MaxContains.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.MaxItems.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.MaxLength.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.MaxProperties.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Minimum.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.MinContains.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.MinItems.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.MinLength.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.MinProperties.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.MultipleOf.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Not.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.OneOf.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Pattern.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.PatternProperties.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Properties.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.PropertyNames.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Required.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Schema.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Then.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Type.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.UnevaluatedProperties.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.UnevaluatedPropertiesSchema.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.UniqueItems.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.UnrecognizedKeywords.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Vocabulary.set -> void +Microsoft.OpenApi.OpenApiSchemaReference.Xml.set -> void +override Microsoft.OpenApi.JsonSchemaReference.SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter! writer) -> void diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs index 6a933fe5f..ed475a621 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs @@ -378,7 +378,14 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum if (pointer != null) { var reference = GetReferenceIdAndExternalResource(pointer); - return new OpenApiSchemaReference(reference.Item1, hostDocument, reference.Item2); + var result = new OpenApiSchemaReference(reference.Item1, hostDocument, reference.Item2); + if (GetJsonSchemaReferenceExtensionObject(jsonObject) is { Count: > 0 } referenceMetadataNode && + Microsoft.OpenApi.Reader.V31.OpenApiV31Deserializer.LoadSchema(referenceMetadataNode, hostDocument, context) is OpenApiSchema referenceMetadata) + { + result.Reference.ApplySchemaMetadata(referenceMetadata, referenceMetadataNode); + } + + return result; } var schema = new OpenApiSchema(); @@ -397,5 +404,21 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum return schema; } + + private static JsonObject? GetJsonSchemaReferenceExtensionObject(JsonObject jsonObject) + { + JsonObject? result = null; + foreach (var property in jsonObject) + { + const string prefix = "x-jsonschema-"; + if (property.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) && property.Value is not null) + { + result ??= new JsonObject(); + result[property.Key.Substring(prefix.Length)] = property.Value.DeepClone(); + } + } + + return result; + } } } diff --git a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs index 4e1cac8ff..e060537b2 100644 --- a/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs @@ -8,6 +8,7 @@ using System.Text.Json.Nodes; namespace Microsoft.OpenApi.Reader.V31; + internal static partial class OpenApiV31Deserializer { private static readonly FixedFieldMap _openApiSchemaFixedFields = new() @@ -450,20 +451,18 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum var nodeLocation = context.GetLocation(); var reference = GetReferenceIdAndExternalResource(pointer); var result = new OpenApiSchemaReference(reference.Item1, hostDocument, reference.Item2); - result.Reference.SetMetadataFromJsonObject(jsonObject); - result.Reference.SetJsonPointerPath(pointer, nodeLocation); - - // Parse $defs sibling — requires LoadSchema for nested schema materialization, - // so it cannot be done inside SetAdditional31MetadataFromMapNode. - if (jsonObject.TryGetPropertyValue(OpenApiConstants.Defs, out var defsNode) && defsNode is JsonObject) - { - context.StartObject(OpenApiConstants.Defs); - if (defsNode.CreateMap(LoadSchema, hostDocument, context) is { Count: > 0 } definitions) + var referenceMetadata = new OpenApiSchema(); + jsonObject.ParseMap(referenceMetadata, _openApiSchemaFixedFields, _openApiSchemaPatternFields, hostDocument, context, + static (schema, name, value) => { - result.Reference.Definitions = definitions; - } - context.EndObject(); - } + if (!string.Equals(name, OpenApiConstants.DollarRef, StringComparison.Ordinal)) + { + schema.UnrecognizedKeywords ??= new Dictionary(StringComparer.Ordinal); + schema.UnrecognizedKeywords[name] = value; + } + }); + result.Reference.ApplySchemaMetadata(referenceMetadata, jsonObject); + result.Reference.SetJsonPointerPath(pointer, nodeLocation); return result; } diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt index be99f20ed..326a6c379 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -1,3 +1,11 @@ { - "$ref": "#/components/schemas/Pet" + "$ref": "#/components/schemas/Pet", + "x-jsonschema-examples": [ + "reference example" + ], + "x-jsonschema-default": "reference default", + "x-jsonschema-title": "Reference Title", + "x-jsonschema-description": "Reference Description", + "x-jsonschema-deprecated": true, + "x-jsonschema-readOnly": true } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt index 5838142cd..51ff85f87 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"$ref":"#/components/schemas/Pet"} \ No newline at end of file +{"$ref":"#/components/schemas/Pet","x-jsonschema-examples":["reference example"],"x-jsonschema-default":"reference default","x-jsonschema-title":"Reference Title","x-jsonschema-description":"Reference Description","x-jsonschema-deprecated":true,"x-jsonschema-readOnly":true} \ 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 cb9e069ec..6ee32d731 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -53,7 +54,7 @@ public void SchemaReferenceWithAnnotationsShouldWork() var schemaReference = new OpenApiSchemaReference(referenceId, workingDocument) { Title = "Override Title", - Description = "Override Description", + Description = "Override Description", ReadOnly = true, WriteOnly = true, Deprecated = true, @@ -164,6 +165,250 @@ public void SchemaReferenceExposesMissingPropertiesFromTarget() Assert.Equal(0, missingProperties.Else?.MaxProperties); } + [Fact] + public void SchemaReferenceWithKeywordSiblingsShouldOverrideTargetValues() + { + var workingDocument = new OpenApiDocument + { + Components = new OpenApiComponents(), + }; + const string referenceId = "targetSchema"; + workingDocument.Components.Schemas = new Dictionary + { + [referenceId] = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Format = "target-format", + MaxLength = 20, + Required = new HashSet { "target" }, + Properties = new Dictionary + { + ["target"] = new OpenApiSchema { Type = JsonSchemaType.String } + }, + AdditionalPropertiesAllowed = true, + ContentEncoding = "gzip", + If = new OpenApiSchema { Required = new HashSet { "target" } } + } + }; + workingDocument.Workspace.RegisterComponents(workingDocument); + + var schemaReference = new OpenApiSchemaReference(referenceId, workingDocument) + { + Type = JsonSchemaType.String, + Format = "reference-format", + MaxLength = 10, + Required = new HashSet { "reference" }, + Properties = new Dictionary + { + ["reference"] = new OpenApiSchema { Type = JsonSchemaType.Integer } + }, + AdditionalPropertiesAllowed = false, + ContentEncoding = "base64", + If = new OpenApiSchema { Required = new HashSet { "reference" } } + }; + + Assert.Equal(JsonSchemaType.String, schemaReference.Type); + Assert.Equal("reference-format", schemaReference.Format); + Assert.Equal(10, schemaReference.MaxLength); + Assert.NotNull(schemaReference.Required); + Assert.Contains("reference", schemaReference.Required); + Assert.Equal(JsonSchemaType.Integer, schemaReference.Properties?["reference"].Type); + Assert.False(schemaReference.AdditionalPropertiesAllowed); + Assert.Equal("base64", schemaReference.ContentEncoding); + Assert.NotNull(schemaReference.If?.Required); + Assert.Contains("reference", schemaReference.If.Required); + } + + [Fact] + public void ParseSchemaReferenceWithKeywordSiblingsWorks() + { + 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"", + ""type"": ""string"", + ""format"": ""uuid"", + ""maxLength"": 36, + ""required"": [""id""], + ""properties"": { + ""id"": { + ""type"": ""string"" + } + }, + ""additionalProperties"": false, + ""contentEncoding"": ""base64"", + ""if"": { + ""required"": [""id""] + } + } + } + } + } + } + } + } + }, + ""components"": { + ""schemas"": { + ""Pet"": { + ""type"": ""object"", + ""format"": ""target-format"", + ""maxLength"": 10, + ""additionalProperties"": true + } + } + } +}"; + + var readResult = OpenApiDocument.Parse(jsonContent, "json"); + + Assert.Empty(readResult.Diagnostic.Errors); + var schemaReference = Assert.IsType(readResult.Document?.Paths["/test"].Operations[HttpMethod.Get] + .Responses["200"].Content["application/json"].Schema); + Assert.Equal(JsonSchemaType.String, schemaReference.Type); + Assert.Equal("uuid", schemaReference.Format); + Assert.Equal(36, schemaReference.MaxLength); + Assert.NotNull(schemaReference.Required); + Assert.Contains("id", schemaReference.Required); + Assert.Equal(JsonSchemaType.String, schemaReference.Properties?["id"].Type); + Assert.False(schemaReference.AdditionalPropertiesAllowed); + Assert.Equal("base64", schemaReference.ContentEncoding); + Assert.NotNull(schemaReference.If?.Required); + Assert.Contains("id", schemaReference.If.Required); + } + + [Fact] + public void ParseV30SchemaReferenceWithJsonSchemaExtensionSiblingsWorks() + { + var jsonContent = @"{ + ""openapi"": ""3.0.4"", + ""info"": { + ""title"": ""Test API"", + ""version"": ""1.0.0"" + }, + ""paths"": { + ""/test"": { + ""get"": { + ""responses"": { + ""200"": { + ""description"": ""OK"", + ""content"": { + ""application/json"": { + ""schema"": { + ""$ref"": ""#/components/schemas/Pet"", + ""x-jsonschema-type"": ""string"", + ""x-jsonschema-format"": ""uuid"", + ""x-jsonschema-maxLength"": 36, + ""x-jsonschema-contentEncoding"": ""base64"" + } + } + } + } + } + } + } + }, + ""components"": { + ""schemas"": { + ""Pet"": { + ""type"": ""object"", + ""format"": ""target-format"", + ""maxLength"": 10 + } + } + } +}"; + + var readResult = OpenApiDocument.Parse(jsonContent, "json"); + + Assert.Empty(readResult.Diagnostic.Errors); + var schemaReference = Assert.IsType(readResult.Document?.Paths["/test"].Operations[HttpMethod.Get] + .Responses["200"].Content["application/json"].Schema); + Assert.Equal(JsonSchemaType.String, schemaReference.Type); + Assert.Equal("uuid", schemaReference.Format); + Assert.Equal(36, schemaReference.MaxLength); + Assert.Equal("base64", schemaReference.ContentEncoding); + } + + [Fact] + public void ParseV30SchemaReferenceWithJsonSchemaExtensionKeywordSiblingsFromJsonSchema202012Works() + { + var jsonContent = @"{ + ""openapi"": ""3.0.4"", + ""info"": { + ""title"": ""Test API"", + ""version"": ""1.0.0"" + }, + ""paths"": { + ""/test"": { + ""get"": { + ""responses"": { + ""200"": { + ""description"": ""OK"", + ""content"": { + ""application/json"": { + ""schema"": { + ""$ref"": ""#/components/schemas/Pet"", + ""x-jsonschema-$id"": ""https://example.com/schemas/pet-response"", + ""x-jsonschema-$schema"": ""https://json-schema.org/draft/2020-12/schema"", + ""x-jsonschema-$comment"": ""reference comment"", + ""x-jsonschema-$vocabulary"": { + ""https://json-schema.org/draft/2020-12/vocab/core"": true + }, + ""x-jsonschema-$defs"": { + ""LocalPet"": { + ""type"": ""object"" + } + }, + ""x-jsonschema-$anchor"": ""petResponse"", + ""x-jsonschema-$dynamicRef"": ""#pet"", + ""x-jsonschema-$dynamicAnchor"": ""pet"" + } + } + } + } + } + } + } + }, + ""components"": { + ""schemas"": { + ""Pet"": { + ""type"": ""object"" + } + } + } +}"; + + var readResult = OpenApiDocument.Parse(jsonContent, "json"); + + Assert.Empty(readResult.Diagnostic.Errors); + var schemaReference = Assert.IsType(readResult.Document?.Paths["/test"].Operations[HttpMethod.Get] + .Responses["200"].Content["application/json"].Schema); + Assert.Equal("https://example.com/schemas/pet-response", schemaReference.Id); + Assert.Equal(new Uri("https://json-schema.org/draft/2020-12/schema"), schemaReference.Schema); + Assert.Equal("reference comment", schemaReference.Comment); + Assert.NotNull(schemaReference.Vocabulary); + Assert.True(schemaReference.Vocabulary["https://json-schema.org/draft/2020-12/vocab/core"]); + Assert.NotNull(schemaReference.Definitions); + Assert.Equal(JsonSchemaType.Object, schemaReference.Definitions["LocalPet"].Type); + Assert.Equal("petResponse", schemaReference.Anchor); + Assert.Equal("#pet", schemaReference.DynamicRef); + Assert.Equal("pet", schemaReference.DynamicAnchor); + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -319,13 +564,13 @@ public void ParseSchemaReferenceWithAnnotationsWorks() // 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 annotations override target values Assert.Equal("Pet Response Schema", schemaRef.Title); Assert.Equal("A pet object returned from the API", schemaRef.Description); @@ -334,7 +579,7 @@ public void ParseSchemaReferenceWithAnnotationsWorks() Assert.False(schemaRef.WriteOnly); Assert.NotNull(schemaRef.Default); Assert.Single(schemaRef.Examples); - + // Test that target schema still has original values var targetSchema = schemaRef.Target; Assert.NotNull(targetSchema); @@ -408,7 +653,7 @@ public void ParseSchemaReferenceWithExtensionsWorks() } [Fact] - public async Task SchemaReferenceExtensionsNotWrittenInV30() + public async Task SchemaReferenceCustomExtensionsNotWrittenInV30() { // Arrange var reference = new OpenApiSchemaReference("Pet", null) @@ -428,8 +673,37 @@ public async Task SchemaReferenceExtensionsNotWrittenInV30() 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); + Assert.Equal(@"{""$ref"":""#/components/schemas/Pet"",""x-jsonschema-description"":""Local description""}", output); + } + + [Fact] + public async Task SchemaReferenceJsonSchema202012KeywordSiblingsWrittenAsExtensionsInV30() + { + var reference = new OpenApiSchemaReference("Pet", null) + { + Id = "https://example.com/schemas/pet-response", + Schema = new Uri("https://json-schema.org/draft/2020-12/schema"), + Comment = "reference comment", + Vocabulary = new Dictionary + { + ["https://json-schema.org/draft/2020-12/vocab/core"] = true + }, + Definitions = new Dictionary + { + ["LocalPet"] = new OpenApiSchema { Type = JsonSchemaType.Object } + }, + Anchor = "petResponse", + DynamicRef = "#pet", + DynamicAnchor = "pet" + }; + + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = true }); + + reference.SerializeAsV3(writer); + await writer.FlushAsync(); + + Assert.Equal(@"{""$ref"":""#/components/schemas/Pet"",""x-jsonschema-$id"":""https://example.com/schemas/pet-response"",""x-jsonschema-$schema"":""https://json-schema.org/draft/2020-12/schema"",""x-jsonschema-$comment"":""reference comment"",""x-jsonschema-$vocabulary"":{""https://json-schema.org/draft/2020-12/vocab/core"":true},""x-jsonschema-$defs"":{""LocalPet"":{""type"":""object""}},""x-jsonschema-$anchor"":""petResponse"",""x-jsonschema-$dynamicRef"":""#pet"",""x-jsonschema-$dynamicAnchor"":""pet""}", outputStringWriter.ToString()); } [Fact] From 8d811c3e76a8963be6e7a605b80c98d0ea0c63a5 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 26 Jun 2026 13:59:20 -0400 Subject: [PATCH 2/5] fix(library): keep v3 schema references ref-only Remove OpenAPI 3.0 schema reference extension sibling parsing and serialization, restoring Reference Object behavior to only. Also updates JsonSchemaReference documentation links to use clickable XML see href entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Models/JsonSchemaReference.cs | 204 +++++------------- src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 1 - .../Reader/V3/OpenApiSchemaDeserializer.cs | 25 +-- ...orks_produceTerseOutput=False.verified.txt | 12 +- ...Works_produceTerseOutput=True.verified.txt | 2 +- .../References/OpenApiSchemaReferenceTests.cs | 156 +------------- 6 files changed, 60 insertions(+), 340 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs index 14f1dcd70..509dadfd1 100644 --- a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs @@ -14,8 +14,6 @@ namespace Microsoft.OpenApi; /// public class JsonSchemaReference : OpenApiReferenceWithDescription { - private const string JsonSchemaExtensionPrefix = "x-jsonschema-"; - /// /// A default value which by default SHOULD override that of the referenced component. /// If the referenced object-type does not allow a default field, then this field has no effect. @@ -61,223 +59,229 @@ public class JsonSchemaReference : OpenApiReferenceWithDescription public IDictionary? Extensions { get; set; } /// - /// A $id which by default SHOULD override that of the referenced component. + /// A $id which by default SHOULD override that of the referenced component. /// Named SchemaId to avoid collision with the inherited reference identifier (BaseOpenApiReference.Id). /// public string? SchemaId { get; set; } /// - /// The $schema dialect URI which by default SHOULD override that of the referenced component. + /// The $schema dialect URI which by default SHOULD override that of the referenced component. /// public Uri? Schema { get; set; } /// - /// A $comment which by default SHOULD override that of the referenced component. + /// A $comment which by default SHOULD override that of the referenced component. /// public string? Comment { get; set; } /// - /// The $vocabulary which by default SHOULD override that of the referenced component. + /// The $vocabulary which by default SHOULD override that of the referenced component. /// public IDictionary? Vocabulary { get; set; } /// - /// The $dynamicRef which by default SHOULD override that of the referenced component. + /// The $dynamicRef which by default SHOULD override that of the referenced component. /// public string? DynamicRef { get; set; } /// - /// The $dynamicAnchor which by default SHOULD override that of the referenced component. + /// The $dynamicAnchor which by default SHOULD override that of the referenced component. /// public string? DynamicAnchor { get; set; } /// - /// The $defs which by default SHOULD override that of the referenced component. + /// The $defs which by default SHOULD override that of the referenced component. /// public IDictionary? Definitions { get; set; } /// - /// The $anchor which by default SHOULD override that of the referenced component. + /// The $anchor which by default SHOULD override that of the referenced component. /// public string? Anchor { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public string? ExclusiveMaximum { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public string? ExclusiveMinimum { get; set; } /// - /// Schema type override. Named SchemaType to avoid collision with . + /// Schema type override. Named SchemaType to avoid collision with . /// public JsonSchemaType? SchemaType { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public string? Const { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public string? Format { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public string? Maximum { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public string? Minimum { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public int? MaxLength { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public int? MinLength { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public string? Pattern { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public decimal? MultipleOf { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public IList? AllOf { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public IList? OneOf { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public IList? AnyOf { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public IOpenApiSchema? Not { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public ISet? Required { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public IOpenApiSchema? Items { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public int? MaxItems { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public int? MinItems { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public bool? UniqueItems { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public IOpenApiSchema? Contains { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public uint? MaxContains { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public uint? MinContains { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public IDictionary? Properties { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public IDictionary? PatternProperties { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public int? MaxProperties { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public int? MinProperties { get; set; } /// /// Indicates if the schema can contain properties other than those defined by the properties map. + /// Follow JSON Schema definition. /// public bool? AdditionalPropertiesAllowed { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public IOpenApiSchema? AdditionalProperties { get; set; } /// /// Adds support for polymorphism. + /// Follow OpenAPI definition. /// public OpenApiDiscriminator? Discriminator { get; set; } /// /// A free-form property to include an example of an instance for this schema. + /// Follow OpenAPI Schema Object definition. /// public JsonNode? Example { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation + /// Follow JSON Schema definition. /// public IList? Enum { get; set; } /// /// Indicates whether unevaluated properties are allowed. + /// Follow JSON Schema definition. /// public bool? UnevaluatedProperties { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-unevaluatedproperties + /// Follow JSON Schema definition. /// public IOpenApiSchema? UnevaluatedPropertiesSchema { get; set; } /// /// Additional external documentation for this schema. + /// Follow OpenAPI definition. /// public OpenApiExternalDocs? ExternalDocs { get; set; } /// /// This MAY be used only on properties schemas. + /// Follow OpenAPI definition. /// public OpenApiXml? Xml { get; set; } @@ -287,47 +291,47 @@ public class JsonSchemaReference : OpenApiReferenceWithDescription public IDictionary? UnrecognizedKeywords { get; set; } /// - /// Follow JSON Schema definition:https://json-schema.org/draft/2020-12/json-schema-validation#section-6.5.4 + /// Follow JSON Schema definition. /// public IDictionary>? DependentRequired { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation#name-contentencoding + /// Follow JSON Schema definition. /// public string? ContentEncoding { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation#name-contentmediatype + /// Follow JSON Schema definition. /// public string? ContentMediaType { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-validation#name-contentschema + /// Follow JSON Schema definition. /// public IOpenApiSchema? ContentSchema { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-propertynames + /// Follow JSON Schema definition. /// public IOpenApiSchema? PropertyNames { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-dependentschemas + /// Follow JSON Schema definition. /// public IDictionary? DependentSchemas { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-if + /// Follow JSON Schema definition. /// public IOpenApiSchema? If { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-then + /// Follow JSON Schema definition. /// public IOpenApiSchema? Then { get; set; } /// - /// Follow JSON Schema definition: https://json-schema.org/draft/2020-12/json-schema-core#name-else + /// Follow JSON Schema definition. /// public IOpenApiSchema? Else { get; set; } @@ -509,108 +513,6 @@ protected override void SerializeAdditionalV31Properties(IOpenApiWriter writer) writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_1); } - /// - public override void SerializeAsV3(IOpenApiWriter writer) - { - if (Type != ReferenceType.Schema) throw new InvalidOperationException( - $"JsonSchemaReference can only be serialized for ReferenceType.Schema, but was {Type}."); - - writer.WriteStartObject(); - writer.WriteProperty(OpenApiConstants.DollarRef, ReferenceV3); - SerializeV3CompatibilityProperties(writer); - writer.WriteEndObject(); - } - - private void SerializeV3CompatibilityProperties(IOpenApiWriter writer) - { - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Id), SchemaId); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.DollarSchema), Schema?.ToString()); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Comment), Comment); - writer.WriteOptionalMap(GetJsonSchemaExtensionName(OpenApiConstants.Vocabulary), Vocabulary, static (w, s) => w.WriteValue(s)); - writer.WriteOptionalMap(GetJsonSchemaExtensionName(OpenApiConstants.Defs), Definitions, static (w, s) => s.SerializeAsV3(w)); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Anchor), Anchor); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.DynamicRef), DynamicRef); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.DynamicAnchor), DynamicAnchor); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Const), Const); - WriteSchemaType(writer, GetJsonSchemaExtensionName(OpenApiConstants.Type), SchemaType, allowMultipleTypes: true); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Format), Format); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MultipleOf), MultipleOf); - WriteRawProperty(writer, GetJsonSchemaExtensionName(OpenApiConstants.Maximum), Maximum); - WriteRawProperty(writer, GetJsonSchemaExtensionName(OpenApiConstants.ExclusiveMaximum), ExclusiveMaximum); - WriteRawProperty(writer, GetJsonSchemaExtensionName(OpenApiConstants.Minimum), Minimum); - WriteRawProperty(writer, GetJsonSchemaExtensionName(OpenApiConstants.ExclusiveMinimum), ExclusiveMinimum); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MaxLength), MaxLength); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MinLength), MinLength); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Pattern), Pattern); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MaxItems), MaxItems); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MinItems), MinItems); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.UniqueItems), UniqueItems); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MaxProperties), MaxProperties); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MinProperties), MinProperties); - writer.WriteOptionalCollection(GetJsonSchemaExtensionName(OpenApiConstants.Required), Required, static (w, s) => - { - if (!string.IsNullOrEmpty(s)) - { - w.WriteValue(s!); - } - }); - writer.WriteOptionalCollection(GetJsonSchemaExtensionName(OpenApiConstants.Enum), Enum, static (w, e) => w.WriteAny(e)); - writer.WriteOptionalCollection(GetJsonSchemaExtensionName(OpenApiConstants.AllOf), AllOf, static (w, s) => s.SerializeAsV3(w)); - writer.WriteOptionalCollection(GetJsonSchemaExtensionName(OpenApiConstants.AnyOf), AnyOf, static (w, s) => s.SerializeAsV3(w)); - writer.WriteOptionalCollection(GetJsonSchemaExtensionName(OpenApiConstants.OneOf), OneOf, static (w, s) => s.SerializeAsV3(w)); - writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Not), Not, static (w, s) => s.SerializeAsV3(w)); - writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Items), Items, static (w, s) => s.SerializeAsV3(w)); - writer.WriteOptionalMap(GetJsonSchemaExtensionName(OpenApiConstants.Properties), Properties, static (w, s) => s.SerializeAsV3(w)); - writer.WriteOptionalMap(GetJsonSchemaExtensionName(OpenApiConstants.PatternProperties), PatternProperties, static (w, s) => s.SerializeAsV3(w)); - if (AdditionalProperties is not null) - { - writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.AdditionalProperties), AdditionalProperties, static (w, s) => s.SerializeAsV3(w)); - } - else if (AdditionalPropertiesAllowed.HasValue) - { - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.AdditionalProperties), AdditionalPropertiesAllowed.Value); - } - writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Example), Example, static (w, e) => w.WriteAny(e)); - writer.WriteOptionalCollection(GetJsonSchemaExtensionName(OpenApiConstants.Examples), Examples, static (w, e) => w.WriteAny(e)); - if (UnevaluatedPropertiesSchema is not null) - { - writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.UnevaluatedProperties), UnevaluatedPropertiesSchema, static (w, s) => s.SerializeAsV3(w)); - } - else if (UnevaluatedProperties.HasValue) - { - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.UnevaluatedProperties), UnevaluatedProperties.Value); - } - writer.WriteOptionalMap(GetJsonSchemaExtensionName(OpenApiConstants.DependentRequired), DependentRequired, static (w, s) => w.WriteValue(s)); - writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Contains), Contains, static (w, s) => s.SerializeAsV3(w)); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MaxContains), MaxContains); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.MinContains), MinContains); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.ContentEncoding), ContentEncoding); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.ContentMediaType), ContentMediaType); - writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.ContentSchema), ContentSchema, static (w, s) => s.SerializeAsV3(w)); - writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.PropertyNames), PropertyNames, static (w, s) => s.SerializeAsV3(w)); - writer.WriteOptionalMap(GetJsonSchemaExtensionName(OpenApiConstants.DependentSchemas), DependentSchemas, static (w, s) => s.SerializeAsV3(w)); - writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.If), If, static (w, s) => s.SerializeAsV3(w)); - writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Then), Then, static (w, s) => s.SerializeAsV3(w)); - writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Else), Else, static (w, s) => s.SerializeAsV3(w)); - writer.WriteOptionalObject(GetJsonSchemaExtensionName(OpenApiConstants.Default), Default, static (w, d) => w.WriteAny(d)); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Title), Title); - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Description), Description); - if (Deprecated.HasValue) - { - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.Deprecated), Deprecated.Value, false); - } - if (ReadOnly.HasValue) - { - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.ReadOnly), ReadOnly.Value, false); - } - if (WriteOnly.HasValue) - { - writer.WriteProperty(GetJsonSchemaExtensionName(OpenApiConstants.WriteOnly), WriteOnly.Value, false); - } - } - - internal static string GetJsonSchemaExtensionName(string keyword) => JsonSchemaExtensionPrefix + keyword; - private static void WriteRawProperty(IOpenApiWriter writer, string name, string? value) { if (!string.IsNullOrEmpty(value)) diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index cf62927a7..5a561ee7d 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -145,4 +145,3 @@ Microsoft.OpenApi.OpenApiSchemaReference.UniqueItems.set -> void Microsoft.OpenApi.OpenApiSchemaReference.UnrecognizedKeywords.set -> void Microsoft.OpenApi.OpenApiSchemaReference.Vocabulary.set -> void Microsoft.OpenApi.OpenApiSchemaReference.Xml.set -> void -override Microsoft.OpenApi.JsonSchemaReference.SerializeAsV3(Microsoft.OpenApi.IOpenApiWriter! writer) -> void diff --git a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs index ed475a621..6a933fe5f 100644 --- a/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V3/OpenApiSchemaDeserializer.cs @@ -378,14 +378,7 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum if (pointer != null) { var reference = GetReferenceIdAndExternalResource(pointer); - var result = new OpenApiSchemaReference(reference.Item1, hostDocument, reference.Item2); - if (GetJsonSchemaReferenceExtensionObject(jsonObject) is { Count: > 0 } referenceMetadataNode && - Microsoft.OpenApi.Reader.V31.OpenApiV31Deserializer.LoadSchema(referenceMetadataNode, hostDocument, context) is OpenApiSchema referenceMetadata) - { - result.Reference.ApplySchemaMetadata(referenceMetadata, referenceMetadataNode); - } - - return result; + return new OpenApiSchemaReference(reference.Item1, hostDocument, reference.Item2); } var schema = new OpenApiSchema(); @@ -404,21 +397,5 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum return schema; } - - private static JsonObject? GetJsonSchemaReferenceExtensionObject(JsonObject jsonObject) - { - JsonObject? result = null; - foreach (var property in jsonObject) - { - const string prefix = "x-jsonschema-"; - if (property.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) && property.Value is not null) - { - result ??= new JsonObject(); - result[property.Key.Substring(prefix.Length)] = property.Value.DeepClone(); - } - } - - return result; - } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt index 326a6c379..0bb71a8f1 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=False.verified.txt @@ -1,11 +1,3 @@ -{ - "$ref": "#/components/schemas/Pet", - "x-jsonschema-examples": [ - "reference example" - ], - "x-jsonschema-default": "reference default", - "x-jsonschema-title": "Reference Title", - "x-jsonschema-description": "Reference Description", - "x-jsonschema-deprecated": true, - "x-jsonschema-readOnly": true +{ + "$ref": "#/components/schemas/Pet" } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt index 51ff85f87..f6b40c73f 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV3JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"$ref":"#/components/schemas/Pet","x-jsonschema-examples":["reference example"],"x-jsonschema-default":"reference default","x-jsonschema-title":"Reference Title","x-jsonschema-description":"Reference Description","x-jsonschema-deprecated":true,"x-jsonschema-readOnly":true} \ No newline at end of file +{"$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 6ee32d731..1a0031060 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -289,126 +288,6 @@ public void ParseSchemaReferenceWithKeywordSiblingsWorks() Assert.Contains("id", schemaReference.If.Required); } - [Fact] - public void ParseV30SchemaReferenceWithJsonSchemaExtensionSiblingsWorks() - { - var jsonContent = @"{ - ""openapi"": ""3.0.4"", - ""info"": { - ""title"": ""Test API"", - ""version"": ""1.0.0"" - }, - ""paths"": { - ""/test"": { - ""get"": { - ""responses"": { - ""200"": { - ""description"": ""OK"", - ""content"": { - ""application/json"": { - ""schema"": { - ""$ref"": ""#/components/schemas/Pet"", - ""x-jsonschema-type"": ""string"", - ""x-jsonschema-format"": ""uuid"", - ""x-jsonschema-maxLength"": 36, - ""x-jsonschema-contentEncoding"": ""base64"" - } - } - } - } - } - } - } - }, - ""components"": { - ""schemas"": { - ""Pet"": { - ""type"": ""object"", - ""format"": ""target-format"", - ""maxLength"": 10 - } - } - } -}"; - - var readResult = OpenApiDocument.Parse(jsonContent, "json"); - - Assert.Empty(readResult.Diagnostic.Errors); - var schemaReference = Assert.IsType(readResult.Document?.Paths["/test"].Operations[HttpMethod.Get] - .Responses["200"].Content["application/json"].Schema); - Assert.Equal(JsonSchemaType.String, schemaReference.Type); - Assert.Equal("uuid", schemaReference.Format); - Assert.Equal(36, schemaReference.MaxLength); - Assert.Equal("base64", schemaReference.ContentEncoding); - } - - [Fact] - public void ParseV30SchemaReferenceWithJsonSchemaExtensionKeywordSiblingsFromJsonSchema202012Works() - { - var jsonContent = @"{ - ""openapi"": ""3.0.4"", - ""info"": { - ""title"": ""Test API"", - ""version"": ""1.0.0"" - }, - ""paths"": { - ""/test"": { - ""get"": { - ""responses"": { - ""200"": { - ""description"": ""OK"", - ""content"": { - ""application/json"": { - ""schema"": { - ""$ref"": ""#/components/schemas/Pet"", - ""x-jsonschema-$id"": ""https://example.com/schemas/pet-response"", - ""x-jsonschema-$schema"": ""https://json-schema.org/draft/2020-12/schema"", - ""x-jsonschema-$comment"": ""reference comment"", - ""x-jsonschema-$vocabulary"": { - ""https://json-schema.org/draft/2020-12/vocab/core"": true - }, - ""x-jsonschema-$defs"": { - ""LocalPet"": { - ""type"": ""object"" - } - }, - ""x-jsonschema-$anchor"": ""petResponse"", - ""x-jsonschema-$dynamicRef"": ""#pet"", - ""x-jsonschema-$dynamicAnchor"": ""pet"" - } - } - } - } - } - } - } - }, - ""components"": { - ""schemas"": { - ""Pet"": { - ""type"": ""object"" - } - } - } -}"; - - var readResult = OpenApiDocument.Parse(jsonContent, "json"); - - Assert.Empty(readResult.Diagnostic.Errors); - var schemaReference = Assert.IsType(readResult.Document?.Paths["/test"].Operations[HttpMethod.Get] - .Responses["200"].Content["application/json"].Schema); - Assert.Equal("https://example.com/schemas/pet-response", schemaReference.Id); - Assert.Equal(new Uri("https://json-schema.org/draft/2020-12/schema"), schemaReference.Schema); - Assert.Equal("reference comment", schemaReference.Comment); - Assert.NotNull(schemaReference.Vocabulary); - Assert.True(schemaReference.Vocabulary["https://json-schema.org/draft/2020-12/vocab/core"]); - Assert.NotNull(schemaReference.Definitions); - Assert.Equal(JsonSchemaType.Object, schemaReference.Definitions["LocalPet"].Type); - Assert.Equal("petResponse", schemaReference.Anchor); - Assert.Equal("#pet", schemaReference.DynamicRef); - Assert.Equal("pet", schemaReference.DynamicAnchor); - } - [Theory] [InlineData(true)] [InlineData(false)] @@ -653,7 +532,7 @@ public void ParseSchemaReferenceWithExtensionsWorks() } [Fact] - public async Task SchemaReferenceCustomExtensionsNotWrittenInV30() + public async Task SchemaReferenceExtensionsNotWrittenInV30() { // Arrange var reference = new OpenApiSchemaReference("Pet", null) @@ -673,37 +552,8 @@ public async Task SchemaReferenceCustomExtensionsNotWrittenInV30() await writer.FlushAsync(); var output = outputStringWriter.ToString(); - Assert.Equal(@"{""$ref"":""#/components/schemas/Pet"",""x-jsonschema-description"":""Local description""}", output); - } - - [Fact] - public async Task SchemaReferenceJsonSchema202012KeywordSiblingsWrittenAsExtensionsInV30() - { - var reference = new OpenApiSchemaReference("Pet", null) - { - Id = "https://example.com/schemas/pet-response", - Schema = new Uri("https://json-schema.org/draft/2020-12/schema"), - Comment = "reference comment", - Vocabulary = new Dictionary - { - ["https://json-schema.org/draft/2020-12/vocab/core"] = true - }, - Definitions = new Dictionary - { - ["LocalPet"] = new OpenApiSchema { Type = JsonSchemaType.Object } - }, - Anchor = "petResponse", - DynamicRef = "#pet", - DynamicAnchor = "pet" - }; - - var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); - var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = true }); - - reference.SerializeAsV3(writer); - await writer.FlushAsync(); - - Assert.Equal(@"{""$ref"":""#/components/schemas/Pet"",""x-jsonschema-$id"":""https://example.com/schemas/pet-response"",""x-jsonschema-$schema"":""https://json-schema.org/draft/2020-12/schema"",""x-jsonschema-$comment"":""reference comment"",""x-jsonschema-$vocabulary"":{""https://json-schema.org/draft/2020-12/vocab/core"":true},""x-jsonschema-$defs"":{""LocalPet"":{""type"":""object""}},""x-jsonschema-$anchor"":""petResponse"",""x-jsonschema-$dynamicRef"":""#pet"",""x-jsonschema-$dynamicAnchor"":""pet""}", outputStringWriter.ToString()); + // Assert: In v3.0, ONLY $ref should appear - no description, no extensions + Assert.Equal(@"{""$ref"":""#/components/schemas/Pet""}", output); } [Fact] From 09ae18cd8c494388469ef884bf81dd9051394e67 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 26 Jun 2026 14:18:49 -0400 Subject: [PATCH 3/5] chore: refactor to avoid duplicate deserialization logic Signed-off-by: Vincent Biret --- .../Models/JsonSchemaReference.cs | 104 +----------------- 1 file changed, 5 insertions(+), 99 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs index 509dadfd1..6f208f0f3 100644 --- a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs @@ -552,107 +552,13 @@ private static void WriteSchemaType(IOpenApiWriter writer, string name, JsonSche } /// + [Obsolete("Use ApplySchemaMetadata instead.")] +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member protected override void SetAdditional31MetadataFromMapNode(JsonObject jsonObject) +#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member { - base.SetAdditional31MetadataFromMapNode(jsonObject); - - var title = GetPropertyValueFromNode(jsonObject, OpenApiConstants.Title); - if (!string.IsNullOrEmpty(title)) - { - Title = title; - } - - // Boolean properties - if (jsonObject.TryGetPropertyValue(OpenApiConstants.Deprecated, out var deprecatedNode) && deprecatedNode is JsonValue deprecatedValue && deprecatedValue.TryGetValue(out var deprecated)) - { - Deprecated = deprecated; - } - - if (jsonObject.TryGetPropertyValue(OpenApiConstants.ReadOnly, out var readOnlyNode) && readOnlyNode is JsonValue readOnlyValue && readOnlyValue.TryGetValue(out var readOnly)) - { - ReadOnly = readOnly; - } - - if (jsonObject.TryGetPropertyValue(OpenApiConstants.WriteOnly, out var writeOnlyNode) && writeOnlyNode is JsonValue writeOnlyValue && writeOnlyValue.TryGetValue(out var writeOnly)) - { - WriteOnly = writeOnly; - } - - // Default value - if (jsonObject.TryGetPropertyValue(OpenApiConstants.Default, out var defaultNode)) - { - Default = defaultNode; - } - - // Examples - if (jsonObject.TryGetPropertyValue(OpenApiConstants.Examples, out var examplesNode) && examplesNode is JsonArray examplesArray) - { - 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()); - } - - // JSON Schema 2020-12 keyword siblings ($defs is parsed separately in the deserializer - // because it requires LoadSchema for nested schema materialization) - var id = GetPropertyValueFromNode(jsonObject, OpenApiConstants.Id); - if (!string.IsNullOrEmpty(id)) - { - SchemaId = id; - } - - var schemaValue = GetPropertyValueFromNode(jsonObject, OpenApiConstants.DollarSchema); - if (!string.IsNullOrEmpty(schemaValue) && Uri.TryCreate(schemaValue, UriKind.Absolute, out var schemaUri)) - { - Schema = schemaUri; - } - - var comment = GetPropertyValueFromNode(jsonObject, OpenApiConstants.Comment); - if (!string.IsNullOrEmpty(comment)) - { - Comment = comment; - } - - var dynamicRef = GetPropertyValueFromNode(jsonObject, OpenApiConstants.DynamicRef); - if (!string.IsNullOrEmpty(dynamicRef)) - { - DynamicRef = dynamicRef; - } - - var dynamicAnchor = GetPropertyValueFromNode(jsonObject, OpenApiConstants.DynamicAnchor); - if (!string.IsNullOrEmpty(dynamicAnchor)) - { - DynamicAnchor = dynamicAnchor; - } - - var anchor = GetPropertyValueFromNode(jsonObject, OpenApiConstants.Anchor); - if (!string.IsNullOrEmpty(anchor)) - { - Anchor = anchor; - } - - if (jsonObject.TryGetPropertyValue(OpenApiConstants.Vocabulary, out var vocabNode) && vocabNode is JsonObject vocabObj) - { - var vocab = new Dictionary(); - foreach (var kvp in vocabObj) - { - if (kvp.Value is JsonValue v && v.TryGetValue(out var b)) - { - vocab[kvp.Key] = b; - } - } - if (vocab.Count > 0) - { - Vocabulary = vocab; - } - } + //TODO remove this method in next major release + // no-op: we're using ApplySchemaMetadata } internal void ApplySchemaMetadata(OpenApiSchema schema, JsonObject jsonObject) From fa1b241750865ebe261129823bdf0f7a67776772 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 26 Jun 2026 15:05:06 -0400 Subject: [PATCH 4/5] chore: adjust after cherry-pick Signed-off-by: Vincent Biret --- .../Models/JsonSchemaReference.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs index 6f208f0f3..e0609ace8 100644 --- a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs @@ -451,45 +451,45 @@ protected override void SerializeAdditionalV31Properties(IOpenApiWriter writer) } }); writer.WriteOptionalCollection(OpenApiConstants.Enum, Enum, (w, e) => w.WriteAny(e)); - writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, serializeCallback); - writer.WriteOptionalCollection(OpenApiConstants.AnyOf, AnyOf, serializeCallback); - writer.WriteOptionalCollection(OpenApiConstants.OneOf, OneOf, serializeCallback); - writer.WriteOptionalObject(OpenApiConstants.Not, Not, serializeCallback); - writer.WriteOptionalObject(OpenApiConstants.Items, Items, serializeCallback); - writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, serializeCallback); - writer.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, serializeCallback); + writer.WriteOptionalCollection(OpenApiConstants.AllOf, AllOf, (w, e) => e.SerializeAsV31(w)); + writer.WriteOptionalCollection(OpenApiConstants.AnyOf, AnyOf, (w, e) => e.SerializeAsV31(w)); + writer.WriteOptionalCollection(OpenApiConstants.OneOf, OneOf, (w, e) => e.SerializeAsV31(w)); + writer.WriteOptionalObject(OpenApiConstants.Not, Not, (w, e) => e.SerializeAsV31(w)); + writer.WriteOptionalObject(OpenApiConstants.Items, Items, (w, e) => e.SerializeAsV31(w)); + writer.WriteOptionalMap(OpenApiConstants.Properties, Properties, (w, e) => e.SerializeAsV31(w)); + writer.WriteOptionalMap(OpenApiConstants.PatternProperties, PatternProperties, (w, e) => e.SerializeAsV31(w)); if (AdditionalProperties is not null) { - writer.WriteOptionalObject(OpenApiConstants.AdditionalProperties, AdditionalProperties, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.AdditionalProperties, AdditionalProperties, (w, e) => e.SerializeAsV31(w)); } else if (AdditionalPropertiesAllowed.HasValue) { writer.WriteProperty(OpenApiConstants.AdditionalProperties, AdditionalPropertiesAllowed.Value); } - writer.WriteOptionalObject(OpenApiConstants.Discriminator, Discriminator, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.Discriminator, Discriminator, (w, e) => e.SerializeAsV31(w)); writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); if (UnevaluatedPropertiesSchema is not null) { - writer.WriteOptionalObject(OpenApiConstants.UnevaluatedProperties, UnevaluatedPropertiesSchema, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.UnevaluatedProperties, UnevaluatedPropertiesSchema, (w, e) => e.SerializeAsV31(w)); } else if (UnevaluatedProperties.HasValue) { writer.WriteProperty(OpenApiConstants.UnevaluatedProperties, UnevaluatedProperties.Value); } - writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, serializeCallback); - writer.WriteOptionalObject(OpenApiConstants.Xml, Xml, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, e) => e.SerializeAsV31(w)); + writer.WriteOptionalObject(OpenApiConstants.Xml, Xml, (w, e) => e.SerializeAsV31(w)); writer.WriteOptionalMap(OpenApiConstants.DependentRequired, DependentRequired, (w, s) => w.WriteValue(s)); - writer.WriteOptionalObject(OpenApiConstants.Contains, Contains, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.Contains, Contains, (w, e) => e.SerializeAsV31(w)); writer.WriteProperty(OpenApiConstants.MaxContains, MaxContains); writer.WriteProperty(OpenApiConstants.MinContains, MinContains); writer.WriteProperty(OpenApiConstants.ContentEncoding, ContentEncoding); writer.WriteProperty(OpenApiConstants.ContentMediaType, ContentMediaType); - writer.WriteOptionalObject(OpenApiConstants.ContentSchema, ContentSchema, serializeCallback); - writer.WriteOptionalObject(OpenApiConstants.PropertyNames, PropertyNames, serializeCallback); - writer.WriteOptionalMap(OpenApiConstants.DependentSchemas, DependentSchemas, serializeCallback); - writer.WriteOptionalObject(OpenApiConstants.If, If, serializeCallback); - writer.WriteOptionalObject(OpenApiConstants.Then, Then, serializeCallback); - writer.WriteOptionalObject(OpenApiConstants.Else, Else, serializeCallback); + writer.WriteOptionalObject(OpenApiConstants.ContentSchema, ContentSchema, (w, e) => e.SerializeAsV31(w)); + writer.WriteOptionalObject(OpenApiConstants.PropertyNames, PropertyNames, (w, e) => e.SerializeAsV31(w)); + writer.WriteOptionalMap(OpenApiConstants.DependentSchemas, DependentSchemas, (w, e) => e.SerializeAsV31(w)); + writer.WriteOptionalObject(OpenApiConstants.If, If, (w, e) => e.SerializeAsV31(w)); + writer.WriteOptionalObject(OpenApiConstants.Then, Then, (w, e) => e.SerializeAsV31(w)); + writer.WriteOptionalObject(OpenApiConstants.Else, Else, (w, e) => e.SerializeAsV31(w)); // Additional schema metadata annotations in 3.1 writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d)); From 2ba0809725bd3fd62325c7c6f23c7b84e958d123 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 26 Jun 2026 15:12:21 -0400 Subject: [PATCH 5/5] chore: refreshes benchmark results --- .../performance.Descriptions-report-github.md | 16 ++--- .../performance.Descriptions-report.csv | 12 ++-- .../performance.Descriptions-report.html | 16 ++--- .../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, 111 insertions(+), 111 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 d35efb942..c6bade521 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 | 267.2 μs | 40.45 μs | 2.22 μs | 74.2188 | - | - | 308.02 KB | -| PetStoreJson | 112.4 μs | 21.93 μs | 1.20 μs | 41.5039 | 2.4414 | - | 170.17 KB | -| GHESYaml | 616,153.2 μs | 136,440.25 μs | 7,478.75 μs | 45000.0000 | 18000.0000 | 3000.0000 | 253472.06 KB | -| GHESJson | 252,074.5 μs | 466,491.71 μs | 25,569.98 μs | 18000.0000 | 9000.0000 | 2000.0000 | 110643.88 KB | -| GHESNextYaml | 793,235.7 μs | 226,448.39 μs | 12,412.40 μs | 80000.0000 | 19000.0000 | 3000.0000 | 447183.69 KB | -| GHESNextJson | 460,463.3 μs | 239,580.50 μs | 13,132.22 μs | 53000.0000 | 13000.0000 | 3000.0000 | 308943.27 KB | +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|------------- |--------------:|---------------:|--------------:|-----------:|-----------:|----------:|-------------:| +| PetStoreYaml | 281.26 μs | 87.951 μs | 4.821 μs | 74.2188 | 15.6250 | - | 313.05 KB | +| PetStoreJson | 99.37 μs | 7.974 μs | 0.437 μs | 42.4805 | 8.3008 | - | 175.2 KB | +| GHESYaml | 558,007.40 μs | 245,240.510 μs | 13,442.460 μs | 45000.0000 | 19000.0000 | 3000.0000 | 254573.63 KB | +| GHESJson | 263,444.90 μs | 586,112.802 μs | 32,126.821 μs | 18000.0000 | 9000.0000 | 2000.0000 | 111745.13 KB | +| GHESNextYaml | 785,094.03 μs | 214,946.431 μs | 11,781.939 μs | 80000.0000 | 20000.0000 | 3000.0000 | 450674.2 KB | +| GHESNextJson | 461,067.00 μs | 50,227.814 μs | 2,753.156 μs | 53000.0000 | 13000.0000 | 3000.0000 | 312434.25 KB | diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv index e61a2efa7..7d98a61ea 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,267.2 μs,40.45 μs,2.22 μs,74.2188,0.0000,0.0000,308.02 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.4 μs,21.93 μs,1.20 μs,41.5039,2.4414,0.0000,170.17 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,"616,153.2 μs","136,440.25 μs","7,478.75 μs",45000.0000,18000.0000,3000.0000,253472.06 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,"252,074.5 μs","466,491.71 μs","25,569.98 μs",18000.0000,9000.0000,2000.0000,110643.88 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,"793,235.7 μs","226,448.39 μs","12,412.40 μs",80000.0000,19000.0000,3000.0000,447183.69 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,"460,463.3 μs","239,580.50 μs","13,132.22 μs",53000.0000,13000.0000,3000.0000,308943.27 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,281.26 μs,87.951 μs,4.821 μs,74.2188,15.6250,0.0000,313.05 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,99.37 μs,7.974 μs,0.437 μs,42.4805,8.3008,0.0000,175.2 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,"558,007.40 μs","245,240.510 μs","13,442.460 μs",45000.0000,19000.0000,3000.0000,254573.63 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,"263,444.90 μs","586,112.802 μs","32,126.821 μs",18000.0000,9000.0000,2000.0000,111745.13 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,"785,094.03 μs","214,946.431 μs","11,781.939 μs",80000.0000,20000.0000,3000.0000,450674.2 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,"461,067.00 μs","50,227.814 μs","2,753.156 μs",53000.0000,13000.0000,3000.0000,312434.25 KB diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html index aab47ee52..f09c24ea8 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-20260626-130032 +performance.Descriptions-20260626-150505