Skip to content

Commit

Permalink
Merge pull request #2090 from microsoft/fix/callback-reference
Browse files Browse the repository at this point in the history
fix: callback reference proxy implementation
  • Loading branch information
baywet authored Jan 27, 2025
2 parents 079ab11 + af3038a commit 028d60b
Show file tree
Hide file tree
Showing 19 changed files with 151 additions and 116 deletions.
3 changes: 2 additions & 1 deletion src/Microsoft.OpenApi.Hidi/StatsVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Services;

namespace Microsoft.OpenApi.Hidi
Expand Down Expand Up @@ -68,7 +69,7 @@ public override void Visit(OpenApiLink link)

public int CallbackCount { get; set; }

public override void Visit(OpenApiCallback callback)
public override void Visit(IOpenApiCallback callback)
{
CallbackCount++;
}
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.OpenApi.Workbench/StatsVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Services;

namespace Microsoft.OpenApi.Workbench
Expand Down Expand Up @@ -68,7 +69,7 @@ public override void Visit(OpenApiLink link)

public int CallbackCount { get; set; }

public override void Visit(OpenApiCallback callback)
public override void Visit(IOpenApiCallback callback)
{
CallbackCount++;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public interface IOpenApiReferenceHolder : IOpenApiSerializable
/// Indicates if object is populated with data or is just a reference to the data
/// </summary>
bool UnresolvedReference { get; set; }
//TODO the UnresolvedReference property setter should be removed and a default implementation that checks whether the target is null for the getter should be provided instead

/// <summary>
/// Reference object.
Expand Down
18 changes: 18 additions & 0 deletions src/Microsoft.OpenApi/Models/Interfaces/IOpenApiCallback.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

using System.Collections.Generic;
using Microsoft.OpenApi.Expressions;
using Microsoft.OpenApi.Interfaces;

namespace Microsoft.OpenApi.Models.Interfaces;

/// <summary>
/// Defines the base properties for the callback object.
/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking.
/// </summary>
public interface IOpenApiCallback : IOpenApiSerializable, IOpenApiReadOnlyExtensible
{
/// <summary>
/// A Path Item Object used to define a callback request and expected responses.
/// </summary>
public Dictionary<RuntimeExpression, OpenApiPathItem> PathItems { get; }
}
30 changes: 9 additions & 21 deletions src/Microsoft.OpenApi/Models/OpenApiCallback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,25 @@
using System.Collections.Generic;
using Microsoft.OpenApi.Expressions;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Writers;

namespace Microsoft.OpenApi.Models
{
/// <summary>
/// Callback Object: A map of possible out-of band callbacks related to the parent operation.
/// </summary>
public class OpenApiCallback : IOpenApiReferenceable, IOpenApiExtensible
public class OpenApiCallback : IOpenApiReferenceable, IOpenApiExtensible, IOpenApiCallback
{
/// <summary>
/// A Path Item Object used to define a callback request and expected responses.
/// </summary>
public virtual Dictionary<RuntimeExpression, OpenApiPathItem> PathItems { get; set; }
= new();
/// <inheritdoc/>
public Dictionary<RuntimeExpression, OpenApiPathItem> PathItems { get; set; }
= [];

/// <summary>
/// Indicates if object is populated with data or is just a reference to the data
/// </summary>
public virtual bool UnresolvedReference { get; set; }

/// <summary>
/// Reference pointer.
/// </summary>
public OpenApiReference Reference { get; set; }

/// <summary>
/// This object MAY be extended with Specification Extensions.
/// </summary>
public virtual IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();
public IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();

/// <summary>
/// Parameter-less constructor
Expand All @@ -43,11 +33,9 @@ public OpenApiCallback() { }
/// <summary>
/// Initializes a copy of an <see cref="OpenApiCallback"/> object
/// </summary>
public OpenApiCallback(OpenApiCallback callback)
public OpenApiCallback(IOpenApiCallback callback)
{
PathItems = callback?.PathItems != null ? new(callback?.PathItems) : null;
UnresolvedReference = callback?.UnresolvedReference ?? UnresolvedReference;
Reference = callback?.Reference != null ? new(callback?.Reference) : null;
Extensions = callback?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(callback.Extensions) : null;
}

Expand All @@ -71,15 +59,15 @@ public void AddPathItem(RuntimeExpression expression, OpenApiPathItem pathItem)
/// </summary>
/// <param name="writer"></param>
/// <exception cref="System.NotImplementedException"></exception>
public virtual void SerializeAsV31(IOpenApiWriter writer)
public void SerializeAsV31(IOpenApiWriter writer)
{
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer));
}

/// <summary>
/// Serialize <see cref="OpenApiCallback"/> to Open Api v3.0
/// </summary>
public virtual void SerializeAsV3(IOpenApiWriter writer)
public void SerializeAsV3(IOpenApiWriter writer)
{
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer));
}
Expand Down
22 changes: 11 additions & 11 deletions src/Microsoft.OpenApi/Models/OpenApiComponents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,55 +25,55 @@ public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible
/// <summary>
/// An object to hold reusable <see cref="OpenApiResponse"/> Objects.
/// </summary>
public virtual IDictionary<string, OpenApiResponse>? Responses { get; set; } = new Dictionary<string, OpenApiResponse>();
public IDictionary<string, OpenApiResponse>? Responses { get; set; } = new Dictionary<string, OpenApiResponse>();

/// <summary>
/// An object to hold reusable <see cref="OpenApiParameter"/> Objects.
/// </summary>
public virtual IDictionary<string, OpenApiParameter>? Parameters { get; set; } =
public IDictionary<string, OpenApiParameter>? Parameters { get; set; } =
new Dictionary<string, OpenApiParameter>();

/// <summary>
/// An object to hold reusable <see cref="OpenApiExample"/> Objects.
/// </summary>
public virtual IDictionary<string, IOpenApiExample>? Examples { get; set; } = new Dictionary<string, IOpenApiExample>();
public IDictionary<string, IOpenApiExample>? Examples { get; set; } = new Dictionary<string, IOpenApiExample>();

/// <summary>
/// An object to hold reusable <see cref="OpenApiRequestBody"/> Objects.
/// </summary>
public virtual IDictionary<string, OpenApiRequestBody>? RequestBodies { get; set; } =
public IDictionary<string, OpenApiRequestBody>? RequestBodies { get; set; } =
new Dictionary<string, OpenApiRequestBody>();

/// <summary>
/// An object to hold reusable <see cref="OpenApiHeader"/> Objects.
/// </summary>
public virtual IDictionary<string, OpenApiHeader>? Headers { get; set; } = new Dictionary<string, OpenApiHeader>();
public IDictionary<string, OpenApiHeader>? Headers { get; set; } = new Dictionary<string, OpenApiHeader>();

/// <summary>
/// An object to hold reusable <see cref="OpenApiSecurityScheme"/> Objects.
/// </summary>
public virtual IDictionary<string, OpenApiSecurityScheme>? SecuritySchemes { get; set; } =
public IDictionary<string, OpenApiSecurityScheme>? SecuritySchemes { get; set; } =
new Dictionary<string, OpenApiSecurityScheme>();

/// <summary>
/// An object to hold reusable <see cref="OpenApiLink"/> Objects.
/// </summary>
public virtual IDictionary<string, OpenApiLink>? Links { get; set; } = new Dictionary<string, OpenApiLink>();
public IDictionary<string, OpenApiLink>? Links { get; set; } = new Dictionary<string, OpenApiLink>();

/// <summary>
/// An object to hold reusable <see cref="OpenApiCallback"/> Objects.
/// </summary>
public virtual IDictionary<string, OpenApiCallback>? Callbacks { get; set; } = new Dictionary<string, OpenApiCallback>();
public IDictionary<string, IOpenApiCallback>? Callbacks { get; set; } = new Dictionary<string, IOpenApiCallback>();

/// <summary>
/// An object to hold reusable <see cref="OpenApiPathItem"/> Object.
/// </summary>
public virtual IDictionary<string, OpenApiPathItem>? PathItems { get; set; } = new Dictionary<string, OpenApiPathItem>();
public IDictionary<string, OpenApiPathItem>? PathItems { get; set; } = new Dictionary<string, OpenApiPathItem>();

/// <summary>
/// This object MAY be extended with Specification Extensions.
/// </summary>
public virtual IDictionary<string, IOpenApiExtension>? Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();
public IDictionary<string, IOpenApiExtension>? Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();

/// <summary>
/// Parameter-less constructor
Expand All @@ -93,7 +93,7 @@ public OpenApiComponents(OpenApiComponents? components)
Headers = components?.Headers != null ? new Dictionary<string, OpenApiHeader>(components.Headers) : null;
SecuritySchemes = components?.SecuritySchemes != null ? new Dictionary<string, OpenApiSecurityScheme>(components.SecuritySchemes) : null;
Links = components?.Links != null ? new Dictionary<string, OpenApiLink>(components.Links) : null;
Callbacks = components?.Callbacks != null ? new Dictionary<string, OpenApiCallback>(components.Callbacks) : null;
Callbacks = components?.Callbacks != null ? new Dictionary<string, IOpenApiCallback>(components.Callbacks) : null;
PathItems = components?.PathItems != null ? new Dictionary<string, OpenApiPathItem>(components.PathItems) : null;
Extensions = components?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(components.Extensions) : null;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi/Models/OpenApiDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ public bool AddComponent<T>(string id, T componentToRegister)
Components.Links.Add(id, openApiLink);
break;
case OpenApiCallback openApiCallback:
Components.Callbacks ??= new Dictionary<string, OpenApiCallback>();
Components.Callbacks ??= new Dictionary<string, IOpenApiCallback>();
Components.Callbacks.Add(id, openApiCallback);
break;
case OpenApiPathItem openApiPathItem:
Expand Down
5 changes: 3 additions & 2 deletions src/Microsoft.OpenApi/Models/OpenApiOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Models.References;
using Microsoft.OpenApi.Writers;

Expand Down Expand Up @@ -80,7 +81,7 @@ public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible, IOpenA
/// The key value used to identify the callback object is an expression, evaluated at runtime,
/// that identifies a URL to use for the callback operation.
/// </summary>
public IDictionary<string, OpenApiCallback>? Callbacks { get; set; } = new Dictionary<string, OpenApiCallback>();
public IDictionary<string, IOpenApiCallback>? Callbacks { get; set; } = new Dictionary<string, IOpenApiCallback>();

/// <summary>
/// Declares this operation to be deprecated. Consumers SHOULD refrain from usage of the declared operation.
Expand Down Expand Up @@ -129,7 +130,7 @@ public OpenApiOperation(OpenApiOperation? operation)
Parameters = operation?.Parameters != null ? new List<OpenApiParameter>(operation.Parameters) : null;
RequestBody = operation?.RequestBody != null ? new(operation?.RequestBody) : null;
Responses = operation?.Responses != null ? new(operation?.Responses) : null;
Callbacks = operation?.Callbacks != null ? new Dictionary<string, OpenApiCallback>(operation.Callbacks) : null;
Callbacks = operation?.Callbacks != null ? new Dictionary<string, IOpenApiCallback>(operation.Callbacks) : null;
Deprecated = operation?.Deprecated ?? Deprecated;
Security = operation?.Security != null ? new List<OpenApiSecurityRequirement>(operation.Security) : null;
Servers = operation?.Servers != null ? new List<OpenApiServer>(operation.Servers) : null;
Expand Down
59 changes: 43 additions & 16 deletions src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@
using System.Collections.Generic;
using Microsoft.OpenApi.Expressions;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Writers;

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 : OpenApiCallback, IOpenApiReferenceHolder<OpenApiCallback>
public class OpenApiCallbackReference : IOpenApiCallback, IOpenApiReferenceHolder<OpenApiCallback, IOpenApiCallback>
{
#nullable enable
internal OpenApiCallback _target;
private readonly OpenApiReference _reference;
/// <inheritdoc/>
public OpenApiReference Reference { get; set; }

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

/// <summary>
/// Gets the target callback.
Expand All @@ -29,7 +34,7 @@ public OpenApiCallback Target
{
get
{
_target ??= Reference.HostDocument.ResolveReferenceTo<OpenApiCallback>(_reference);
_target ??= Reference.HostDocument.ResolveReferenceTo<OpenApiCallback>(Reference);
return _target;
}
}
Expand All @@ -48,41 +53,49 @@ public OpenApiCallbackReference(string referenceId, OpenApiDocument hostDocument
{
Utils.CheckArgumentNullOrEmpty(referenceId);

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

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

internal OpenApiCallbackReference(OpenApiCallback target, string referenceId)
{
_target = target;

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

/// <inheritdoc/>
public override Dictionary<RuntimeExpression, OpenApiPathItem> PathItems { get => Target.PathItems; set => Target.PathItems = value; }
public Dictionary<RuntimeExpression, OpenApiPathItem> PathItems { get => Target.PathItems; }

/// <inheritdoc/>
public override IDictionary<string, IOpenApiExtension> Extensions { get => Target.Extensions; set => Target.Extensions = value; }
public IDictionary<string, IOpenApiExtension> Extensions { get => Target.Extensions; }

/// <inheritdoc/>
public override void SerializeAsV3(IOpenApiWriter writer)
public void SerializeAsV3(IOpenApiWriter writer)
{
if (!writer.GetSettings().ShouldInlineReference(_reference))
if (!writer.GetSettings().ShouldInlineReference(Reference))
{
_reference.SerializeAsV3(writer);
return;
Reference.SerializeAsV3(writer);
}
else
{
Expand All @@ -91,19 +104,33 @@ public override void SerializeAsV3(IOpenApiWriter writer)
}

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

/// <inheritdoc/>
public IOpenApiCallback CopyReferenceAsTargetElementWithOverrides(IOpenApiCallback openApiExample)
{
// 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 openApiExample is OpenApiCallback ? new OpenApiCallback(this) : openApiExample;
}

/// <inheritdoc/>
public 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class OpenApiExampleReference : IOpenApiReferenceHolder<OpenApiExample, I
public OpenApiReference Reference { get; set; }

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

/// <summary>
Expand Down Expand Up @@ -68,7 +68,7 @@ public OpenApiExampleReference(OpenApiExampleReference example)
{
Utils.CheckArgumentNull(example);
Reference = example?.Reference != null ? new(example.Reference) : null;
UnresolvedReference = example?.UnresolvedReference ?? UnresolvedReference;
UnresolvedReference = example?.UnresolvedReference ?? false;
//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
}
Expand Down
Loading

0 comments on commit 028d60b

Please sign in to comment.