Skip to content

Commit e4c3bd2

Browse files
committed
fix: revert Count==1 guard removal, keep bare dynamicRef as reference only
The Count==1 guard is correct: bare (no siblings) creates an OpenApiSchemaReference with Target resolution. with structural siblings (maxProperties, type, etc.) falls through to ParseMap as a plain OpenApiSchema, preserving siblings. This matches the behavior where structural constraints coexist with the reference. Reverts the sibling serialization changes since bare dynamic-only refs carry no siblings. Removes two incorrect tests that expected annotation siblings on a reference.
1 parent 005db34 commit e4c3bd2

4 files changed

Lines changed: 6 additions & 105 deletions

File tree

src/Microsoft.OpenApi/Models/JsonSchemaReference.cs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,8 @@ public override void SerializeAsV31(IOpenApiWriter writer)
423423
if (IsDynamicRefOnly)
424424
{
425425
writer.WriteStartObject();
426-
SerializeDynamicRefOnly(writer, (w, e) => e.SerializeAsV31(w));
426+
writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef);
427+
writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_1);
427428
writer.WriteEndObject();
428429
}
429430
else
@@ -437,7 +438,8 @@ public override void SerializeAsV32(IOpenApiWriter writer)
437438
if (IsDynamicRefOnly)
438439
{
439440
writer.WriteStartObject();
440-
SerializeDynamicRefOnly(writer, (w, e) => e.SerializeAsV32(w));
441+
writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef);
442+
writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_1);
441443
writer.WriteEndObject();
442444
}
443445
else
@@ -457,16 +459,6 @@ protected override void SerializeAdditionalV32Properties(IOpenApiWriter writer)
457459
SerializeAdditionalV3XProperties(writer, (w, e) => e.SerializeAsV32(w), base.SerializeAdditionalV32Properties);
458460
}
459461

460-
private void SerializeDynamicRefOnly(IOpenApiWriter writer, Action<IOpenApiWriter, IOpenApiSerializable> serializeCallback)
461-
{
462-
// A dynamic-only reference is always a pure $dynamicRef object (the deserializer only sets
463-
// IsDynamicRefOnly when the schema has no other keywords), so it carries no schema siblings.
464-
// Serialize $dynamicRef (without $ref) plus any extensions. The serializeCallback is accepted
465-
// for signature symmetry with the version-specific serialize paths.
466-
_ = serializeCallback;
467-
writer.WriteProperty(OpenApiConstants.DynamicRef, DynamicRef);
468-
writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi3_1);
469-
}
470462
private void SerializeAdditionalV3XProperties(IOpenApiWriter writer, Action<IOpenApiWriter, IOpenApiSerializable> serializeCallback, Action<IOpenApiWriter> baseSerializer)
471463
{
472464
if (Type != ReferenceType.Schema) throw new InvalidOperationException(

src/Microsoft.OpenApi/Reader/V31/OpenApiSchemaDeserializer.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -468,22 +468,12 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum
468468
return result;
469469
}
470470

471-
if (dynamicPointer != null)
471+
if (dynamicPointer != null && jsonObject.Count == 1)
472472
{
473473
var anchorName = JsonNodeHelper.ExtractDynamicAnchorName(dynamicPointer);
474474
var result = new OpenApiSchemaReference(!string.IsNullOrEmpty(anchorName) ? anchorName! : dynamicPointer, hostDocument);
475475
result.Reference.DynamicRef = dynamicPointer;
476476
result.Reference.IsDynamicRefOnly = true;
477-
result.Reference.SetMetadataFromJsonObject(jsonObject);
478-
479-
if (jsonObject.TryGetPropertyValue(OpenApiConstants.Defs, out var dynDefsNode) && dynDefsNode is JsonObject)
480-
{
481-
context.StartObject(OpenApiConstants.Defs);
482-
if (dynDefsNode.CreateMap(LoadSchema, hostDocument, context) is { Count: > 0 } dynDefinitions)
483-
result.Reference.Definitions = dynDefinitions;
484-
context.EndObject();
485-
}
486-
487477
return result;
488478
}
489479

src/Microsoft.OpenApi/Reader/V32/OpenApiSchemaDeserializer.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -468,22 +468,12 @@ public static IOpenApiSchema LoadSchema(JsonNode node, OpenApiDocument hostDocum
468468
return result;
469469
}
470470

471-
if (dynamicPointer != null)
471+
if (dynamicPointer != null && jsonObject.Count == 1)
472472
{
473473
var anchorName = JsonNodeHelper.ExtractDynamicAnchorName(dynamicPointer);
474474
var result = new OpenApiSchemaReference(!string.IsNullOrEmpty(anchorName) ? anchorName! : dynamicPointer, hostDocument);
475475
result.Reference.DynamicRef = dynamicPointer;
476476
result.Reference.IsDynamicRefOnly = true;
477-
result.Reference.SetMetadataFromJsonObject(jsonObject);
478-
479-
if (jsonObject.TryGetPropertyValue(OpenApiConstants.Defs, out var dynDefsNode) && dynDefsNode is JsonObject)
480-
{
481-
context.StartObject(OpenApiConstants.Defs);
482-
if (dynDefsNode.CreateMap(LoadSchema, hostDocument, context) is { Count: > 0 } dynDefinitions)
483-
result.Reference.Definitions = dynDefinitions;
484-
context.EndObject();
485-
}
486-
487477
return result;
488478
}
489479

test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDynamicRefTests.cs

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -706,75 +706,4 @@ public async Task CreateShallowCopyPreservesValidationSiblings()
706706
Assert.NotNull(copy.AllOf);
707707
Assert.Single(copy.AllOf);
708708
}
709-
710-
[Fact]
711-
public async Task DynamicRefWithAnnotationSiblingResolvesAndPreservesSibling()
712-
{
713-
var yaml =
714-
"""
715-
openapi: 3.1.0
716-
info:
717-
title: Test
718-
version: 1.0.0
719-
paths: {}
720-
components:
721-
schemas:
722-
Tree:
723-
$dynamicAnchor: node
724-
type: object
725-
properties:
726-
value:
727-
type: string
728-
next:
729-
$dynamicRef: '#node'
730-
description: The next node in the tree
731-
""";
732-
733-
var doc = await LoadDocumentAsync(yaml);
734-
735-
var tree = doc.Components.Schemas["Tree"];
736-
var nextSchema = tree.Properties["next"];
737-
738-
var reference = Assert.IsType<OpenApiSchemaReference>(nextSchema);
739-
Assert.True(reference.Reference.IsDynamicRefOnly);
740-
Assert.NotNull(reference.Target);
741-
Assert.Same(tree, reference.Target);
742-
Assert.Equal("The next node in the tree", reference.Description);
743-
}
744-
745-
[Fact]
746-
public async Task DynamicRefWithDefsSiblingParsesDefs()
747-
{
748-
var yaml =
749-
"""
750-
openapi: 3.1.0
751-
info:
752-
title: Test
753-
version: 1.0.0
754-
paths: {}
755-
components:
756-
schemas:
757-
Tree:
758-
$dynamicAnchor: node
759-
type: object
760-
properties:
761-
next:
762-
$dynamicRef: '#node'
763-
$defs:
764-
extra:
765-
type: string
766-
""";
767-
768-
var doc = await LoadDocumentAsync(yaml);
769-
770-
var tree = doc.Components.Schemas["Tree"];
771-
var nextSchema = tree.Properties["next"];
772-
773-
var reference = Assert.IsType<OpenApiSchemaReference>(nextSchema);
774-
Assert.True(reference.Reference.IsDynamicRefOnly);
775-
Assert.NotNull(reference.Target);
776-
Assert.Same(tree, reference.Target);
777-
Assert.NotNull(reference.Definitions);
778-
Assert.True(reference.Definitions.ContainsKey("extra"));
779-
}
780709
}

0 commit comments

Comments
 (0)