Skip to content

Commit

Permalink
Merge pull request #2121 from microsoft/fix/immutable-reference
Browse files Browse the repository at this point in the history
fix/immutable reference
  • Loading branch information
baywet authored Feb 5, 2025
2 parents de9d979 + e0aba68 commit 878dd08
Show file tree
Hide file tree
Showing 38 changed files with 279 additions and 309 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ public interface IOpenApiReferenceHolder : IOpenApiSerializable
/// <summary>
/// Reference object.
/// </summary>
OpenApiReference Reference { get; set; }
OpenApiReference Reference { get; init; }
}
}
13 changes: 13 additions & 0 deletions src/Microsoft.OpenApi/IsExternalInit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//TODO remove this if we ever remove the netstandard2.0 target

Check warning on line 1 in src/Microsoft.OpenApi/IsExternalInit.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)
#if !NET5_0_OR_GREATER
namespace System.Runtime.CompilerServices {
using System.ComponentModel;
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class IsExternalInit {
}
}
#endif
7 changes: 7 additions & 0 deletions src/Microsoft.OpenApi/Models/OpenApiDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ namespace Microsoft.OpenApi.Models
/// </summary>
public class OpenApiDocument : IOpenApiSerializable, IOpenApiExtensible, IOpenApiAnnotatable
{
/// <summary>
/// Register components in the document to the workspace
/// </summary>
public void RegisterComponents()
{
Workspace?.RegisterComponents(this);
}
/// <summary>
/// Related workspace containing components that are referenced in a document
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi/Models/OpenApiMediaType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public void SerializeAsV3(IOpenApiWriter writer)
private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
Action<IOpenApiWriter, IOpenApiSerializable> callback)
{
Utils.CheckArgumentNull(writer);;
Utils.CheckArgumentNull(writer);

writer.WriteStartObject();

Expand Down
38 changes: 26 additions & 12 deletions src/Microsoft.OpenApi/Models/OpenApiReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
using System;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Writers;

namespace Microsoft.OpenApi.Models
{
/// <summary>
/// A simple object to allow referencing other components in the specification, internally and externally.
/// </summary>
public class OpenApiReference : IOpenApiSerializable
public class OpenApiReference : IOpenApiSerializable, IOpenApiDescribedElement, IOpenApiSummarizedElement
{
/// <summary>
/// A short summary which by default SHOULD override that of the referenced component.
Expand All @@ -32,13 +33,13 @@ public class OpenApiReference : IOpenApiSerializable
/// 1. a absolute/relative file path, for example: ../commons/pet.json
/// 2. a Url, for example: http://localhost/pet.json
/// </summary>
public string ExternalResource { get; set; }
public string ExternalResource { get; init; }

/// <summary>
/// The element type referenced.
/// </summary>
/// <remarks>This must be present if <see cref="ExternalResource"/> is not present.</remarks>
public ReferenceType? Type { get; set; }
public ReferenceType? Type { get; init; }

/// <summary>
/// The identifier of the reusable component of one particular ReferenceType.
Expand All @@ -47,7 +48,7 @@ public class OpenApiReference : IOpenApiSerializable
/// If ExternalResource is not present, this is the name of the component without the reference type name.
/// For example, if the reference is '#/components/schemas/componentName', the Id is 'componentName'.
/// </summary>
public string Id { get; set; }
public string Id { get; init; }

/// <summary>
/// Gets a flag indicating whether this reference is an external reference.
Expand All @@ -62,12 +63,13 @@ public class OpenApiReference : IOpenApiSerializable
/// <summary>
/// Gets a flag indicating whether a file is a valid OpenAPI document or a fragment
/// </summary>
public bool IsFragment = false;
public bool IsFragment { get; init; }

private OpenApiDocument hostDocument;
/// <summary>
/// The OpenApiDocument that is hosting the OpenApiReference instance. This is used to enable dereferencing the reference.
/// </summary>
public OpenApiDocument HostDocument { get; set; }
public OpenApiDocument HostDocument { get => hostDocument; init => hostDocument = value; }

/// <summary>
/// Gets the full reference string for v3.0.
Expand Down Expand Up @@ -145,12 +147,13 @@ public OpenApiReference() { }
/// </summary>
public OpenApiReference(OpenApiReference reference)
{
Summary = reference?.Summary;
Description = reference?.Description;
ExternalResource = reference?.ExternalResource;
Type = reference?.Type;
Id = reference?.Id;
HostDocument = reference?.HostDocument;
Utils.CheckArgumentNull(reference);
Summary = reference.Summary;
Description = reference.Description;
ExternalResource = reference.ExternalResource;
Type = reference.Type;
Id = reference.Id;
HostDocument = reference.HostDocument;
}

/// <summary>
Expand Down Expand Up @@ -276,5 +279,16 @@ private string GetReferenceTypeNameAsV2(ReferenceType type)
// to indicate that the reference is not pointing to any object.
};
}

/// <summary>
/// Sets the host document after deserialization or before serialization.
/// This method is internal on purpose to avoid consumers mutating the host document.
/// </summary>
/// <param name="currentDocument">Host document to set if none is present</param>
internal void EnsureHostDocumentIsSet(OpenApiDocument currentDocument)
{
Utils.CheckArgumentNull(currentDocument);
hostDocument ??= currentDocument;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,12 @@ namespace Microsoft.OpenApi.Models.References;
/// <typeparam name="V">The interface type for the model.</typeparam>
public abstract class BaseOpenApiReferenceHolder<T, V> : IOpenApiReferenceHolder<T, V> where T : class, IOpenApiReferenceable, V where V : IOpenApiSerializable
{
/// <summary>
/// The resolved target object.
/// </summary>
protected T _target;
/// <inheritdoc/>
public virtual T Target
{
get
{
_target ??= Reference.HostDocument?.ResolveReferenceTo<T>(Reference);
return _target;
return Reference.HostDocument?.ResolveReferenceTo<T>(Reference);
}
}
/// <summary>
Expand All @@ -34,16 +29,6 @@ protected BaseOpenApiReferenceHolder(BaseOpenApiReferenceHolder<T, V> source)
//no need to copy summary and description as if they are not overridden, they will be fetched from the target
//if they are, the reference copy will handle it
}
private protected BaseOpenApiReferenceHolder(T target, string referenceId, ReferenceType referenceType)
{
_target = target;

Reference = new OpenApiReference()
{
Id = referenceId,
Type = referenceType,
};
}
/// <summary>
/// Constructor initializing the reference object.
/// </summary>
Expand All @@ -55,9 +40,11 @@ private protected BaseOpenApiReferenceHolder(T target, string referenceId, Refer
/// 1. a absolute/relative file path, for example: ../commons/pet.json
/// 2. a Url, for example: http://localhost/pet.json
/// </param>
protected BaseOpenApiReferenceHolder(string referenceId, OpenApiDocument hostDocument, ReferenceType referenceType, string externalResource = null)
protected BaseOpenApiReferenceHolder(string referenceId, OpenApiDocument hostDocument, ReferenceType referenceType, string externalResource)
{
Utils.CheckArgumentNullOrEmpty(referenceId);
// we're not checking for null hostDocument as it's optional and can be set via additional methods by a walker
// this way object initialization of a whole document is supported

Reference = new OpenApiReference()
{
Expand All @@ -70,7 +57,7 @@ protected BaseOpenApiReferenceHolder(string referenceId, OpenApiDocument hostDoc
/// <inheritdoc/>
public bool UnresolvedReference { get => Reference is null || Target is null; }
/// <inheritdoc/>
public OpenApiReference Reference { get; set; }
public OpenApiReference Reference { get; init; }
/// <inheritdoc/>
public abstract V CopyReferenceAsTargetElementWithOverrides(V source);
/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class OpenApiCallbackReference : BaseOpenApiReferenceHolder<OpenApiCallba
/// 1. an absolute/relative file path, for example: ../commons/pet.json
/// 2. a Url, for example: http://localhost/pet.json
/// </param>
public OpenApiCallbackReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Callback, externalResource)
public OpenApiCallbackReference(string referenceId, OpenApiDocument hostDocument = null, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Callback, externalResource)
{
}
/// <summary>
Expand All @@ -37,10 +37,6 @@ private OpenApiCallbackReference(OpenApiCallbackReference callback):base(callbac

}

internal OpenApiCallbackReference(OpenApiCallback target, string referenceId):base(target, referenceId, ReferenceType.Callback)
{
}

/// <inheritdoc/>
public Dictionary<RuntimeExpression, IOpenApiPathItem> PathItems { get => Target?.PathItems; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class OpenApiExampleReference : BaseOpenApiReferenceHolder<OpenApiExample
/// 1. a absolute/relative file path, for example: ../commons/pet.json
/// 2. a Url, for example: http://localhost/pet.json
/// </param>
public OpenApiExampleReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Example, externalResource)
public OpenApiExampleReference(string referenceId, OpenApiDocument hostDocument = null, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Example, externalResource)
{
}
/// <summary>
Expand All @@ -36,10 +36,6 @@ private OpenApiExampleReference(OpenApiExampleReference example):base(example)
{
}

internal OpenApiExampleReference(OpenApiExample target, string referenceId):base(target, referenceId, ReferenceType.Example)
{
}

/// <inheritdoc/>
public string Description
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class OpenApiHeaderReference : BaseOpenApiReferenceHolder<OpenApiHeader,
/// 1. a absolute/relative file path, for example: ../commons/pet.json
/// 2. a Url, for example: http://localhost/pet.json
/// </param>
public OpenApiHeaderReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Header, externalResource)
public OpenApiHeaderReference(string referenceId, OpenApiDocument hostDocument = null, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Header, externalResource)
{
}

Expand All @@ -36,10 +36,6 @@ private OpenApiHeaderReference(OpenApiHeaderReference header):base(header)
{
}

internal OpenApiHeaderReference(OpenApiHeader target, string referenceId):base(target, referenceId, ReferenceType.Header)
{
}

/// <inheritdoc/>
public string Description
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class OpenApiLinkReference : BaseOpenApiReferenceHolder<OpenApiLink, IOpe
/// 1. a absolute/relative file path, for example: ../commons/pet.json
/// 2. a Url, for example: http://localhost/pet.json
/// </param>
public OpenApiLinkReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Link, externalResource)
public OpenApiLinkReference(string referenceId, OpenApiDocument hostDocument = null, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Link, externalResource)
{
}
/// <summary>
Expand All @@ -34,9 +34,6 @@ public OpenApiLinkReference(string referenceId, OpenApiDocument hostDocument, st
private OpenApiLinkReference(OpenApiLinkReference reference):base(reference)
{
}
internal OpenApiLinkReference(OpenApiLink target, string referenceId):base(target, referenceId, ReferenceType.Link)
{
}

/// <inheritdoc/>
public string Description
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class OpenApiParameterReference : BaseOpenApiReferenceHolder<OpenApiParam
/// 1. a absolute/relative file path, for example: ../commons/pet.json
/// 2. a Url, for example: http://localhost/pet.json
/// </param>
public OpenApiParameterReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Parameter, externalResource)
public OpenApiParameterReference(string referenceId, OpenApiDocument hostDocument = null, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Parameter, externalResource)
{
}

Expand All @@ -35,10 +35,6 @@ private OpenApiParameterReference(OpenApiParameterReference parameter):base(para
{
}

internal OpenApiParameterReference(OpenApiParameter target, string referenceId):base(target, referenceId, ReferenceType.Parameter)
{
}

/// <inheritdoc/>
public string Name { get => Target?.Name; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class OpenApiPathItemReference : BaseOpenApiReferenceHolder<OpenApiPathIt
/// 1. a absolute/relative file path, for example: ../commons/pet.json
/// 2. a Url, for example: http://localhost/pet.json
/// </param>
public OpenApiPathItemReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null): base(referenceId, hostDocument, ReferenceType.PathItem, externalResource)
public OpenApiPathItemReference(string referenceId, OpenApiDocument hostDocument = null, string externalResource = null): base(referenceId, hostDocument, ReferenceType.PathItem, externalResource)
{
}

Expand All @@ -37,10 +37,6 @@ private OpenApiPathItemReference(OpenApiPathItemReference pathItem):base(pathIte

}

internal OpenApiPathItemReference(OpenApiPathItem target, string referenceId):base(target, referenceId, ReferenceType.PathItem)
{
}

/// <inheritdoc/>
public string Summary
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class OpenApiRequestBodyReference : BaseOpenApiReferenceHolder<OpenApiReq
/// 1. a absolute/relative file path, for example: ../commons/pet.json
/// 2. a Url, for example: http://localhost/pet.json
/// </param>
public OpenApiRequestBodyReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.RequestBody, externalResource)
public OpenApiRequestBodyReference(string referenceId, OpenApiDocument hostDocument = null, string externalResource = null):base(referenceId, hostDocument, ReferenceType.RequestBody, externalResource)
{
}
/// <summary>
Expand All @@ -35,9 +35,6 @@ public OpenApiRequestBodyReference(string referenceId, OpenApiDocument hostDocum
private OpenApiRequestBodyReference(OpenApiRequestBodyReference openApiRequestBodyReference):base(openApiRequestBodyReference)
{

}
internal OpenApiRequestBodyReference(OpenApiRequestBody target, string referenceId):base(target, referenceId, ReferenceType.RequestBody)
{
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class OpenApiResponseReference : BaseOpenApiReferenceHolder<OpenApiRespon
/// 1. a absolute/relative file path, for example: ../commons/pet.json
/// 2. a Url, for example: http://localhost/pet.json
/// </param>
public OpenApiResponseReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Response, externalResource)
public OpenApiResponseReference(string referenceId, OpenApiDocument hostDocument = null, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Response, externalResource)
{
}
/// <summary>
Expand All @@ -35,10 +35,6 @@ private OpenApiResponseReference(OpenApiResponseReference openApiResponseReferen

}

internal OpenApiResponseReference(OpenApiResponse target, string referenceId):base(target, referenceId, ReferenceType.Response)
{
}

/// <inheritdoc/>
public string Description
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class OpenApiSchemaReference : BaseOpenApiReferenceHolder<OpenApiSchema,
/// 1. a absolute/relative file path, for example: ../commons/pet.json
/// 2. a Url, for example: http://localhost/pet.json
/// </param>
public OpenApiSchemaReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Schema, externalResource)
public OpenApiSchemaReference(string referenceId, OpenApiDocument hostDocument = null, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Schema, externalResource)
{
}
/// <summary>
Expand All @@ -36,10 +36,6 @@ private OpenApiSchemaReference(OpenApiSchemaReference schema):base(schema)
{
}

internal OpenApiSchemaReference(OpenApiSchema target, string referenceId):base(target, referenceId, ReferenceType.Schema)
{
}

/// <inheritdoc/>
public string Description
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class OpenApiSecuritySchemeReference : BaseOpenApiReferenceHolder<OpenApi
/// <param name="referenceId">The reference Id.</param>
/// <param name="hostDocument">The host OpenAPI document.</param>
/// <param name="externalResource">The externally referenced file.</param>
public OpenApiSecuritySchemeReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.SecurityScheme, externalResource)
public OpenApiSecuritySchemeReference(string referenceId, OpenApiDocument hostDocument = null, string externalResource = null):base(referenceId, hostDocument, ReferenceType.SecurityScheme, externalResource)
{
}
/// <summary>
Expand All @@ -29,9 +29,6 @@ public OpenApiSecuritySchemeReference(string referenceId, OpenApiDocument hostDo
private OpenApiSecuritySchemeReference(OpenApiSecuritySchemeReference openApiSecuritySchemeReference):base(openApiSecuritySchemeReference)
{

}
internal OpenApiSecuritySchemeReference(OpenApiSecurityScheme target, string referenceId):base(target, referenceId, ReferenceType.SecurityScheme)
{
}

/// <inheritdoc/>
Expand Down
Loading

0 comments on commit 878dd08

Please sign in to comment.