Skip to content

Commit

Permalink
Merge pull request #2097 from microsoft/fix/request-body-reference
Browse files Browse the repository at this point in the history
fix/request body reference
  • Loading branch information
MaggieKimani1 authored Jan 28, 2025
2 parents 56f291b + 425335e commit 817acac
Show file tree
Hide file tree
Showing 25 changed files with 179 additions and 197 deletions.
2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi.Hidi/StatsVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public override void Visit(IOpenApiPathItem pathItem)

public int RequestBodyCount { get; set; }

public override void Visit(OpenApiRequestBody requestBody)
public override void Visit(IOpenApiRequestBody requestBody)
{
RequestBodyCount++;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi.Workbench/StatsVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public override void Visit(IOpenApiPathItem pathItem)

public int RequestBodyCount { get; set; }

public override void Visit(OpenApiRequestBody requestBody)
public override void Visit(IOpenApiRequestBody requestBody)
{
RequestBodyCount++;
}
Expand Down
35 changes: 35 additions & 0 deletions src/Microsoft.OpenApi/Models/Interfaces/IOpenApiRequestBody.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Collections.Generic;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Writers;

namespace Microsoft.OpenApi.Models.Interfaces;

/// <summary>
/// Defines the base properties for the request body object.
/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking.
/// </summary>
public interface IOpenApiRequestBody : IOpenApiDescribedElement, IOpenApiSerializable, IOpenApiReadOnlyExtensible
{
/// <summary>
/// Determines if the request body is required in the request. Defaults to false.
/// </summary>
public bool Required { get; }

/// <summary>
/// REQUIRED. The content of the request body. The key is a media type or media type range and the value describes it.
/// For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/*
/// </summary>
public IDictionary<string, OpenApiMediaType> Content { get; }
/// <summary>
/// Converts the request body to a body parameter in preparation for a v2 serialization.
/// </summary>
/// <param name="writer">The writer to use to read settings from.</param>
/// <returns>The converted OpenAPI parameter</returns>
IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer);
/// <summary>
/// Converts the request body to a set of form data parameters in preparation for a v2 serialization.
/// </summary>
/// <param name="writer">The writer to use to read settings from</param>
/// <returns>The converted OpenAPI parameters</returns>
IEnumerable<IOpenApiParameter> ConvertToFormDataParameters(IOpenApiWriter writer);
}
8 changes: 4 additions & 4 deletions src/Microsoft.OpenApi/Models/OpenApiComponents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible
public IDictionary<string, IOpenApiExample>? Examples { get; set; } = new Dictionary<string, IOpenApiExample>();

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

/// <summary>
/// An object to hold reusable <see cref="IOpenApiHeader"/> Objects.
Expand Down Expand Up @@ -89,7 +89,7 @@ public OpenApiComponents(OpenApiComponents? components)
Responses = components?.Responses != null ? new Dictionary<string, OpenApiResponse>(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, OpenApiRequestBody>(components.RequestBodies) : null;
RequestBodies = components?.RequestBodies != null ? new Dictionary<string, IOpenApiRequestBody>(components.RequestBodies) : null;
Headers = components?.Headers != null ? new Dictionary<string, IOpenApiHeader>(components.Headers) : null;
SecuritySchemes = components?.SecuritySchemes != null ? new Dictionary<string, OpenApiSecurityScheme>(components.SecuritySchemes) : null;
Links = components?.Links != null ? new Dictionary<string, IOpenApiLink>(components.Links) : 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 @@ -600,7 +600,7 @@ public bool AddComponent<T>(string id, T componentToRegister)
Components.Responses.Add(id, openApiResponse);
break;
case OpenApiRequestBody openApiRequestBody:
Components.RequestBodies ??= new Dictionary<string, OpenApiRequestBody>();
Components.RequestBodies ??= new Dictionary<string, IOpenApiRequestBody>();
Components.RequestBodies.Add(id, openApiRequestBody);
break;
case OpenApiLink openApiLink:
Expand Down
20 changes: 6 additions & 14 deletions src/Microsoft.OpenApi/Models/OpenApiOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public class OpenApiOperation : IOpenApiSerializable, IOpenApiExtensible, IOpenA
/// has explicitly defined semantics for request bodies.
/// In other cases where the HTTP spec is vague, requestBody SHALL be ignored by consumers.
/// </summary>
public OpenApiRequestBody? RequestBody { get; set; }
public IOpenApiRequestBody? RequestBody { get; set; }

/// <summary>
/// REQUIRED. The list of possible responses as they are returned from executing this operation.
Expand Down Expand Up @@ -128,7 +128,7 @@ public OpenApiOperation(OpenApiOperation? operation)
ExternalDocs = operation?.ExternalDocs != null ? new(operation?.ExternalDocs) : null;
OperationId = operation?.OperationId ?? OperationId;
Parameters = operation?.Parameters != null ? new List<IOpenApiParameter>(operation.Parameters) : null;
RequestBody = operation?.RequestBody != null ? new(operation?.RequestBody) : null;
RequestBody = operation?.RequestBody != null ? new OpenApiRequestBody(operation?.RequestBody) : null;
Responses = operation?.Responses != null ? new(operation?.Responses) : null;
Callbacks = operation?.Callbacks != null ? new Dictionary<string, IOpenApiCallback>(operation.Callbacks) : null;
Deprecated = operation?.Deprecated ?? Deprecated;
Expand Down Expand Up @@ -235,15 +235,7 @@ public void SerializeAsV2(IOpenApiWriter writer)
// operationId
writer.WriteProperty(OpenApiConstants.OperationId, OperationId);

List<IOpenApiParameter> parameters;
if (Parameters == null)
{
parameters = [];
}
else
{
parameters = [.. Parameters];
}
List<IOpenApiParameter> parameters = Parameters is null ? new() : new(Parameters);

if (RequestBody != null)
{
Expand All @@ -255,17 +247,17 @@ public void SerializeAsV2(IOpenApiWriter writer)
if (consumes.Contains("application/x-www-form-urlencoded") ||
consumes.Contains("multipart/form-data"))
{
parameters.AddRange(RequestBody.ConvertToFormDataParameters());
parameters.AddRange(RequestBody.ConvertToFormDataParameters(writer));
}
else
{
parameters.Add(RequestBody.ConvertToBodyParameter(writer));
}
}
else if (RequestBody.Reference != null && RequestBody.Reference.HostDocument is {} hostDocument)
else if (RequestBody is OpenApiRequestBodyReference requestBodyReference)
{
parameters.Add(
new OpenApiParameterReference(RequestBody.Reference.Id, hostDocument));
new OpenApiParameterReference(requestBodyReference.Reference.Id, requestBodyReference.Reference.HostDocument));
}

if (consumes.Count > 0)
Expand Down
61 changes: 21 additions & 40 deletions src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,52 +15,31 @@ namespace Microsoft.OpenApi.Models
/// <summary>
/// Request Body Object
/// </summary>
public class OpenApiRequestBody : IOpenApiReferenceable, IOpenApiExtensible
public class OpenApiRequestBody : IOpenApiReferenceable, IOpenApiExtensible, IOpenApiRequestBody
{
/// <summary>
/// Indicates if object is populated with data or is just a reference to the data
/// </summary>
public bool UnresolvedReference { get; set; }

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

/// <summary>
/// A brief description of the request body. This could contain examples of use.
/// CommonMark syntax MAY be used for rich text representation.
/// </summary>
public virtual string Description { get; set; }
/// <inheritdoc />
public string Description { get; set; }

/// <summary>
/// Determines if the request body is required in the request. Defaults to false.
/// </summary>
public virtual bool Required { get; set; }
/// <inheritdoc />
public bool Required { get; set; }

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

/// <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, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();

/// <summary>
/// Parameter-less constructor
/// </summary>
public OpenApiRequestBody() { }

/// <summary>
/// Initializes a copy instance of an <see cref="OpenApiRequestBody"/> object
/// Initializes a copy instance of an <see cref="IOpenApiRequestBody"/> object
/// </summary>
public OpenApiRequestBody(OpenApiRequestBody requestBody)
public OpenApiRequestBody(IOpenApiRequestBody requestBody)
{
UnresolvedReference = requestBody?.UnresolvedReference ?? UnresolvedReference;
Reference = requestBody?.Reference != null ? new(requestBody?.Reference) : null;
Utils.CheckArgumentNull(requestBody);
Description = requestBody?.Description ?? Description;
Required = requestBody?.Required ?? Required;
Content = requestBody?.Content != null ? new Dictionary<string, OpenApiMediaType>(requestBody.Content) : null;
Expand All @@ -70,20 +49,20 @@ public OpenApiRequestBody(OpenApiRequestBody requestBody)
/// <summary>
/// Serialize <see cref="OpenApiRequestBody"/> 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="OpenApiRequestBody"/> 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,
internal void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
Action<IOpenApiWriter, IOpenApiSerializable> callback)
{
Utils.CheckArgumentNull(writer);
Expand Down Expand Up @@ -113,7 +92,8 @@ public void SerializeAsV2(IOpenApiWriter writer)
// RequestBody object does not exist in V2.
}

internal virtual IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer)
/// <inheritdoc/>
public IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer)
{
var bodyParameter = new OpenApiBodyParameter
{
Expand All @@ -135,22 +115,23 @@ internal virtual IOpenApiParameter ConvertToBodyParameter(IOpenApiWriter writer)
return bodyParameter;
}

internal IEnumerable<OpenApiFormDataParameter> ConvertToFormDataParameters()
/// <inheritdoc/>
public IEnumerable<IOpenApiParameter> ConvertToFormDataParameters(IOpenApiWriter writer)
{
if (Content == null || !Content.Any())
yield break;

foreach (var property in Content.First().Value.Schema.Properties)
{
var paramSchema = property.Value;
if ("string".Equals(paramSchema.Type.ToIdentifier(), StringComparison.OrdinalIgnoreCase)
if ((paramSchema.Type & JsonSchemaType.String) == JsonSchemaType.String
&& ("binary".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase)
|| "base64".Equals(paramSchema.Format, StringComparison.OrdinalIgnoreCase)))
{
paramSchema.Type = "file".ToJsonSchemaType();
paramSchema.Format = null;
}
yield return new()
yield return new OpenApiFormDataParameter()
{
Description = property.Value.Description,
Name = property.Key,
Expand Down
Loading

0 comments on commit 817acac

Please sign in to comment.