Skip to content

Commit

Permalink
Merge pull request #2095 from microsoft/chore/references-refactoring
Browse files Browse the repository at this point in the history
chore: refactors common reference work to a base class to reduce duplication
  • Loading branch information
MaggieKimani1 authored Jan 28, 2025
2 parents ed6ffa1 + a72aa29 commit ee4d4c3
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 483 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Microsoft.OpenApi.Models.Interfaces;

/// <summary>
/// Defines the base properties for the example object.
/// Defines the base properties for the parameter object.
/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking.
/// </summary>
public interface IOpenApiParameter : IOpenApiDescribedElement, IOpenApiSerializable, IOpenApiReadOnlyExtensible
Expand Down
125 changes: 125 additions & 0 deletions src/Microsoft.OpenApi/Models/References/BaseOpenApiReferenceHolder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using System;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Writers;

namespace Microsoft.OpenApi.Models.References;
/// <summary>
/// Base class for OpenApiReferenceHolder.
/// </summary>
/// <typeparam name="T">The concrete class implementation type for the model.</typeparam>
/// <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
{
internal T _target;
/// <inheritdoc/>
public T Target
{
get
{
_target ??= Reference.HostDocument.ResolveReferenceTo<T>(Reference);
return _target;
}
}
/// <summary>
/// Copy constructor
/// </summary>
/// <param name="source">The parameter reference to copy</param>
protected BaseOpenApiReferenceHolder(BaseOpenApiReferenceHolder<T, V> source)
{
Utils.CheckArgumentNull(source);
Reference = source.Reference != null ? new(source.Reference) : null;
UnresolvedReference = source.UnresolvedReference;
//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>
/// <param name="referenceId">The reference Id.</param>
/// <param name="hostDocument">The host OpenAPI document.</param>
/// <param name="referenceType">The reference type.</param>
/// <param name="externalResource">Optional: External resource in the reference.
/// It may be:
/// 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)
{
Utils.CheckArgumentNullOrEmpty(referenceId);

Reference = new OpenApiReference()
{
Id = referenceId,
HostDocument = hostDocument,
Type = referenceType,
ExternalResource = externalResource
};
}
/// <inheritdoc/>
public bool UnresolvedReference { get; set; }
/// <inheritdoc/>
public OpenApiReference Reference { get; set; }
/// <inheritdoc/>
public abstract V CopyReferenceAsTargetElementWithOverrides(V source);
/// <inheritdoc/>
public void SerializeAsV3(IOpenApiWriter writer)
{
if (!writer.GetSettings().ShouldInlineReference(Reference))
{
Reference.SerializeAsV3(writer);
}
else
{
SerializeInternal(writer, (writer, element) => CopyReferenceAsTargetElementWithOverrides(element).SerializeAsV3(writer));
}
}

/// <inheritdoc/>
public void SerializeAsV31(IOpenApiWriter writer)
{
if (!writer.GetSettings().ShouldInlineReference(Reference))
{
Reference.SerializeAsV31(writer);
}
else
{
SerializeInternal(writer, (writer, element) => CopyReferenceAsTargetElementWithOverrides(element).SerializeAsV31(writer));
}
}

/// <inheritdoc/>
public virtual void SerializeAsV2(IOpenApiWriter writer)
{
if (!writer.GetSettings().ShouldInlineReference(Reference))
{
Reference.SerializeAsV2(writer);
}
else
{
SerializeInternal(writer, (writer, element) => CopyReferenceAsTargetElementWithOverrides(element).SerializeAsV2(writer));
}
}

/// <summary>
/// Serialize the reference as a reference or the target object.
/// This method is used to accelerate the serialization methods implementations.
/// </summary>
/// <param name="writer">The OpenApiWriter.</param>
/// <param name="action">The action to serialize the target object.</param>
private protected void SerializeInternal(IOpenApiWriter writer,
Action<IOpenApiWriter, V> action)
{
Utils.CheckArgumentNull(writer);
action(writer, Target);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,8 @@ namespace Microsoft.OpenApi.Models.References
/// <summary>
/// Callback Object Reference: A reference to a map of possible out-of band callbacks related to the parent operation.
/// </summary>
public class OpenApiCallbackReference : IOpenApiCallback, IOpenApiReferenceHolder<OpenApiCallback, IOpenApiCallback>
public class OpenApiCallbackReference : BaseOpenApiReferenceHolder<OpenApiCallback, IOpenApiCallback>, IOpenApiCallback
{
#nullable enable
internal OpenApiCallback _target;
/// <inheritdoc/>
public OpenApiReference Reference { get; set; }

/// <inheritdoc/>
public bool UnresolvedReference { get; set; }

/// <summary>
/// Gets the target callback.
/// </summary>
/// <remarks>
/// If the reference is not resolved, this will return null.
/// </remarks>
public OpenApiCallback Target
#nullable restore
{
get
{
_target ??= Reference.HostDocument.ResolveReferenceTo<OpenApiCallback>(Reference);
return _target;
}
}

/// <summary>
/// Constructor initializing the reference object.
/// </summary>
Expand All @@ -49,39 +25,20 @@ public OpenApiCallback Target
/// 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)
public OpenApiCallbackReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Callback, externalResource)
{
Utils.CheckArgumentNullOrEmpty(referenceId);

Reference = new OpenApiReference()
{
Id = referenceId,
HostDocument = hostDocument,
Type = ReferenceType.Callback,
ExternalResource = externalResource
};
}

/// <summary>
/// Copy constructor
/// </summary>
/// <param name="callback">The callback reference to copy</param>
public OpenApiCallbackReference(OpenApiCallbackReference callback)
public OpenApiCallbackReference(OpenApiCallbackReference callback):base(callback)
{
Utils.CheckArgumentNull(callback);
Reference = callback.Reference != null ? new(callback.Reference) : null;
UnresolvedReference = callback.UnresolvedReference;
}

internal OpenApiCallbackReference(OpenApiCallback target, string referenceId)
internal OpenApiCallbackReference(OpenApiCallback target, string referenceId):base(target, referenceId, ReferenceType.Callback)
{
_target = target;

Reference = new OpenApiReference()
{
Id = referenceId,
Type = ReferenceType.Callback,
};
}

/// <inheritdoc/>
Expand All @@ -91,52 +48,16 @@ internal OpenApiCallbackReference(OpenApiCallback target, string referenceId)
public IDictionary<string, IOpenApiExtension> Extensions { get => Target?.Extensions; }

/// <inheritdoc/>
public void SerializeAsV3(IOpenApiWriter writer)
public override IOpenApiCallback CopyReferenceAsTargetElementWithOverrides(IOpenApiCallback source)
{
if (!writer.GetSettings().ShouldInlineReference(Reference))
{
Reference.SerializeAsV3(writer);
}
else
{
SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer));
}
}

/// <inheritdoc/>
public void SerializeAsV31(IOpenApiWriter writer)
{
if (!writer.GetSettings().ShouldInlineReference(Reference))
{
Reference.SerializeAsV31(writer);
}
else
{
SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer));
}
}

/// <inheritdoc/>
public IOpenApiCallback CopyReferenceAsTargetElementWithOverrides(IOpenApiCallback source)
{
// the copy here is never called since callbacks do not have any overridable fields.
// if the spec evolves to include overridable fields for callbacks, the serialize methods will need to call this copy method.
return source is OpenApiCallback ? new OpenApiCallback(this) : source;
}

/// <inheritdoc/>
public void SerializeAsV2(IOpenApiWriter writer)
public override void SerializeAsV2(IOpenApiWriter writer)
{
// examples components are not supported in OAS 2.0
Reference.SerializeAsV2(writer);
}

/// <inheritdoc/>
private void SerializeInternal(IOpenApiWriter writer,
Action<IOpenApiWriter, IOpenApiReferenceable> action)
{
Utils.CheckArgumentNull(writer);
action(writer, Target);
}
}
}
89 changes: 6 additions & 83 deletions src/Microsoft.OpenApi/Models/References/OpenApiExampleReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,8 @@ namespace Microsoft.OpenApi.Models.References
/// <summary>
/// Example Object Reference.
/// </summary>
public class OpenApiExampleReference : IOpenApiReferenceHolder<OpenApiExample, IOpenApiExample>, IOpenApiExample
public class OpenApiExampleReference : BaseOpenApiReferenceHolder<OpenApiExample, IOpenApiExample>, IOpenApiExample
{
/// <inheritdoc/>
public OpenApiReference Reference { get; set; }

/// <inheritdoc/>
public bool UnresolvedReference { get; set; }
internal OpenApiExample _target;

/// <summary>
/// Gets the target example.
/// </summary>
/// <remarks>
/// If the reference is not resolved, this will return null.
/// </remarks>
public OpenApiExample Target
{
get
{
_target ??= Reference.HostDocument.ResolveReferenceTo<OpenApiExample>(Reference);
return _target;
}
}

/// <summary>
/// Constructor initializing the reference object.
/// </summary>
Expand All @@ -47,41 +25,20 @@ public OpenApiExample Target
/// 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)
public OpenApiExampleReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Example, externalResource)
{
Utils.CheckArgumentNullOrEmpty(referenceId);

Reference = new OpenApiReference()
{
Id = referenceId,
HostDocument = hostDocument,
Type = ReferenceType.Example,
ExternalResource = externalResource
};
}

/// <summary>
/// Copy constructor
/// </summary>
/// <param name="example">The reference to copy.</param>
public OpenApiExampleReference(OpenApiExampleReference example)
public OpenApiExampleReference(OpenApiExampleReference example):base(example)
{
Utils.CheckArgumentNull(example);
Reference = example.Reference != null ? new(example.Reference) : null;
UnresolvedReference = example.UnresolvedReference;
//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
}

internal OpenApiExampleReference(OpenApiExample target, string referenceId)
internal OpenApiExampleReference(OpenApiExample target, string referenceId):base(target, referenceId, ReferenceType.Example)
{
_target = target;

Reference = new OpenApiReference()
{
Id = referenceId,
Type = ReferenceType.Example,
};
}

/// <inheritdoc/>
Expand Down Expand Up @@ -120,50 +77,16 @@ public string Summary
public JsonNode Value { get => Target?.Value; }

/// <inheritdoc/>
public void SerializeAsV3(IOpenApiWriter writer)
{
if (!writer.GetSettings().ShouldInlineReference(Reference))
{
Reference.SerializeAsV3(writer);
}
else
{
SerializeInternal(writer, (writer, referenceElement) => CopyReferenceAsTargetElementWithOverrides(referenceElement).SerializeAsV3(writer));
}
}

/// <inheritdoc/>
public void SerializeAsV31(IOpenApiWriter writer)
{
if (!writer.GetSettings().ShouldInlineReference(Reference))
{
Reference.SerializeAsV31(writer);
}
else
{
SerializeInternal(writer, (writer, referenceElement) => CopyReferenceAsTargetElementWithOverrides(referenceElement).SerializeAsV31(writer));
}
}

/// <inheritdoc/>
public IOpenApiExample CopyReferenceAsTargetElementWithOverrides(IOpenApiExample source)
public override IOpenApiExample CopyReferenceAsTargetElementWithOverrides(IOpenApiExample source)
{
return source is OpenApiExample ? new OpenApiExample(this) : source;
}

/// <inheritdoc/>
public void SerializeAsV2(IOpenApiWriter writer)
public override void SerializeAsV2(IOpenApiWriter writer)
{
// examples components are not supported in OAS 2.0
Reference.SerializeAsV2(writer);
}

/// <inheritdoc/>
private void SerializeInternal(IOpenApiWriter writer,
Action<IOpenApiWriter, IOpenApiExample> action)
{
Utils.CheckArgumentNull(writer);
action(writer, Target);
}
}
}
Loading

0 comments on commit ee4d4c3

Please sign in to comment.