Skip to content

Commit 8103c20

Browse files
Merge pull request #2099 from microsoft/fix/response-reference
fix: response reference proxy design pattern implementation
2 parents df0aafb + 5b4003b commit 8103c20

File tree

46 files changed

+285
-287
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+285
-287
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System.Collections.Generic;
2+
using Microsoft.OpenApi.Interfaces;
3+
4+
namespace Microsoft.OpenApi.Models.Interfaces;
5+
6+
/// <summary>
7+
/// Defines the base properties for the response object.
8+
/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking.
9+
/// </summary>
10+
public interface IOpenApiResponse : IOpenApiDescribedElement, IOpenApiSerializable, IOpenApiReadOnlyExtensible
11+
{
12+
/// <summary>
13+
/// Maps a header name to its definition.
14+
/// </summary>
15+
public IDictionary<string, IOpenApiHeader> Headers { get; }
16+
17+
/// <summary>
18+
/// A map containing descriptions of potential response payloads.
19+
/// The key is a media type or media type range and the value describes it.
20+
/// </summary>
21+
public IDictionary<string, OpenApiMediaType> Content { get; }
22+
23+
/// <summary>
24+
/// A map of operations links that can be followed from the response.
25+
/// The key of the map is a short name for the link,
26+
/// following the naming constraints of the names for Component Objects.
27+
/// </summary>
28+
public IDictionary<string, IOpenApiLink> Links { get; }
29+
}

src/Microsoft.OpenApi/Models/OpenApiComponents.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible
2323
public IDictionary<string, OpenApiSchema>? Schemas { get; set; } = new Dictionary<string, OpenApiSchema>();
2424

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

3030
/// <summary>
3131
/// An object to hold reusable <see cref="IOpenApiParameter"/> Objects.
@@ -86,7 +86,7 @@ public OpenApiComponents() { }
8686
public OpenApiComponents(OpenApiComponents? components)
8787
{
8888
Schemas = components?.Schemas != null ? new Dictionary<string, OpenApiSchema>(components.Schemas) : null;
89-
Responses = components?.Responses != null ? new Dictionary<string, OpenApiResponse>(components.Responses) : null;
89+
Responses = components?.Responses != null ? new Dictionary<string, IOpenApiResponse>(components.Responses) : null;
9090
Parameters = components?.Parameters != null ? new Dictionary<string, IOpenApiParameter>(components.Parameters) : null;
9191
Examples = components?.Examples != null ? new Dictionary<string, IOpenApiExample>(components.Examples) : null;
9292
RequestBodies = components?.RequestBodies != null ? new Dictionary<string, IOpenApiRequestBody>(components.RequestBodies) : null;

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ public bool AddComponent<T>(string id, T componentToRegister)
596596
Components.Parameters.Add(id, openApiParameter);
597597
break;
598598
case OpenApiResponse openApiResponse:
599-
Components.Responses ??= new Dictionary<string, OpenApiResponse>();
599+
Components.Responses ??= new Dictionary<string, IOpenApiResponse>();
600600
Components.Responses.Add(id, openApiResponse);
601601
break;
602602
case OpenApiRequestBody openApiRequestBody:

src/Microsoft.OpenApi/Models/OpenApiOperation.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,10 @@ public void SerializeAsV2(IOpenApiWriter writer)
279279
.SelectMany(static r => r.Value.Content?.Keys ?? [])
280280
.Concat(
281281
Responses
282-
.Where(static r => r.Value.Reference is {HostDocument: not null})
283-
.SelectMany(static r => r.Value.Content?.Keys ?? []))
282+
.Select(static r => r.Value)
283+
.OfType<OpenApiResponseReference>()
284+
.Where(static r => r.Reference is {HostDocument: not null})
285+
.SelectMany(static r => r.Content?.Keys ?? []))
284286
.Distinct(StringComparer.OrdinalIgnoreCase)
285287
.ToArray();
286288

src/Microsoft.OpenApi/Models/OpenApiResponse.cs

+18-42
Original file line numberDiff line numberDiff line change
@@ -13,82 +13,58 @@ namespace Microsoft.OpenApi.Models
1313
/// <summary>
1414
/// Response object.
1515
/// </summary>
16-
public class OpenApiResponse : IOpenApiReferenceable, IOpenApiExtensible
16+
public class OpenApiResponse : IOpenApiReferenceable, IOpenApiExtensible, IOpenApiResponse
1717
{
18-
/// <summary>
19-
/// REQUIRED. A short description of the response.
20-
/// </summary>
21-
public virtual string Description { get; set; }
22-
23-
/// <summary>
24-
/// Maps a header name to its definition.
25-
/// </summary>
26-
public virtual IDictionary<string, IOpenApiHeader> Headers { get; set; } = new Dictionary<string, IOpenApiHeader>();
27-
28-
/// <summary>
29-
/// A map containing descriptions of potential response payloads.
30-
/// The key is a media type or media type range and the value describes it.
31-
/// </summary>
32-
public virtual IDictionary<string, OpenApiMediaType> Content { get; set; } = new Dictionary<string, OpenApiMediaType>();
18+
/// <inheritdoc/>
19+
public string Description { get; set; }
3320

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

41-
/// <summary>
42-
/// This object MAY be extended with Specification Extensions.
43-
/// </summary>
44-
public virtual IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();
24+
/// <inheritdoc/>
25+
public IDictionary<string, OpenApiMediaType> Content { get; set; } = new Dictionary<string, OpenApiMediaType>();
4526

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

51-
/// <summary>
52-
/// Reference pointer.
53-
/// </summary>
54-
public OpenApiReference Reference { get; set; }
30+
/// <inheritdoc/>
31+
public IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();
5532

5633
/// <summary>
5734
/// Parameterless constructor
5835
/// </summary>
5936
public OpenApiResponse() { }
6037

6138
/// <summary>
62-
/// Initializes a copy of <see cref="OpenApiResponse"/> object
39+
/// Initializes a copy of <see cref="IOpenApiResponse"/> object
6340
/// </summary>
64-
public OpenApiResponse(OpenApiResponse response)
41+
public OpenApiResponse(IOpenApiResponse response)
6542
{
43+
Utils.CheckArgumentNull(response);
6644
Description = response?.Description ?? Description;
6745
Headers = response?.Headers != null ? new Dictionary<string, IOpenApiHeader>(response.Headers) : null;
6846
Content = response?.Content != null ? new Dictionary<string, OpenApiMediaType>(response.Content) : null;
6947
Links = response?.Links != null ? new Dictionary<string, IOpenApiLink>(response.Links) : null;
7048
Extensions = response?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(response.Extensions) : null;
71-
UnresolvedReference = response?.UnresolvedReference ?? UnresolvedReference;
72-
Reference = response?.Reference != null ? new(response?.Reference) : null;
7349
}
7450

7551
/// <summary>
7652
/// Serialize <see cref="OpenApiResponse"/> to Open Api v3.1
7753
/// </summary>
78-
public virtual void SerializeAsV31(IOpenApiWriter writer)
54+
public void SerializeAsV31(IOpenApiWriter writer)
7955
{
8056
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer));
8157
}
8258

8359
/// <summary>
8460
/// Serialize <see cref="OpenApiResponse"/> to Open Api v3.0.
8561
/// </summary>
86-
public virtual void SerializeAsV3(IOpenApiWriter writer)
62+
public void SerializeAsV3(IOpenApiWriter writer)
8763
{
8864
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer));
8965
}
9066

91-
internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
67+
private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
9268
Action<IOpenApiWriter, IOpenApiSerializable> callback)
9369
{
9470
Utils.CheckArgumentNull(writer);
@@ -116,7 +92,7 @@ internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersio
11692
/// <summary>
11793
/// Serialize to OpenAPI V2 document without using reference.
11894
/// </summary>
119-
public virtual void SerializeAsV2(IOpenApiWriter writer)
95+
public void SerializeAsV2(IOpenApiWriter writer)
12096
{
12197
Utils.CheckArgumentNull(writer);
12298

src/Microsoft.OpenApi/Models/OpenApiResponses.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

4+
using Microsoft.OpenApi.Models.Interfaces;
5+
46
namespace Microsoft.OpenApi.Models
57
{
68
/// <summary>
79
/// Responses object.
810
/// </summary>
9-
public class OpenApiResponses : OpenApiExtensibleDictionary<OpenApiResponse>
11+
public class OpenApiResponses : OpenApiExtensibleDictionary<IOpenApiResponse>
1012
{
1113
/// <summary>
1214
/// Parameterless constructor

src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs

+18-97
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,14 @@
55
using System.Collections.Generic;
66
using Microsoft.OpenApi.Interfaces;
77
using Microsoft.OpenApi.Models.Interfaces;
8-
using Microsoft.OpenApi.Writers;
98

109
namespace Microsoft.OpenApi.Models.References
1110
{
1211
/// <summary>
1312
/// Response Object Reference.
1413
/// </summary>
15-
public class OpenApiResponseReference : OpenApiResponse, IOpenApiReferenceHolder<OpenApiResponse>
14+
public class OpenApiResponseReference : BaseOpenApiReferenceHolder<OpenApiResponse, IOpenApiResponse>, IOpenApiResponse
1615
{
17-
internal OpenApiResponse _target;
18-
private readonly OpenApiReference _reference;
19-
private string _description;
20-
21-
/// <summary>
22-
/// Gets the target response.
23-
/// </summary>
24-
/// <remarks>
25-
/// If the reference is not resolved, this will return null.
26-
/// </remarks>
27-
public OpenApiResponse Target
28-
{
29-
get
30-
{
31-
_target ??= Reference.HostDocument?.ResolveReferenceTo<OpenApiResponse>(_reference);
32-
return _target;
33-
}
34-
}
35-
3616
/// <summary>
3717
/// Constructor initializing the reference object.
3818
/// </summary>
@@ -43,102 +23,43 @@ public OpenApiResponse Target
4323
/// 1. a absolute/relative file path, for example: ../commons/pet.json
4424
/// 2. a Url, for example: http://localhost/pet.json
4525
/// </param>
46-
public OpenApiResponseReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null)
26+
public OpenApiResponseReference(string referenceId, OpenApiDocument hostDocument, string externalResource = null):base(referenceId, hostDocument, ReferenceType.Response, externalResource)
4727
{
48-
Utils.CheckArgumentNullOrEmpty(referenceId);
49-
50-
_reference = new OpenApiReference()
51-
{
52-
Id = referenceId,
53-
HostDocument = hostDocument,
54-
Type = ReferenceType.Response,
55-
ExternalResource = externalResource
56-
};
57-
58-
Reference = _reference;
5928
}
6029

61-
internal OpenApiResponseReference(string referenceId, OpenApiResponse target)
30+
internal OpenApiResponseReference(OpenApiResponse target, string referenceId):base(target, referenceId, ReferenceType.Response)
6231
{
63-
_target ??= target;
64-
65-
_reference = new OpenApiReference()
66-
{
67-
Id = referenceId,
68-
Type = ReferenceType.Response,
69-
};
70-
71-
Reference = _reference;
7232
}
7333

7434
/// <inheritdoc/>
75-
public override string Description
35+
public string Description
7636
{
77-
get => string.IsNullOrEmpty(_description) ? Target?.Description : _description;
78-
set => _description = value;
37+
get => string.IsNullOrEmpty(Reference?.Description) ? Target?.Description : Reference.Description;
38+
set
39+
{
40+
if (Reference is not null)
41+
{
42+
Reference.Description = value;
43+
}
44+
}
7945
}
8046

81-
private IDictionary<string, OpenApiMediaType> _content;
8247
/// <inheritdoc/>
83-
public override IDictionary<string, OpenApiMediaType> Content { get => _content is not null ? _content : Target?.Content; set => _content = value; }
48+
public IDictionary<string, OpenApiMediaType> Content { get => Target?.Content; }
8449

85-
private IDictionary<string, IOpenApiHeader> _headers;
8650
/// <inheritdoc/>
87-
public override IDictionary<string, IOpenApiHeader> Headers { get => _headers is not null ? _headers : Target?.Headers; set => _headers = value; }
51+
public IDictionary<string, IOpenApiHeader> Headers { get => Target?.Headers; }
8852

89-
private IDictionary<string, IOpenApiLink> _links;
9053
/// <inheritdoc/>
91-
public override IDictionary<string, IOpenApiLink> Links { get => _links is not null ? _links : Target?.Links; set => _links = value; }
54+
public IDictionary<string, IOpenApiLink> Links { get => Target?.Links; }
9255

93-
private IDictionary<string, IOpenApiExtension> _extensions;
94-
/// <inheritdoc/>
95-
public override IDictionary<string, IOpenApiExtension> Extensions { get => _extensions is not null ? _extensions : Target?.Extensions; set => _extensions = value; }
96-
9756
/// <inheritdoc/>
98-
public override void SerializeAsV3(IOpenApiWriter writer)
99-
{
100-
if (!writer.GetSettings().ShouldInlineReference(_reference))
101-
{
102-
_reference.SerializeAsV3(writer);
103-
}
104-
else
105-
{
106-
SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer));
107-
}
108-
}
109-
110-
/// <inheritdoc/>
111-
public override void SerializeAsV31(IOpenApiWriter writer)
112-
{
113-
if (!writer.GetSettings().ShouldInlineReference(_reference))
114-
{
115-
_reference.SerializeAsV31(writer);
116-
}
117-
else
118-
{
119-
SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer));
120-
}
121-
}
122-
123-
/// <inheritdoc/>
124-
public override void SerializeAsV2(IOpenApiWriter writer)
125-
{
126-
if (!writer.GetSettings().ShouldInlineReference(_reference))
127-
{
128-
_reference.SerializeAsV2(writer);
129-
}
130-
else
131-
{
132-
SerializeInternal(writer, (writer, element) => element.SerializeAsV2(writer));
133-
}
134-
}
57+
public IDictionary<string, IOpenApiExtension> Extensions { get => Target?.Extensions; }
13558

13659
/// <inheritdoc/>
137-
private void SerializeInternal(IOpenApiWriter writer,
138-
Action<IOpenApiWriter, IOpenApiReferenceable> action)
60+
public override IOpenApiResponse CopyReferenceAsTargetElementWithOverrides(IOpenApiResponse source)
13961
{
140-
Utils.CheckArgumentNull(writer);
141-
action(writer, this);
62+
return source is OpenApiResponse ? new OpenApiResponse(this) : source;
14263
}
14364
}
14465
}

src/Microsoft.OpenApi/Reader/V2/OpenApiDocumentDeserializer.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode)
235235
rootNode.GetMap(),
236236
openApiDoc.Paths.Values
237237
.SelectMany(path => path.Operations?.Values ?? Enumerable.Empty<OpenApiOperation>())
238-
.SelectMany(operation => operation.Responses?.Values ?? Enumerable.Empty<OpenApiResponse>()),
238+
.SelectMany(operation => operation.Responses?.Values ?? Enumerable.Empty<IOpenApiResponse>()),
239239
openApiNode.Context);
240240
}
241241

@@ -257,11 +257,11 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode)
257257
return openApiDoc;
258258
}
259259

260-
private static void ProcessResponsesMediaTypes(MapNode mapNode, IEnumerable<OpenApiResponse> responses, ParsingContext context)
260+
private static void ProcessResponsesMediaTypes(MapNode mapNode, IEnumerable<IOpenApiResponse> responses, ParsingContext context)
261261
{
262262
if (responses != null)
263263
{
264-
foreach (var response in responses)
264+
foreach (var response in responses.OfType<OpenApiResponse>())
265265
{
266266
ProcessProduces(mapNode, response, context);
267267

src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ internal static OpenApiOperation LoadOperation(ParseNode node, OpenApiDocument h
121121
}
122122
}
123123

124-
foreach (var response in operation.Responses.Values)
124+
foreach (var response in operation.Responses.Values.OfType<OpenApiResponse>())
125125
{
126126
ProcessProduces(node.CheckMapNode("responses"), response, node.Context);
127127
}

src/Microsoft.OpenApi/Reader/V2/OpenApiResponseDeserializer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ private static void LoadExample(OpenApiResponse response, string mediaType, Pars
179179
mediaTypeObject.Example = exampleNode;
180180
}
181181

182-
public static OpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument)
182+
public static IOpenApiResponse LoadResponse(ParseNode node, OpenApiDocument hostDocument)
183183
{
184184
var mapNode = node.CheckMapNode("response");
185185

0 commit comments

Comments
 (0)