Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix/request body reference #2097

Merged
merged 2 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading