Skip to content

Commit 425335e

Browse files
committed
fix: proxy design pattern implementation for request body
Signed-off-by: Vincent Biret <[email protected]>
1 parent 88badd4 commit 425335e

22 files changed

+161
-194
lines changed

src/Microsoft.OpenApi.Hidi/StatsVisitor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public override void Visit(IOpenApiPathItem pathItem)
4141

4242
public int RequestBodyCount { get; set; }
4343

44-
public override void Visit(OpenApiRequestBody requestBody)
44+
public override void Visit(IOpenApiRequestBody requestBody)
4545
{
4646
RequestBodyCount++;
4747
}

src/Microsoft.OpenApi.Workbench/StatsVisitor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public override void Visit(IOpenApiPathItem pathItem)
4141

4242
public int RequestBodyCount { get; set; }
4343

44-
public override void Visit(OpenApiRequestBody requestBody)
44+
public override void Visit(IOpenApiRequestBody requestBody)
4545
{
4646
RequestBodyCount++;
4747
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.Collections.Generic;
2+
using Microsoft.OpenApi.Interfaces;
3+
using Microsoft.OpenApi.Writers;
4+
5+
namespace Microsoft.OpenApi.Models.Interfaces;
6+
7+
/// <summary>
8+
/// Defines the base properties for the request body object.
9+
/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking.
10+
/// </summary>
11+
public interface IOpenApiRequestBody : IOpenApiDescribedElement, IOpenApiSerializable, IOpenApiReadOnlyExtensible
12+
{
13+
/// <summary>
14+
/// Determines if the request body is required in the request. Defaults to false.
15+
/// </summary>
16+
public bool Required { get; }
17+
18+
/// <summary>
19+
/// REQUIRED. The content of the request body. The key is a media type or media type range and the value describes it.
20+
/// For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/*
21+
/// </summary>
22+
public IDictionary<string, OpenApiMediaType> Content { get; }
23+
/// <summary>
24+
/// Converts the request body to a body parameter in preparation for a v2 serialization.
25+
/// </summary>
26+
/// <param name="writer">The writer to use to read settings from.</param>
27+
/// <returns>The converted OpenAPI parameter</returns>
28+
IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer);
29+
/// <summary>
30+
/// Converts the request body to a set of form data parameters in preparation for a v2 serialization.
31+
/// </summary>
32+
/// <param name="writer">The writer to use to read settings from</param>
33+
/// <returns>The converted OpenAPI parameters</returns>
34+
IEnumerable<IOpenApiParameter> ConvertToFormDataParameters(IOpenApiWriter writer);
35+
}

src/Microsoft.OpenApi/Models/OpenApiComponents.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible
3939
public IDictionary<string, IOpenApiExample>? Examples { get; set; } = new Dictionary<string, IOpenApiExample>();
4040

4141
/// <summary>
42-
/// An object to hold reusable <see cref="OpenApiRequestBody"/> Objects.
42+
/// An object to hold reusable <see cref="IOpenApiRequestBody"/> Objects.
4343
/// </summary>
44-
public IDictionary<string, OpenApiRequestBody>? RequestBodies { get; set; } =
45-
new Dictionary<string, OpenApiRequestBody>();
44+
public IDictionary<string, IOpenApiRequestBody>? RequestBodies { get; set; } =
45+
new Dictionary<string, IOpenApiRequestBody>();
4646

4747
/// <summary>
4848
/// An object to hold reusable <see cref="IOpenApiHeader"/> Objects.
@@ -89,7 +89,7 @@ public OpenApiComponents(OpenApiComponents? components)
8989
Responses = components?.Responses != null ? new Dictionary<string, OpenApiResponse>(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;
92-
RequestBodies = components?.RequestBodies != null ? new Dictionary<string, OpenApiRequestBody>(components.RequestBodies) : null;
92+
RequestBodies = components?.RequestBodies != null ? new Dictionary<string, IOpenApiRequestBody>(components.RequestBodies) : null;
9393
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, IOpenApiLink>(components.Links) : null;

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ public bool AddComponent<T>(string id, T componentToRegister)
600600
Components.Responses.Add(id, openApiResponse);
601601
break;
602602
case OpenApiRequestBody openApiRequestBody:
603-
Components.RequestBodies ??= new Dictionary<string, OpenApiRequestBody>();
603+
Components.RequestBodies ??= new Dictionary<string, IOpenApiRequestBody>();
604604
Components.RequestBodies.Add(id, openApiRequestBody);
605605
break;
606606
case OpenApiLink openApiLink:

src/Microsoft.OpenApi/Models/OpenApiOperation.cs

+6-14
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible, IOpenA
6666
/// has explicitly defined semantics for request bodies.
6767
/// In other cases where the HTTP spec is vague, requestBody SHALL be ignored by consumers.
6868
/// </summary>
69-
public OpenApiRequestBody? RequestBody { get; set; }
69+
public IOpenApiRequestBody? RequestBody { get; set; }
7070

7171
/// <summary>
7272
/// REQUIRED. The list of possible responses as they are returned from executing this operation.
@@ -128,7 +128,7 @@ public OpenApiOperation(OpenApiOperation? operation)
128128
ExternalDocs = operation?.ExternalDocs != null ? new(operation?.ExternalDocs) : null;
129129
OperationId = operation?.OperationId ?? OperationId;
130130
Parameters = operation?.Parameters != null ? new List<IOpenApiParameter>(operation.Parameters) : null;
131-
RequestBody = operation?.RequestBody != null ? new(operation?.RequestBody) : null;
131+
RequestBody = operation?.RequestBody != null ? new OpenApiRequestBody(operation?.RequestBody) : null;
132132
Responses = operation?.Responses != null ? new(operation?.Responses) : null;
133133
Callbacks = operation?.Callbacks != null ? new Dictionary<string, IOpenApiCallback>(operation.Callbacks) : null;
134134
Deprecated = operation?.Deprecated ?? Deprecated;
@@ -235,15 +235,7 @@ public void SerializeAsV2(IOpenApiWriter writer)
235235
// operationId
236236
writer.WriteProperty(OpenApiConstants.OperationId, OperationId);
237237

238-
List<IOpenApiParameter> parameters;
239-
if (Parameters == null)
240-
{
241-
parameters = [];
242-
}
243-
else
244-
{
245-
parameters = [.. Parameters];
246-
}
238+
List<IOpenApiParameter> parameters = Parameters is null ? new() : new(Parameters);
247239

248240
if (RequestBody != null)
249241
{
@@ -255,17 +247,17 @@ public void SerializeAsV2(IOpenApiWriter writer)
255247
if (consumes.Contains("application/x-www-form-urlencoded") ||
256248
consumes.Contains("multipart/form-data"))
257249
{
258-
parameters.AddRange(RequestBody.ConvertToFormDataParameters());
250+
parameters.AddRange(RequestBody.ConvertToFormDataParameters(writer));
259251
}
260252
else
261253
{
262254
parameters.Add(RequestBody.ConvertToBodyParameter(writer));
263255
}
264256
}
265-
else if (RequestBody.Reference != null && RequestBody.Reference.HostDocument is {} hostDocument)
257+
else if (RequestBody is OpenApiRequestBodyReference requestBodyReference)
266258
{
267259
parameters.Add(
268-
new OpenApiParameterReference(RequestBody.Reference.Id, hostDocument));
260+
new OpenApiParameterReference(requestBodyReference.Reference.Id, requestBodyReference.Reference.HostDocument));
269261
}
270262

271263
if (consumes.Count > 0)

src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs

+21-40
Original file line numberDiff line numberDiff line change
@@ -15,52 +15,31 @@ namespace Microsoft.OpenApi.Models
1515
/// <summary>
1616
/// Request Body Object
1717
/// </summary>
18-
public class OpenApiRequestBody : IOpenApiReferenceable, IOpenApiExtensible
18+
public class OpenApiRequestBody : IOpenApiReferenceable, IOpenApiExtensible, IOpenApiRequestBody
1919
{
20-
/// <summary>
21-
/// Indicates if object is populated with data or is just a reference to the data
22-
/// </summary>
23-
public bool UnresolvedReference { get; set; }
24-
25-
/// <summary>
26-
/// Reference object.
27-
/// </summary>
28-
public OpenApiReference Reference { get; set; }
29-
30-
/// <summary>
31-
/// A brief description of the request body. This could contain examples of use.
32-
/// CommonMark syntax MAY be used for rich text representation.
33-
/// </summary>
34-
public virtual string Description { get; set; }
20+
/// <inheritdoc />
21+
public string Description { get; set; }
3522

36-
/// <summary>
37-
/// Determines if the request body is required in the request. Defaults to false.
38-
/// </summary>
39-
public virtual bool Required { get; set; }
23+
/// <inheritdoc />
24+
public bool Required { get; set; }
4025

41-
/// <summary>
42-
/// REQUIRED. The content of the request body. The key is a media type or media type range and the value describes it.
43-
/// For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/*
44-
/// </summary>
45-
public virtual IDictionary<string, OpenApiMediaType> Content { get; set; } = new Dictionary<string, OpenApiMediaType>();
26+
/// <inheritdoc />
27+
public IDictionary<string, OpenApiMediaType> Content { get; set; } = new Dictionary<string, OpenApiMediaType>();
4628

47-
/// <summary>
48-
/// This object MAY be extended with Specification Extensions.
49-
/// </summary>
50-
public virtual IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();
29+
/// <inheritdoc />
30+
public IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();
5131

5232
/// <summary>
5333
/// Parameter-less constructor
5434
/// </summary>
5535
public OpenApiRequestBody() { }
5636

5737
/// <summary>
58-
/// Initializes a copy instance of an <see cref="OpenApiRequestBody"/> object
38+
/// Initializes a copy instance of an <see cref="IOpenApiRequestBody"/> object
5939
/// </summary>
60-
public OpenApiRequestBody(OpenApiRequestBody requestBody)
40+
public OpenApiRequestBody(IOpenApiRequestBody requestBody)
6141
{
62-
UnresolvedReference = requestBody?.UnresolvedReference ?? UnresolvedReference;
63-
Reference = requestBody?.Reference != null ? new(requestBody?.Reference) : null;
42+
Utils.CheckArgumentNull(requestBody);
6443
Description = requestBody?.Description ?? Description;
6544
Required = requestBody?.Required ?? Required;
6645
Content = requestBody?.Content != null ? new Dictionary<string, OpenApiMediaType>(requestBody.Content) : null;
@@ -70,20 +49,20 @@ public OpenApiRequestBody(OpenApiRequestBody requestBody)
7049
/// <summary>
7150
/// Serialize <see cref="OpenApiRequestBody"/> to Open Api v3.1
7251
/// </summary>
73-
public virtual void SerializeAsV31(IOpenApiWriter writer)
52+
public void SerializeAsV31(IOpenApiWriter writer)
7453
{
7554
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer));
7655
}
7756

7857
/// <summary>
7958
/// Serialize <see cref="OpenApiRequestBody"/> to Open Api v3.0
8059
/// </summary>
81-
public virtual void SerializeAsV3(IOpenApiWriter writer)
60+
public void SerializeAsV3(IOpenApiWriter writer)
8261
{
8362
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer));
8463
}
8564

86-
internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
65+
internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
8766
Action<IOpenApiWriter, IOpenApiSerializable> callback)
8867
{
8968
Utils.CheckArgumentNull(writer);
@@ -113,7 +92,8 @@ public void SerializeAsV2(IOpenApiWriter writer)
11392
// RequestBody object does not exist in V2.
11493
}
11594

116-
internal virtual IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer)
95+
/// <inheritdoc/>
96+
public IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer)
11797
{
11898
var bodyParameter = new OpenApiBodyParameter
11999
{
@@ -135,22 +115,23 @@ internal virtual IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer)
135115
return bodyParameter;
136116
}
137117

138-
internal IEnumerable<OpenApiFormDataParameter> ConvertToFormDataParameters()
118+
/// <inheritdoc/>
119+
public IEnumerable<IOpenApiParameter> ConvertToFormDataParameters(IOpenApiWriter writer)
139120
{
140121
if (Content == null || !Content.Any())
141122
yield break;
142123

143124
foreach (var property in Content.First().Value.Schema.Properties)
144125
{
145126
var paramSchema = property.Value;
146-
if ("string".Equals(paramSchema.Type.ToIdentifier(), StringComparison.OrdinalIgnoreCase)
127+
if ((paramSchema.Type & JsonSchemaType.String) == JsonSchemaType.String
147128
&& ("binary".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase)
148129
|| "base64".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase)))
149130
{
150131
paramSchema.Type = "file".ToJsonSchemaType();
151132
paramSchema.Format = null;
152133
}
153-
yield return new()
134+
yield return new OpenApiFormDataParameter()
154135
{
155136
Description = property.Value.Description,
156137
Name = property.Key,

0 commit comments

Comments
 (0)