Skip to content

Commit

Permalink
Merge pull request #2099 from microsoft/fix/response-reference
Browse files Browse the repository at this point in the history
fix: response reference proxy design pattern implementation
  • Loading branch information
MaggieKimani1 authored Jan 28, 2025
2 parents df0aafb + 5b4003b commit 8103c20
Show file tree
Hide file tree
Showing 46 changed files with 285 additions and 287 deletions.
29 changes: 29 additions & 0 deletions src/Microsoft.OpenApi/Models/Interfaces/IOpenApiResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Collections.Generic;
using Microsoft.OpenApi.Interfaces;

namespace Microsoft.OpenApi.Models.Interfaces;

/// <summary>
/// Defines the base properties for the response object.
/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking.
/// </summary>
public interface IOpenApiResponse : IOpenApiDescribedElement, IOpenApiSerializable, IOpenApiReadOnlyExtensible
{
/// <summary>
/// Maps a header name to its definition.
/// </summary>
public IDictionary<string, IOpenApiHeader> Headers { get; }

/// <summary>
/// A map containing descriptions of potential response payloads.
/// The key is a media type or media type range and the value describes it.
/// </summary>
public IDictionary<string, OpenApiMediaType> Content { get; }

/// <summary>
/// A map of operations links that can be followed from the response.
/// The key of the map is a short name for the link,
/// following the naming constraints of the names for Component Objects.
/// </summary>
public IDictionary<string, IOpenApiLink> Links { get; }
}
6 changes: 3 additions & 3 deletions src/Microsoft.OpenApi/Models/OpenApiComponents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible
public IDictionary<string, OpenApiSchema>? Schemas { get; set; } = new Dictionary<string, OpenApiSchema>();

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

/// <summary>
/// An object to hold reusable <see cref="IOpenApiParameter"/> Objects.
Expand Down Expand Up @@ -86,7 +86,7 @@ public OpenApiComponents() { }
public OpenApiComponents(OpenApiComponents? components)
{
Schemas = components?.Schemas != null ? new Dictionary<string, OpenApiSchema>(components.Schemas) : null;
Responses = components?.Responses != null ? new Dictionary<string, OpenApiResponse>(components.Responses) : null;
Responses = components?.Responses != null ? new Dictionary<string, IOpenApiResponse>(components.Responses) : null;
Parameters = components?.Parameters != null ? new Dictionary<string, IOpenApiParameter>(components.Parameters) : null;
Examples = components?.Examples != null ? new Dictionary<string, IOpenApiExample>(components.Examples) : null;
RequestBodies = components?.RequestBodies != null ? new Dictionary<string, IOpenApiRequestBody>(components.RequestBodies) : 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 @@ -596,7 +596,7 @@ public bool AddComponent<T>(string id, T componentToRegister)
Components.Parameters.Add(id, openApiParameter);
break;
case OpenApiResponse openApiResponse:
Components.Responses ??= new Dictionary<string, OpenApiResponse>();
Components.Responses ??= new Dictionary<string, IOpenApiResponse>();
Components.Responses.Add(id, openApiResponse);
break;
case OpenApiRequestBody openApiRequestBody:
Expand Down
6 changes: 4 additions & 2 deletions src/Microsoft.OpenApi/Models/OpenApiOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,10 @@ public void SerializeAsV2(IOpenApiWriter writer)
.SelectMany(static r => r.Value.Content?.Keys ?? [])
.Concat(
Responses
.Where(static r => r.Value.Reference is {HostDocument: not null})
.SelectMany(static r => r.Value.Content?.Keys ?? []))
.Select(static r => r.Value)
.OfType<OpenApiResponseReference>()
.Where(static r => r.Reference is {HostDocument: not null})
.SelectMany(static r => r.Content?.Keys ?? []))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();

Expand Down
60 changes: 18 additions & 42 deletions src/Microsoft.OpenApi/Models/OpenApiResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,82 +13,58 @@ namespace Microsoft.OpenApi.Models
/// <summary>
/// Response object.
/// </summary>
public class OpenApiResponse : IOpenApiReferenceable, IOpenApiExtensible
public class OpenApiResponse : IOpenApiReferenceable, IOpenApiExtensible, IOpenApiResponse
{
/// <summary>
/// REQUIRED. A short description of the response.
/// </summary>
public virtual string Description { get; set; }

/// <summary>
/// Maps a header name to its definition.
/// </summary>
public virtual IDictionary<string, IOpenApiHeader> Headers { get; set; } = new Dictionary<string, IOpenApiHeader>();

/// <summary>
/// A map containing descriptions of potential response payloads.
/// The key is a media type or media type range and the value describes it.
/// </summary>
public virtual IDictionary<string, OpenApiMediaType> Content { get; set; } = new Dictionary<string, OpenApiMediaType>();
/// <inheritdoc/>
public string Description { get; set; }

/// <summary>
/// A map of operations links that can be followed from the response.
/// The key of the map is a short name for the link,
/// following the naming constraints of the names for Component Objects.
/// </summary>
public virtual IDictionary<string, IOpenApiLink> Links { get; set; } = new Dictionary<string, IOpenApiLink>();
/// <inheritdoc/>
public IDictionary<string, IOpenApiHeader> Headers { get; set; } = new Dictionary<string, IOpenApiHeader>();

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

/// <summary>
/// Indicates if object is populated with data or is just a reference to the data
/// </summary>
public bool UnresolvedReference { get; set; }
/// <inheritdoc/>
public IDictionary<string, IOpenApiLink> Links { get; set; } = new Dictionary<string, IOpenApiLink>();

/// <summary>
/// Reference pointer.
/// </summary>
public OpenApiReference Reference { get; set; }
/// <inheritdoc/>
public IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();

/// <summary>
/// Parameterless constructor
/// </summary>
public OpenApiResponse() { }

/// <summary>
/// Initializes a copy of <see cref="OpenApiResponse"/> object
/// Initializes a copy of <see cref="IOpenApiResponse"/> object
/// </summary>
public OpenApiResponse(OpenApiResponse response)
public OpenApiResponse(IOpenApiResponse response)
{
Utils.CheckArgumentNull(response);
Description = response?.Description ?? Description;
Headers = response?.Headers != null ? new Dictionary<string, IOpenApiHeader>(response.Headers) : null;
Content = response?.Content != null ? new Dictionary<string, OpenApiMediaType>(response.Content) : null;
Links = response?.Links != null ? new Dictionary<string, IOpenApiLink>(response.Links) : null;
Extensions = response?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(response.Extensions) : null;
UnresolvedReference = response?.UnresolvedReference ?? UnresolvedReference;
Reference = response?.Reference != null ? new(response?.Reference) : null;
}

/// <summary>
/// Serialize <see cref="OpenApiResponse"/> to Open Api v3.1
/// </summary>
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="OpenApiResponse"/> 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));
}

internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
Action<IOpenApiWriter, IOpenApiSerializable> callback)
{
Utils.CheckArgumentNull(writer);
Expand Down Expand Up @@ -116,7 +92,7 @@ internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersio
/// <summary>
/// Serialize to OpenAPI V2 document without using reference.
/// </summary>
public virtual void SerializeAsV2(IOpenApiWriter writer)
public void SerializeAsV2(IOpenApiWriter writer)
{
Utils.CheckArgumentNull(writer);

Expand Down
4 changes: 3 additions & 1 deletion src/Microsoft.OpenApi/Models/OpenApiResponses.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using Microsoft.OpenApi.Models.Interfaces;

namespace Microsoft.OpenApi.Models
{
/// <summary>
/// Responses object.
/// </summary>
public class OpenApiResponses : OpenApiExtensibleDictionary<OpenApiResponse>
public class OpenApiResponses : OpenApiExtensibleDictionary<IOpenApiResponse>
{
/// <summary>
/// Parameterless constructor
Expand Down
115 changes: 18 additions & 97 deletions src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,14 @@
using System.Collections.Generic;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Writers;

namespace Microsoft.OpenApi.Models.References
{
/// <summary>
/// Response Object Reference.
/// </summary>
public class OpenApiResponseReference : OpenApiResponse, IOpenApiReferenceHolder<OpenApiResponse>
public class OpenApiResponseReference : BaseOpenApiReferenceHolder<OpenApiResponse, IOpenApiResponse>, IOpenApiResponse
{
internal OpenApiResponse _target;
private readonly OpenApiReference _reference;
private string _description;

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

/// <summary>
/// Constructor initializing the reference object.
/// </summary>
Expand All @@ -43,102 +23,43 @@ public OpenApiResponse Target
/// 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)
public OpenApiResponseReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Response, externalResource)
{
Utils.CheckArgumentNullOrEmpty(referenceId);

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

Reference = _reference;
}

internal OpenApiResponseReference(string referenceId, OpenApiResponse target)
internal OpenApiResponseReference(OpenApiResponse target, string referenceId):base(target, referenceId, ReferenceType.Response)
{
_target ??= target;

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

Reference = _reference;
}

/// <inheritdoc/>
public override string Description
public string Description
{
get => string.IsNullOrEmpty(_description) ? Target?.Description : _description;
set => _description = value;
get => string.IsNullOrEmpty(Reference?.Description) ? Target?.Description : Reference.Description;
set
{
if (Reference is not null)
{
Reference.Description = value;
}
}
}

private IDictionary<string, OpenApiMediaType> _content;
/// <inheritdoc/>
public override IDictionary<string, OpenApiMediaType> Content { get => _content is not null ? _content : Target?.Content; set => _content = value; }
public IDictionary<string, OpenApiMediaType> Content { get => Target?.Content; }

private IDictionary<string, IOpenApiHeader> _headers;
/// <inheritdoc/>
public override IDictionary<string, IOpenApiHeader> Headers { get => _headers is not null ? _headers : Target?.Headers; set => _headers = value; }
public IDictionary<string, IOpenApiHeader> Headers { get => Target?.Headers; }

private IDictionary<string, IOpenApiLink> _links;
/// <inheritdoc/>
public override IDictionary<string, IOpenApiLink> Links { get => _links is not null ? _links : Target?.Links; set => _links = value; }
public IDictionary<string, IOpenApiLink> Links { get => Target?.Links; }

private IDictionary<string, IOpenApiExtension> _extensions;
/// <inheritdoc/>
public override IDictionary<string, IOpenApiExtension> Extensions { get => _extensions is not null ? _extensions : Target?.Extensions; set => _extensions = value; }

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

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

/// <inheritdoc/>
public override void SerializeAsV2(IOpenApiWriter writer)
{
if (!writer.GetSettings().ShouldInlineReference(_reference))
{
_reference.SerializeAsV2(writer);
}
else
{
SerializeInternal(writer, (writer, element) => element.SerializeAsV2(writer));
}
}
public IDictionary<string, IOpenApiExtension> Extensions { get => Target?.Extensions; }

/// <inheritdoc/>
private void SerializeInternal(IOpenApiWriter writer,
Action<IOpenApiWriter, IOpenApiReferenceable> action)
public override IOpenApiResponse CopyReferenceAsTargetElementWithOverrides(IOpenApiResponse source)
{
Utils.CheckArgumentNull(writer);
action(writer, this);
return source is OpenApiResponse ? new OpenApiResponse(this) : source;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode)
rootNode.GetMap(),
openApiDoc.Paths.Values
.SelectMany(path => path.Operations?.Values ?? Enumerable.Empty<OpenApiOperation>())
.SelectMany(operation => operation.Responses?.Values ?? Enumerable.Empty<OpenApiResponse>()),
.SelectMany(operation => operation.Responses?.Values ?? Enumerable.Empty<IOpenApiResponse>()),
openApiNode.Context);
}

Expand All @@ -257,11 +257,11 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode)
return openApiDoc;
}

private static void ProcessResponsesMediaTypes(MapNode mapNode, IEnumerable<OpenApiResponse> responses, ParsingContext context)
private static void ProcessResponsesMediaTypes(MapNode mapNode, IEnumerable<IOpenApiResponse> responses, ParsingContext context)
{
if (responses != null)
{
foreach (var response in responses)
foreach (var response in responses.OfType<OpenApiResponse>())
{
ProcessProduces(mapNode, response, context);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ internal static OpenApiOperation LoadOperation(ParseNode node, OpenApiDocument h
}
}

foreach (var response in operation.Responses.Values)
foreach (var response in operation.Responses.Values.OfType<OpenApiResponse>())
{
ProcessProduces(node.CheckMapNode("responses"), response, node.Context);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ private static void LoadExample(OpenApiResponse response, string mediaType, Pars
mediaTypeObject.Example = exampleNode;
}

public static OpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument)
public static IOpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument)
{
var mapNode = node.CheckMapNode("response");

Expand Down
Loading

0 comments on commit 8103c20

Please sign in to comment.