Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 124 additions & 3 deletions src/Microsoft.OpenApi/Models/JsonSchemaReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,47 @@ public class JsonSchemaReference : OpenApiReferenceWithDescription
/// </summary>
public IDictionary<string, IOpenApiExtension>? Extensions { get; set; }

/// <summary>
/// A $id which by default SHOULD override that of the referenced component.
/// Named SchemaId to avoid collision with the inherited reference identifier (BaseOpenApiReference.Id).
/// </summary>
public string? SchemaId { get; set; }

/// <summary>
/// The $schema dialect URI which by default SHOULD override that of the referenced component.
/// </summary>
public Uri? Schema { get; set; }

/// <summary>
/// A $comment which by default SHOULD override that of the referenced component.
/// </summary>
public string? Comment { get; set; }

/// <summary>
/// The $vocabulary which by default SHOULD override that of the referenced component.
/// </summary>
public IDictionary<string, bool>? Vocabulary { get; set; }

/// <summary>
/// The $dynamicRef which by default SHOULD override that of the referenced component.
/// </summary>
public string? DynamicRef { get; set; }

/// <summary>
/// The $dynamicAnchor which by default SHOULD override that of the referenced component.
/// </summary>
public string? DynamicAnchor { get; set; }

/// <summary>
/// The $defs which by default SHOULD override that of the referenced component.
/// </summary>
public IDictionary<string, IOpenApiSchema>? Definitions { get; set; }

/// <summary>
/// The $anchor which by default SHOULD override that of the referenced component.
/// </summary>
public string? Anchor { get; set; }

/// <summary>
/// Parameterless constructor
/// </summary>
Expand All @@ -76,24 +117,50 @@ public JsonSchemaReference(JsonSchemaReference reference) : base(reference)
WriteOnly = reference.WriteOnly;
Examples = reference.Examples;
Extensions = reference.Extensions != null ? new Dictionary<string, IOpenApiExtension>(reference.Extensions) : null;
SchemaId = reference.SchemaId;
Schema = reference.Schema;
Comment = reference.Comment;
Vocabulary = reference.Vocabulary != null ? new Dictionary<string, bool>(reference.Vocabulary) : null;
DynamicRef = reference.DynamicRef;
DynamicAnchor = reference.DynamicAnchor;
Definitions = reference.Definitions != null ? new Dictionary<string, IOpenApiSchema>(reference.Definitions) : null;
Anchor = reference.Anchor;
}

/// <inheritdoc/>
protected override void SerializeAdditionalV31Properties(IOpenApiWriter writer)
{
SerializeAdditionalV3XProperties(writer, base.SerializeAdditionalV31Properties);
SerializeAdditionalV3XProperties(writer, OpenApiSpecVersion.OpenApi3_1, base.SerializeAdditionalV31Properties);
}
/// <inheritdoc/>
protected override void SerializeAdditionalV32Properties(IOpenApiWriter writer)
{
SerializeAdditionalV3XProperties(writer, base.SerializeAdditionalV32Properties);
SerializeAdditionalV3XProperties(writer, OpenApiSpecVersion.OpenApi3_2, base.SerializeAdditionalV32Properties);
}
private void SerializeAdditionalV3XProperties(IOpenApiWriter writer, Action<IOpenApiWriter> baseSerializer)
private void SerializeAdditionalV3XProperties(IOpenApiWriter writer, OpenApiSpecVersion version, Action<IOpenApiWriter> baseSerializer)
{
if (Type != ReferenceType.Schema) throw new InvalidOperationException(
$"JsonSchemaReference can only be serialized for ReferenceType.Schema, but was {Type}.");

baseSerializer(writer);

// JSON Schema 2020-12 keyword siblings (preserved per OAS 3.1+ / JSON Schema 2020-12 semantics)
writer.WriteProperty(OpenApiConstants.Id, SchemaId);
writer.WriteProperty(OpenApiConstants.DollarSchema, Schema?.ToString());
writer.WriteProperty(OpenApiConstants.Comment, Comment);
writer.WriteOptionalMap(OpenApiConstants.Vocabulary, Vocabulary, (w, s) => w.WriteValue(s));
if (version == OpenApiSpecVersion.OpenApi3_1)
{
writer.WriteOptionalMap(OpenApiConstants.Defs, Definitions, (w, s) => s.SerializeAsV31(w));
}
else
{
writer.WriteOptionalMap(OpenApiConstants.Defs, Definitions, (w, s) => s.SerializeAsV32(w));
}
Comment thread
baywet marked this conversation as resolved.
Outdated
writer.WriteProperty(OpenApiConstants.Anchor, Anchor);
writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef);
writer.WriteProperty(OpenApiConstants.DynamicAnchor, DynamicAnchor);

// Additional schema metadata annotations in 3.1
writer.WriteOptionalObject(OpenApiConstants.Default, Default, (w, d) => w.WriteAny(d));
writer.WriteProperty(OpenApiConstants.Title, Title);
Comment thread
baywet marked this conversation as resolved.
Expand Down Expand Up @@ -164,5 +231,59 @@ protected override void SetAdditional31MetadataFromMapNode(JsonObject jsonObject
Extensions ??= new Dictionary<string, IOpenApiExtension>(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<string, bool>();
foreach (var kvp in vocabObj)
{
if (kvp.Value is JsonValue v && v.TryGetValue<bool>(out var b))
{
vocab[kvp.Key] = b;
}
}
Comment thread
baywet marked this conversation as resolved.
Dismissed
if (vocab.Count > 0)
{
Vocabulary = vocab;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,21 @@ public string? Title
set => Reference.Title = value;
}
/// <inheritdoc/>
public Uri? Schema { get => Target?.Schema; }
public Uri? Schema { get => Reference.Schema ?? Target?.Schema; }
/// <inheritdoc/>
public string? Id { get => Target?.Id; }
public string? Id { get => string.IsNullOrEmpty(Reference.SchemaId) ? Target?.Id : Reference.SchemaId; }
/// <inheritdoc/>
public string? Comment { get => Target?.Comment; }
public string? Comment { get => string.IsNullOrEmpty(Reference.Comment) ? Target?.Comment : Reference.Comment; }
/// <inheritdoc/>
public IDictionary<string, bool>? Vocabulary { get => Target?.Vocabulary; }
public IDictionary<string, bool>? Vocabulary { get => Reference.Vocabulary ?? Target?.Vocabulary; }
/// <inheritdoc/>
public string? DynamicRef { get => Target?.DynamicRef; }
public string? DynamicRef { get => string.IsNullOrEmpty(Reference.DynamicRef) ? Target?.DynamicRef : Reference.DynamicRef; }
/// <inheritdoc/>
public string? DynamicAnchor { get => Target?.DynamicAnchor; }
public string? DynamicAnchor { get => string.IsNullOrEmpty(Reference.DynamicAnchor) ? Target?.DynamicAnchor : Reference.DynamicAnchor; }
/// <inheritdoc/>
public IDictionary<string, IOpenApiSchema>? Definitions { get => Target?.Definitions; }
public IDictionary<string, IOpenApiSchema>? Definitions { get => Reference.Definitions ?? Target?.Definitions; }
/// <inheritdoc/>
public string? Anchor { get => (Target as IOpenApiSchemaMissingProperties)?.Anchor; }
public string? Anchor { get => string.IsNullOrEmpty(Reference.Anchor) ? (Target as IOpenApiSchemaMissingProperties)?.Anchor : Reference.Anchor; }
/// <inheritdoc/>
public string? ExclusiveMaximum { get => Target?.ExclusiveMaximum; }
/// <inheritdoc/>
Expand Down
16 changes: 16 additions & 0 deletions src/Microsoft.OpenApi/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
#nullable enable
Microsoft.OpenApi.JsonSchemaReference.Anchor.get -> string?
Microsoft.OpenApi.JsonSchemaReference.Anchor.set -> void
Microsoft.OpenApi.JsonSchemaReference.Comment.get -> string?
Microsoft.OpenApi.JsonSchemaReference.Comment.set -> void
Microsoft.OpenApi.JsonSchemaReference.Definitions.get -> System.Collections.Generic.IDictionary<string!, Microsoft.OpenApi.IOpenApiSchema!>?
Microsoft.OpenApi.JsonSchemaReference.Definitions.set -> void
Microsoft.OpenApi.JsonSchemaReference.DynamicAnchor.get -> string?
Microsoft.OpenApi.JsonSchemaReference.DynamicAnchor.set -> void
Microsoft.OpenApi.JsonSchemaReference.DynamicRef.get -> string?
Microsoft.OpenApi.JsonSchemaReference.DynamicRef.set -> void
Microsoft.OpenApi.JsonSchemaReference.Schema.get -> System.Uri?
Microsoft.OpenApi.JsonSchemaReference.Schema.set -> void
Microsoft.OpenApi.JsonSchemaReference.SchemaId.get -> string?
Microsoft.OpenApi.JsonSchemaReference.SchemaId.set -> void
Microsoft.OpenApi.JsonSchemaReference.Vocabulary.get -> System.Collections.Generic.IDictionary<string!, bool>?
Comment thread
baywet marked this conversation as resolved.
Microsoft.OpenApi.JsonSchemaReference.Vocabulary.set -> void
25 changes: 25 additions & 0 deletions src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,31 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum
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 defsObj)
Comment thread
baywet marked this conversation as resolved.
Outdated
Comment thread
baywet marked this conversation as resolved.
Outdated
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
{
var defs = new Dictionary<string, IOpenApiSchema>(StringComparer.Ordinal);
foreach (var kvp in defsObj)
{
if (kvp.Value is null) continue;
context.StartObject(kvp.Key);
try
{
defs[kvp.Key] = LoadSchema(kvp.Value, hostDocument, context);
}
finally
{
context.EndObject();
}
}
if (defs.Count > 0)
{
result.Reference.Definitions = defs;
}
}
Comment thread
baywet marked this conversation as resolved.
Comment thread
baywet marked this conversation as resolved.

return result;
}

Expand Down
25 changes: 25 additions & 0 deletions src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,31 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum
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 defsObj)
Comment thread
baywet marked this conversation as resolved.
Outdated
{
var defs = new Dictionary<string, IOpenApiSchema>(StringComparer.Ordinal);
foreach (var kvp in defsObj)
{
if (kvp.Value is null) continue;
context.StartObject(kvp.Key);
try
{
defs[kvp.Key] = LoadSchema(kvp.Value, hostDocument, context);
}
finally
{
context.EndObject();
}
}
if (defs.Count > 0)
{
result.Reference.Definitions = defs;
}
}
Comment thread
baywet marked this conversation as resolved.
Comment thread
baywet marked this conversation as resolved.

return result;
}

Expand Down
Loading