Skip to content

Commit 77e0ad1

Browse files
committed
fix: Open API header proxy design pattern implementation
Signed-off-by: Vincent Biret <[email protected]>
1 parent d7e1f91 commit 77e0ad1

29 files changed

+294
-228
lines changed

src/Microsoft.OpenApi.Hidi/StatsVisitor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public override void Visit(OpenApiSchema schema)
2727

2828
public int HeaderCount { get; set; }
2929

30-
public override void Visit(IDictionary<string, OpenApiHeader> headers)
30+
public override void Visit(IDictionary<string, IOpenApiHeader> headers)
3131
{
3232
HeaderCount++;
3333
}

src/Microsoft.OpenApi.Workbench/StatsVisitor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public override void Visit(OpenApiSchema schema)
2727

2828
public int HeaderCount { get; set; }
2929

30-
public override void Visit(IDictionary<string, OpenApiHeader> headers)
30+
public override void Visit(IDictionary<string, IOpenApiHeader> headers)
3131
{
3232
HeaderCount++;
3333
}

src/Microsoft.OpenApi/Extensions/OpenApiReferencableExtensions.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,24 @@ private static IOpenApiReferenceable ResolveReferenceOnResponseElement(
9292
string mapKey,
9393
JsonPointer pointer)
9494
{
95-
switch (propertyName)
95+
if (!string.IsNullOrEmpty(mapKey))
9696
{
97-
case OpenApiConstants.Headers when mapKey != null:
98-
return responseElement.Headers[mapKey];
99-
case OpenApiConstants.Links when mapKey != null:
100-
return responseElement.Links[mapKey];
101-
default:
102-
throw new OpenApiException(string.Format(SRResource.InvalidReferenceId, pointer));
97+
if (OpenApiConstants.Headers.Equals(propertyName, StringComparison.Ordinal) &&
98+
responseElement?.Headers != null &&
99+
responseElement.Headers.TryGetValue(mapKey, out var headerElement) &&
100+
headerElement is IOpenApiReferenceable referenceable)
101+
{
102+
return referenceable;
103+
}
104+
if (OpenApiConstants.Links.Equals(propertyName, StringComparison.Ordinal) &&
105+
responseElement?.Links != null &&
106+
responseElement.Links.TryGetValue(mapKey, out var linkElement) &&
107+
linkElement is IOpenApiReferenceable referenceable2)
108+
{
109+
return referenceable2;
110+
}
103111
}
112+
throw new OpenApiException(string.Format(SRResource.InvalidReferenceId, pointer));
104113
}
105114
}
106115
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
2+
using System.Collections.Generic;
3+
using System.Text.Json.Nodes;
4+
using Microsoft.OpenApi.Interfaces;
5+
6+
namespace Microsoft.OpenApi.Models.Interfaces;
7+
8+
/// <summary>
9+
/// Defines the base properties for the headers object.
10+
/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking.
11+
/// </summary>
12+
public interface IOpenApiHeader : IOpenApiDescribedElement, IOpenApiSerializable, IOpenApiReadOnlyExtensible
13+
{
14+
/// <summary>
15+
/// Determines whether this header is mandatory.
16+
/// </summary>
17+
public bool Required { get; }
18+
19+
/// <summary>
20+
/// Specifies that a header is deprecated and SHOULD be transitioned out of usage.
21+
/// </summary>
22+
public bool Deprecated { get; }
23+
24+
/// <summary>
25+
/// Sets the ability to pass empty-valued headers.
26+
/// </summary>
27+
public bool AllowEmptyValue { get; }
28+
29+
/// <summary>
30+
/// Describes how the header value will be serialized depending on the type of the header value.
31+
/// </summary>
32+
public ParameterStyle? Style { get; }
33+
34+
/// <summary>
35+
/// When this is true, header values of type array or object generate separate parameters
36+
/// for each value of the array or key-value pair of the map.
37+
/// </summary>
38+
public bool Explode { get; }
39+
40+
/// <summary>
41+
/// Determines whether the header value SHOULD allow reserved characters, as defined by RFC3986.
42+
/// </summary>
43+
public bool AllowReserved { get; }
44+
45+
/// <summary>
46+
/// The schema defining the type used for the request body.
47+
/// </summary>
48+
public OpenApiSchema Schema { get; }
49+
50+
/// <summary>
51+
/// Example of the media type.
52+
/// </summary>
53+
public JsonNode Example { get; }
54+
55+
/// <summary>
56+
/// Examples of the media type.
57+
/// </summary>
58+
public IDictionary<string, IOpenApiExample> Examples { get; }
59+
60+
/// <summary>
61+
/// A map containing the representations for the header.
62+
/// </summary>
63+
public IDictionary<string, OpenApiMediaType> Content { get; }
64+
65+
}

src/Microsoft.OpenApi/Models/OpenApiComponents.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible
4545
new Dictionary<string, OpenApiRequestBody>();
4646

4747
/// <summary>
48-
/// An object to hold reusable <see cref="OpenApiHeader"/> Objects.
48+
/// An object to hold reusable <see cref="IOpenApiHeader"/> Objects.
4949
/// </summary>
50-
public IDictionary<string, OpenApiHeader>? Headers { get; set; } = new Dictionary<string, OpenApiHeader>();
50+
public IDictionary<string, IOpenApiHeader>? Headers { get; set; } = new Dictionary<string, IOpenApiHeader>();
5151

5252
/// <summary>
5353
/// An object to hold reusable <see cref="OpenApiSecurityScheme"/> Objects.
@@ -90,7 +90,7 @@ public OpenApiComponents(OpenApiComponents? components)
9090
Parameters = components?.Parameters != null ? new Dictionary<string, OpenApiParameter>(components.Parameters) : null;
9191
Examples = components?.Examples != null ? new Dictionary<string, IOpenApiExample>(components.Examples) : null;
9292
RequestBodies = components?.RequestBodies != null ? new Dictionary<string, OpenApiRequestBody>(components.RequestBodies) : null;
93-
Headers = components?.Headers != null ? new Dictionary<string, OpenApiHeader>(components.Headers) : null;
93+
Headers = components?.Headers != null ? new Dictionary<string, IOpenApiHeader>(components.Headers) : null;
9494
SecuritySchemes = components?.SecuritySchemes != null ? new Dictionary<string, OpenApiSecurityScheme>(components.SecuritySchemes) : null;
9595
Links = components?.Links != null ? new Dictionary<string, OpenApiLink>(components.Links) : null;
9696
Callbacks = components?.Callbacks != null ? new Dictionary<string, IOpenApiCallback>(components.Callbacks) : null;

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ public bool AddComponent<T>(string id, T componentToRegister)
620620
Components.Examples.Add(id, openApiExample);
621621
break;
622622
case OpenApiHeader openApiHeader:
623-
Components.Headers ??= new Dictionary<string, OpenApiHeader>();
623+
Components.Headers ??= new Dictionary<string, IOpenApiHeader>();
624624
Components.Headers.Add(id, openApiHeader);
625625
break;
626626
case OpenApiSecurityScheme openApiSecurityScheme:

src/Microsoft.OpenApi/Models/OpenApiEncoding.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using Microsoft.OpenApi.Extensions;
77
using Microsoft.OpenApi.Interfaces;
8+
using Microsoft.OpenApi.Models.Interfaces;
89
using Microsoft.OpenApi.Writers;
910

1011
namespace Microsoft.OpenApi.Models
@@ -24,7 +25,7 @@ public class OpenApiEncoding : IOpenApiSerializable, IOpenApiExtensible
2425
/// <summary>
2526
/// A map allowing additional information to be provided as headers.
2627
/// </summary>
27-
public IDictionary<string, OpenApiHeader> Headers { get; set; } = new Dictionary<string, OpenApiHeader>();
28+
public IDictionary<string, IOpenApiHeader> Headers { get; set; } = new Dictionary<string, IOpenApiHeader>();
2829

2930
/// <summary>
3031
/// Describes how a specific property value will be serialized depending on its type.
@@ -64,7 +65,7 @@ public OpenApiEncoding() { }
6465
public OpenApiEncoding(OpenApiEncoding encoding)
6566
{
6667
ContentType = encoding?.ContentType ?? ContentType;
67-
Headers = encoding?.Headers != null ? new Dictionary<string, OpenApiHeader>(encoding.Headers) : null;
68+
Headers = encoding?.Headers != null ? new Dictionary<string, IOpenApiHeader>(encoding.Headers) : null;
6869
Style = encoding?.Style ?? Style;
6970
Explode = encoding?.Explode ?? Explode;
7071
AllowReserved = encoding?.AllowReserved ?? AllowReserved;

src/Microsoft.OpenApi/Models/OpenApiHeader.cs

Lines changed: 31 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -16,84 +16,43 @@ namespace Microsoft.OpenApi.Models
1616
/// Header Object.
1717
/// The Header Object follows the structure of the Parameter Object.
1818
/// </summary>
19-
public class OpenApiHeader : IOpenApiReferenceable, IOpenApiExtensible
19+
public class OpenApiHeader : IOpenApiHeader, IOpenApiReferenceable, IOpenApiExtensible
2020
{
21-
private OpenApiSchema _schema;
21+
/// <inheritdoc/>
22+
public string Description { get; set; }
2223

23-
/// <summary>
24-
/// Indicates if object is populated with data or is just a reference to the data
25-
/// </summary>
26-
public virtual bool UnresolvedReference { get; set; }
27-
28-
/// <summary>
29-
/// Reference pointer.
30-
/// </summary>
31-
public OpenApiReference Reference { get; set; }
32-
33-
/// <summary>
34-
/// A brief description of the header.
35-
/// </summary>
36-
public virtual string Description { get; set; }
24+
/// <inheritdoc/>
25+
public bool Required { get; set; }
3726

38-
/// <summary>
39-
/// Determines whether this header is mandatory.
40-
/// </summary>
41-
public virtual bool Required { get; set; }
42-
43-
/// <summary>
44-
/// Specifies that a header is deprecated and SHOULD be transitioned out of usage.
45-
/// </summary>
46-
public virtual bool Deprecated { get; set; }
27+
/// <inheritdoc/>
28+
public bool Deprecated { get; set; }
4729

48-
/// <summary>
49-
/// Sets the ability to pass empty-valued headers.
50-
/// </summary>
51-
public virtual bool AllowEmptyValue { get; set; }
30+
/// <inheritdoc/>
31+
public bool AllowEmptyValue { get; set; }
5232

53-
/// <summary>
54-
/// Describes how the header value will be serialized depending on the type of the header value.
55-
/// </summary>
56-
public virtual ParameterStyle? Style { get; set; }
33+
/// <inheritdoc/>
34+
public ParameterStyle? Style { get; set; }
5735

58-
/// <summary>
59-
/// When this is true, header values of type array or object generate separate parameters
60-
/// for each value of the array or key-value pair of the map.
61-
/// </summary>
62-
public virtual bool Explode { get; set; }
36+
/// <inheritdoc/>
37+
public bool Explode { get; set; }
6338

64-
/// <summary>
65-
/// Determines whether the header value SHOULD allow reserved characters, as defined by RFC3986.
66-
/// </summary>
67-
public virtual bool AllowReserved { get; set; }
39+
/// <inheritdoc/>
40+
public bool AllowReserved { get; set; }
6841

69-
/// <summary>
70-
/// The schema defining the type used for the request body.
71-
/// </summary>
72-
public virtual OpenApiSchema Schema
73-
{
74-
get => _schema;
75-
set => _schema = value;
76-
}
42+
/// <inheritdoc/>
43+
public OpenApiSchema Schema { get; set; }
7744

78-
/// <summary>
79-
/// Example of the media type.
80-
/// </summary>
81-
public virtual JsonNode Example { get; set; }
45+
/// <inheritdoc/>
46+
public JsonNode Example { get; set; }
8247

83-
/// <summary>
84-
/// Examples of the media type.
85-
/// </summary>
86-
public virtual IDictionary<string, IOpenApiExample> Examples { get; set; } = new Dictionary<string, IOpenApiExample>();
48+
/// <inheritdoc/>
49+
public IDictionary<string, IOpenApiExample> Examples { get; set; } = new Dictionary<string, IOpenApiExample>();
8750

88-
/// <summary>
89-
/// A map containing the representations for the header.
90-
/// </summary>
91-
public virtual IDictionary<string, OpenApiMediaType> Content { get; set; } = new Dictionary<string, OpenApiMediaType>();
51+
/// <inheritdoc/>
52+
public IDictionary<string, OpenApiMediaType> Content { get; set; } = new Dictionary<string, OpenApiMediaType>();
9253

93-
/// <summary>
94-
/// This object MAY be extended with Specification Extensions.
95-
/// </summary>
96-
public virtual IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();
54+
/// <inheritdoc/>
55+
public IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();
9756

9857
/// <summary>
9958
/// Parameter-less constructor
@@ -103,18 +62,16 @@ public OpenApiHeader() { }
10362
/// <summary>
10463
/// Initializes a copy of an <see cref="OpenApiHeader"/> object
10564
/// </summary>
106-
public OpenApiHeader(OpenApiHeader header)
65+
public OpenApiHeader(IOpenApiHeader header)
10766
{
108-
UnresolvedReference = header?.UnresolvedReference ?? UnresolvedReference;
109-
Reference = header?.Reference != null ? new(header?.Reference) : null;
11067
Description = header?.Description ?? Description;
11168
Required = header?.Required ?? Required;
11269
Deprecated = header?.Deprecated ?? Deprecated;
11370
AllowEmptyValue = header?.AllowEmptyValue ?? AllowEmptyValue;
11471
Style = header?.Style ?? Style;
11572
Explode = header?.Explode ?? Explode;
11673
AllowReserved = header?.AllowReserved ?? AllowReserved;
117-
_schema = header?.Schema != null ? new(header.Schema) : null;
74+
Schema = header?.Schema != null ? new(header.Schema) : null;
11875
Example = header?.Example != null ? JsonNodeCloneHelper.Clone(header.Example) : null;
11976
Examples = header?.Examples != null ? new Dictionary<string, IOpenApiExample>(header.Examples) : null;
12077
Content = header?.Content != null ? new Dictionary<string, OpenApiMediaType>(header.Content) : null;
@@ -124,20 +81,20 @@ public OpenApiHeader(OpenApiHeader header)
12481
/// <summary>
12582
/// Serialize <see cref="OpenApiHeader"/> to Open Api v3.1
12683
/// </summary>
127-
public virtual void SerializeAsV31(IOpenApiWriter writer)
84+
public void SerializeAsV31(IOpenApiWriter writer)
12885
{
12986
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV31(writer));
13087
}
13188

13289
/// <summary>
13390
/// Serialize <see cref="OpenApiHeader"/> to Open Api v3.0
13491
/// </summary>
135-
public virtual void SerializeAsV3(IOpenApiWriter writer)
92+
public void SerializeAsV3(IOpenApiWriter writer)
13693
{
13794
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer));
13895
}
13996

140-
internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
97+
internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
14198
Action<IOpenApiWriter, IOpenApiSerializable> callback)
14299
{
143100
Utils.CheckArgumentNull(writer);
@@ -186,7 +143,7 @@ internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersio
186143
/// <summary>
187144
/// Serialize to OpenAPI V2 document without using reference.
188145
/// </summary>
189-
public virtual void SerializeAsV2(IOpenApiWriter writer)
146+
public void SerializeAsV2(IOpenApiWriter writer)
190147
{
191148
Utils.CheckArgumentNull(writer);
192149

src/Microsoft.OpenApi/Models/OpenApiResponse.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using Microsoft.OpenApi.Interfaces;
8+
using Microsoft.OpenApi.Models.Interfaces;
89
using Microsoft.OpenApi.Writers;
910

1011
namespace Microsoft.OpenApi.Models
@@ -22,7 +23,7 @@ public class OpenApiResponse : IOpenApiReferenceable, IOpenApiExtensible
2223
/// <summary>
2324
/// Maps a header name to its definition.
2425
/// </summary>
25-
public virtual IDictionary<string, OpenApiHeader> Headers { get; set; } = new Dictionary<string, OpenApiHeader>();
26+
public virtual IDictionary<string, IOpenApiHeader> Headers { get; set; } = new Dictionary<string, IOpenApiHeader>();
2627

2728
/// <summary>
2829
/// A map containing descriptions of potential response payloads.
@@ -63,7 +64,7 @@ public OpenApiResponse() { }
6364
public OpenApiResponse(OpenApiResponse response)
6465
{
6566
Description = response?.Description ?? Description;
66-
Headers = response?.Headers != null ? new Dictionary<string, OpenApiHeader>(response.Headers) : null;
67+
Headers = response?.Headers != null ? new Dictionary<string, IOpenApiHeader>(response.Headers) : null;
6768
Content = response?.Content != null ? new Dictionary<string, OpenApiMediaType>(response.Content) : null;
6869
Links = response?.Links != null ? new Dictionary<string, OpenApiLink>(response.Links) : null;
6970
Extensions = response?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(response.Extensions) : null;

0 commit comments

Comments
 (0)