From 66a9d04036556945ee3222849b7d071a30016b81 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 | 573 +++++++++++++++++- .../References/OpenApiSchemaReference.cs | 110 ++-- src/Microsoft.OpenApi/PublicAPI.Unshipped.txt | 147 +++++ .../Reader/V3/OpenApiSchemaDeserializer.cs | 25 +- .../Reader/V31/OpenApiSchemaDeserializer.cs | 25 +- .../Reader/V32/OpenApiSchemaDeserializer.cs | 25 +- ...orks_produceTerseOutput=False.verified.txt | 10 +- ...Works_produceTerseOutput=True.verified.txt | 2 +- .../References/OpenApiSchemaReferenceTests.cs | 292 ++++++++- 9 files changed, 1115 insertions(+), 94 deletions(-) diff --git a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs index e8a48f153..ee2ac633a 100644 --- a/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/JsonSchemaReference.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; @@ -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; } /// @@ -154,6 +432,70 @@ private void SerializeAdditionalV3XProperties(IOpenApiWriter writer, Action + { + 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); @@ -176,6 +518,146 @@ private void SerializeAdditionalV3XProperties(IOpenApiWriter writer, Action + 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) { @@ -279,4 +761,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 80f16c655..51187591c 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) @@ -251,6 +251,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/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs index 26fb318fe..71a422516 100644 --- a/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs @@ -8,6 +8,7 @@ using System.Text.Json.Nodes; namespace Microsoft.OpenApi.Reader.V32; + internal static partial class OpenApiV32Deserializer { 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 ebf8423d1..3d5cbcbb9 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. +// 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)] @@ -351,13 +596,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); @@ -366,7 +611,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); @@ -440,7 +685,7 @@ public void ParseSchemaReferenceWithExtensionsWorks() } [Fact] - public async Task SchemaReferenceExtensionsNotWrittenInV30() + public async Task SchemaReferenceCustomExtensionsNotWrittenInV30() { // Arrange var reference = new OpenApiSchemaReference("Pet", null) @@ -460,8 +705,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 9d71746b2da752871e7379e82587a897eed17de9 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 26 Jun 2026 13:42:24 -0400 Subject: [PATCH 2/5] chore(benchmark): update performance reports Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../performance.Descriptions-report-github.md | 12 ++-- .../performance.Descriptions-report.csv | 12 ++-- .../performance.Descriptions-report.html | 14 ++--- .../performance.Descriptions-report.json | 2 +- .../performance.EmptyModels-report-github.md | 56 +++++++++--------- .../performance.EmptyModels-report.csv | 56 +++++++++--------- .../performance.EmptyModels-report.html | 58 +++++++++---------- .../performance.EmptyModels-report.json | 2 +- 8 files changed, 106 insertions(+), 106 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 a17bc1630..1fe9c4c51 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md @@ -12,9 +12,9 @@ WarmupCount=3 ``` | Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | |------------- |-------------:|--------------:|-------------:|-----------:|-----------:|----------:|-------------:| -| PetStoreYaml | 276.5 μs | 30.06 μs | 1.65 μs | 74.2188 | 7.8125 | - | 308.47 KB | -| PetStoreJson | 111.8 μs | 21.37 μs | 1.17 μs | 41.5039 | - | - | 170.61 KB | -| GHESYaml | 622,060.6 μs | 86,657.16 μs | 4,749.97 μs | 45000.0000 | 18000.0000 | 3000.0000 | 253531.41 KB | -| GHESJson | 261,208.7 μs | 8,228.39 μs | 451.03 μs | 18000.0000 | 9000.0000 | 2000.0000 | 110703.15 KB | -| GHESNextYaml | 837,135.5 μs | 595,784.29 μs | 32,656.95 μs | 80000.0000 | 19000.0000 | 3000.0000 | 447250.7 KB | -| GHESNextJson | 456,841.5 μs | 104,869.19 μs | 5,748.23 μs | 53000.0000 | 13000.0000 | 3000.0000 | 309010.66 KB | +| PetStoreYaml | 275.1 μs | 37.61 μs | 2.06 μs | 76.1719 | 13.6719 | - | 314.16 KB | +| PetStoreJson | 117.9 μs | 0.53 μs | 0.03 μs | 42.9688 | 0.4883 | - | 176.3 KB | +| GHESYaml | 634,726.6 μs | 63,613.02 μs | 3,486.84 μs | 45000.0000 | 19000.0000 | 3000.0000 | 254776.13 KB | +| GHESJson | 255,639.6 μs | 104,996.42 μs | 5,755.21 μs | 18000.0000 | 9000.0000 | 2000.0000 | 111947.58 KB | +| GHESNextYaml | 809,785.9 μs | 250,759.05 μs | 13,744.95 μs | 80000.0000 | 20000.0000 | 3000.0000 | 450741.27 KB | +| GHESNextJson | 465,627.4 μs | 58,289.71 μs | 3,195.06 μs | 53000.0000 | 13000.0000 | 3000.0000 | 312505.27 KB | diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv index ea72a344a..7fadb3d83 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv @@ -1,7 +1,7 @@ Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,LargeAddressAware,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,Mean,Error,StdDev,Gen0,Gen1,Gen2,Allocated -PetStoreYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,Arm64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,276.5 μs,30.06 μs,1.65 μs,74.2188,7.8125,0.0000,308.47 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,111.8 μs,21.37 μs,1.17 μs,41.5039,0.0000,0.0000,170.61 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,"622,060.6 μs","86,657.16 μs","4,749.97 μs",45000.0000,18000.0000,3000.0000,253531.41 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,"261,208.7 μs","8,228.39 μs",451.03 μs,18000.0000,9000.0000,2000.0000,110703.15 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,"837,135.5 μs","595,784.29 μs","32,656.95 μs",80000.0000,19000.0000,3000.0000,447250.7 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,"456,841.5 μs","104,869.19 μs","5,748.23 μs",53000.0000,13000.0000,3000.0000,309010.66 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,275.1 μs,37.61 μs,2.06 μs,76.1719,13.6719,0.0000,314.16 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,117.9 μs,0.53 μs,0.03 μs,42.9688,0.4883,0.0000,176.3 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,"634,726.6 μs","63,613.02 μs","3,486.84 μs",45000.0000,19000.0000,3000.0000,254776.13 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,"255,639.6 μs","104,996.42 μs","5,755.21 μs",18000.0000,9000.0000,2000.0000,111947.58 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,"809,785.9 μs","250,759.05 μs","13,744.95 μs",80000.0000,20000.0000,3000.0000,450741.27 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,"465,627.4 μs","58,289.71 μs","3,195.06 μs",53000.0000,13000.0000,3000.0000,312505.27 KB diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html index 39af85dba..3e552eb26 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-123335 +performance.Descriptions-20260626-133405