From a182f44bfb74ccbbb5b4bbf842693de48d60dac1 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 4 Feb 2025 13:02:18 -0500 Subject: [PATCH] fix: makes reference of holder immutable Signed-off-by: Vincent Biret --- .../Interfaces/IOpenApiReferenceHolder.cs | 2 +- src/Microsoft.OpenApi/Models/OpenApiReference.cs | 14 +++++++++++++- .../References/BaseOpenApiReferenceHolder.cs | 2 +- .../Services/ReferenceHostDocumentSetter.cs | 8 +------- .../ReferenceService/TryLoadReferenceV2Tests.cs | 6 +----- .../V3Tests/OpenApiDocumentTests.cs | 12 ++++++------ .../PublicApi/PublicApi.approved.txt | 4 ++-- 7 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceHolder.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceHolder.cs index c244263f6..8883a90f5 100644 --- a/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceHolder.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceHolder.cs @@ -34,6 +34,6 @@ public interface IOpenApiReferenceHolder : IOpenApiSerializable /// /// Reference object. /// - OpenApiReference Reference { get; set; } + OpenApiReference Reference { get; init; } } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiReference.cs b/src/Microsoft.OpenApi/Models/OpenApiReference.cs index ea614ae0a..bed22a7c3 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiReference.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiReference.cs @@ -78,10 +78,11 @@ public class OpenApiReference : IOpenApiSerializable, IOpenApiDescribedElement, /// public bool IsFragment { get; init; } + private OpenApiDocument openApiDocument; /// /// The OpenApiDocument that is hosting the OpenApiReference instance. This is used to enable dereferencing the reference. /// - public OpenApiDocument HostDocument { get; init; } + public OpenApiDocument HostDocument { get => openApiDocument; init => openApiDocument = value; } /// /// Gets the full reference string for v3.0. @@ -291,5 +292,16 @@ private string GetReferenceTypeNameAsV2(ReferenceType type) // to indicate that the reference is not pointing to any object. }; } + + /// + /// Sets the host document after deserialization or before serialization. + /// This method is internal on purpose to avoid consumers mutating the host document. + /// + /// Host document to set if none is present + internal void EnsureHostDocumentIsSet(OpenApiDocument currentDocument) + { + Utils.CheckArgumentNull(currentDocument); + openApiDocument ??= currentDocument; + } } } diff --git a/src/Microsoft.OpenApi/Models/References/BaseOpenApiReferenceHolder.cs b/src/Microsoft.OpenApi/Models/References/BaseOpenApiReferenceHolder.cs index 86b31fa06..4d1eaf4c0 100644 --- a/src/Microsoft.OpenApi/Models/References/BaseOpenApiReferenceHolder.cs +++ b/src/Microsoft.OpenApi/Models/References/BaseOpenApiReferenceHolder.cs @@ -71,7 +71,7 @@ protected BaseOpenApiReferenceHolder(string referenceId, OpenApiDocument hostDoc /// public bool UnresolvedReference { get => Reference is null || Target is null; } /// - public OpenApiReference Reference { get; set; } + public OpenApiReference Reference { get; init; } /// public abstract V CopyReferenceAsTargetElementWithOverrides(V source); /// diff --git a/src/Microsoft.OpenApi/Services/ReferenceHostDocumentSetter.cs b/src/Microsoft.OpenApi/Services/ReferenceHostDocumentSetter.cs index 7a9685ba4..c660d21bd 100644 --- a/src/Microsoft.OpenApi/Services/ReferenceHostDocumentSetter.cs +++ b/src/Microsoft.OpenApi/Services/ReferenceHostDocumentSetter.cs @@ -21,13 +21,7 @@ public ReferenceHostDocumentSetter(OpenApiDocument currentDocument) /// public override void Visit(IOpenApiReferenceHolder referenceHolder) { - if (referenceHolder.Reference != null) - { - referenceHolder.Reference = new OpenApiReference(referenceHolder.Reference) - { - HostDocument = _currentDocument, - }; - } + referenceHolder.Reference?.EnsureHostDocumentIsSet(_currentDocument); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs index 2380c07e3..9208fabd8 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs @@ -118,11 +118,7 @@ public async Task LoadResponseAndSchemaReference() } }; - var schemaReference = (OpenApiSchemaReference)expected.Content["application/json"].Schema; - schemaReference.Reference = new OpenApiReference(schemaReference.Reference) - { - HostDocument = result.Document, - }; + ((OpenApiSchemaReference)expected.Content["application/json"].Schema).Reference.EnsureHostDocumentIsSet(result.Document); var actual = reference.Target; // Assert diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index 44775f27d..6a5d80f0f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -1046,11 +1046,11 @@ public async Task ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() } }; - tagReference1.Reference = new OpenApiReference(tagReference1.Reference) {HostDocument = expected }; - tagReference2.Reference = new OpenApiReference(tagReference2.Reference) {HostDocument = expected }; - petSchemaReference.Reference = new OpenApiReference(petSchemaReference.Reference) {HostDocument = expected }; - newPetSchemaReference.Reference = new OpenApiReference(newPetSchemaReference.Reference) {HostDocument = expected }; - errorModelSchemaReference.Reference = new OpenApiReference(errorModelSchemaReference.Reference) {HostDocument = expected }; + tagReference1.Reference.EnsureHostDocumentIsSet(expected); + tagReference2.Reference.EnsureHostDocumentIsSet(expected); + petSchemaReference.Reference.EnsureHostDocumentIsSet(expected); + newPetSchemaReference.Reference.EnsureHostDocumentIsSet(expected); + errorModelSchemaReference.Reference.EnsureHostDocumentIsSet(expected); actual.Document.Should().BeEquivalentTo(expected, options => options .IgnoringCyclicReferences() @@ -1284,7 +1284,7 @@ public async Task ParseDocWithRefsUsingProxyReferencesSucceeds() var outputDoc = (await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0)).MakeLineBreaksEnvironmentNeutral(); var expectedParam = expected.Paths["/pets"].Operations[OperationType.Get].Parameters[0]; var expectedParamReference = Assert.IsType(expectedParam); - expectedParamReference.Reference = new OpenApiReference(expectedParamReference.Reference) {HostDocument = doc}; + expectedParamReference.Reference.EnsureHostDocumentIsSet(doc); var actualParamReference = Assert.IsType(actualParam); diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index f8679c32d..5b6c34d8e 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -224,8 +224,8 @@ namespace Microsoft.OpenApi.Interfaces } public interface IOpenApiReferenceHolder : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiSerializable { - Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } bool UnresolvedReference { get; } + Microsoft.OpenApi.Models.OpenApiReference Reference { get; init; } } public interface IOpenApiReferenceHolder : Microsoft.OpenApi.Interfaces.IOpenApiElement, Microsoft.OpenApi.Interfaces.IOpenApiReferenceHolder, Microsoft.OpenApi.Interfaces.IOpenApiSerializable where out T : Microsoft.OpenApi.Interfaces.IOpenApiReferenceable, V @@ -1243,9 +1243,9 @@ namespace Microsoft.OpenApi.Models.References protected readonly T _target; protected BaseOpenApiReferenceHolder(Microsoft.OpenApi.Models.References.BaseOpenApiReferenceHolder source) { } protected BaseOpenApiReferenceHolder(string referenceId, Microsoft.OpenApi.Models.OpenApiDocument hostDocument, Microsoft.OpenApi.Models.ReferenceType referenceType, string externalResource = null) { } - public Microsoft.OpenApi.Models.OpenApiReference Reference { get; set; } public virtual T Target { get; } public bool UnresolvedReference { get; } + public Microsoft.OpenApi.Models.OpenApiReference Reference { get; init; } public abstract V CopyReferenceAsTargetElementWithOverrides(V source); public virtual void SerializeAsV2(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { } public virtual void SerializeAsV3(Microsoft.OpenApi.Writers.IOpenApiWriter writer) { }